瀏覽代碼

refactor widget layout and remove redundant data layer

Daniel Sheffield 2 年之前
父節點
當前提交
a81afb0419
共有 2 個文件被更改,包括 174 次插入168 次删除
  1. 144 132
      app/activities/PriceCheck.py
  2. 30 36
      price_check.py

+ 144 - 132
app/activities/PriceCheck.py

@@ -7,14 +7,28 @@
 from collections import OrderedDict
 from decimal import Decimal, InvalidOperation
 from typing import Callable, List, Union
-
+import itertools
 import numpy as np
-import urwid
+from urwid import (
+    connect_signal,
+    AttrMap,
+    Columns,
+    Divider,
+    Filler,
+    LineBox,
+    Padding,
+    Pile,
+    Text,
+)
 from app.db_utils import QueryManager
 
 from .. import COPYRIGHT
-from ..widgets import (AutoCompleteEdit, AutoCompleteFloatEdit,
-                       AutoCompletePopUp, FocusWidget, NoTabCheckBox)
+from ..widgets import (
+    AutoCompleteEdit,
+    AutoCompleteFloatEdit,
+    FocusWidget,
+    AutoCompletePopUp
+)
 from . import ActivityManager, show_or_exit
 
 class PriceCheck(FocusWidget):
@@ -44,42 +58,46 @@ class PriceCheck(FocusWidget):
 
     def apply_changes(self, name, value):
         if name == 'organic':
-            self.data.update({
+            self.data = {
                 name: {
                     'yes': True, 'no': False,
                     True: True, False: False,
                     'mixed': 'mixed'
                 }[value]
-            })
+            }
         elif name in self.data:
-            self.data.update({
+            self.data = {
                 name: value,
-            })
-        #self.update_historic_prices()
+            }
+
+    @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:
-            if k == 'organic':
-                self.data[k] = 'mixed'
-                continue
-            self.data[k] = ''
-        self.text_fields['rating'].set_text('')
-        self.text_fields['spread'].set_text('')
-        self.text_fields['marker'].set_text('')
-        self.text_fields['dbview'].set_text('')
+        for (_, ef) in self.edit_fields.items():
+            ef.set_edit_text('')
+        for (_, cb) in self.checkboxes.items():
+            cb.set_state('mixed')
+        for (_, tf) in self.text_fields.items():
+            tf.set_text('')
         self.update()
         return self
 
     def update(self):
-        for k in self.edit_fields:
-            if self.data[k] != self.edit_fields[k].get_edit_text():
-                self.edit_fields[k].set_edit_text(self.data[k])
-
-        if self.data['organic'] != self.organic_checkbox.state:
-            self.organic_checkbox.set_state(self.data['organic'])
-
-        self.update_historic_prices()
-
+        self.update_historic_prices(self.data)
         return self
 
     def update_rating(self, _avg, _min, _max, unit, price=None, quantity=None):
@@ -121,17 +139,17 @@ class PriceCheck(FocusWidget):
             self.text_fields['marker'].set_text('')
 
 
-    def update_historic_prices(self):
-        organic = None if self.data['organic'] == 'mixed' else self.data['organic']
+    def update_historic_prices(self, data):
+        organic = None if data['organic'] == 'mixed' else data['organic']
         sort = '$/unit' if self.buttons['sort_price'].state else 'ts'
-        product, unit = self.data['product'] or None, self.data['unit'] or None
+        product, unit = data['product'] or None, data['unit'] or None
         try:
-            price = Decimal(self.data['price'])
+            price = Decimal(data['price'])
         except InvalidOperation:
             price = None
 
         try:
-            quantity = Decimal(self.data['quantity'])
+            quantity = Decimal(data['quantity'])
         except InvalidOperation:
             quantity = None
 
@@ -145,77 +163,75 @@ class PriceCheck(FocusWidget):
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
-        fields: List[str],
-        top_pane, left_pane, right_pane, bottom_pane, badge,
+        buttons: OrderedDict, button_group: List,
+        checkboxes: OrderedDict,
+        edit_fields: OrderedDict,
+        text_fields: OrderedDict,
         autocomplete_cb: Callable[[
             Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
         ], None],
     ):
+        top_pane = [ 'clear', 'exit', ['sort_price', 'sort_date'], ]
+
+        left_pane = [
+            'product',
+            'organic',
+        ]
+        badge = [
+            'rating',
+            'spread',
+            'marker',
+        ]
+        right_pane = [
+            'unit',
+            'quantity',
+            'price',
+        ]
+        bottom_pane = [ 'dbview', ]
+
+        layout = [
+            [ top_pane, ],
+            [ left_pane, right_pane, ],
+            [ badge, ],
+            [ bottom_pane, ],
+        ]
         self.query_manager = query_manager
-        self.top_pane = top_pane
-        self.left_pane = left_pane
-        self.right_pane = right_pane
-        self.bottom_pane = bottom_pane
-        self.badge = badge
-        self.edit_fields = OrderedDict()
-        self.text_fields = OrderedDict()
-        self.data = OrderedDict()
-        self.organic_checkbox = NoTabCheckBox(
-            u"Organic",
-            state='mixed',
-        )
-        urwid.connect_signal(self.organic_checkbox, 'change', lambda _,v: self.apply_changes('organic', v))
-
-        fields = [f for f in fields ]
-        for k in fields:
-            if k in self.right_pane and k != 'unit':
-                ef = AutoCompleteFloatEdit(('bg', k))
-            elif k != 'organic':
-                ef = AutoCompleteEdit(('bg', k))
-            elif k == 'organic':
-                self.data[k] = 'mixed'
-                continue
+        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():
+            connect_signal(ef, 'postchange', lambda _,v: self.update())
+            connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
 
-            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 b in button_group:
+            connect_signal(b, 'postchange',lambda *_: self.update())
 
-        self.buttons = OrderedDict()
-        group = []
-        for k in top_pane:
-            if k != 'sort':
-                self.buttons.update({ k:  urwid.Button(('streak', f'{k.title()}')) })
-                continue
-            self.buttons.update({ f'{k}_price':  urwid.RadioButton(group, ('streak', u'Best'), state="first True") })
-            self.buttons.update({ f'{k}_date':  urwid.RadioButton(group, ('streak', u'Last'), state="first True") })
-            for button in group:
-                urwid.connect_signal(button, 'postchange', lambda *args: self.update_historic_prices())
+        connect_signal(self.buttons['clear'], 'click', lambda x: self.clear().update())
+        connect_signal(self.buttons['exit'], 'click', lambda x: show_or_exit('esc'))
 
-        urwid.connect_signal(self.buttons['clear'], 'click', lambda x: self.clear().update())
-        urwid.connect_signal(self.buttons['exit'], 'click', lambda x: show_or_exit('esc'))
-
-        dbview, spread, rating, marker = [ urwid.Text('') for i in range(0,4) ]
-
-        self.text_fields.update({
-            'dbview': dbview,
-            'spread': spread,
-            'rating': rating,
-            'marker': marker,
-        })
 
         self.clear()
 
-        header = urwid.Text(u'Price Check', 'center')
-        _copyright = urwid.Text(COPYRIGHT, 'center')
+        header = Text(u'Price Check', 'center')
+        _copyright = Text(COPYRIGHT, 'center')
 
-        banner = urwid.Pile([
-            urwid.Padding(header, 'center', width=('relative', 100)),
-            urwid.Padding(_copyright, 'center', width=('relative', 100)),
+        banner = Pile([
+            Padding(header, 'center', width=('relative', 100)),
+            Padding(_copyright, 'center', width=('relative', 100)),
         ])
-        banner = urwid.AttrMap(banner, 'banner')
-        fields = dict([
-            (k, urwid.LineBox(urwid.AttrMap(
+        banner = AttrMap(banner, 'banner')
+
+        _widgets = dict(itertools.chain(*[
+            [(k, v) for k,v in x] for x in map(lambda x: x.items(), [
+                self.edit_fields, self.text_fields, self.checkboxes
+            ])
+        ]))
+
+        _widgets.update([
+            (k, LineBox(AttrMap(
                 AutoCompletePopUp(
                     self.edit_fields[k],
                     self.apply_choice,
@@ -223,68 +239,64 @@ class PriceCheck(FocusWidget):
                 ), 'streak'), title=k.title(), title_align='left')
             ) for k in self.edit_fields
         ])
-        fields['organic'] = urwid.AttrMap(self.organic_checkbox, 'bg')
-        fields.update({
-            'dbview': urwid.LineBox(
-                urwid.AttrMap(dbview, 'streak'),
+        _widgets.update({
+            'dbview': LineBox(
+                AttrMap(self.text_fields['dbview'], 'streak'),
                 title="Historic Prices",
                 title_align='center',
             ),
-            'spread': spread,
-            'rating': rating,
-            'marker': marker,
         })
-
-        right_pane_widget = (16, urwid.Pile(map(
-            lambda x: fields[x] if x is not None else urwid.Divider(),
-            self.right_pane
-        )))
-        left_pane_widget = (urwid.Pile(map(
-            lambda x: fields[x] if x is not None else urwid.Divider(),
-            self.left_pane,
-        )))
-        left_pane_widget = (urwid.Pile([
-            left_pane_widget,
-            urwid.LineBox(urwid.AttrMap(
-                urwid.Pile(map(
-                    lambda x: fields[x] if x is not None else urwid.Divider(),
-                    self.badge,
-                )), 'badge',),
-                title="Current Price",
-                title_align='left',
+        components = OrderedDict((
+          ('right_pane', (16, Pile(map(
+            lambda x: _widgets[x] if x is not None else Divider,
+            right_pane
+          )))),
+          ('left_pane', Pile([_widgets['product'], _widgets['organic']])),
+          ('badge', Pile(map(
+            lambda x: _widgets[x] if x is not None else Divider,
+            badge
+          ))),
+          ('bottom_pane', _widgets['dbview']),
+        ))
+
+        left_pane_widget = (Pile([
+            components['left_pane'],
+            LineBox(
+                AttrMap(components['badge'], 'badge'),
+                title="Current Price", title_align='left',
             )
         ]))
 
-        widget = urwid.Pile([
+        widget = Pile([
             banner,
-            urwid.Divider(),
-            urwid.Columns(
+            Divider(),
+            Columns(
                 [
-                    (9, urwid.Pile([
-                        urwid.Divider(),
-                        urwid.AttrMap(self.buttons['clear'], 'streak'),
-                        urwid.Divider(),
+                    (9, Pile([
+                        Divider(),
+                        AttrMap(self.buttons['clear'], 'streak'),
+                        Divider(),
                     ])),
-                    urwid.LineBox(
-                        urwid.Columns([ v for k,v in self.buttons.items() if 'sort' in k]),
+                    LineBox(
+                        Columns([ v for k,v in self.buttons.items() if 'sort' in k]),
                         title="Sort price by",
                         title_align='left',
                     ),
-                    (9, urwid.Pile([
-                        urwid.Divider(),
-                        urwid.AttrMap(self.buttons['exit'], 'streak'),
-                        urwid.Divider(),
+                    (9, Pile([
+                        Divider(),
+                        AttrMap(self.buttons['exit'], 'streak'),
+                        Divider(),
                     ]))
                 ],
                 dividechars=1,
             ),
-            urwid.Columns((left_pane_widget, right_pane_widget),
+            Columns((left_pane_widget, components['right_pane']),
                 dividechars=0,
             ),
-            *[ fields[c] if c is not None else urwid.Divider() for c in self.bottom_pane ],
+            components['bottom_pane'],
         ])
-        widget = urwid.Filler(widget, 'top')
-        widget = urwid.AttrMap(widget, 'bg')
+        widget = Filler(widget, 'top')
+        widget = AttrMap(widget, 'bg')
         widget.original_widget.original_widget.set_focus_path([3,0,0])
         super().__init__(widget, 4, [3,0,0,0], [
                 [0, 0,], [0,1,],

+ 30 - 36
price_check.py

@@ -5,17 +5,19 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-import itertools
+from collections import OrderedDict
 from sqlite3 import Cursor
 from typing import Union
 
 import urwid
-from urwid import raw_display
-
+from urwid import (
+    raw_display,
+    Button, RadioButton, Text,
+)
+from app.widgets import (AutoCompleteEdit, AutoCompleteFloatEdit, NoTabCheckBox)
 from app.activities import ActivityManager, show_or_exit
 from app.activities.PriceCheck import PriceCheck
 from app.db_utils import QueryManager, display_mapper
-from app.widgets import AutoCompleteEdit, AutoCompleteFloatEdit
 
 try:
     from db_credentials import HOST, PASSWORD
@@ -62,37 +64,29 @@ light_palette = [
     ('badge_bad', 'white', 'light red'),
     ('badge_neutral', 'white', 'dark gray'),
 ]
-
-top_pane = [
-    'clear',
-    'exit',
-    'sort',
-]
-
-left_pane = [
-    'product',
-    'organic',
-]
-badge = [
-    'rating',
-    'spread',
-    'marker',
-]
-right_pane = [
-    'unit',
-    'quantity',
-    'price',
-]
-bottom_pane = [
-    'dbview',
-]
-
-inputs = filter(
-    lambda x: x is not None and x not in ('rating','spread', 'marker', 'div'),
-    itertools.chain(
-        left_pane, right_pane
-    )
-)
+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,
@@ -119,7 +113,7 @@ query_manager = QueryManager(cur, display_mapper)
 
 activity_manager.create(PriceCheck, 'price_check',
     activity_manager, query_manager,
-    inputs, top_pane, left_pane, right_pane, bottom_pane, badge,
+    buttons, sort_group, check_boxes, edit_fields, text_fields,
     lambda widget, name, data: _autocomplete_callback(query_manager, widget, name, data),
 )