Coverage for C:\Repos\leo-editor\leo\core\leoMenu.py: 22%

384 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1#@+leo-ver=5-thin 

2#@+node:ekr.20031218072017.3749: * @file leoMenu.py 

3"""Gui-independent menu handling for Leo.""" 

4#@+<< imports leoMenu.py >> 

5#@+node:ekr.20220414095908.1: ** << imports leoMenu.py >> 

6from typing import Any, Callable, Dict, List, Tuple 

7from leo.core import leoGlobals as g 

8#@-<< imports leoMenu.py >> 

9Event = Any 

10Widget = Any 

11#@+others 

12#@+node:ekr.20031218072017.3750: ** class LeoMenu 

13class LeoMenu: 

14 """The base class for all Leo menus.""" 

15 #@+others 

16 #@+node:ekr.20120124042346.12938: *3* LeoMenu.Birth 

17 def __init__(self, frame: Widget) -> None: 

18 self.c = frame.c 

19 self.enable_dict: Dict[str, Callable] = {} # Created by finishCreate. 

20 self.frame = frame 

21 self.isNull = False 

22 self.menus: Dict[str, Any] = {} # Menu dictionary. 

23 

24 def finishCreate(self) -> None: 

25 self.define_enable_dict() 

26 #@+node:ekr.20120124042346.12937: *4* LeoMenu.define_enable_table 

27 #@@nobeautify 

28 

29 def define_enable_dict(self) -> None: 

30 

31 # pylint: disable=unnecessary-lambda 

32 # The lambdas *are* necessary. 

33 c = self.c 

34 if not c.commandsDict: 

35 return # This is not an error: it happens during init. 

36 self.enable_dict = d = { 

37 

38 # File menu... 

39 # 'revert': True, # Revert is always enabled. 

40 # 'open-with': True, # Open-With is always enabled. 

41 

42 # Edit menu... 

43 'undo': c.undoer.canUndo, 

44 'redo': c.undoer.canRedo, 

45 'extract-names': c.canExtractSectionNames, 

46 'extract': c.canExtract, 

47 'match-brackets': c.canFindMatchingBracket, 

48 

49 # Top-level Outline menu... 

50 'cut-node': c.canCutOutline, 

51 'delete-node': c.canDeleteHeadline, 

52 'paste-node': c.canPasteOutline, 

53 'paste-retaining-clones': c.canPasteOutline, 

54 'clone-node': c.canClone, 

55 'sort-siblings': c.canSortSiblings, 

56 'hoist': c.canHoist, 

57 'de-hoist': c.canDehoist, 

58 

59 # Outline:Expand/Contract menu... 

60 'contract-parent': c.canContractParent, 

61 'contract-node': lambda: c.p.hasChildren() and c.p.isExpanded(), 

62 'contract-or-go-left': lambda: c.p.hasChildren() and c.p.isExpanded() or c.p.hasParent(), 

63 'expand-node': lambda: c.p.hasChildren() and not c.p.isExpanded(), 

64 'expand-prev-level': lambda: c.p.hasChildren() and c.p.isExpanded(), 

65 'expand-next-level': lambda: c.p.hasChildren(), 

66 'expand-to-level-1': lambda: c.p.hasChildren() and c.p.isExpanded(), 

67 'expand-or-go-right': lambda: c.p.hasChildren(), 

68 

69 # Outline:Move menu... 

70 'move-outline-down': lambda: c.canMoveOutlineDown(), 

71 'move-outline-left': lambda: c.canMoveOutlineLeft(), 

72 'move-outline-right': lambda: c.canMoveOutlineRight(), 

73 'move-outline-up': lambda: c.canMoveOutlineUp(), 

74 'promote': lambda: c.canPromote(), 

75 'demote': lambda: c.canDemote(), 

76 

77 # Outline:Go To menu... 

78 'goto-prev-history-node': lambda: c.nodeHistory.canGoToPrevVisited(), 

79 'goto-next-history-node': lambda: c.nodeHistory.canGoToNextVisited(), 

80 'goto-prev-visible': lambda: c.canSelectVisBack(), 

81 'goto-next-visible': lambda: c.canSelectVisNext(), 

82 # These are too slow... 

83 # 'go-to-next-marked': c.canGoToNextMarkedHeadline, 

84 # 'go-to-next-changed': c.canGoToNextDirtyHeadline, 

85 'goto-next-clone': lambda: c.p.isCloned(), 

86 'goto-prev-node': lambda: c.canSelectThreadBack(), 

87 'goto-next-node': lambda: c.canSelectThreadNext(), 

88 'goto-parent': lambda: c.p.hasParent(), 

89 'goto-prev-sibling': lambda: c.p.hasBack(), 

90 'goto-next-sibling': lambda: c.p.hasNext(), 

91 

92 # Outline:Mark menu... 

93 'mark-subheads': lambda: c.p.hasChildren(), 

94 # too slow... 

95 # 'mark-changed-items': c.canMarkChangedHeadlines, 

96 } 

97 

98 for i in range(1,9): 

99 d [f"expand-to-level-{i}"] = lambda: c.p.hasChildren() 

100 

101 if 0: # Initial testing. 

102 commandKeys = list(c.commandsDict.keys()) 

103 for key in sorted(d.keys()): 

104 if key not in commandKeys: 

105 g.trace(f"*** bad entry for {key}") 

106 #@+node:ekr.20031218072017.3775: *3* LeoMenu.error and oops 

107 def oops(self) -> None: 

108 g.pr("LeoMenu oops:", g.callers(4), "should be overridden in subclass") 

109 

110 def error(self, s: str) -> None: 

111 g.error('', s) 

112 #@+node:ekr.20031218072017.3781: *3* LeoMenu.Gui-independent menu routines 

113 #@+node:ekr.20060926213642: *4* LeoMenu.capitalizeMinibufferMenuName 

114 #@@nobeautify 

115 

116 def capitalizeMinibufferMenuName(self, s: str, removeHyphens: bool) -> str: 

117 result = [] 

118 for i, ch in enumerate(s): 

119 prev = s[i - 1] if i > 0 else '' 

120 prevprev = s[i - 2] if i > 1 else '' 

121 if ( 

122 i == 0 or 

123 i == 1 and prev == '&' or 

124 prev == '-' or 

125 prev == '&' and prevprev == '-' 

126 ): 

127 result.append(ch.capitalize()) 

128 elif removeHyphens and ch == '-': 

129 result.append(' ') 

130 else: 

131 result.append(ch) 

132 return ''.join(result) 

133 #@+node:ekr.20031218072017.3785: *4* LeoMenu.createMenusFromTables & helpers 

134 def createMenusFromTables(self) -> None: 

135 """(leoMenu) Usually over-ridden.""" 

136 c = self.c 

137 aList = c.config.getMenusList() 

138 if aList: 

139 self.createMenusFromConfigList(aList) 

140 else: 

141 g.es_print('No @menu setting found') 

142 #@+node:ekr.20070926135612: *5* LeoMenu.createMenusFromConfigList & helpers 

143 def createMenusFromConfigList(self, aList: List) -> None: 

144 """ 

145 Create menus from aList. 

146 The 'top' menu has already been created. 

147 """ 

148 # Called from createMenuBar. 

149 c = self.c 

150 for z in aList: 

151 kind, val, val2 = z 

152 if kind.startswith('@menu'): 

153 name = kind[len('@menu') :].strip() 

154 if not self.handleSpecialMenus(name, parentName=None): 

155 # #528: Don't create duplicate menu items. 

156 # Create top-level menu. 

157 menu = self.createNewMenu(name) 

158 if menu: 

159 self.createMenuFromConfigList(name, val, level=0) 

160 else: 

161 g.trace('no menu', name) 

162 else: 

163 self.error(f"{kind} {val} not valid outside @menu tree") 

164 aList = c.config.getOpenWith() 

165 if aList: 

166 # a list of dicts. 

167 self.createOpenWithMenuFromTable(aList) 

168 #@+node:ekr.20070927082205: *6* LeoMenu.createMenuFromConfigList 

169 def createMenuFromConfigList(self, parentName: str, aList: List, level: int=0) -> None: 

170 """Build menu based on nested list 

171 

172 List entries are either: 

173 

174 ['@item', 'command-name', 'optional-view-name'] 

175 

176 or: 

177 

178 ['@menu Submenu name', <nested list>, None] 

179 

180 :param str parentName: name of menu under which to place this one 

181 :param list aList: list of entries as described above 

182 """ 

183 parentMenu = self.getMenu(parentName) 

184 if not parentMenu: 

185 g.trace('NO PARENT', parentName, g.callers()) 

186 return # #2030 

187 table: List[Any] = [] 

188 z: Tuple[str, List, str] 

189 for z in aList: 

190 kind, val, val2 = z 

191 if kind.startswith('@menu'): 

192 # Menu names can be unicode without any problem. 

193 name = kind[5:].strip() 

194 if table: 

195 self.createMenuEntries(parentMenu, table) 

196 if not self.handleSpecialMenus(name, parentName, 

197 alt_name=val2, #848. 

198 table=table, 

199 ): 

200 # Create submenu of parent menu. 

201 menu = self.createNewMenu(name, parentName) 

202 if menu: 

203 # Partial fix for #528. 

204 self.createMenuFromConfigList(name, val, level + 1) 

205 table = [] 

206 elif kind == '@item': 

207 name = str(val) # Item names must always be ascii. 

208 if val2: 

209 # Translated names can be unicode. 

210 table.append((val2, name),) 

211 else: 

212 table.append(name) 

213 else: 

214 g.trace('can not happen: bad kind:', kind) 

215 if table: 

216 self.createMenuEntries(parentMenu, table) 

217 #@+node:ekr.20070927172712: *6* LeoMenu.handleSpecialMenus 

218 def handleSpecialMenus(self, name: str, parentName: str, alt_name: str=None, table: List=None) -> bool: 

219 """ 

220 Handle a special menu if name is the name of a special menu. 

221 return True if this method handles the menu. 

222 """ 

223 c = self.c 

224 if table is None: 

225 table = [] 

226 name2 = name.replace('&', '').replace(' ', '').lower() 

227 if name2 == 'plugins': 

228 # Create the plugins menu using a hook. 

229 g.doHook("create-optional-menus", c=c, menu_name=name) 

230 return True 

231 if name2.startswith('recentfiles'): 

232 # Just create the menu. 

233 # createRecentFilesMenuItems will create the contents later. 

234 g.app.recentFilesManager.recentFilesMenuName = alt_name or name # #848 

235 self.createNewMenu(alt_name or name, parentName) 

236 return True 

237 if name2 == 'help' and g.isMac: 

238 helpMenu = self.getMacHelpMenu(table) 

239 return helpMenu is not None 

240 return False 

241 #@+node:ekr.20031218072017.3780: *4* LeoMenu.hasSelection 

242 # Returns True if text in the outline or body text is selected. 

243 

244 def hasSelection(self) -> bool: 

245 c = self.c 

246 w = c.frame.body.wrapper 

247 if c.frame.body: 

248 first, last = w.getSelectionRange() 

249 return first != last 

250 return False 

251 #@+node:ekr.20051022053758.1: *3* LeoMenu.Helpers 

252 #@+node:ekr.20031218072017.3783: *4* LeoMenu.canonicalize* 

253 def canonicalizeMenuName(self, name: str) -> str: 

254 

255 # #1121 & #1188. Allow Chinese characters in command names 

256 if g.isascii(name): 

257 return ''.join([ch for ch in name.lower() if ch.isalnum()]) 

258 return name 

259 

260 def canonicalizeTranslatedMenuName(self, name: str) -> str: 

261 

262 # #1121 & #1188. Allow Chinese characters in command names 

263 if g.isascii(name): 

264 return ''.join([ch for ch in name.lower() if ch not in '& \t\n\r']) 

265 return ''.join([ch for ch in name if ch not in '& \t\n\r']) 

266 #@+node:ekr.20031218072017.1723: *4* LeoMenu.createMenuEntries & helpers 

267 def createMenuEntries(self, menu: Any, table: List) -> None: 

268 """ 

269 Create a menu entry from the table. 

270 

271 This method shows the shortcut in the menu, but **never** binds any shortcuts. 

272 """ 

273 c = self.c 

274 if g.unitTesting: 

275 return 

276 if not menu: 

277 return 

278 self.traceMenuTable(table) 

279 for data in table: 

280 label, command, done = self.getMenuEntryInfo(data, menu) 

281 if done: 

282 continue 

283 commandName = self.getMenuEntryBindings(command, label) 

284 if not commandName: 

285 continue 

286 masterMenuCallback = self.createMasterMenuCallback(command, commandName) 

287 realLabel = self.getRealMenuName(label) 

288 amp_index = realLabel.find("&") 

289 realLabel = realLabel.replace("&", "") 

290 # c.add_command ensures that c.outerUpdate is called. 

291 c.add_command(menu, label=realLabel, 

292 accelerator='', # The accelerator is now computed dynamically. 

293 command=masterMenuCallback, 

294 commandName=commandName, 

295 underline=amp_index) 

296 #@+node:ekr.20111102072143.10016: *5* LeoMenu.createMasterMenuCallback 

297 def createMasterMenuCallback(self, command: str, commandName: str) -> Callable: 

298 """ 

299 Create a callback for the given args. 

300 

301 - If command is a string, it is treated as a command name. 

302 - Otherwise, it should be a callable representing the actual command. 

303 """ 

304 c = self.c 

305 

306 def getWidget() -> Widget: 

307 """Carefully return the widget that has focus.""" 

308 w = c.frame.getFocus() 

309 if w and g.isMac: 

310 # Redirect (MacOS only). 

311 wname = c.widget_name(w) 

312 if wname.startswith('head'): 

313 w = c.frame.tree.edit_widget(c.p) 

314 # Return a wrapper if possible. 

315 if not g.isTextWrapper(w): 

316 w = getattr(w, 'wrapper', w) 

317 return w 

318 

319 if isinstance(command, str): 

320 

321 def static_menu_callback() -> None: 

322 event = g.app.gui.create_key_event(c, w=getWidget()) 

323 c.doCommandByName(commandName, event) 

324 

325 return static_menu_callback 

326 

327 # The command must be a callable. 

328 if not callable(command): 

329 

330 def dummy_menu_callback(event: Event=None) -> None: 

331 pass 

332 

333 g.trace(f"bad command: {command!r}", color='red') 

334 return dummy_menu_callback 

335 

336 # Create a command dynamically. 

337 

338 def dynamic_menu_callback() -> None: 

339 event = g.app.gui.create_key_event(c, w=getWidget()) 

340 return c.doCommand(command, commandName, event) # #1595 

341 

342 return dynamic_menu_callback 

343 #@+node:ekr.20111028060955.16568: *5* LeoMenu.getMenuEntryBindings 

344 def getMenuEntryBindings(self, command: str, label: str) -> str: 

345 """Compute commandName from command.""" 

346 c = self.c 

347 if isinstance(command, str): 

348 # Command is really a command name. 

349 commandName = command 

350 else: 

351 # First, get the old-style name. 

352 # #1121: Allow Chinese characters in command names 

353 commandName = label.strip() 

354 command = c.commandsDict.get(commandName) 

355 return commandName 

356 #@+node:ekr.20111028060955.16565: *5* LeoMenu.getMenuEntryInfo 

357 def getMenuEntryInfo(self, data: Any, menu: Any) -> Tuple[str, str, bool]: 

358 """ 

359 Parse a single entry in the table passed to createMenuEntries. 

360 

361 Table entries have the following formats: 

362 

363 1. A string, used as the command name. 

364 2. A 2-tuple: (command_name, command_func) 

365 3. A 3-tuple: (command_name, menu_shortcut, command_func) 

366 

367 Special case: If command_name is None or "-" it represents a menu separator. 

368 """ 

369 done = False 

370 if isinstance(data, str): 

371 # A single string is both the label and the command. 

372 s = data 

373 removeHyphens = bool(s and s[0] == '*') 

374 if removeHyphens: 

375 s = s[1:] 

376 label = self.capitalizeMinibufferMenuName(s, removeHyphens) 

377 command = s.replace('&', '').lower() 

378 if label == '-': 

379 self.add_separator(menu) 

380 done = True # That's all. 

381 else: 

382 ok = isinstance(data, (list, tuple)) and len(data) in (2, 3) 

383 if ok: 

384 if len(data) == 2: 

385 # Command can be a minibuffer-command name. 

386 label, command = data 

387 else: 

388 # Ignore shortcuts bound in menu tables. 

389 label, junk, command = data 

390 if label in (None, '-'): 

391 self.add_separator(menu) 

392 done = True # That's all. 

393 else: 

394 g.trace(f"bad data in menu table: {repr(data)}") 

395 done = True # Ignore bad data 

396 return label, command, done 

397 #@+node:ekr.20111028060955.16563: *5* LeoMenu.traceMenuTable 

398 def traceMenuTable(self, table: List) -> None: 

399 

400 trace = False and not g.unitTesting 

401 if not trace: 

402 return 

403 format = '%40s %s' 

404 g.trace('*' * 40) 

405 for data in table: 

406 if isinstance(data, (list, tuple)): 

407 n = len(data) 

408 if n == 2: 

409 print(format % (data[0], data[1])) 

410 elif n == 3: 

411 name, junk, func = data 

412 print(format % (name, func and func.__name__ or '<NO FUNC>')) 

413 else: 

414 print(format % (data, '')) 

415 #@+node:ekr.20031218072017.3784: *4* LeoMenu.createMenuItemsFromTable 

416 def createMenuItemsFromTable(self, menuName: str, table: List) -> None: 

417 

418 if g.app.gui.isNullGui: 

419 return 

420 try: 

421 menu = self.getMenu(menuName) 

422 if menu is None: 

423 return 

424 self.createMenuEntries(menu, table) 

425 except Exception: 

426 g.es_print("exception creating items for", menuName, "menu") 

427 g.es_exception() 

428 g.app.menuWarningsGiven = True 

429 #@+node:ekr.20031218072017.3804: *4* LeoMenu.createNewMenu 

430 def createNewMenu(self, menuName: str, parentName: str="top", before: str=None) -> Any: 

431 try: 

432 parent = self.getMenu(parentName) # parent may be None. 

433 menu = self.getMenu(menuName) 

434 if menu: 

435 # Not an error. 

436 # g.error("menu already exists:", menuName) 

437 return None # Fix #528. 

438 menu = self.new_menu(parent, tearoff=0, label=menuName) 

439 self.setMenu(menuName, menu) 

440 label = self.getRealMenuName(menuName) 

441 amp_index = label.find("&") 

442 label = label.replace("&", "") 

443 if before: # Insert the menu before the "before" menu. 

444 index_label = self.getRealMenuName(before) 

445 amp_index = index_label.find("&") 

446 index_label = index_label.replace("&", "") 

447 index = parent.index(index_label) 

448 self.insert_cascade( 

449 parent, index=index, label=label, menu=menu, underline=amp_index) 

450 else: 

451 self.add_cascade(parent, label=label, menu=menu, underline=amp_index) 

452 return menu 

453 except Exception: 

454 g.es("exception creating", menuName, "menu") 

455 g.es_exception() 

456 return None 

457 #@+node:ekr.20031218072017.4116: *4* LeoMenu.createOpenWithMenuFromTable & helpers 

458 def createOpenWithMenuFromTable(self, table: List[Dict]) -> None: 

459 """ 

460 Table is a list of dictionaries, created from @openwith settings nodes. 

461 

462 This menu code uses these keys: 

463 

464 'name': menu label. 

465 'shortcut': optional menu shortcut. 

466 

467 efc.open_temp_file uses these keys: 

468 

469 'args': the command-line arguments to be used to open the file. 

470 'ext': the file extension. 

471 'kind': the method used to open the file, such as subprocess.Popen. 

472 """ 

473 k = self.c.k 

474 if not table: 

475 return 

476 g.app.openWithTable = table # Override any previous table. 

477 # Delete the previous entry. 

478 parent = self.getMenu("File") 

479 if not parent: 

480 if not g.app.batchMode: 

481 g.error('', 'createOpenWithMenuFromTable:', 'no File menu') 

482 return 

483 label = self.getRealMenuName("Open &With...") 

484 amp_index = label.find("&") 

485 label = label.replace("&", "") 

486 try: 

487 index = parent.index(label) 

488 parent.delete(index) 

489 except Exception: 

490 try: 

491 index = parent.index("Open With...") 

492 parent.delete(index) 

493 except Exception: 

494 g.trace('unexpected exception') 

495 g.es_exception() 

496 return 

497 # Create the Open With menu. 

498 openWithMenu = self.createOpenWithMenu(parent, label, index, amp_index) 

499 if not openWithMenu: 

500 g.trace('openWithMenu returns None') 

501 return 

502 self.setMenu("Open With...", openWithMenu) 

503 # Create the menu items in of the Open With menu. 

504 self.createOpenWithMenuItemsFromTable(openWithMenu, table) 

505 for d in table: 

506 k.bindOpenWith(d) 

507 #@+node:ekr.20051022043608.1: *5* LeoMenu.createOpenWithMenuItemsFromTable & callback 

508 def createOpenWithMenuItemsFromTable(self, menu: str, table: List[Dict]) -> None: 

509 """ 

510 Create an entry in the Open with Menu from the table, a list of dictionaries. 

511 

512 Each dictionary d has the following keys: 

513 

514 'args': the command-line arguments used to open the file. 

515 'ext': not used here: used by efc.open_temp_file. 

516 'kind': not used here: used by efc.open_temp_file. 

517 'name': menu label. 

518 'shortcut': optional menu shortcut. 

519 """ 

520 c = self.c 

521 if g.unitTesting: 

522 return 

523 for d in table: 

524 label = d.get('name') 

525 args = d.get('args', []) 

526 accel = d.get('shortcut') or '' 

527 if label and args: 

528 realLabel = self.getRealMenuName(label) 

529 underline = realLabel.find("&") 

530 realLabel = realLabel.replace("&", "") 

531 callback = self.defineOpenWithMenuCallback(d) 

532 c.add_command(menu, 

533 label=realLabel, 

534 accelerator=accel, 

535 command=callback, 

536 underline=underline) 

537 #@+node:ekr.20031218072017.4118: *6* LeoMenu.defineOpenWithMenuCallback 

538 def defineOpenWithMenuCallback(self, d: Dict[str, str]=None) -> Callable: 

539 # The first parameter must be event, and it must default to None. 

540 

541 def openWithMenuCallback(event: Event=None, self: Any=self, d: Dict[str, str]=d) -> Any: 

542 d1 = d.copy() if d else {} 

543 return self.c.openWith(d=d1) 

544 

545 return openWithMenuCallback 

546 #@+node:tbrown.20080509212202.7: *4* LeoMenu.deleteRecentFilesMenuItems 

547 def deleteRecentFilesMenuItems(self, menu: str) -> None: 

548 """Delete recent file menu entries""" 

549 rf = g.app.recentFilesManager 

550 # Why not just delete all the entries? 

551 recentFiles = rf.getRecentFiles() 

552 toDrop = len(recentFiles) + len(rf.getRecentFilesTable()) 

553 self.delete_range(menu, 0, toDrop) 

554 for i in rf.groupedMenus: 

555 menu = self.getMenu(i) 

556 if menu: 

557 self.destroy(menu) 

558 self.destroyMenu(i) 

559 #@+node:ekr.20031218072017.3805: *4* LeoMenu.deleteMenu 

560 def deleteMenu(self, menuName: str) -> None: 

561 try: 

562 menu = self.getMenu(menuName) 

563 if menu: 

564 self.destroy(menu) 

565 self.destroyMenu(menuName) 

566 else: 

567 g.es("can't delete menu:", menuName) 

568 except Exception: 

569 g.es("exception deleting", menuName, "menu") 

570 g.es_exception() 

571 #@+node:ekr.20031218072017.3806: *4* LeoMenu.deleteMenuItem 

572 def deleteMenuItem(self, itemName: str, menuName: str="top") -> None: 

573 """Delete itemName from the menu whose name is menuName.""" 

574 try: 

575 menu = self.getMenu(menuName) 

576 if menu: 

577 realItemName = self.getRealMenuName(itemName) 

578 self.delete(menu, realItemName) 

579 else: 

580 g.es("menu not found:", menuName) 

581 except Exception: 

582 g.es("exception deleting", itemName, "from", menuName, "menu") 

583 g.es_exception() 

584 #@+node:ekr.20031218072017.3782: *4* LeoMenu.get/setRealMenuName & setRealMenuNamesFromTable 

585 # Returns the translation of a menu name or an item name. 

586 

587 def getRealMenuName(self, menuName: str) -> str: 

588 cmn = self.canonicalizeTranslatedMenuName(menuName) 

589 return g.app.realMenuNameDict.get(cmn, menuName) 

590 

591 def setRealMenuName(self, untrans: str, trans: List) -> None: 

592 cmn = self.canonicalizeTranslatedMenuName(untrans) 

593 g.app.realMenuNameDict[cmn] = trans 

594 

595 def setRealMenuNamesFromTable(self, table: List) -> None: 

596 try: 

597 for untrans, trans in table: 

598 self.setRealMenuName(untrans, trans) 

599 except Exception: 

600 g.es("exception in", "setRealMenuNamesFromTable") 

601 g.es_exception() 

602 #@+node:ekr.20031218072017.3807: *4* LeoMenu.getMenu, setMenu, destroyMenu 

603 def getMenu(self, menuName: str) -> Any: 

604 cmn = self.canonicalizeMenuName(menuName) 

605 return self.menus.get(cmn) 

606 

607 def setMenu(self, menuName: str, menu: str) -> None: 

608 cmn = self.canonicalizeMenuName(menuName) 

609 self.menus[cmn] = menu 

610 

611 def destroyMenu(self, menuName: str) -> None: 

612 cmn = self.canonicalizeMenuName(menuName) 

613 del self.menus[cmn] 

614 #@+node:ekr.20031218072017.3808: *3* LeoMenu.Must be overridden in menu subclasses 

615 #@+node:ekr.20031218072017.3809: *4* LeoMenu.9 Routines with Tk spellings 

616 def add_cascade(self, parent: Any, label: str, menu: Any, underline: int) -> None: 

617 self.oops() 

618 

619 def add_command(self, menu: Widget, 

620 accelerator: str='', command: Callable=None, commandName: str=None, label: str=None, underline: int=0, 

621 ) -> None: 

622 self.oops() 

623 

624 def add_separator(self, menu: str) -> None: 

625 self.oops() 

626 

627 # def bind (self,bind_shortcut,callback): 

628 # self.oops() 

629 

630 def delete(self, menu: Any, realItemName: str) -> None: 

631 self.oops() 

632 

633 def delete_range(self, menu: Any, n1: int, n2: int) -> None: 

634 self.oops() 

635 

636 def destroy(self, menu: Any) -> None: 

637 self.oops() 

638 

639 def insert(self, menuName: str, position: int, label: str, command: Callable, underline: int=None) -> None: 

640 self.oops() 

641 

642 def insert_cascade(self, parent: Widget, index: int, label: str, menu: Any, underline: int) -> Widget: 

643 self.oops() 

644 

645 def new_menu(self, parent: Widget, tearoff: int=0, label: str='') -> Any: 

646 # 2010: added label arg for pylint. 

647 self.oops() 

648 #@+node:ekr.20031218072017.3810: *4* LeoMenu.9 Routines with new spellings 

649 def activateMenu(self, menuName: str) -> None: # New in Leo 4.4b2. 

650 self.oops() 

651 

652 def clearAccel(self, menu: str, name: str) -> None: 

653 self.oops() 

654 

655 def createMenuBar(self, frame: Widget) -> None: 

656 self.oops() 

657 

658 def createOpenWithMenu(self, parent: Any, label: str, index: int, amp_index: int) -> Any: 

659 self.oops() 

660 

661 def disableMenu(self, menu: str, name: str) -> None: 

662 self.oops() 

663 

664 def enableMenu(self, menu: Widget, name: str, val: bool) -> None: 

665 self.oops() 

666 

667 def getMacHelpMenu(self, table: List) -> Any: 

668 self.oops() 

669 

670 def getMenuLabel(self, menu: str, name: str) -> str: 

671 self.oops() 

672 return '' 

673 

674 def setMenuLabel(self, menu: str, name: str, label: str, underline: int=-1) -> None: 

675 self.oops() 

676 #@-others 

677#@+node:ekr.20031218072017.3811: ** class NullMenu 

678class NullMenu(LeoMenu): 

679 """A null menu class for testing and batch execution.""" 

680 #@+others 

681 #@+node:ekr.20050104094308: *3* ctor (NullMenu) 

682 def __init__(self, frame: Widget) -> None: 

683 super().__init__(frame) 

684 self.isNull = True 

685 #@+node:ekr.20050104094029: *3* oops 

686 def oops(self) -> None: 

687 pass 

688 #@-others 

689#@-others 

690#@@language python 

691#@@tabwidth -4 

692#@@pagewidth 70 

693#@-leo