123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- 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
- self.name = title
- title = title.title()
- passthrough = (pallete, u' ' if title.lower() == 'unit' else u'')
- else:
- self.name = name
- title = name.title()
- passthrough = u' ' if name.lower() == 'unit' else u''
-
- super().__init__(passthrough, *args, **kwargs)
-
- def keypress(self, size, key):
- if key == 'enter':
- self._emit('apply', self.name)
- return
- elif key == 'delete':
- self.set_edit_text('')
- return
-
- 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 = '='
- self.pallete = None
- if isinstance(name, tuple):
- self.pallete, self.name = name
- title = self.name.title()
- passthrough = (self.pallete, f'{self.op} ')
- else:
- self.name = name
- title = name.title()
- passthrough = title
-
- super().__init__(passthrough, *args, **kwargs)
-
- def update_caption(self):
- if self.pallete is not None:
- self.set_caption((self.pallete, f'{self.op} '))
- else:
- self.set_caption(f'{self.op} ')
- def set_op(self, op):
- self.op = op
- self.last_val = self.value()
- self.set_edit_text('')
- self.update_caption()
-
- def calc(self):
- x = self.last_val
- op = self.op
- if op in ('+', '-',):
- y = self.value() or Decimal(0.0)
- if op == '+':
- z = x + y
- else:
- z = x - y
- elif op in ('*', '/'):
- y = self.value() or Decimal(1.0)
- if op == '*':
- z = x * y
- else:
- z = x / y
- else:
- y = self.value() or Decimal(0.0)
- z = y
-
- self.op = '='
- self.update_caption()
- self.set_edit_text(f'{z:.2f}')
- def keypress(self, size, key):
- if isinstance(key, tuple):
- return
- ops = ('+', '-', '*', '/',)
- if key in ops:
- if self.op in ops:
- self.calc()
- self.set_op(key)
- return
- elif key == 'enter':
- if self.get_edit_text() == '' or self.value() == Decimal(0.0):
- self._emit('open', self.name)
- return
- self.calc()
- return
- elif key == '=':
- self.calc()
- return
- elif key == 'delete':
- self.set_edit_text('')
- self.op = '='
- self.update_caption()
- return
- 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().keypress(size, key)
- class FocusWidget(urwid.WidgetPlaceholder):
- def __init__(self, widget, initial_focus, skip_focus):
- super().__init__(widget)
- self._initial_focus = tuple([ i for i in initial_focus ])
- self._skip_focus = tuple([ i for i in skip_focus ])
-
- @property
- def skip_focus(self):
- return self._skip_focus
-
- @property
- def initial_focus(self):
- return list(self._initial_focus)
-
- @property
- def container(self):
- return self.original_widget.original_widget.original_widget
-
- def _set_focus_path(self, path):
- try:
- self.container.set_focus_path(path)
- return
- except IndexError:
- pass
-
- if path[-1] == 0 and len(path) > 1:
- self._set_focus_path(path[:-1])
- return
-
- raise IndexError
- def iter_focus_paths(self):
- self._set_focus_path(self.initial_focus)
- while True:
- path = self.container.get_focus_path()
- yield path
- self.advance_focus()
- path = self.container.get_focus_path()
- while len(path) < len(self.initial_focus):
- path.extend([0])
- if path == self.initial_focus:
- return
- def advance_focus(self, reverse=False):
- path = self.container.get_focus_path()
-
- if reverse:
- paths = [ i for i in self.iter_focus_paths() ]
- zipped_paths = zip(paths, [
- *paths[1:], paths[0]
- ])
- prev_path = map(lambda x: x[0], filter(
- lambda x: x[1] == path,
- zipped_paths
- ))
- p = next(prev_path)
- self._set_focus_path(p)
- return
-
- _iter = [ i for i in enumerate(path) ][::-1]
- for idx, part in _iter:
- p = [ i for i in path ]
- if reverse:
- p[idx] -= 1
- else:
- p[idx] += 1
-
- try:
- self._set_focus_path(p)
- if p in self.skip_focus:
- self.advance_focus(reverse=reverse)
- return
- except IndexError:
- path[idx] = 0
- while len(path) < len(self.initial_focus):
- path.extend([0])
- self._set_focus_path(self.initial_focus)
- class AutoCompletePopUp(PopUpLauncher):
- def __init__(self,
- widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
- apply_choice_cb: Callable[[str, str], None]
- ):
- super().__init__(widget)
- 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
- self.open_pop_up()
-
- def create_pop_up(self):
- pop_up = SuggestionPopup(
- self._original_widget.name, self.options,
- self.apply_choice_cb,
- )
- urwid.connect_signal(pop_up, 'close',
- lambda _: self.close_pop_up())
- return pop_up
- def get_pop_up_parameters(self):
- return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height': 10}
- class SuggestionPopup(urwid.WidgetWrap):
-
- signals = ['close']
- 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', 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)
- listbox = urwid.ListBox(walker)
- super().__init__(urwid.AttrWrap(listbox, 'banner'))
-
- def keypress(self, size, key):
- if key == 'esc':
- self._emit("close")
- return
- if key == 'tab':
- return
- return super().keypress(size, key)
|