Browse Source

Implement multiple selections with CliMultiMenu

CliMultiMenu works much in the same way as CliMenu but it allows for
selecting multiple items.
Sebastian Lohff 1 year ago
parent
commit
c74e3d68d6
2 changed files with 78 additions and 2 deletions
  1. 3
    1
      clintermission/__init__.py
  2. 75
    1
      clintermission/climenu.py

+ 3
- 1
clintermission/__init__.py View File

@@ -1,11 +1,13 @@
1 1
 # Written by Sebastian Lohff <seba@someserver.de>
2 2
 # Licensed under Apache License 2.0
3 3
 
4
-from clintermission.climenu import CliMenu, CliMenuStyle, CliMenuCursor, CliMenuTheme, cli_select_item
4
+from clintermission.climenu import CliMenu, CliMultiMenu, CliMenuStyle, CliSelectionStyle, CliMenuCursor, CliMenuTheme, cli_select_item
5 5
 
6 6
 __all__ = [
7 7
     CliMenu,
8
+    CliMultiMenu,
8 9
     CliMenuStyle,
10
+    CliSelectionStyle,
9 11
     CliMenuCursor,
10 12
     CliMenuTheme,
11 13
     cli_select_item,

+ 75
- 1
clintermission/climenu.py View File

@@ -61,6 +61,15 @@ class CliMenuStyle:
61 61
         # self.header_indent = 4
62 62
 
63 63
 
64
+class CliSelectionStyle:
65
+    SQUARE_BRACKETS = ('[x]', '[ ]')
66
+    ROUND_BRACKETS = ('(x)', '( )')
67
+    CHECKMARK = ('✔', '✖')
68
+    THUMBS = ('👍', '👎')
69
+    SMILEY = ('🙂', '🙁')
70
+    SMILEY_EXTREME = ('😁', '😨')
71
+
72
+
64 73
 class CliMenuTheme:
65 74
     BASIC = CliMenuStyle()
66 75
     BASIC_BOLD = CliMenuStyle(header_style='bold', highlight_style='bold')
@@ -135,7 +144,6 @@ class CliMenu:
135 144
     def get_selection(self):
136 145
         if self.success:
137 146
             item = self._items[self._pos]
138
-
139 147
             return (item.num, item.item)
140 148
         else:
141 149
             return (None, None)
@@ -154,6 +162,9 @@ class CliMenu:
154 162
         # cursor with spaces minus dedent
155 163
         return ' ' * (len(self._cursor) + 1 * self._dedent_selection)
156 164
 
165
+    def _transform_prefix(self, item, lineno, prefix):
166
+        return prefix
167
+
157 168
     def _transform_line(self, ti):
158 169
         if len(list(ti.fragments)) == 0:
159 170
             return Transformation(ti.fragments)
@@ -179,6 +190,7 @@ class CliMenu:
179 190
             style = s.header_style
180 191
 
181 192
         items = [(s if s else style, t) for s, t in ti.fragments]
193
+        prefix = self._transform_prefix(item, ti.lineno, prefix)
182 194
 
183 195
         return Transformation([('', indent), (style, prefix)] + items)
184 196
 
@@ -227,6 +239,9 @@ class CliMenu:
227 239
 
228 240
         return lines
229 241
 
242
+    def _register_extra_kb_cbs(self, kb):
243
+        pass
244
+
230 245
     def _run(self):
231 246
         class MenuColorizer(Processor):
232 247
             def apply_transformation(_self, ti):
@@ -274,6 +289,8 @@ class CliMenu:
274 289
             new_line, _ = self._doc.translate_index_to_position(self._buf.cursor_position)
275 290
             self.sync_cursor_to_line(new_line)
276 291
 
292
+        self._register_extra_kb_cbs(self._kb)
293
+
277 294
         self._searchbar = SearchToolbar(ignore_case=True)
278 295
 
279 296
         text = '\n'.join(map(lambda _x: _x.text, self._items))
@@ -300,6 +317,63 @@ class CliMenu:
300 317
         self._ran = True
301 318
 
302 319
 
320
+class CliMultiMenu(CliMenu):
321
+    default_selected_icon = '[x]'
322
+    default_unselected_icon = '[ ]'
323
+
324
+    @classmethod
325
+    def set_default_selector_icons(cls, selected_icon, unselected_icon):
326
+        cls.default_selected_icon = selected_icon
327
+        cls.default_unselected_icon = unselected_icon
328
+
329
+    def __init__(self, *args, selected_icon=None, unselected_icon=None, **kwargs):
330
+        self._multi_selected = []
331
+        self._selected_icon = selected_icon or self.default_selected_icon
332
+        self._unselected_icon = unselected_icon or self.default_unselected_icon
333
+        super().__init__(*args, **kwargs)
334
+
335
+    def add_option(self, text, item=None, selected=False):
336
+        super().add_option(text, item)
337
+        if selected:
338
+            self._multi_selected.append(len(self._items) - 1)
339
+
340
+    def get_selection(self):
341
+        if self.success:
342
+            return [(self._items[n].num, self._items[n].item) for n in self._multi_selected]
343
+        else:
344
+            return None
345
+
346
+    def get_selection_num(self):
347
+        if self.success:
348
+            return [self._items[n].num for n in self._multi_selected]
349
+        else:
350
+            return None
351
+
352
+    def get_selection_item(self):
353
+        if self.success:
354
+            return [self._items[n].item for n in self._multi_selected]
355
+        else:
356
+            return None
357
+
358
+    def _register_extra_kb_cbs(self, kb):
359
+        @kb.add('space', filter=~is_searching)
360
+        def mark(event):
361
+            if self._pos not in self._multi_selected:
362
+                self._multi_selected.append(self._pos)
363
+            else:
364
+                self._multi_selected.remove(self._pos)
365
+
366
+    def _transform_prefix(self, item, lineno, prefix):
367
+        if item.focusable:
368
+            if lineno in self._multi_selected:
369
+                icon = self._selected_icon
370
+            else:
371
+                icon = self._unselected_icon
372
+            return "{}{} ".format(prefix, icon)
373
+        else:
374
+            return prefix
375
+
376
+
303 377
 def cli_select_item(options, header=None, abort_exc=ValueError, abort_text="Selection aborted.", style=None,
304 378
                     return_single=True):
305 379
     """Helper function to quickly get a selection with just a few arguments"""

Loading…
Cancel
Save