Coverage for src/hods/tui/base/menu.py: 100.00%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""hods - home directory synchronization.
3Copyright (C) 2016-2020 Mathias Stelzer <knoppo@rolln.de>
5hods is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
10hods is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
15You should have received a copy of the GNU General Public License
16along with this program. If not, see <http://www.gnu.org/licenses/>.
17"""
18import logging
20import urwid
22from hods.tui.base.edit import ListBoxWindow
23from hods.tui.base.list import Item, ItemColumns
26logger = logging.getLogger(__name__)
29class MenuColumns(ItemColumns):
30 """Root menu columns widget."""
32 def __init__(self, app, items, *args, **kwargs):
33 """Initialize column widget.
35 :param app: :class`hods.tui.__main__.App` object
36 :param items: child menu items
37 :param args: pass to parent `urwid.Columns`
38 :param kwargs: pass to parent `urwid.Columns`
39 """
40 self.app = app
41 super().__init__(items, *args, **kwargs)
43 def set_items(self, items):
44 """Set columns contents to given list of items."""
45 left = 0
46 previous = None
47 for item in items:
48 if previous is not None:
49 previous.next_item = item
51 item.menu = self
52 item.left = left
53 item.prev_item = previous
55 left += item.width
56 previous = item
58 super().set_items(items)
60 def focus_placeholder(self):
61 """Switch focus to the frame body."""
62 self.app.frame.set_focus('body')
64 def focus_menu(self):
65 """Switch focus to the frame header."""
66 self.app.frame.set_focus('header')
68 def close(self, focus_body=True):
69 """Close the menu.
71 :param focus_body: `bool` - Close all menus.
72 """
73 if focus_body:
74 self.focus_placeholder()
75 else:
76 self.focus_menu()
78 def keypress(self, size, key):
79 """Key event callback.
81 Give focus back to main placeholder on escape and
82 handle a 'down' key as an 'enter' key to open the pulldown.
83 """
84 # this is only called when focus is in header
85 logger.debug('%s.keypress(%s, %s)', self.__class__.__name__, repr(size), repr(key))
87 # give focus back to main placeholder
88 if key == 'esc':
89 self.focus_placeholder()
90 return
92 if key == 'down':
93 key = 'enter'
95 # pass key to Columns
96 return super().keypress(size, key)
99class PullDownMenu(ListBoxWindow):
100 """A pulldown submenu window."""
102 def __init__(self, item, **kwargs):
103 """Initialize pulldown submenu.
105 :param item: `hods.tui.base.menu.MenuItem` object - Parent
106 menu item containing this submenu.
107 :param kwargs: Pass to `hods.tui.base.view.Window`
108 """
109 self.item = item
111 children = self.item._on_press
112 if not children:
113 raise TypeError('PullDown menu item has no child items!') # pragma: no cover
115 for child in children:
116 child.menu = self
118 max_label_width = max(c.label_width for c in children)
119 max_key_width = max(c.text_right_width for c in children)
120 width = max_label_width + max_key_width + 5 # 5 == 2(border) + 2(padding) + 1(dividechar)
122 kwargs.setdefault('width', width)
123 kwargs.setdefault('height', len(children) + 2)
125 kwargs['left'] = item.left + 1
126 kwargs.setdefault('attr', 'menu window')
127 kwargs.setdefault('align', urwid.LEFT)
128 kwargs.setdefault('valign', urwid.TOP)
129 kwargs.setdefault('padding', 0)
130 super().__init__(item.menu.app, **kwargs)
132 def get_items(self):
133 """Colleect widgets to display."""
134 return self.item._on_press
136 def keypress(self, size, key):
137 """Key event callback.
139 Close the window if the key is *escape* and `close_on_escape` is True.
140 """
141 key = super().keypress(size, key)
143 if key == 'f2':
144 self.close()
145 return
147 if key == 'up':
148 self.close(focus_body=False)
149 return
151 if key == 'left':
152 if self.item.prev_item:
153 self.close()
154 self.item.menu.set_focus(self.item.prev_item)
155 self.item.prev_item.on_press()
156 return
158 if key == 'right':
159 if self.item.next_item:
160 self.close()
161 self.item.menu.set_focus(self.item.next_item)
162 self.item.next_item.on_press()
163 return
165 return key
167 def on_open(self):
168 """Give focus back to placeholder when opening the level 1 menu."""
169 super().on_open()
170 self.item.menu.focus_placeholder()
172 for item, options in self.item.menu.contents:
173 item.selected = False
174 item._invalidate()
175 self.item.selected = True
176 self.item._invalidate()
178 def close(self, focus_body=True):
179 """Close the menu.
181 :param focus_body: `bool` - Close all parent menus.
182 """
183 self.item.selected = False
184 self.item._invalidate()
185 super().close()
186 self.item.menu.close(focus_body=focus_body)
189class MenuItem(Item):
190 """A widget to display a (sub-)menu item."""
192 def __init__(self, label, on_press=None, key=None, **kwargs):
193 """Initialize menu item.
195 :param label: `str` - Item text to display.
196 :param on_press: `callable` - Call this when the item is
197 selected.
198 :param key: `str` - Key combination to show on the right.
199 :param kwargs: pass to parent `Item`
200 """
201 super().__init__(label, 'menu', text_right=key, on_press=on_press, **kwargs)
203 # set by parent menu
204 self.menu = None
205 self.left = 0
206 self.prev_item = None
207 self.next_item = None
209 def on_press(self, user_data=None):
210 """Menu item was pressed.
212 1. Close the window
213 2. Check for a on_press method and call it
214 3. If on_press is not a callable it is assumed to be a list of
215 submenu items. A pulldown will be built and displayed.
216 """
217 if callable(self._on_press):
218 self.menu.close(focus_body=True)
219 self._on_press()
220 return
221 PullDownMenu(self).show()