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

104 statements  

1"""hods - home directory synchronization. 

2 

3Copyright (C) 2016-2020 Mathias Stelzer <knoppo@rolln.de> 

4 

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. 

9 

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. 

14 

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 

19 

20import urwid 

21 

22from hods.tui.base.edit import ListBoxWindow 

23from hods.tui.base.list import Item, ItemColumns 

24 

25 

26logger = logging.getLogger(__name__) 

27 

28 

29class MenuColumns(ItemColumns): 

30 """Root menu columns widget.""" 

31 

32 def __init__(self, app, items, *args, **kwargs): 

33 """Initialize column widget. 

34 

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) 

42 

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 

50 

51 item.menu = self 

52 item.left = left 

53 item.prev_item = previous 

54 

55 left += item.width 

56 previous = item 

57 

58 super().set_items(items) 

59 

60 def focus_placeholder(self): 

61 """Switch focus to the frame body.""" 

62 self.app.frame.set_focus('body') 

63 

64 def focus_menu(self): 

65 """Switch focus to the frame header.""" 

66 self.app.frame.set_focus('header') 

67 

68 def close(self, focus_body=True): 

69 """Close the menu. 

70 

71 :param focus_body: `bool` - Close all menus. 

72 """ 

73 if focus_body: 

74 self.focus_placeholder() 

75 else: 

76 self.focus_menu() 

77 

78 def keypress(self, size, key): 

79 """Key event callback. 

80 

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)) 

86 

87 # give focus back to main placeholder 

88 if key == 'esc': 

89 self.focus_placeholder() 

90 return 

91 

92 if key == 'down': 

93 key = 'enter' 

94 

95 # pass key to Columns 

96 return super().keypress(size, key) 

97 

98 

99class PullDownMenu(ListBoxWindow): 

100 """A pulldown submenu window.""" 

101 

102 def __init__(self, item, **kwargs): 

103 """Initialize pulldown submenu. 

104 

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 

110 

111 children = self.item._on_press 

112 if not children: 

113 raise TypeError('PullDown menu item has no child items!') # pragma: no cover 

114 

115 for child in children: 

116 child.menu = self 

117 

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) 

121 

122 kwargs.setdefault('width', width) 

123 kwargs.setdefault('height', len(children) + 2) 

124 

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) 

131 

132 def get_items(self): 

133 """Colleect widgets to display.""" 

134 return self.item._on_press 

135 

136 def keypress(self, size, key): 

137 """Key event callback. 

138 

139 Close the window if the key is *escape* and `close_on_escape` is True. 

140 """ 

141 key = super().keypress(size, key) 

142 

143 if key == 'f2': 

144 self.close() 

145 return 

146 

147 if key == 'up': 

148 self.close(focus_body=False) 

149 return 

150 

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 

157 

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 

164 

165 return key 

166 

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() 

171 

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() 

177 

178 def close(self, focus_body=True): 

179 """Close the menu. 

180 

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) 

187 

188 

189class MenuItem(Item): 

190 """A widget to display a (sub-)menu item.""" 

191 

192 def __init__(self, label, on_press=None, key=None, **kwargs): 

193 """Initialize menu item. 

194 

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) 

202 

203 # set by parent menu 

204 self.menu = None 

205 self.left = 0 

206 self.prev_item = None 

207 self.next_item = None 

208 

209 def on_press(self, user_data=None): 

210 """Menu item was pressed. 

211 

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()