Переглянути джерело

replace callbacks with signal handlers for SuggestionPopup

Daniel Sheffield 3 роки тому
батько
коміт
1a7cdcd541

+ 13 - 14
app/activities/NewProduct.py

@@ -4,17 +4,20 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
+from collections import OrderedDict
+from typing import Callable
+
 import urwid
 from app.db_utils import NON_IDENTIFIER_COLUMNS
-from ..widgets import (
-    AutoCompleteEdit,
-    AutoCompletePopUp,
-)
 
-from collections import OrderedDict
+from ..widgets import AutoCompleteEdit, AutoCompletePopUp
+
+
 class NewProduct(urwid.Overlay):
 
-    def __init__(self, query_manager, under, name, data, autocomplete_cb, change_cb, apply_cb, esc_cb, apply_choice_cb):
+    def __init__(self, query_manager, under, name, data, autocomplete_cb, change_cb, apply_cb, esc_cb,
+        apply_choice_cb: Callable[[str, str], None]
+    ):
         self.esc_cb = esc_cb
         self.under = under
         self._data = data
@@ -27,7 +30,8 @@ class NewProduct(urwid.Overlay):
             w = AutoCompleteEdit(('bg', f))
             self.fields[f] = w
             w.set_edit_text(data[f])
-            urwid.connect_signal(w, 'change', change_cb(f))
+            urwid.connect_signal(w, 'change', lambda w, v: change_cb(w.name, v))
+            urwid.connect_signal(w, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
 
         ok = urwid.Button('Done', on_press=lambda w: apply_cb(**self.data))
 
@@ -39,9 +43,7 @@ class NewProduct(urwid.Overlay):
                         urwid.AttrMap(
                             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,
+                                    v, apply_choice_cb
                                 ),'streak'), title=k.title(), title_align='left'), 'banner'
                         ) for k,v in self.fields.items()
                     ],
@@ -58,7 +60,7 @@ class NewProduct(urwid.Overlay):
     def data(self):
         return dict([(k,v) for k,v in self._data.items() if k in ('product', 'category', 'group')])
     
-    def _apply_choice(self, name, value):
+    def apply_choice(self, name, value):
         self._data.update({
             name: value
         })
@@ -70,9 +72,6 @@ class NewProduct(urwid.Overlay):
                 self._data.update({
                     k: list(options)[0]
                 })
-    
-    def apply_choice(self, name):
-        return  lambda w,x: self._apply_choice(name, x)
 
     def update(self):
         for k, v in self.data.items():

+ 25 - 23
app/activities/PriceCheck.py

@@ -4,21 +4,19 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from .. import COPYRIGHT
-from ..widgets import (
-    AutoCompletePopUp,
-    NoTabCheckBox,
-    AutoCompleteEdit,
-    AutoCompleteFloatEdit,
-    FocusWidget
-)
-from . import show_or_exit
+from collections import OrderedDict
 from decimal import Decimal, InvalidOperation
-from collections import (
-    OrderedDict,
-)
+from typing import Callable, List, Union
+
 import numpy as np
 import urwid
+
+from .. import COPYRIGHT
+from ..widgets import (AutoCompleteEdit, AutoCompleteFloatEdit,
+                       AutoCompletePopUp, FocusWidget, NoTabCheckBox)
+from . import show_or_exit
+
+
 class PriceCheck(FocusWidget):
 
     def keypress(self, size, key):
@@ -35,16 +33,16 @@ class PriceCheck(FocusWidget):
         else:
             return super().keypress(size, key)
 
-    def apply_choice(self, name, widget, value):
-        self.apply_changes(name, widget, value)
+    def apply_choice(self, name, value):
+        self.apply_changes(name, value)
         for k,v in self.data.items():
             if k == name or v:
                 continue
             options = self.query_manager.unique_suggestions(k, **self.data)
             if len(options) == 1:
-                self.apply_changes(k, widget, list(options)[0])
+                self.apply_changes(k, list(options)[0])
 
-    def apply_changes(self, name, widget, value):
+    def apply_changes(self, name, value):
         if name in self.data:
             
             self.data.update({
@@ -130,9 +128,14 @@ class PriceCheck(FocusWidget):
                     sort, product, unit, organic=organic) 
             )
 
-    def __init__(self, query_manager, fields,
+    def __init__(self, query_manager,
+        fields: List[str],
         top_pane, left_pane, right_pane, bottom_pane, badge,
-        autocomplete_cb, apply_choice_cb):
+        autocomplete_cb: Callable[[
+            Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
+        ], None],
+        apply_choice_cb: Callable[[str, str], None]
+    ):
         self.query_manager = query_manager
         self.top_pane = top_pane
         self.left_pane = left_pane
@@ -159,7 +162,8 @@ class PriceCheck(FocusWidget):
                 continue
             
             self.data[k] = ''
-            urwid.connect_signal(ef, 'change', lambda w,v,name=k: self.apply_changes(name, w, v))
+            urwid.connect_signal(ef, 'change', lambda w, v: self.apply_changes(w.name, v))
+            urwid.connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
             self.edit_fields[k] = ef
         
         self.buttons = OrderedDict()
@@ -197,9 +201,7 @@ class PriceCheck(FocusWidget):
         banner = urwid.AttrMap(banner, 'banner')
         fields = dict([
             (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,
+                    self.edit_fields[k], apply_choice_cb
                 ), 'streak'), title=k.title(), title_align='left')) for k in self.edit_fields
         ])
         fields['organic'] = urwid.AttrMap(self.organic_checkbox, 'bg')
@@ -271,4 +273,4 @@ class PriceCheck(FocusWidget):
                 [3,1,3,],
                 [4,0,0,],
             ]
-        )
+        )

+ 27 - 31
app/activities/TransactionEditor.py

@@ -4,19 +4,17 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from .. import COPYRIGHT
-from ..widgets import (
-    AutoCompletePopUp,
-    NoTabCheckBox,
-    AutoCompleteEdit,
-    AutoCompleteFloatEdit,
-    FocusWidget
-)
-from collections import (
-    OrderedDict,
-)
+from collections import OrderedDict
+from typing import Callable, List, Union
 
 import urwid
+from app.db_utils import QueryManager
+
+from .. import COPYRIGHT
+from ..widgets import (AutoCompleteEdit, AutoCompleteFloatEdit,
+                       AutoCompletePopUp, FocusWidget, NoTabCheckBox)
+
+
 class TransactionEditor(FocusWidget):
 
     def keypress(self, size, key):
@@ -33,31 +31,25 @@ class TransactionEditor(FocusWidget):
         else:
             return super().keypress(size, key)
 
-    def _init_data(self, fields):
+    def _init_data(self, fields: List[str]) -> None:
         self.data = OrderedDict([
             (k, '') for k in fields
         ])
         self.clear()
 
-    def _apply_choice(self, name, value):
-        self._apply_changes(name, value)
+    def apply_choice(self, name, value):
+        self.apply_changes(name, value)
         for k,v in self.data.items():
             if k == name or v:
                 continue
             options = self.query_manager.unique_suggestions(k, **self.data)
             if len(options) == 1 and k != 'ts':
-                self._apply_changes(k, list(options)[0])
-    
-    def apply_choice(self, name):
-        return  lambda w,x: self._apply_choice(name, x)
+                self.apply_changes(k, list(options)[0])
 
-    def _apply_changes(self, name, value):
+    def apply_changes(self, name, value):
         self.data.update({
             name: value,
         })
-    
-    def apply_changes(self, name):
-        return lambda w,x: self._apply_changes(name, x)
 
     def apply_organic_state(self, w, state):
         self.data['organic'] = repr(state).lower()
@@ -81,9 +73,16 @@ class TransactionEditor(FocusWidget):
         self.organic_checkbox.set_state(True if self.data['organic'] == 'true' else False)
         return self
 
-    def __init__(self, query_manager, fields,
+    def __init__(self,
+        query_manager: QueryManager,
+        fields: List[str],
         layout, side_pane, bottom_pane,
-        save_and_clear_cb, autocomplete_cb, apply_choice_cb):
+        save_and_clear_cb,
+        autocomplete_cb: Callable[[
+            Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
+        ], None],
+        apply_choice_cb: Callable[[str, str], None]
+    ):
         self.organic_checkbox = NoTabCheckBox(
             u"Organic",
             on_state_change=self.apply_organic_state
@@ -104,7 +103,8 @@ class TransactionEditor(FocusWidget):
                 continue
 
             ef.set_edit_text(self.data[k])
-            urwid.connect_signal(ef, 'change', self.apply_changes(k))
+            urwid.connect_signal(ef, 'change', lambda w, v: self.apply_changes(w.name, v))
+            urwid.connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
             self.edit_fields[k] = ef
     
         header = urwid.Text(u'Fill Transaction', 'center')
@@ -118,17 +118,13 @@ class TransactionEditor(FocusWidget):
         banner = urwid.AttrMap(banner, 'banner')
         fields = dict([
             (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,
+                    self.edit_fields[k], 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(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,
+                            self.edit_fields['product'], apply_choice_cb,
                         ), 'streak'),
                 self.organic_checkbox
             ], dividechars=2), title='Product', title_align='left')

+ 27 - 12
app/widgets.py

@@ -4,13 +4,17 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from typing import Iterable
-import urwid
 from decimal import Decimal
+from typing import Callable, Iterable, Union
+
+import urwid
 from urwid import numedit
 from urwid.wimp import PopUpLauncher
 
+
 class AutoCompleteEdit(urwid.Edit):
+    signals = [ *urwid.Edit.signals, 'apply', 'open' ]
+
     def __init__(self, name: str, *args: Iterable, **kwargs):
         if isinstance(name, tuple):
             pallete, title = name
@@ -26,7 +30,7 @@ class AutoCompleteEdit(urwid.Edit):
     
     def keypress(self, size, key):
         if key == 'enter':
-            self.apply(self.name)
+            self._emit('apply', self.name)
             return
         elif key == 'delete':
             self.set_edit_text('')
@@ -35,6 +39,8 @@ class AutoCompleteEdit(urwid.Edit):
         return super().keypress(size, key)
 
 class AutoCompleteFloatEdit(numedit.FloatEdit):
+    signals = [ *urwid.Edit.signals, 'apply', 'open' ]
+
     def __init__(self, name: str, *args: Iterable, **kwargs: dict):
         self.last_val = None
         self.op = '='
@@ -48,7 +54,7 @@ class AutoCompleteFloatEdit(numedit.FloatEdit):
             title = name.title()
             passthrough = title
         
-        super().__init__(passthrough, *args, **kwargs) 
+        super().__init__(passthrough, *args, **kwargs)
     
     def update_caption(self):
         if self.pallete is not None:
@@ -97,7 +103,8 @@ class AutoCompleteFloatEdit(numedit.FloatEdit):
             return
         elif key == 'enter':
             if self.get_edit_text() == '' or self.value() == Decimal(0.0):
-                return self.apply(self.name)
+                self._emit('open', self.name)
+                return
             self.calc()
             return
         elif key == '=':
@@ -201,10 +208,13 @@ class FocusWidget(urwid.WidgetPlaceholder):
         self._set_focus_path(self.initial_focus)
 
 class AutoCompletePopUp(PopUpLauncher):
-    def __init__(self, widget, apply_change_func=None, apply_choice_cb=None):
+    def __init__(self,
+        widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
+        apply_choice_cb: Callable[[str, str], 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
+        self.apply_choice_cb = apply_choice_cb
+        urwid.connect_signal(self._original_widget, 'open', lambda _, options: self._open_pop_up(options))
 
     def _open_pop_up(self, options):
         self.options = options
@@ -213,7 +223,7 @@ class AutoCompletePopUp(PopUpLauncher):
     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),
+            self.apply_choice_cb,
         )
         urwid.connect_signal(pop_up, 'close',
             lambda _: self.close_pop_up())
@@ -226,11 +236,16 @@ class SuggestionPopup(urwid.WidgetWrap):
     
     signals = ['close']
 
-    def __init__(self, name, options, apply_cb):
-        body = [] #[urwid.Text(name.title()), urwid.Divider()]
+    def __init__(self,
+        name: str,
+        options: Iterable,
+        apply_cb: Callable[[str, str], None]
+    ):
+        self.apply_cb = lambda _, v: apply_cb(name, v)
+        body = []
         for c in options:
             button = urwid.Button(c)
-            urwid.connect_signal(button, 'click', apply_cb, c)
+            urwid.connect_signal(button, 'click', self.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)

+ 17 - 15
grocery_transactions.py

@@ -8,9 +8,9 @@
 import time
 import itertools
 import sys
+from typing import Union
 import urwid
 from app.db_utils import (
-    NON_IDENTIFIER_COLUMNS,
     QueryManager,
     get_insert_product_statement
 )
@@ -20,6 +20,7 @@ from app.activities import (
 )  
 from app.activities.TransactionEditor import TransactionEditor
 from app.activities.NewProduct import NewProduct
+from app.widgets import AutoCompleteEdit, AutoCompleteFloatEdit
 
 try:
     from db_credentials import HOST, PASSWORD
@@ -77,9 +78,12 @@ display_map = {
 }
 display = lambda data, name: display_map[name](data) if name in display_map else data
 
-def _apply_choice_callback(activity_manager, base, name, widget, value):
+def _apply_choice_callback(
+    activity_manager: ActivityManager,
+    base: str, name: str, value: str
+):
     base = activity_manager.get(base)
-    base.apply_choice(name)(widget, value)
+    base.apply_choice(name, value)
     activity_manager.show(base.update())
 
 def _insert_new_product_callback(activity_manager, query_manager, product, category, group):
@@ -98,26 +102,24 @@ def _new_product_callback(
     activity_manager.show(
         activity_manager.create(NewProduct, 'new_product',
             query_manager, cur, name, txn.data,
-            lambda t, open_pop_up_cb: _autocomplete_callback(activity_manager, query_manager, 'new_product', t, data, open_pop_up_cb),
+            lambda w, t, data: _autocomplete_callback(activity_manager, query_manager, w, t, data),
             txn.apply_changes,
             lambda product, category, group: _insert_new_product_callback(
                 activity_manager, query_manager, product, category, group),
             lambda: activity_manager.show(cur),
-            lambda name, widget, value: _apply_choice_callback(activity_manager, 'new_product', name, widget, value)
+            lambda name, value: _apply_choice_callback(activity_manager, 'new_product', name, value)
         )
     )
 
 def _autocomplete_callback(
     activity_manager: ActivityManager,
     query_manager: QueryManager,
-    base: str,
-    name: str, data: dict,
-    open_pop_up_cb,
+    widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
+    name: str, data: dict
 ):
     options = query_manager.unique_suggestions(name, **data)
     if len(options) > 0:
-        open_pop_up_cb(options)
-        
+        widget._emit('open', 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)
@@ -137,7 +139,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
         super().__init__(urwid.SolidFill(u'/'))
         self.activity_manager = activity_manager
         self.cur = cur
-        txn = self.activity_manager.get('transaction')
+        txn: TransactionEditor = self.activity_manager.get('transaction')
 
         with open(log, 'r') as f:
             date = None
@@ -159,8 +161,8 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
                 self.cur.execute(line)
         
             if None not in (date, store):
-                txn._apply_choice('ts', date)
-                txn._apply_choice('store', store)
+                txn.apply_choice('ts', date)
+                txn.apply_choice('store', store)
 
         self.activity_manager.show(self)
         self.activity_manager.show(txn.update())
@@ -204,8 +206,8 @@ 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, 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))
+    lambda widget, name, data: _autocomplete_callback(activity_manager, query_manager, widget, name, data),
+    lambda name, value: _apply_choice_callback(activity_manager, 'transaction', name, value))
 
 app = GroceryTransactionEditor(activity_manager, cur, log)
 

+ 18 - 10
price_check.py

@@ -6,14 +6,15 @@
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 import itertools
+from typing import Union
 import urwid
-from app import COPYRIGHT
 from app.db_utils import QueryManager
 from app.activities import (
     show_or_exit,
     ActivityManager,
 )
 from app.activities.PriceCheck import PriceCheck
+from app.widgets import AutoCompleteEdit, AutoCompleteFloatEdit
 try:
     from db_credentials import HOST, PASSWORD
     host = f'host={HOST}'
@@ -80,15 +81,22 @@ display_map = {
 }
 display = lambda data, name: display_map[name](data) if name in display_map else data
 
-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 _autocomplete_callback(activity_manager, query_manager, name, data, open_pop_up_cb):
+def _apply_choice_callback(
+    activity_manager: ActivityManager,
+    base: str, name: str, value: str
+):
+    base = activity_manager.get(base)
+    base.apply_choice(name, value)
+    activity_manager.show(base.update())
+
+def _autocomplete_callback(
+    query_manager: QueryManager,
+    widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
+    name: str, data: dict
+):
     options = query_manager.unique_suggestions(name, **data)
     if len(options) > 0:
-        open_pop_up_cb(options)
+        widget._emit('open', options)
 
 class GroceryPriceCheck(urwid.WidgetPlaceholder):
     def __init__(self, activity_manager):
@@ -106,8 +114,8 @@ 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, 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))
+    lambda widget, name, data: _autocomplete_callback(query_manager, widget, name, data),
+    lambda name, value: _apply_choice_callback(activity_manager, 'price_check', name, value))
 
 app = GroceryPriceCheck(activity_manager)