Browse Source

improve layout and palette

Daniel Sheffield 3 năm trước cách đây
mục cha
commit
92e9dca7cb
1 tập tin đã thay đổi với 149 bổ sung28 xóa
  1. 149 28
      grocery_transactions.py

+ 149 - 28
grocery_transactions.py

@@ -8,7 +8,9 @@
 
 import psycopg2
 import urwid
+from urwid import numedit
 import time
+import itertools
 from collections import (
     OrderedDict,
 )
@@ -18,18 +20,37 @@ COPYRIGHT = "Copyright (c) Daniel Sheffield 2021"
 conn = psycopg2.connect("dbname=das user=das")
 cur = conn.cursor()
 
-cols = [
-    'ts',
-    'store',
-    'product',
-    'description',
+palette = [
+    ('banner', 'light gray', 'dark red'),
+    ('streak', 'light red', 'dark gray'),
+    ('bg', 'light red', 'black'),
+]
+
+grid_layout = [
+    [ 'ts',          'store',    ],
+    [ 'organic',     'product',  ],
+    [ 'category',    'group',   ],
+]
+side_pane = [
     'quantity',
     'unit',
     'price',
-    'organic',
-    'category',
-    'group',
 ]
+bottom_pane = [
+    'description',
+]
+
+cols = [
+    c for c in filter(
+        lambda x: x is not None,
+        itertools.chain(
+            *grid_layout, side_pane, bottom_pane
+        )
+    )
+]
+
+#cols.remove(None)
+
 NON_IDENTIFIER_COLUMNS = [
     'ts',
     'store',
@@ -51,20 +72,34 @@ cur.execute("SELECT * FROM transaction_view;")
 col_idx_map = dict([ (d.name, i) for i,d in enumerate(cur.description) if d.name in cols ])
 def records(cursor, col_idx_map):
     for row in cursor.fetchall():
-        #print(row)
-        #raise(Exception(repr(row)))
         yield dict([
             (name, display(row[i], name)) for name, i in col_idx_map.items()
         ])
     cur.execute("SELECT * FROM transaction_view;")
 
 
-def record_matches(record, **kwargs):
+def record_matches(record, strict=None, **kwargs):
+    strict = strict or []
+    if kwargs['product']:
+        with open('log', 'a') as f:
+            f.write(f"Filter {strict}: {kwargs}\n")
+        
     for k,v in kwargs.items():
+        with open('log', 'a') as f:
+            f.write(f"Record: {record}\n")
         if not v:
             continue
+        
+        if k in strict and v.lower() != record[k].lower():
+            return False
+        
         if v.lower() not in record[k].lower():
+            with open('log', 'a') as f:
+                f.write(f"No match\n")
             return False
+        
+    with open('log', 'a') as f:
+        f.write(f"Match\n")
     return True
 
 def unique_suggestions(name, exclude=NON_IDENTIFIER_COLUMNS, **kwargs):
@@ -73,7 +108,9 @@ def unique_suggestions(name, exclude=NON_IDENTIFIER_COLUMNS, **kwargs):
 
 def suggestions(name, exclude=NON_IDENTIFIER_COLUMNS, **kwargs):
     [ kwargs.pop(k) for k in exclude if k in kwargs]
-    yield from filter(lambda x: record_matches(x, **kwargs), records(cur, col_idx_map))
+    yield from filter(lambda x: record_matches(
+        x, strict=[ k for k in kwargs if k != name ], **kwargs
+    ), records(cur, col_idx_map))
 
 def show_or_exit(key):
     if isinstance(key, tuple):
@@ -81,11 +118,24 @@ def show_or_exit(key):
     if key in ('esc'):
         raise urwid.ExitMainLoop()
 
+def interleave(_list, div):
+    for element in _list:
+        yield element
+        yield div
+
 class AutoCompleteEdit(urwid.Edit):
     def __init__(self, name, *args, apply_change_func=None, **kwargs):
-        title = name.title()
-        super(AutoCompleteEdit, self).__init__(f"{title:12s}: ", *args, **kwargs)
-        self.name = name
+        if isinstance(name, tuple):
+            pallete, title = name
+            self.name = title
+            title = title.title()
+            passthrough = (pallete, u'')
+        else:
+            self.name = name
+            title = name.title()
+            passthrough = title
+            
+        super(AutoCompleteEdit, self).__init__(passthrough, *args, **kwargs)
         self.apply = apply_change_func
 
     def keypress(self, size, key):
@@ -93,9 +143,40 @@ class AutoCompleteEdit(urwid.Edit):
             return super(AutoCompleteEdit, self).keypress(size, key)
         self.apply(self.name)
 
+class AutoCompleteFloatEdit(numedit.FloatEdit):
+    def __init__(self, name, *args, apply_change_func=None, **kwargs):
+        if isinstance(name, tuple):
+            pallete, title = name
+            self.name = title
+            title = title.title()
+            passthrough = (pallete, u'')
+        else:
+            self.name = name
+            title = name.title()
+            passthrough = title
+            
+        super(AutoCompleteFloatEdit, self).__init__(passthrough, *args, **kwargs)
+        self.apply = apply_change_func
+
+    def keypress(self, size, key):
+        if isinstance(key, tuple):
+            return
+        
+        if key == 'tab':
+           self.apply(self.name)
+           return
+        elif key in ('esc', 'up', 'down', 'left', 'right',):
+            return super(AutoCompleteFloatEdit, self).keypress(size, key)
+        else:
+            return
+
 class GroceryTransactionEditor(urwid.WidgetPlaceholder):
     def __init__(self, fields):
         super(GroceryTransactionEditor, self).__init__(urwid.SolidFill(u'/'))
+        self.organic_checkbox = urwid.CheckBox(
+            u"Organic",
+            on_state_change=self.apply_organic_state
+        )
         self._init_data(fields)
         self.widgets = dict()
         self.show('transaction')
@@ -130,7 +211,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
             if k == name or v:
                 continue
             options = unique_suggestions(k, **self.data)
-            if len(options) == 1:
+            if len(options) == 1 and k != 'ts':
                 self.data.update({
                     k: list(options)[0],
                 })
@@ -149,6 +230,9 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
         options = unique_suggestions(name, **self.data)
         if 0 < len(options):
             self.show('suggestions', name, options=options)
+    
+    def apply_organic_state(self, w, state):
+        self.data['organic'] = repr(state).lower()
 
     def apply_changes(self, name):
         apply = lambda w,x: self._apply_changes(name, x)
@@ -172,7 +256,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
                 return original(size, key)
             self.show('transaction')
         top.keypress = keypress
-        return top
+        return urwid.AttrMap(top, 'banner')
     
     def save(self):
         fmt = '%Y-%m-%dT%H%M'
@@ -198,7 +282,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
             if k in ('ts', 'store',):
                 continue
             self.data[k] = ''
-        #self.data['organic'] = 'false'
+        self.organic_checkbox.set_state(False)
 
     def save_and_clear(self):
         self.save()
@@ -209,32 +293,69 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
         getattr(self, f"update_{name}")()
     
     def update_transaction(self):
-        for k in self.data:
+        for k in self.edit_fields:
             self.edit_fields[k].set_edit_text(self.data[k])
+        self.organic_checkbox.set_state(True if self.data['organic'] == 'true' else False)
 
     def transaction(self):
         self.edit_fields = OrderedDict()
         for k in self.data:
-            self.edit_fields[k] = AutoCompleteEdit(k, apply_change_func=self._autocomplete)
-            self.edit_fields[k].set_edit_text(self.data[k])
-            urwid.connect_signal(self.edit_fields[k], 'change', self.apply_changes(k))
+            if k in side_pane and k != 'unit':
+                ef = AutoCompleteFloatEdit(('bg', k), apply_change_func=self._autocomplete)
+            elif k != 'organic':
+                ef = AutoCompleteEdit(('bg', k), apply_change_func=self._autocomplete)
+            else:
+                continue
+            ef.set_edit_text(self.data[k])
+            urwid.connect_signal(ef, 'change', self.apply_changes(k))
+            self.edit_fields[k] = ef
     
-        header = urwid.Text('Fill transaction', 'center')
+        header = urwid.Text(u'Fill Transaction', 'center')
         _copyright = urwid.Text(COPYRIGHT, 'center')
-        button = urwid.Button(u'Done')
+        button = urwid.Button(('streak', u'Done'))
         urwid.connect_signal(button, 'click', lambda w: self.save_and_clear())
+        banner = urwid.Pile([
+            urwid.Padding(header, 'center', width=('relative', 100)),
+            urwid.Padding(_copyright, 'center', width=('relative', 100)),
+        ])
+        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
+        ])
+        side_pane_widget = (15, urwid.Pile([
+            fields[r] if r is not None else urwid.Divider() for r in side_pane
+        ]))
+        main_pane_widgets = []
+        for r in grid_layout:
+            widgets = []
+            for c in r:
+                if c is not None:
+                    if c != 'organic':
+                        widgets.append(fields[c])
+                    else:
+                        widgets.append(urwid.LineBox(urwid.AttrMap(self.organic_checkbox, 'bg')))
+                else:
+                    widgets.append(urwid.Divider())
+            main_pane_widgets.append(urwid.Columns(widgets))
+
+        main_pane_widget = (62, urwid.Pile(main_pane_widgets))
+        
         widget = urwid.Pile([
-            urwid.Padding(header, 'center', width=('relative', 30)),
-            urwid.Padding(_copyright, 'center', width=('relative', 30)),
-            *[ self.edit_fields[k] for k in self.data ],
+            banner,
+            urwid.Divider(),
+            urwid.Columns((main_pane_widget, side_pane_widget),
+                dividechars=2,
+            ),
+            *[ fields[c] if c is not None else urwid.Divider() for c in bottom_pane ],
             urwid.Divider(),
             button,
         ])
         widget = urwid.Filler(widget, 'top') 
+        widget = urwid.AttrMap(widget, 'bg')
         return widget
 
 app = GroceryTransactionEditor(cols)
-loop = urwid.MainLoop(app, unhandled_input=show_or_exit)
+loop = urwid.MainLoop(app, palette, unhandled_input=show_or_exit)
 loop.run()
 
 cur.close()