From c74e3d68d67b059528dddf796571c8a3d7d4640a Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Wed, 5 Aug 2020 02:49:05 +0200 Subject: [PATCH] Implement multiple selections with CliMultiMenu CliMultiMenu works much in the same way as CliMenu but it allows for selecting multiple items. --- clintermission/__init__.py | 4 +- clintermission/climenu.py | 76 +++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/clintermission/__init__.py b/clintermission/__init__.py index 9d05b0d..9dfa509 100644 --- a/clintermission/__init__.py +++ b/clintermission/__init__.py @@ -1,11 +1,13 @@ # Written by Sebastian Lohff # Licensed under Apache License 2.0 -from clintermission.climenu import CliMenu, CliMenuStyle, CliMenuCursor, CliMenuTheme, cli_select_item +from clintermission.climenu import CliMenu, CliMultiMenu, CliMenuStyle, CliSelectionStyle, CliMenuCursor, CliMenuTheme, cli_select_item __all__ = [ CliMenu, + CliMultiMenu, CliMenuStyle, + CliSelectionStyle, CliMenuCursor, CliMenuTheme, cli_select_item, diff --git a/clintermission/climenu.py b/clintermission/climenu.py index f71868b..b44d7af 100644 --- a/clintermission/climenu.py +++ b/clintermission/climenu.py @@ -61,6 +61,15 @@ class CliMenuStyle: # self.header_indent = 4 +class CliSelectionStyle: + SQUARE_BRACKETS = ('[x]', '[ ]') + ROUND_BRACKETS = ('(x)', '( )') + CHECKMARK = ('✔', '✖') + THUMBS = ('👍', '👎') + SMILEY = ('🙂', '🙁') + SMILEY_EXTREME = ('😁', '😨') + + class CliMenuTheme: BASIC = CliMenuStyle() BASIC_BOLD = CliMenuStyle(header_style='bold', highlight_style='bold') @@ -135,7 +144,6 @@ class CliMenu: def get_selection(self): if self.success: item = self._items[self._pos] - return (item.num, item.item) else: return (None, None) @@ -154,6 +162,9 @@ class CliMenu: # cursor with spaces minus dedent return ' ' * (len(self._cursor) + 1 * self._dedent_selection) + def _transform_prefix(self, item, lineno, prefix): + return prefix + def _transform_line(self, ti): if len(list(ti.fragments)) == 0: return Transformation(ti.fragments) @@ -179,6 +190,7 @@ class CliMenu: style = s.header_style items = [(s if s else style, t) for s, t in ti.fragments] + prefix = self._transform_prefix(item, ti.lineno, prefix) return Transformation([('', indent), (style, prefix)] + items) @@ -227,6 +239,9 @@ class CliMenu: return lines + def _register_extra_kb_cbs(self, kb): + pass + def _run(self): class MenuColorizer(Processor): def apply_transformation(_self, ti): @@ -274,6 +289,8 @@ class CliMenu: new_line, _ = self._doc.translate_index_to_position(self._buf.cursor_position) self.sync_cursor_to_line(new_line) + self._register_extra_kb_cbs(self._kb) + self._searchbar = SearchToolbar(ignore_case=True) text = '\n'.join(map(lambda _x: _x.text, self._items)) @@ -300,6 +317,63 @@ class CliMenu: self._ran = True +class CliMultiMenu(CliMenu): + default_selected_icon = '[x]' + default_unselected_icon = '[ ]' + + @classmethod + def set_default_selector_icons(cls, selected_icon, unselected_icon): + cls.default_selected_icon = selected_icon + cls.default_unselected_icon = unselected_icon + + def __init__(self, *args, selected_icon=None, unselected_icon=None, **kwargs): + self._multi_selected = [] + self._selected_icon = selected_icon or self.default_selected_icon + self._unselected_icon = unselected_icon or self.default_unselected_icon + super().__init__(*args, **kwargs) + + def add_option(self, text, item=None, selected=False): + super().add_option(text, item) + if selected: + self._multi_selected.append(len(self._items) - 1) + + def get_selection(self): + if self.success: + return [(self._items[n].num, self._items[n].item) for n in self._multi_selected] + else: + return None + + def get_selection_num(self): + if self.success: + return [self._items[n].num for n in self._multi_selected] + else: + return None + + def get_selection_item(self): + if self.success: + return [self._items[n].item for n in self._multi_selected] + else: + return None + + def _register_extra_kb_cbs(self, kb): + @kb.add('space', filter=~is_searching) + def mark(event): + if self._pos not in self._multi_selected: + self._multi_selected.append(self._pos) + else: + self._multi_selected.remove(self._pos) + + def _transform_prefix(self, item, lineno, prefix): + if item.focusable: + if lineno in self._multi_selected: + icon = self._selected_icon + else: + icon = self._unselected_icon + return "{}{} ".format(prefix, icon) + else: + return prefix + + def cli_select_item(options, header=None, abort_exc=ValueError, abort_text="Selection aborted.", style=None, return_single=True): """Helper function to quickly get a selection with just a few arguments"""