Browse Source

refactor widget layout and remove redundant data layer (TransactionEditor.py, grocery_transactions.py)

Daniel Sheffield 2 years ago
parent
commit
793929e3c0
4 changed files with 156 additions and 165 deletions
  1. 30 21
      app/activities/PriceCheck.py
  2. 123 87
      app/activities/TransactionEditor.py
  3. 1 27
      grocery_transactions.py
  4. 2 30
      price_check.py

+ 30 - 21
app/activities/PriceCheck.py

@@ -12,12 +12,14 @@ import numpy as np
 from urwid import (
     connect_signal,
     AttrMap,
+    Button,
     Columns,
     Divider,
     Filler,
     LineBox,
     Padding,
     Pile,
+    RadioButton,
     Text,
 )
 from app.db_utils import QueryManager
@@ -27,7 +29,8 @@ from ..widgets import (
     AutoCompleteEdit,
     AutoCompleteFloatEdit,
     FocusWidget,
-    AutoCompletePopUp
+    AutoCompletePopUp,
+    NoTabCheckBox
 )
 from . import ActivityManager, show_or_exit
 
@@ -57,18 +60,13 @@ class PriceCheck(FocusWidget):
                 self.apply_changes(k, list(options)[0])
 
     def apply_changes(self, name, value):
-        if name == 'organic':
-            self.data = {
-                name: {
-                    'yes': True, 'no': False,
-                    True: True, False: False,
-                    'mixed': 'mixed'
-                }[value]
-            }
-        elif name in self.data:
-            self.data = {
-                name: value,
-            }
+        self.data = {
+            name: value if name != 'organic' else {
+                'yes': True, 'no': False,
+                True: True, False: False,
+                'mixed': '',
+            }[value],
+        }
 
     @property
     def data(self):
@@ -163,14 +161,29 @@ class PriceCheck(FocusWidget):
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
-        buttons: OrderedDict, button_group: List,
-        checkboxes: OrderedDict,
-        edit_fields: OrderedDict,
-        text_fields: OrderedDict,
         autocomplete_cb: Callable[[
             Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
         ], None],
     ):
+        button_group = []
+        self.buttons = {
+          'clear': Button(('streak', 'Clear')),
+          'exit': Button(('streak', 'Exit')),
+          'sort_price': RadioButton(button_group, ('streak', 'Best'), state="first True"),
+          'sort_date': RadioButton(button_group, ('streak', 'Last'), state="first True"),
+        }
+        self.edit_fields = {
+          'product': AutoCompleteEdit(('bg', 'product')),
+          'unit': AutoCompleteEdit(('bg', 'unit')),
+          'quantity': AutoCompleteFloatEdit(('bg', 'quantity')),
+          'price': AutoCompleteFloatEdit(('bg', 'price')),
+        }
+        self.checkboxes = {
+          'organic': NoTabCheckBox(('bg', "Organic"), state='mixed'),
+        }
+        self.text_fields = dict((
+         (k, Text('')) for k in ('dbview', 'spread', 'rating', 'marker')
+        ))
         top_pane = [ 'clear', 'exit', ['sort_price', 'sort_date'], ]
 
         left_pane = [
@@ -196,10 +209,6 @@ class PriceCheck(FocusWidget):
             [ bottom_pane, ],
         ]
         self.query_manager = query_manager
-        self.edit_fields = edit_fields
-        self.text_fields = text_fields
-        self.buttons = buttons
-        self.checkboxes = checkboxes
         self.organic_checkbox = self.checkboxes['organic']
         connect_signal(self.organic_checkbox, 'postchange', lambda _,v: self.update())
         for (k, ef) in self.edit_fields.items():

+ 123 - 87
app/activities/TransactionEditor.py

@@ -4,16 +4,31 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from collections import OrderedDict
+import itertools
 from typing import Callable, List, Union
-
-import urwid
+from urwid import (
+    connect_signal,
+    AttrMap,
+    Button,
+    Columns,
+    Divider,
+    Filler,
+    LineBox,
+    Padding,
+    Pile,
+    Text,
+)
 from app.activities import ActivityManager
 from app.db_utils import QueryManager
 
 from .. import COPYRIGHT
-from ..widgets import (AutoCompleteEdit, AutoCompleteFloatEdit,
-                       AutoCompletePopUp, FocusWidget, NoTabCheckBox)
+from ..widgets import (
+    AutoCompleteEdit,
+    AutoCompleteFloatEdit,
+    FocusWidget,
+    AutoCompletePopUp,
+    NoTabCheckBox
+)
 
 
 class TransactionEditor(FocusWidget):
@@ -39,12 +54,6 @@ class TransactionEditor(FocusWidget):
         else:
             return super().keypress(size, key)
 
-    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)
         for k,v in self.data.items():
@@ -55,35 +64,50 @@ class TransactionEditor(FocusWidget):
                 self.apply_changes(k, list(options)[0])
 
     def apply_changes(self, name, value):
-        self.data.update({
+        self.data = {
             name: value if name != 'organic' else {
                 'yes': True, 'no': False,
                 True: True, False: False,
                 'mixed': '',
             }[value],
-        })
-
-    def apply_organic_state(self, w, state):
-        self.data['organic'] = state if state != 'mixed' else ''
+        }
+
+    @property
+    def data(self):
+        ret = dict(itertools.chain(
+            [(k, v.get_edit_text()) for k,v in self.edit_fields.items()],
+            [(k, v.state) for k,v in self.checkboxes.items()]
+        ))
+        return ret
+
+    @data.setter
+    def data(self, _data: dict):
+      for k,v in _data.items():
+        if k in self.edit_fields and v != self.edit_fields[k].get_edit_text():
+            self.edit_fields[k].set_edit_text(v)
+        if k in self.checkboxes and v != self.checkboxes[k].state:
+            self.checkboxes[k].set_state(v)
 
     def clear(self):
-        for k in self.data:
+        for (k, ef) in self.edit_fields.items():
             if k in ('ts', 'store',):
                 continue
-            self.data[k] = ''
-
+            ef.set_edit_text('')
+        for (_, cb) in self.checkboxes.items():
+            cb.set_state('mixed')
+        for (k, tf) in self.text_fields.items():
+            if k != 'dbview':
+                tf.set_text('')
         return self.update()
     
     def update(self):
-        for k in self.edit_fields:
-            self.edit_fields[k].set_edit_text(self.data[k])
-        date, store = self.data['ts'], self.data['store']
+        data = self.data
+        date, store = data['ts'], data['store']
         self.text_fields['dbview'].set_text(
             self.query_manager.get_session_transactions(date, store) if None not in (
                 date or None, store or None
             ) else ''
         )
-        self.organic_checkbox.set_state(self.data['organic'] if self.data['organic'] != '' else 'mixed')
         return self
 
     def focus_on_product(self):
@@ -95,71 +119,90 @@ class TransactionEditor(FocusWidget):
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
-        fields: List[str],
-        layout, side_pane, bottom_pane,
         save_and_clear_cb,
         autocomplete_cb: Callable[[
             Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
         ], None],
     ):
-        self.organic_checkbox = NoTabCheckBox(
-            u"Organic",
-            on_state_change=self.apply_organic_state
-        )
         self.query_manager = query_manager
         self.save_and_clear_cb = save_and_clear_cb
-        self.layout = layout
-        self.side_pane = side_pane
-        self.bottom_pane = bottom_pane
-        self.edit_fields = OrderedDict()
-        self.text_fields = OrderedDict()
+        self.buttons = {
+            'done': Button(('streak', u'Done')),
+            'clear': Button(('streak', u'Clear')),
+        }
+        self.edit_fields = {
+          'ts': AutoCompleteEdit(('bg', 'ts')),
+          'store': AutoCompleteEdit(('bg', 'store')),
+          'product': AutoCompleteEdit(('bg', 'product')),
+          'category': AutoCompleteEdit(('bg', 'category')),
+          'group': AutoCompleteEdit(('bg', 'group')),
+          'description': AutoCompleteEdit(('bg', 'description')),
+          'unit': AutoCompleteEdit(('bg', 'unit')),
+          'quantity': AutoCompleteFloatEdit(('bg', 'quantity')),
+          'price': AutoCompleteFloatEdit(('bg', 'price')),
+        }
+        self.checkboxes = {
+          'organic': NoTabCheckBox(('bg', "Organic"), state='mixed'),
+        }
+        self.text_fields = {
+            'dbview': Text('')
+        }
+        self.organic_checkbox = self.checkboxes['organic']
+        connect_signal(self.organic_checkbox, 'postchange', lambda _,v: self.update())
+        layout = [
+            [ 'ts',          'store',    ],
+            [ 'organic',     'product',  ],
+            [ 'category',    'group',   ],
+        ]
+        side_pane = [
+            'unit',
+            'quantity',
+            'price',
+        ]
+        bottom_pane = [
+            'description',
+            'dbview',
+        ]
+        self.clear()
         widgets = dict()
-        txn_view = urwid.Text('')
-        self.text_fields.update({'dbview': txn_view})
         widgets.update({
-            'dbview': urwid.LineBox(
-                urwid.AttrMap(txn_view, 'streak'),
+            'dbview': LineBox(
+                AttrMap(self.text_fields['dbview'], 'streak'),
                 title="Session Data",
                 title_align='left',
             )
         })
-        self._init_data(fields)
-        for k in self.data:
-            if k in self.side_pane and k != 'unit':
-                ef = AutoCompleteFloatEdit(('bg', k))
-            elif k != 'organic':
-                ef = AutoCompleteEdit(('bg', k))
-            else:
-                continue
-
-            ef.set_edit_text(self.data[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
+        for (k, ef) in self.edit_fields.items():
+            connect_signal(ef, 'postchange', lambda _,v: self.update())
+            connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
     
         widgets.update(dict([
-            (k, urwid.LineBox(
-                urwid.AttrMap(AutoCompletePopUp(
+            (k, LineBox(
+                AttrMap(AutoCompletePopUp(
                     self.edit_fields[k],
                     self.apply_choice,
                     lambda: activity_manager.show(self.update())
                 ), 'streak'), title=k.title(), title_align='left')
             ) for k in self.edit_fields if k != 'product'
         ]))
-        header = urwid.Text(u'Fill Transaction', 'center')
-        _copyright = urwid.Text(COPYRIGHT, 'center')
-        done_button = urwid.Button(('streak', u'Done'))
-        clear_button = urwid.Button(('streak', u'Clear'))
-        urwid.connect_signal(done_button, 'click', lambda _: save_and_clear_cb())
-        urwid.connect_signal(clear_button, 'click', lambda _: self.clear())
-        banner = urwid.Pile([
-            urwid.Padding(header, 'center', width=('relative', 100)),
-            urwid.Padding(_copyright, 'center', width=('relative', 100)),
+        header = Text(u'Fill Transaction', 'center')
+        _copyright = Text(COPYRIGHT, 'center')
+
+        components = dict()
+        components['bottom_pane'] = Columns(
+            [(8, self.buttons['done']), Divider(), (9, self.buttons['clear'])]
+        )
+        connect_signal(self.buttons['done'], 'click', lambda _: save_and_clear_cb())
+        connect_signal(self.buttons['clear'], 'click', lambda _: self.clear())
+        
+        banner = Pile([
+            Padding(header, 'center', width=('relative', 100)),
+            Padding(_copyright, 'center', width=('relative', 100)),
         ])
-        banner = urwid.AttrMap(banner, 'banner')
+        banner = AttrMap(banner, 'banner')
         widgets.update({
-            'product': urwid.LineBox(urwid.Columns([
-                urwid.AttrMap(AutoCompletePopUp(
+            'product': LineBox(Columns([
+                AttrMap(AutoCompletePopUp(
                     self.edit_fields['product'],
                     self.apply_choice,
                     lambda: activity_manager.show(self.update())
@@ -168,11 +211,11 @@ class TransactionEditor(FocusWidget):
             ], dividechars=2), title='Product', title_align='left')
         })
         
-        side_pane_widget = (12, urwid.Pile([
-            widgets[r] if r is not None else urwid.Divider() for r in self.side_pane
+        side_pane_widget = (12, Pile([
+            widgets[r] if r is not None else Divider() for r in side_pane
         ]))
         main_pane_widgets = []
-        for i, r in enumerate(self.layout):
+        for i, r in enumerate(layout):
             _widgets = []
             for c in r:
                 if c is not None:
@@ -180,28 +223,21 @@ class TransactionEditor(FocusWidget):
                         continue
                     _widgets.append(widgets[c])
                 else:
-                    _widgets.append(urwid.Divider())
-            main_pane_widgets.append(urwid.Columns(_widgets))
+                    _widgets.append(Divider())
+            main_pane_widgets.append(Columns(_widgets))
 
-
-        main_pane_widget = urwid.Pile(main_pane_widgets)
-        
-        widget = urwid.Pile([
+        widget = Pile([
             banner,
-            urwid.Divider(),
-            urwid.Columns([
-                    main_pane_widget, side_pane_widget
-                ],
-                dividechars=2,
-            ),
-            *[ widgets[c] if c is not None else urwid.Divider() for c in self.bottom_pane ],
-            urwid.Divider(),
-            urwid.Columns([
-                (8, done_button), urwid.Divider(), (9, clear_button)
-            ])
+            Divider(),
+            Columns([
+                Pile(main_pane_widgets), side_pane_widget
+            ], dividechars=2),
+            *[ widgets[c] if c is not None else Divider() for c in bottom_pane ],
+            Divider(),
+            components['bottom_pane']
         ])
-        widget = urwid.Filler(widget, 'top') 
-        widget = urwid.AttrMap(widget, 'bg')
+        widget = Filler(widget, 'top') 
+        widget = AttrMap(widget, 'bg')
         super().__init__(widget, 5, [2,0,0,0], [
             [0, 0], [0,1],
             [1,],

+ 1 - 27
grocery_transactions.py

@@ -12,7 +12,6 @@ from typing import Union
 
 import urwid
 from urwid import raw_display
-
 from app.activities import ActivityManager, show_or_exit
 from app.activities.NewProduct import NewProduct
 from app.activities.TransactionEditor import TransactionEditor
@@ -55,30 +54,6 @@ light_palette = [
     ('bg', 'white', 'dark blue'),
 ]
 
-grid_layout = [
-    [ 'ts',          'store',    ],
-    [ 'organic',     'product',  ],
-    [ 'category',    'group',   ],
-]
-side_pane = [
-    'unit',
-    'quantity',
-    'price',
-]
-bottom_pane = [
-    'description',
-    'dbview',
-]
-
-cols = [
-    c for c in filter(
-        lambda x: x is not None,
-        itertools.chain(
-            *grid_layout, side_pane, set(bottom_pane) - set(['dbview'])
-        )
-    )
-]
-
 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)))
@@ -184,7 +159,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
         unit = data['unit']
         price = data['price']
         product = data['product']
-        organic = 'true' if data['organic'] else 'false'
+        organic = 'true' if data['organic'] is True else 'false'
         statement = \
             f"CALL insert_transaction('{ts}', $store${store}$store$, " \
             f"$descr${description}$descr$, {quantity}, $unit${unit}$unit$, " \
@@ -199,7 +174,6 @@ query_manager = QueryManager(cur, display_mapper)
 
 activity_manager.create(TransactionEditor, 'transaction',
     activity_manager, query_manager,
-    cols, grid_layout, side_pane, bottom_pane,
     lambda: _save_and_clear_callback(activity_manager),
     lambda widget, name, data: _autocomplete_callback(activity_manager, query_manager, widget, name, data),
 )

+ 2 - 30
price_check.py

@@ -5,16 +5,12 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from collections import OrderedDict
 from sqlite3 import Cursor
 from typing import Union
 
 import urwid
-from urwid import (
-    raw_display,
-    Button, RadioButton, Text,
-)
-from app.widgets import (AutoCompleteEdit, AutoCompleteFloatEdit, NoTabCheckBox)
+from urwid import raw_display
+from app.widgets import (AutoCompleteEdit, AutoCompleteFloatEdit)
 from app.activities import ActivityManager, show_or_exit
 from app.activities.PriceCheck import PriceCheck
 from app.db_utils import QueryManager, display_mapper
@@ -64,29 +60,6 @@ light_palette = [
     ('badge_bad', 'white', 'light red'),
     ('badge_neutral', 'white', 'dark gray'),
 ]
-sort_group = []
-buttons = OrderedDict((
-  ('clear', Button(('streak', 'Clear')),),
-  ('exit', Button(('streak', 'Exit')),),
-  ('sort_price', RadioButton(sort_group, ('streak', 'Best'), state="first True"),),
-  ('sort_date', RadioButton(sort_group, ('streak', 'Last'), state="first True"),),
-))
-
-edit_fields = OrderedDict((
-  ('product', AutoCompleteEdit(('bg', 'product'))),
-  ('unit', AutoCompleteEdit(('bg', 'unit'))),
-  ('quantity', AutoCompleteFloatEdit(('bg', 'quantity'))),
-  ('price', AutoCompleteFloatEdit(('bg', 'price'))),
-))
-
-check_boxes = OrderedDict((
-  ('organic', NoTabCheckBox(('bg', "Organic"), state='mixed')),
-))
-
-text_fields = OrderedDict((
- (k, Text('')) for k in ('dbview', 'spread', 'rating', 'marker')
-))
-text_fields['dbview'] = text_fields['dbview']
 
 def _autocomplete_callback(
     query_manager: QueryManager,
@@ -113,7 +86,6 @@ query_manager = QueryManager(cur, display_mapper)
 
 activity_manager.create(PriceCheck, 'price_check',
     activity_manager, query_manager,
-    buttons, sort_group, check_boxes, edit_fields, text_fields,
     lambda widget, name, data: _autocomplete_callback(query_manager, widget, name, data),
 )