Coverage for src/robotide/ui/actiontriggers.py: 79%

260 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2008-2015 Nokia Networks 

2# Copyright 2016- Robot Framework Foundation 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16import builtins 1ad

17import wx 1ad

18 

19from ..context import IS_WINDOWS, IS_MAC 1ad

20_ = wx.GetTranslation # To keep linter/code analyser happy 1ad

21builtins.__dict__['_'] = wx.GetTranslation 1ad

22 

23ID_CustomizeToolbar = wx.ID_HIGHEST + 1 1ad

24 

25 

26def accel_index(menu: list, item: str) -> int: 1ad

27 """ Gets the index from a list ignoring the accelarator marker, & 

28 :param menu: list - list to get index 

29 :param item: str - item to find in menu list 

30 :returns index: int 

31 """ 

32 for idx, m in enumerate(menu): 32 ↛ 35line 32 didn't jump to line 35 because the loop on line 32 didn't complete

33 if m.replace('&', '') == item.replace('&', ''): 

34 return idx 

35 raise ValueError(f"{item} is not in list") 

36 

37 

38class MenuBar(object): 1ad

39 

40 def __init__(self, frame): 1ad

41 self._mb = wx.MenuBar() 

42 self._name_builder = _NameBuilder() 

43 self.m_frame = frame 

44 self._accelerators = [] 

45 self._menus = [] 

46 self._create_default_menus() 

47 

48 def take_menu_bar_into_use(self): 1ad

49 """This should be called after fully populating menus, 

50 Otherwise help menu will not be functional in osx.""" 

51 self.m_frame.SetMenuBar(self._mb) 

52 

53 def _create_default_menus(self): 1ad

54 for name in [_('File'), _('Edit'), _('Tools'), _('Help')]: 

55 self._create_menu(name, before_help=False) 

56 

57 def _create_menu(self, name, before_help=True): 1ad

58 # print(f"DEBUG: actiontriggers.py _create_menu ENTER name={name}") 

59 menu = _Menu(self._name_builder.get_name(name), self.m_frame) 

60 self._insert_menu(menu, before_help) 

61 return menu 

62 

63 def _insert_menu(self, menu, before_help): 1ad

64 if before_help: 

65 index = accel_index([m.name for m in self._menus], _('Help')) 

66 else: 

67 index = len(self._menus) 

68 self._menus.insert(index, menu) 

69 self._mb.Insert(index, menu.wx_menu, menu.name) 

70 

71 def register(self, action): 1ad

72 menu = self._find_menu(action.menu_name) 1abc

73 if not menu: 1abc

74 menu = self._create_menu(action.menu_name) 

75 menu.add_menu_item(action) 1abc

76 

77 def _find_menu(self, name): 1ad

78 t_name = name.replace('&', '') 1abc

79 t_registered = self._name_builder.get_registered_name(_(t_name)) 1abc

80 registered = self._name_builder.get_registered_name(name) 1abc

81 if not t_registered and not registered: 1abc

82 return None 

83 if t_registered: 83 ↛ 85line 83 didn't jump to line 85 because the condition on line 83 was always true1abc

84 registered = t_registered 1abc

85 for menu in self._menus: 85 ↛ exitline 85 didn't return from function '_find_menu' because the loop on line 85 didn't complete1abc

86 if menu.name == registered: 1abc

87 return menu 1abc

88 

89 

90class _Menu(object): 1ad

91 

92 def __init__(self, name, frame): 1ad

93 self.name = name 

94 # print(f"DEBUG: actiontriggers.py _Menu name={name}") 

95 self._frame = frame 

96 self.wx_menu = wx.Menu() 

97 self._menu_items = {} 

98 self._name_builder = _NameBuilder() 

99 self._open = False 

100 self._frame.Bind(wx.EVT_MENU_OPEN, self.on_menu_open) 

101 self._frame.Bind(wx.EVT_MENU_CLOSE, self.on_menu_close) 

102 

103 def on_menu_open(self, event): 1ad

104 if self.wx_menu == event.GetMenu() and not self._open: 

105 self._open = True 

106 for menu_item in self._menu_items.values(): 

107 menu_item.refresh_availability() 

108 event.Skip() 

109 

110 def on_menu_close(self, event): 1ad

111 if self._open: 

112 self._open = False 

113 for menu_item in self._menu_items.values(): 

114 menu_item.set_enabled() 

115 event.Skip() 

116 

117 def add_menu_item(self, action): 1ad

118 menu_item = self._construct_menu_item(action) 1abc

119 self._menu_items[menu_item.id] = menu_item 1abc

120 menu_item.register(action) 1abc

121 

122 def _construct_menu_item(self, action): 1ad

123 if action.is_separator(): 1abc

124 return self._create_separator(action) 1abc

125 menu_item = self._get_menu_item(action) 1abc

126 if not menu_item: 1abc

127 menu_item = self._create_menu_item(action) 1abc

128 return menu_item 1abc

129 

130 def _create_separator(self, action): 1ad

131 menu_item = SeparatorMenuItem(self._frame, self, action) 1abc

132 pos = action.get_insertion_index(self.wx_menu) 1abc

133 menu_item.set_wx_menu_item(self.wx_menu.InsertSeparator(pos)) 1abc

134 return menu_item 1abc

135 

136 def _get_menu_item(self, action): 1ad

137 for menu_item in self._menu_items.values(): 1abc

138 if self._names_equal(menu_item, action): 1abc

139 return menu_item 

140 return None 1abc

141 

142 def _names_equal(self, menu_item, action): 1ad

143 return menu_item.name == self._get_name(action, build_new=False) 1abc

144 

145 def _get_name(self, action, build_new): 1ad

146 get_name = build_new and self._name_builder.get_name or \ 1abc

147 self._name_builder.get_registered_name 

148 if not action.shortcut: # DEBUG not action.shortcut: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true1abc

149 return get_name(action.name) 

150 sht = action.get_shortcut() 1abc

151 if sht: 1abc

152 return '%s (%s)' % (get_name(action.name), sht) 1abc

153 return '%s' % get_name(action.name) 1ac

154 

155 def _create_menu_item(self, action): 1ad

156 name_with_accelerator = self._get_name(action, build_new=True) 1abc

157 menu_item = MenuItem(self._frame, self, name_with_accelerator) 1abc

158 pos = action.get_insertion_index(self.wx_menu) 1abc

159 wx_menu_item = wx.MenuItem(self.wx_menu, menu_item.id, menu_item.name, action.doc) 1abc

160 if action.icon: 1abc

161 wx_menu_item.SetBitmap(action.icon) 

162 wx_menu_item = self.wx_menu.Insert(pos, wx_menu_item) 1abc

163 menu_item.set_wx_menu_item(wx_menu_item) 1abc

164 return menu_item 1abc

165 

166 def remove_menu_item(self, idd): 1ad

167 self.wx_menu.Delete(idd) 

168 del(self._menu_items[idd]) 

169 

170 

171class _NameBuilder(object): 1ad

172 

173 def __init__(self): 1ad

174 self._names = {} 

175 self._accelerators = [] 

176 

177 def get_name(self, name): 1ad

178 registered = self.get_registered_name(name) 1abcfjglmkehi

179 if registered: 1abcfjglmkehi

180 return registered 1k

181 try: 1abcfjglmkehi

182 name = self._use_given_accelerator(name) 1abcfjglmkehi

183 except ValueError: 1abcfjgehi

184 name = self._generate_accelerator(name) 1abcfjgehi

185 self._register(name) 1abcfjglmkehi

186 # print(f"DEBUG: actiontriggers.py get_name RETURN name={name}") 

187 return name 1abcfjglmkehi

188 

189 def get_registered_name(self, name): 1ad

190 try: 1abcfjglmkehi

191 return self._names[name.replace('&', '').upper()] 1abcfjglmkehi

192 except KeyError: 1abcfjglmkehi

193 return None 1abcfjglmkehi

194 

195 def _register(self, name): 1ad

196 self._names[name.replace('&', '').upper()] = name 1abcfjglmkehi

197 

198 def _use_given_accelerator(self, name): 1ad

199 index = name.find('&') + 1 1abcfjglmkehi

200 if 0 < index < len(name) and self._accelerator_is_free(name[index]): 1abcfjglmkehi

201 return name 1ablmke

202 raise ValueError 1abcfjgehi

203 

204 def _generate_accelerator(self, name): 1ad

205 name = name.replace('&', '') 1abcfjgehi

206 for pos, char in enumerate(name): 1abcfjgehi

207 if self._accelerator_is_free(char): 1abcfjgehi

208 return '%s&%s' % (name[:pos], name[pos:]) 1abcfjgehi

209 return name 1abfe

210 

211 def _accelerator_is_free(self, char): 1ad

212 char = char.upper() 1abcfjglmkehi

213 if char not in self._accelerators and char != u' ': 1abcfjglmkehi

214 self._accelerators.append(char) 1abcfjglmkehi

215 return True 1abcfjglmkehi

216 return False 1abfgehi

217 

218 

219class _MenuItem(object): 1ad

220 

221 def __init__(self, frame, menu, name): 1ad

222 self._frame = frame 1abc

223 self._menu = menu 1abc

224 self.name = name 1abc

225 self._wx_menu_item = None 1abc

226 self._action_delegator = ActionDelegator(self._frame) 1abc

227 self.id = self._action_delegator.id 1abc

228 

229 def set_wx_menu_item(self, wx_menu_item): 1ad

230 self._wx_menu_item = wx_menu_item 1abc

231 

232 def register(self, action): 1ad

233 self._action_delegator.add(action) 1abc

234 action.register(self) 1abc

235 

236 def unregister(self, action): 1ad

237 if self._action_delegator.remove(action): 

238 self._menu.remove_menu_item(self.id) 

239 

240 def refresh_availability(self): 1ad

241 self._wx_menu_item.Enable(self._is_enabled()) 

242 

243 def set_enabled(self): 1ad

244 self._wx_menu_item.Enable(True) 

245 

246 @staticmethod 1ad

247 def _is_enabled(): 1ad

248 return NotImplemented 

249 

250 

251class MenuItem(_MenuItem): 1ad

252 

253 def _is_enabled(self): 1ad

254 return self._action_delegator.is_active() 

255 

256 

257class SeparatorMenuItem(_MenuItem): 1ad

258 

259 def set_wx_menu_item(self, wx_menu_item): 1ad

260 _MenuItem.set_wx_menu_item(self, wx_menu_item) 1abc

261 # Should get ITEM_SEPARATOR 

262 self.id = wx.ID_SEPARATOR 1abc

263 

264 @staticmethod 1ad

265 def _is_enabled(): 1ad

266 return False 

267 

268 def set_enabled(self): 1ad

269 """ Just ignore it """ 

270 pass 

271 

272 

273class _RideSearchMenuItem(object): 1ad

274 

275 def __init__(self, handler, icon): 1ad

276 self._handler = handler 

277 self.icon = icon 

278 

279 def __call__(self, *args, **kwargs): 1ad

280 self._handler(*args, **kwargs) 

281 

282 

283class ToolBarButton(object): 1ad

284 

285 def __init__(self, frame, toolbar, action): 1ad

286 self._toolbar = toolbar 

287 self.icon = action.icon 

288 self._action_delegator = ActionDelegator(frame) 

289 self.id = self._action_delegator.id 

290 

291 def register(self, action): 1ad

292 self._action_delegator.add(action) 

293 action.register(self) 

294 action.inform_changes_in_enabled_status(self) 

295 

296 def unregister(self, action): 1ad

297 if self._action_delegator.remove(action): 

298 self._toolbar.remove_toolbar_button(self) 

299 

300 def enabled_status_changed(self, action): 1ad

301 self._toolbar.enabled_status_changed(self.id, action) 

302 

303 

304class ShortcutRegistry(object): 1ad

305 

306 def __init__(self, frame): 1ad

307 self._frame = frame 

308 self._actions = {} 

309 

310 def register(self, action): 1ad

311 if action.has_shortcut() and action.has_action(): 1ab

312 delegator = self._actions.setdefault(action.get_shortcut(), 1ab

313 ActionDelegator(self._frame, 

314 action.shortcut)) 

315 delegator.add(action) 1ab

316 action.register(self) 1ab

317 self._update_accelerator_table() 1ab

318 

319 def unregister(self, action): 1ad

320 key = action.get_shortcut() 

321 if self._actions[key].remove(action): 

322 del(self._actions[key]) 

323 self._update_accelerator_table() 

324 

325 def _update_accelerator_table(self): 1ad

326 accelerators = [] 1ab

327 for delegator in self._actions.values(): 1ab

328 try: 1ab

329 flags, key_code = delegator.shortcut.parse() 1ab

330 except TypeError: 1ab

331 continue 1ab

332 accelerators.append(wx.AcceleratorEntry(flags, key_code, 1ab

333 delegator.id)) 

334 self._frame.SetAcceleratorTable(wx.AcceleratorTable(accelerators)) 1ab

335 

336 

337class ActionDelegator(object): 1ad

338 

339 def __init__(self, frame, shortcut=None): 1ad

340 self._frame = frame 1abc

341 self.shortcut = shortcut 1abc

342 self.id = wx.NewIdRef() 1abc

343 self._actions = [] 1abc

344 

345 def add(self, action): 1ad

346 self._actions.append(action) 1abc

347 if len(self._actions) == 1: 1abc

348 self._frame.Bind(wx.EVT_MENU, self, id=self.id) 1abc

349 

350 def remove(self, action): 1ad

351 """Removes action and returns True if delegator is empty.""" 

352 self._actions.remove(action) 

353 if len(self._actions) == 0: 

354 self._frame.Unbind(wx.EVT_MENU, id=self.id) 

355 return True 

356 return False 

357 

358 def is_active(self): 1ad

359 for action in self._actions: 

360 if action.is_active(): 

361 return True 

362 return False 

363 

364 def __call__(self, event): 1ad

365 for action in self._actions: 

366 action.act(event) 

367 if not (IS_WINDOWS or IS_MAC): 

368 event.Skip()