|
@@ -24,7 +24,7 @@ class AutoCompleteEdit(urwid.Edit):
|
|
|
title = name.title()
|
|
|
passthrough = u' ' if name.lower() == 'unit' else u''
|
|
|
|
|
|
- super(AutoCompleteEdit, self).__init__(passthrough, *args, **kwargs)
|
|
|
+ super().__init__(passthrough, *args, **kwargs)
|
|
|
self.apply = apply_change_func
|
|
|
|
|
|
def keypress(self, size, key):
|
|
@@ -35,7 +35,7 @@ class AutoCompleteEdit(urwid.Edit):
|
|
|
self.set_edit_text('')
|
|
|
return
|
|
|
|
|
|
- return super(AutoCompleteEdit, self).keypress(size, key)
|
|
|
+ return super().keypress(size, key)
|
|
|
|
|
|
|
|
|
class AutoCompleteFloatEdit(numedit.FloatEdit):
|
|
@@ -114,14 +114,14 @@ class AutoCompleteFloatEdit(numedit.FloatEdit):
|
|
|
self.update_caption()
|
|
|
return
|
|
|
|
|
|
- return super(AutoCompleteFloatEdit, self).keypress(size, key)
|
|
|
+ return super().keypress(size, key)
|
|
|
|
|
|
class NoTabCheckBox(urwid.CheckBox):
|
|
|
def keypress(self, size, key):
|
|
|
if not isinstance(key, tuple) and key == 'tab':
|
|
|
return
|
|
|
else:
|
|
|
- return super(NoTabCheckBox, self).keypress(size, key)
|
|
|
+ return super().keypress(size, key)
|
|
|
|
|
|
def _set_focus_path(container, path):
|
|
|
try:
|
|
@@ -137,23 +137,12 @@ def _set_focus_path(container, path):
|
|
|
raise IndexError
|
|
|
|
|
|
class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
- def __init__(self, cur, unique_suggestions, get_session_transactions,
|
|
|
- log, fields, layout, side_pane, bottom_pane):
|
|
|
- super(GroceryTransactionEditor, self).__init__(urwid.SolidFill(u'/'))
|
|
|
- self.organic_checkbox = NoTabCheckBox(
|
|
|
- u"Organic",
|
|
|
- on_state_change=self.apply_organic_state
|
|
|
- )
|
|
|
- self._init_data(fields)
|
|
|
- self.widgets = dict()
|
|
|
- self.layout = layout
|
|
|
- self.side_pane = side_pane
|
|
|
- self.bottom_pane = bottom_pane
|
|
|
- self.show('transaction')
|
|
|
+ def __init__(self, activity_manager, cur, log):
|
|
|
+ super().__init__(urwid.SolidFill(u'/'))
|
|
|
+ self.activity_manager = activity_manager
|
|
|
self.cur = cur
|
|
|
- self.unique_suggestions = unique_suggestions
|
|
|
- self.get_session_transactions = get_session_transactions
|
|
|
-
|
|
|
+ txn = self.activity_manager.get('transaction')
|
|
|
+
|
|
|
with open(log, 'r') as f:
|
|
|
date = None
|
|
|
store = None
|
|
@@ -174,11 +163,46 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
self.cur.execute(line)
|
|
|
|
|
|
if None not in (date, store):
|
|
|
- self._apply_choice('ts', date)
|
|
|
- self._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())
|
|
|
|
|
|
self.log = self.open(log)
|
|
|
|
|
|
+
|
|
|
+ def _open(self, log):
|
|
|
+ with open(log, 'a') as f:
|
|
|
+ yield f
|
|
|
+
|
|
|
+ def open(self, log):
|
|
|
+ self._to_close = self._open(log)
|
|
|
+ return next(self._to_close)
|
|
|
+
|
|
|
+ def close(self):
|
|
|
+ if self._to_close is not None:
|
|
|
+ self._to_close.close()
|
|
|
+ self._to_close = None
|
|
|
+
|
|
|
+ def save(self, data):
|
|
|
+ ts = data['ts']
|
|
|
+ store = data['store']
|
|
|
+ description = data['description']
|
|
|
+ quantity = data['quantity']
|
|
|
+ unit = data['unit']
|
|
|
+ price = data['price']
|
|
|
+ product = data['product']
|
|
|
+ organic = data['organic'] if data['organic'] else 'false'
|
|
|
+ statement = \
|
|
|
+ f"CALL insert_transaction('{ts}', $store${store}$store$, " \
|
|
|
+ f"$descr${description}$descr$, {quantity}, $unit${unit}$unit$, " \
|
|
|
+ f"{price}, $produ${product}$produ$, {organic});\n"
|
|
|
+ self.log.write(statement)
|
|
|
+ self.cur.execute(statement)
|
|
|
+
|
|
|
+class TransactionEditor(urwid.WidgetPlaceholder):
|
|
|
+
|
|
|
def iter_focus_paths(self):
|
|
|
initial = [2,0,0,0]
|
|
|
container = self.original_widget.original_widget.original_widget
|
|
@@ -227,21 +251,19 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
|
|
|
container.set_focus_path([2,0,0,0])
|
|
|
|
|
|
-
|
|
|
def keypress(self, size, key):
|
|
|
if isinstance(key, tuple):
|
|
|
return
|
|
|
|
|
|
if getattr(self.original_widget.original_widget, 'original_widget', None) is None:
|
|
|
- return super(GroceryTransactionEditor, self).keypress(size, key)
|
|
|
+ return super().keypress(size, key)
|
|
|
|
|
|
if key == 'tab':
|
|
|
self.advance_focus()
|
|
|
elif key == 'shift tab':
|
|
|
self.advance_focus(reverse=True)
|
|
|
else:
|
|
|
- return super(GroceryTransactionEditor, self).keypress(size, key)
|
|
|
-
|
|
|
+ return super().keypress(size, key)
|
|
|
|
|
|
def _init_data(self, fields):
|
|
|
self.data = OrderedDict([
|
|
@@ -249,35 +271,14 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
])
|
|
|
self.clear()
|
|
|
|
|
|
- def get_activity(self, name):
|
|
|
- return getattr(self, name)
|
|
|
-
|
|
|
- def show(self, name, *args, **kwargs):
|
|
|
- if name in self.widgets:
|
|
|
- self.original_widget = self.widgets[name]
|
|
|
- self.update(name)
|
|
|
- return
|
|
|
-
|
|
|
- a = self.get_activity(name)
|
|
|
- widget = a(*args, **kwargs)
|
|
|
- if name != 'suggestions':
|
|
|
- self.widgets[name] = widget
|
|
|
-
|
|
|
- self.original_widget = widget
|
|
|
-
|
|
|
def _apply_choice(self, name, value):
|
|
|
- self.data.update({
|
|
|
- name: value,
|
|
|
- })
|
|
|
+ self._apply_changes(name, value)
|
|
|
for k,v in self.data.items():
|
|
|
if k == name or v:
|
|
|
continue
|
|
|
- options = self.unique_suggestions(k, **self.data)
|
|
|
+ options = self.query_manager.unique_suggestions(k, **self.data)
|
|
|
if len(options) == 1 and k != 'ts':
|
|
|
- self.data.update({
|
|
|
- k: list(options)[0],
|
|
|
- })
|
|
|
- self.show('transaction')
|
|
|
+ self._apply_changes(k, list(options)[0])
|
|
|
|
|
|
def apply_choice(self, name):
|
|
|
return lambda w,x: self._apply_choice(name, x)
|
|
@@ -286,73 +287,12 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
self.data.update({
|
|
|
name: value,
|
|
|
})
|
|
|
-
|
|
|
- def _autocomplete(self, name):
|
|
|
- options = self.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)
|
|
|
- return apply
|
|
|
-
|
|
|
- def suggestions(self, name, options=None):
|
|
|
- body = [urwid.Text(name.title()), urwid.Divider()]
|
|
|
- for c in options:
|
|
|
- button = urwid.Button(c)
|
|
|
- urwid.connect_signal(button, 'click', self.apply_choice(name), c)
|
|
|
- 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)
|
|
|
- top = urwid.Overlay(pad, self.original_widget,
|
|
|
- align='center', width=('relative', 60),
|
|
|
- valign='middle', height=('relative', 60),
|
|
|
- min_width=20, min_height=9)
|
|
|
- def keypress(size, key, original=top.keypress):
|
|
|
- if key == 'esc':
|
|
|
- self.show('transaction')
|
|
|
- return
|
|
|
-
|
|
|
- if key == 'tab':
|
|
|
- return
|
|
|
+ return lambda w,x: self._apply_changes(name, x)
|
|
|
|
|
|
- return original(size, key)
|
|
|
-
|
|
|
- top.keypress = keypress
|
|
|
- return urwid.AttrMap(top, 'banner')
|
|
|
-
|
|
|
- def _open(self, log):
|
|
|
- with open(log, 'a') as f:
|
|
|
- yield f
|
|
|
-
|
|
|
- def open(self, log):
|
|
|
- self._to_close = self._open(log)
|
|
|
- return next(self._to_close)
|
|
|
-
|
|
|
- def close(self):
|
|
|
- if self._to_close is not None:
|
|
|
- self._to_close.close()
|
|
|
- self._to_close = None
|
|
|
-
|
|
|
- def save(self):
|
|
|
- ts = self.data['ts']
|
|
|
- store = self.data['store']
|
|
|
- description = self.data['description']
|
|
|
- quantity = self.data['quantity']
|
|
|
- unit = self.data['unit']
|
|
|
- price = self.data['price']
|
|
|
- product = self.data['product']
|
|
|
- organic = self.data['organic'] if self.data['organic'] else 'false'
|
|
|
- statement = \
|
|
|
- f"CALL insert_transaction('{ts}', $store${store}$store$, " \
|
|
|
- f"$descr${description}$descr$, {quantity}, $unit${unit}$unit$, " \
|
|
|
- f"{price}, $produ${product}$produ$, {organic});\n"
|
|
|
- self.log.write(statement)
|
|
|
- self.cur.execute(statement)
|
|
|
+ def apply_organic_state(self, w, state):
|
|
|
+ self.data['organic'] = repr(state).lower()
|
|
|
|
|
|
def clear(self):
|
|
|
for k in self.data:
|
|
@@ -360,34 +300,39 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
continue
|
|
|
self.data[k] = ''
|
|
|
self.organic_checkbox.set_state(False)
|
|
|
-
|
|
|
- def save_and_clear(self):
|
|
|
- self.save()
|
|
|
- self.clear()
|
|
|
- self.show('transaction')
|
|
|
-
|
|
|
- def update(self, name):
|
|
|
- getattr(self, f"update_{name}")()
|
|
|
|
|
|
- def update_transaction(self):
|
|
|
+ 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']
|
|
|
self.text_fields['dbview'].set_text(
|
|
|
- self.get_session_transactions(date, store) if None not in (
|
|
|
+ self.query_manager.get_session_transactions(date, store) if None not in (
|
|
|
date or None, store or None
|
|
|
) else ''
|
|
|
)
|
|
|
self.organic_checkbox.set_state(True if self.data['organic'] == 'true' else False)
|
|
|
+ return self
|
|
|
|
|
|
- def transaction(self):
|
|
|
+ def __init__(self, query_manager, fields,
|
|
|
+ layout, side_pane, bottom_pane,
|
|
|
+ save_and_clear_cb):
|
|
|
+ super().__init__(urwid.SolidFill(u'/'))
|
|
|
+ self.organic_checkbox = NoTabCheckBox(
|
|
|
+ u"Organic",
|
|
|
+ on_state_change=self.apply_organic_state
|
|
|
+ )
|
|
|
+ self.query_manager = query_manager
|
|
|
+ self._init_data(fields)
|
|
|
+ self.layout = layout
|
|
|
+ self.side_pane = side_pane
|
|
|
+ self.bottom_pane = bottom_pane
|
|
|
self.edit_fields = OrderedDict()
|
|
|
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=self._autocomplete)
|
|
|
+ ef = AutoCompleteFloatEdit(('bg', k), apply_change_func=lambda name: query_manager.autocomplete(name, self.data))
|
|
|
elif k != 'organic':
|
|
|
- ef = AutoCompleteEdit(('bg', k), apply_change_func=self._autocomplete)
|
|
|
+ ef = AutoCompleteEdit(('bg', k), apply_change_func=lambda name: query_manager.autocomplete(name, self.data))
|
|
|
else:
|
|
|
continue
|
|
|
ef.set_edit_text(self.data[k])
|
|
@@ -397,7 +342,7 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
header = urwid.Text(u'Fill Transaction', 'center')
|
|
|
_copyright = urwid.Text(COPYRIGHT, 'center')
|
|
|
done_button = urwid.Button(('streak', u'Done'))
|
|
|
- urwid.connect_signal(done_button, 'click', lambda w: self.save_and_clear())
|
|
|
+ urwid.connect_signal(done_button, 'click', lambda w: save_and_clear_cb())
|
|
|
banner = urwid.Pile([
|
|
|
urwid.Padding(header, 'center', width=('relative', 100)),
|
|
|
urwid.Padding(_copyright, 'center', width=('relative', 100)),
|
|
@@ -447,4 +392,60 @@ class GroceryTransactionEditor(urwid.WidgetPlaceholder):
|
|
|
])
|
|
|
widget = urwid.Filler(widget, 'top')
|
|
|
widget = urwid.AttrMap(widget, 'bg')
|
|
|
+ self.original_widget = widget
|
|
|
+
|
|
|
+class SuggestionPopup(urwid.Overlay):
|
|
|
+
|
|
|
+ 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()]
|
|
|
+ for c in options:
|
|
|
+ button = urwid.Button(c)
|
|
|
+ urwid.connect_signal(button, 'click', apply_cb, c)
|
|
|
+ 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)
|
|
|
+
|
|
|
+ def keypress(self, size, key):
|
|
|
+ if key == 'esc':
|
|
|
+ self.esc_cb()
|
|
|
+ return
|
|
|
+
|
|
|
+ if key == 'tab':
|
|
|
+ return
|
|
|
+
|
|
|
+ return super().keypress(size, key)
|
|
|
+
|
|
|
+class ActivityManager(object):
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.widgets = dict()
|
|
|
+ self.app = None
|
|
|
+
|
|
|
+ def add(self, widget, name):
|
|
|
+ self.widgets[name] = widget
|
|
|
+ return widget
|
|
|
+
|
|
|
+ def get(self, name):
|
|
|
+ if name in self.widgets:
|
|
|
+ return self.widgets[name]
|
|
|
+ raise Exception("Widget {name} not found")
|
|
|
+
|
|
|
+ def create(self, cls, name, *args, **kwargs):
|
|
|
+ widget = cls(*args, **kwargs)
|
|
|
+ if name is not None:
|
|
|
+ self.add(widget, name)
|
|
|
return widget
|
|
|
+
|
|
|
+ def show(self, widget):
|
|
|
+ if self.app is not None:
|
|
|
+ self.app.original_widget = widget
|
|
|
+ return
|
|
|
+ self.app = widget
|