Эх сурвалжийг харах

convert SuggestionPopup overlay to true PopUpLauncher

Daniel Sheffield 3 жил өмнө
parent
commit
26dc9cce83

+ 11 - 7
app/activities/NewProduct.py

@@ -5,14 +5,16 @@
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 import urwid
+from app.db_utils import NON_IDENTIFIER_COLUMNS
 from ..widgets import (
     AutoCompleteEdit,
-    NON_IDENTIFIER_COLUMNS,
+    AutoCompletePopUp,
 )
+
 from collections import OrderedDict
 class NewProduct(urwid.Overlay):
 
-    def __init__(self, query_manager, under, name, data, autocomplete_cb, change_cb, apply_cb, esc_cb):
+    def __init__(self, query_manager, under, name, data, autocomplete_cb, change_cb, apply_cb, esc_cb, apply_choice_cb):
         self.esc_cb = esc_cb
         self.under = under
         self._data = data
@@ -22,10 +24,7 @@ class NewProduct(urwid.Overlay):
         title = urwid.Text('Enter Product Info', align='center')
         self.fields = OrderedDict()
         for f in ('product', 'category', 'group'):
-            w = AutoCompleteEdit(
-                    ('bg', f),
-                    apply_change_func=lambda name: autocomplete_cb(name)
-                )
+            w = AutoCompleteEdit(('bg', f))
             self.fields[f] = w
             w.set_edit_text(data[f])
             urwid.connect_signal(w, 'change', change_cb(f))
@@ -38,7 +37,12 @@ class NewProduct(urwid.Overlay):
                     urwid.Divider(),
                     *[
                         urwid.AttrMap(
-                            urwid.LineBox(urwid.AttrMap(v,'streak'), title=k.title(), title_align='left'), 'banner'
+                            urwid.LineBox(urwid.AttrMap(
+                                AutoCompletePopUp(
+                                    v,
+                                    apply_change_func=lambda name, pop_up_cb: autocomplete_cb(name, pop_up_cb),
+                                    apply_choice_cb=apply_choice_cb,
+                                ),'streak'), title=k.title(), title_align='left'), 'banner'
                         ) for k,v in self.fields.items()
                     ],
                     urwid.AttrMap(ok, 'banner'),

+ 9 - 4
app/activities/PriceCheck.py

@@ -6,6 +6,7 @@
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 from .. import COPYRIGHT
 from ..widgets import (
+    AutoCompletePopUp,
     NoTabCheckBox,
     AutoCompleteEdit,
     AutoCompleteFloatEdit,
@@ -131,7 +132,7 @@ class PriceCheck(FocusWidget):
 
     def __init__(self, query_manager, fields,
         top_pane, left_pane, right_pane, bottom_pane, badge,
-        autocomplete_cb):
+        autocomplete_cb, apply_choice_cb):
         self.query_manager = query_manager
         self.top_pane = top_pane
         self.left_pane = left_pane
@@ -150,9 +151,9 @@ class PriceCheck(FocusWidget):
         fields = [f for f in fields ]
         for k in fields:
             if k in self.right_pane and k != 'unit':
-                ef = AutoCompleteFloatEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
+                ef = AutoCompleteFloatEdit(('bg', k))
             elif k != 'organic':
-                ef = AutoCompleteEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
+                ef = AutoCompleteEdit(('bg', k))
             elif k == 'organic':
                 self.data[k] = 'mixed'
                 continue
@@ -195,7 +196,11 @@ class PriceCheck(FocusWidget):
         ])
         banner = urwid.AttrMap(banner, 'banner')
         fields = dict([
-            (k, urwid.LineBox(urwid.AttrMap(self.edit_fields[k], 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields
+            (k, urwid.LineBox(urwid.AttrMap(AutoCompletePopUp(
+                    self.edit_fields[k],
+                    apply_change_func=lambda name, pop_up_cb: autocomplete_cb(name, self.data, pop_up_cb),
+                    apply_choice_cb=apply_choice_cb,
+                ), 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields
         ])
         fields['organic'] = urwid.AttrMap(self.organic_checkbox, 'bg')
         fields.update({

+ 15 - 5
app/activities/TransactionEditor.py

@@ -6,6 +6,7 @@
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 from .. import COPYRIGHT
 from ..widgets import (
+    AutoCompletePopUp,
     NoTabCheckBox,
     AutoCompleteEdit,
     AutoCompleteFloatEdit,
@@ -82,7 +83,7 @@ class TransactionEditor(FocusWidget):
 
     def __init__(self, query_manager, fields,
         layout, side_pane, bottom_pane,
-        save_and_clear_cb, autocomplete_cb):
+        save_and_clear_cb, autocomplete_cb, apply_choice_cb):
         self.organic_checkbox = NoTabCheckBox(
             u"Organic",
             on_state_change=self.apply_organic_state
@@ -96,11 +97,12 @@ class TransactionEditor(FocusWidget):
         self.text_fields = OrderedDict()
         for k in self.data:
             if k in self.side_pane and k != 'unit':
-                ef = AutoCompleteFloatEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
+                ef = AutoCompleteFloatEdit(('bg', k))
             elif k != 'organic':
-                ef = AutoCompleteEdit(('bg', k), apply_change_func=lambda name: autocomplete_cb(name, self.data))
+                ef = AutoCompleteEdit(('bg', k))
             else:
                 continue
+
             ef.set_edit_text(self.data[k])
             urwid.connect_signal(ef, 'change', self.apply_changes(k))
             self.edit_fields[k] = ef
@@ -115,11 +117,19 @@ class TransactionEditor(FocusWidget):
         ])
         banner = urwid.AttrMap(banner, 'banner')
         fields = dict([
-            (k, urwid.LineBox(urwid.AttrMap(self.edit_fields[k], 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields if k != 'product'
+            (k, urwid.LineBox(urwid.AttrMap(AutoCompletePopUp(
+                    self.edit_fields[k],
+                    apply_change_func=lambda name, pop_up_cb: autocomplete_cb(name, self.data, pop_up_cb),
+                    apply_choice_cb=apply_choice_cb,
+                ), 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields if k != 'product'
         ])
         fields.update({
             'product': urwid.LineBox(urwid.Columns([
-                urwid.AttrMap(self.edit_fields['product'],'streak'),
+                        urwid.AttrMap(AutoCompletePopUp(
+                            self.edit_fields['product'],
+                            apply_change_func=lambda name, pop_up_cb: autocomplete_cb(name, self.data, pop_up_cb),
+                            apply_choice_cb=apply_choice_cb,
+                        ), 'streak'),
                 self.organic_checkbox
             ], dividechars=2), title='Product', title_align='left')
         })

+ 40 - 27
app/widgets.py

@@ -4,16 +4,14 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from collections import OrderedDict
+from typing import Iterable
 import urwid
 from decimal import Decimal
 from urwid import numedit
-
-import app
-from app.db_utils import NON_IDENTIFIER_COLUMNS
+from urwid.wimp import PopUpLauncher
 
 class AutoCompleteEdit(urwid.Edit):
-    def __init__(self, name, *args, apply_change_func=None, **kwargs):
+    def __init__(self, name: str, *args: Iterable, **kwargs):
         if isinstance(name, tuple):
             pallete, title = name
             self.name = title
@@ -23,10 +21,9 @@ class AutoCompleteEdit(urwid.Edit):
             self.name = name
             title = name.title()
             passthrough = u'  ' if name.lower() == 'unit' else u''
-            
+        
         super().__init__(passthrough, *args, **kwargs)
-        self.apply = apply_change_func
-
+    
     def keypress(self, size, key):
         if key == 'enter':
             self.apply(self.name)
@@ -37,9 +34,8 @@ class AutoCompleteEdit(urwid.Edit):
         
         return super().keypress(size, key)
 
-
 class AutoCompleteFloatEdit(numedit.FloatEdit):
-    def __init__(self, name, *args, apply_change_func=None, **kwargs):
+    def __init__(self, name: str, *args: Iterable, **kwargs: dict):
         self.last_val = None
         self.op = '='
         self.pallete = None
@@ -51,10 +47,9 @@ class AutoCompleteFloatEdit(numedit.FloatEdit):
             self.name = name
             title = name.title()
             passthrough = title
-            
-        super(AutoCompleteFloatEdit, self).__init__(passthrough, *args, **kwargs)
-        self.apply = apply_change_func
-
+        
+        super().__init__(passthrough, *args, **kwargs) 
+    
     def update_caption(self):
         if self.pallete is not None:
             self.set_caption((self.pallete, f'{self.op} '))
@@ -205,28 +200,46 @@ class FocusWidget(urwid.WidgetPlaceholder):
 
         self._set_focus_path(self.initial_focus)
 
-class SuggestionPopup(urwid.Overlay):
+class AutoCompletePopUp(PopUpLauncher):
+    def __init__(self, widget, apply_change_func=None, apply_choice_cb=None):
+        super().__init__(widget)
+        self._original_widget.apply = lambda name: apply_change_func(name, self._open_pop_up)
+        self._original_widget.apply_choice_cb = apply_choice_cb
+
+    def _open_pop_up(self, options):
+        self.options = options
+        self.open_pop_up()
     
-    def __init__(self, under, name, options, apply_cb, esc_cb):
-        self.esc_cb = esc_cb
-        self.under = under
-        body = [urwid.Text(name.title()), urwid.Divider()]
+    def create_pop_up(self):
+        pop_up = SuggestionPopup(
+            self._original_widget.name, self.options,
+            lambda w,x: self._original_widget.apply_choice_cb(self._original_widget.name, w, x),
+        )
+        urwid.connect_signal(pop_up, 'close',
+            lambda _: self.close_pop_up())
+        return pop_up
+
+    def get_pop_up_parameters(self):
+        return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height': 10}
+
+class SuggestionPopup(urwid.WidgetWrap):
+    
+    signals = ['close']
+
+    def __init__(self, name, options, apply_cb):
+        body = [] #[urwid.Text(name.title()), urwid.Divider()]
         for c in options:
             button = urwid.Button(c)
             urwid.connect_signal(button, 'click', apply_cb, c)
+            urwid.connect_signal(button, 'click', lambda _: self._emit("close"))
             body.append(urwid.AttrMap(button, None, focus_map='reversed'))
         walker = urwid.SimpleFocusListWalker(body, wrap_around=False)
         listbox = urwid.ListBox(walker)
-        pad = urwid.Padding(listbox, left=2, right=2)
-        pad = urwid.AttrMap(pad, 'banner')
-        super().__init__(pad, under,
-            align='center', width=('relative', 60),
-            valign='middle', height=('relative', 60),
-            min_width=20, min_height=9)
-
+        super().__init__(urwid.AttrWrap(listbox, 'banner'))
+        
     def keypress(self, size, key):
         if key == 'esc':
-            self.esc_cb()
+            self._emit("close")
             return
 
         if key == 'tab':

+ 10 - 18
grocery_transactions.py

@@ -14,9 +14,6 @@ from app.db_utils import (
     QueryManager,
     get_insert_product_statement
 )
-from app.widgets import (
-    SuggestionPopup,
-)
 from app.activities import (
     show_or_exit,
     ActivityManager,
@@ -85,15 +82,6 @@ def _apply_choice_callback(activity_manager, base, name, widget, value):
     base.apply_choice(name)(widget, value)
     activity_manager.show(base.update())
 
-def _show_suggestions_callback(activity_manager, base, name, options):
-    cur = activity_manager.current()
-    activity_manager.show(
-        activity_manager.create(SuggestionPopup, None,
-            cur, name, options,
-            lambda w,x: _apply_choice_callback(activity_manager, base, name, w, x),
-            lambda: activity_manager.show(cur)
-        ))
-
 def _insert_new_product_callback(activity_manager, query_manager, product, category, group):
     activity_manager.app.log.write(
         '{};\n'.format(get_insert_product_statement(product, category, group)))
@@ -110,11 +98,12 @@ def _new_product_callback(
     activity_manager.show(
         activity_manager.create(NewProduct, 'new_product',
             query_manager, cur, name, txn.data,
-            lambda t: _autocomplete_callback(activity_manager, query_manager, 'new_product', t, data),
+            lambda t, open_pop_up_cb: _autocomplete_callback(activity_manager, query_manager, 'new_product', t, data, open_pop_up_cb),
             txn.apply_changes,
             lambda product, category, group: _insert_new_product_callback(
                 activity_manager, query_manager, product, category, group),
-            lambda: activity_manager.show(cur)
+            lambda: activity_manager.show(cur),
+            lambda name, widget, value: _apply_choice_callback(activity_manager, 'new_product', name, widget, value)
         )
     )
 
@@ -122,11 +111,13 @@ def _autocomplete_callback(
     activity_manager: ActivityManager,
     query_manager: QueryManager,
     base: str,
-    name: str, data: dict
+    name: str, data: dict,
+    open_pop_up_cb,
 ):
     options = query_manager.unique_suggestions(name, **data)
     if len(options) > 0:
-        _show_suggestions_callback(activity_manager, base, name, options)
+        open_pop_up_cb(options)
+        
     elif len(options) == 0 and activity_manager.current() is not activity_manager.get('new_product'):
         if name in ('product', 'category', 'group'):
             _new_product_callback(activity_manager, query_manager, name, data)
@@ -213,11 +204,12 @@ query_manager = QueryManager(cur, display)
 activity_manager.create(TransactionEditor, 'transaction',
     query_manager, cols, grid_layout, side_pane, bottom_pane,
     lambda: _save_and_clear_callback(activity_manager),
-    lambda name, data: _autocomplete_callback(activity_manager, query_manager, 'transaction', name, data))
+    lambda name, data, open_pop_up_cb: _autocomplete_callback(activity_manager, query_manager, 'transaction', name, data, open_pop_up_cb),
+    lambda name, widget, value: _apply_choice_callback(activity_manager, 'transaction', name, widget, value))
 
 app = GroceryTransactionEditor(activity_manager, cur, log)
 
-loop = urwid.MainLoop(app, palette, unhandled_input=show_or_exit)
+loop = urwid.MainLoop(app, palette, unhandled_input=show_or_exit, pop_ups=True)
 loop.run()
 
 cur.close()

+ 7 - 18
price_check.py

@@ -9,9 +9,6 @@ import itertools
 import urwid
 from app import COPYRIGHT
 from app.db_utils import QueryManager
-from app.widgets import (
-    SuggestionPopup,
-)
 from app.activities import (
     show_or_exit,
     ActivityManager,
@@ -83,24 +80,15 @@ display_map = {
 }
 display = lambda data, name: display_map[name](data) if name in display_map else data
 
-def _apply_choice_callback(activity_manager, name, widget, value):
-    activity = activity_manager.get('price_check')
+def _apply_choice_callback(activity_manager, base, name, widget, value):
+    activity = activity_manager.get(base)
     activity.apply_choice(name, widget, value)
     activity_manager.show(activity.update())
 
-def _show_suggestions_callback(activity_manager, name, options):
-    txn = activity_manager.get('price_check')
-    activity_manager.show(
-        activity_manager.create(SuggestionPopup, None,
-            txn.original_widget, name, options,
-            lambda w,x: _apply_choice_callback(activity_manager, name, w, x),
-            lambda: activity_manager.show(txn.update())
-        ))
-
-def _autocomplete_callback(activity_manager, query_manager, name, data):
+def _autocomplete_callback(activity_manager, query_manager, name, data, open_pop_up_cb):
     options = query_manager.unique_suggestions(name, **data)
     if len(options) > 0:
-        _show_suggestions_callback(activity_manager, name, options)
+        open_pop_up_cb(options)
 
 class GroceryPriceCheck(urwid.WidgetPlaceholder):
     def __init__(self, activity_manager):
@@ -118,11 +106,12 @@ query_manager = QueryManager(cur, display)
 
 activity_manager.create(PriceCheck, 'price_check',
     query_manager, inputs, top_pane, left_pane, right_pane, bottom_pane, badge,
-    lambda name, data: _autocomplete_callback(activity_manager, query_manager, name, data))
+    lambda name, data, open_pop_up_cb: _autocomplete_callback(activity_manager, query_manager, name, data, open_pop_up_cb),
+    lambda name, widget, value: _apply_choice_callback(activity_manager, 'price_check', name, widget, value))
 
 app = GroceryPriceCheck(activity_manager)
 
-loop = urwid.MainLoop(app, palette, unhandled_input=show_or_exit)
+loop = urwid.MainLoop(app, palette, unhandled_input=show_or_exit, pop_ups=True)
 loop.run()
 
 cur.close()