Hide keyboard shortcuts

Hot-keys 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#@+leo-ver=5-thin 

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

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

4from typing import Any, List 

5from leo.core import leoGlobals as g 

6#@+others 

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

8class LeoMenu: 

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

10 #@+others 

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

12 def __init__(self, frame): 

13 self.c = frame.c 

14 self.enable_dict = {} # Created by finishCreate. 

15 self.frame = frame 

16 self.isNull = False 

17 self.menus = {} # Menu dictionary. 

18 self.menuShortcuts = {} 

19 

20 def finishCreate(self): 

21 self.define_enable_dict() 

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

23 #@@nobeautify 

24 

25 def define_enable_dict (self): 

26 

27 # pylint: disable=unnecessary-lambda 

28 # The lambdas *are* necessary. 

29 c = self.c 

30 if not c.commandsDict: 

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

32 self.enable_dict = d = { 

33 

34 # File menu... 

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

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

37 

38 # Edit menu... 

39 'undo': c.undoer.canUndo, 

40 'redo': c.undoer.canRedo, 

41 'extract-names': c.canExtractSectionNames, 

42 'extract': c.canExtract, 

43 'match-brackets': c.canFindMatchingBracket, 

44 

45 # Top-level Outline menu... 

46 'cut-node': c.canCutOutline, 

47 'delete-node': c.canDeleteHeadline, 

48 'paste-node': c.canPasteOutline, 

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

50 'clone-node': c.canClone, 

51 'sort-siblings': c.canSortSiblings, 

52 'hoist': c.canHoist, 

53 'de-hoist': c.canDehoist, 

54 

55 # Outline:Expand/Contract menu... 

56 'contract-parent': c.canContractParent, 

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

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

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

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

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

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

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

64 

65 # Outline:Move menu... 

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

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

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

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

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

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

72 

73 # Outline:Go To menu... 

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

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

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

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

78 # These are too slow... 

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

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

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

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

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

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

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

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

87 

88 # Outline:Mark menu... 

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

90 # too slow... 

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

92 } 

93 

94 for i in range(1,9): 

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

96 

97 if 0: # Initial testing. 

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

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

100 if key not in commandKeys: 

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

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

103 def oops(self): 

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

105 

106 def error(self, s): 

107 g.error('', s) 

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

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

110 #@@nobeautify 

111 

112 def capitalizeMinibufferMenuName(self, s, removeHyphens): 

113 result = [] 

114 for i, ch in enumerate(s): 

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

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

117 if ( 

118 i == 0 or 

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

120 prev == '-' or 

121 prev == '&' and prevprev == '-' 

122 ): 

123 result.append(ch.capitalize()) 

124 elif removeHyphens and ch == '-': 

125 result.append(' ') 

126 else: 

127 result.append(ch) 

128 return ''.join(result) 

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

130 def createMenusFromTables(self): 

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

132 c = self.c 

133 aList = c.config.getMenusList() 

134 if aList: 

135 self.createMenusFromConfigList(aList) 

136 else: 

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

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

139 def createMenusFromConfigList(self, aList): 

140 """ 

141 Create menus from aList. 

142 The 'top' menu has already been created. 

143 """ 

144 # Called from createMenuBar. 

145 c = self.c 

146 for z in aList: 

147 kind, val, val2 = z 

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

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

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

151 # Fix #528: Don't create duplicate menu items. 

152 menu = self.createNewMenu(name) 

153 # Create top-level menu. 

154 if menu: 

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

156 else: 

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

158 else: 

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

160 aList = c.config.getOpenWith() 

161 if aList: 

162 # a list of dicts. 

163 self.createOpenWithMenuFromTable(aList) 

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

165 def createMenuFromConfigList(self, parentName, aList, level=0): 

166 """Build menu based on nested list 

167 

168 List entries are either: 

169 

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

171 

172 or: 

173 

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

175 

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

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

178 """ 

179 parentMenu = self.getMenu(parentName) 

180 if not parentMenu: 

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

182 return # #2030 

183 table: List[Any] = [] 

184 for z in aList: 

185 kind, val, val2 = z 

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

187 # Menu names can be unicode without any problem. 

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

189 if table: 

190 self.createMenuEntries(parentMenu, table) 

191 if not self.handleSpecialMenus(name, parentName, 

192 alt_name=val2, #848. 

193 table=table, 

194 ): 

195 menu = self.createNewMenu(name, parentName) 

196 # Create submenu of parent menu. 

197 if menu: 

198 # Partial fix for #528. 

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

200 table = [] 

201 elif kind == '@item': 

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

203 if val2: 

204 # Translated names can be unicode. 

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

206 else: 

207 table.append(name) 

208 else: 

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

210 if table: 

211 self.createMenuEntries(parentMenu, table) 

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

213 def handleSpecialMenus(self, name, parentName, alt_name=None, table=None): 

214 """ 

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

216 return True if this method handles the menu. 

217 """ 

218 c = self.c 

219 if table is None: 

220 table = [] 

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

222 if name2 == 'plugins': 

223 # Create the plugins menu using a hook. 

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

225 return True 

226 if name2.startswith('recentfiles'): 

227 # Just create the menu. 

228 # createRecentFilesMenuItems will create the contents later. 

229 g.app.recentFilesManager.recentFilesMenuName = alt_name or name 

230 #848 

231 self.createNewMenu(alt_name or name, parentName) 

232 return True 

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

234 helpMenu = self.getMacHelpMenu(table) 

235 return helpMenu is not None 

236 return False 

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

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

239 

240 def hasSelection(self): 

241 c = self.c 

242 w = c.frame.body.wrapper 

243 if c.frame.body: 

244 first, last = w.getSelectionRange() 

245 return first != last 

246 return False 

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

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

249 def canonicalizeMenuName(self, name): 

250 

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

252 if g.isascii(name): 

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

254 return name 

255 

256 def canonicalizeTranslatedMenuName(self, name): 

257 

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

259 if g.isascii(name): 

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

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

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

263 def createMenuEntries(self, menu, table): 

264 """ 

265 Create a menu entry from the table. 

266 

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

268 """ 

269 c = self.c 

270 if g.unitTesting: 

271 return 

272 if not menu: 

273 return 

274 self.traceMenuTable(table) 

275 for data in table: 

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

277 if done: 

278 continue 

279 commandName = self.getMenuEntryBindings(command, label) 

280 if not commandName: 

281 continue 

282 masterMenuCallback = self.createMasterMenuCallback(command, commandName) 

283 realLabel = self.getRealMenuName(label) 

284 amp_index = realLabel.find("&") 

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

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

287 c.add_command(menu, label=realLabel, 

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

289 command=masterMenuCallback, 

290 commandName=commandName, 

291 underline=amp_index) 

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

293 def createMasterMenuCallback(self, command, commandName): 

294 """ 

295 Create a callback for the given args. 

296 

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

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

299 """ 

300 c = self.c 

301 

302 def getWidget(): 

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

304 w = c.frame.getFocus() 

305 if w and g.isMac: 

306 # Redirect (MacOS only). 

307 wname = c.widget_name(w) 

308 if wname.startswith('head'): 

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

310 # Return a wrapper if possible. 

311 if not g.isTextWrapper(w): 

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

313 return w 

314 

315 if isinstance(command, str): 

316 

317 def static_menu_callback(): 

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

319 c.doCommandByName(commandName, event) 

320 

321 return static_menu_callback 

322 

323 # The command must be a callable. 

324 if not callable(command): 

325 

326 def dummy_menu_callback(event=None): 

327 pass 

328 

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

330 return dummy_menu_callback 

331 

332 # Create a command dynamically. 

333 

334 def dynamic_menu_callback(): 

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

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

337 

338 return dynamic_menu_callback 

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

340 def getMenuEntryBindings(self, command, label): 

341 """Compute commandName from command.""" 

342 c = self.c 

343 if isinstance(command, str): 

344 # Command is really a command name. 

345 commandName = command 

346 else: 

347 # First, get the old-style name. 

348 # #1121: Allow Chinese characters in command names 

349 commandName = label.strip() 

350 command = c.commandsDict.get(commandName) 

351 return commandName 

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

353 def getMenuEntryInfo(self, data, menu): 

354 """ 

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

356 

357 Table entries have the following formats: 

358 

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

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

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

362 

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

364 """ 

365 done = False 

366 if isinstance(data, str): 

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

368 s = data 

369 removeHyphens = s and s[0] == '*' 

370 if removeHyphens: 

371 s = s[1:] 

372 label = self.capitalizeMinibufferMenuName(s, removeHyphens) 

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

374 if label == '-': 

375 self.add_separator(menu) 

376 done = True # That's all. 

377 else: 

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

379 if ok: 

380 if len(data) == 2: 

381 # Command can be a minibuffer-command name. 

382 label, command = data 

383 else: 

384 # Ignore shortcuts bound in menu tables. 

385 label, junk, command = data 

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

387 self.add_separator(menu) 

388 done = True # That's all. 

389 else: 

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

391 done = True # Ignore bad data 

392 return label, command, done 

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

394 def traceMenuTable(self, table): 

395 

396 trace = False and not g.unitTesting 

397 if not trace: 

398 return 

399 format = '%40s %s' 

400 g.trace('*' * 40) 

401 for data in table: 

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

403 n = len(data) 

404 if n == 2: 

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

406 elif n == 3: 

407 name, junk, func = data 

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

409 else: 

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

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

412 def createMenuItemsFromTable(self, menuName, table): 

413 

414 if g.app.gui.isNullGui: 

415 return 

416 try: 

417 menu = self.getMenu(menuName) 

418 if menu is None: 

419 return 

420 self.createMenuEntries(menu, table) 

421 except Exception: 

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

423 g.es_exception() 

424 g.app.menuWarningsGiven = True 

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

426 def createNewMenu(self, menuName, parentName="top", before=None): 

427 try: 

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

429 menu = self.getMenu(menuName) 

430 if menu: 

431 # Not an error. 

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

433 return None # Fix #528. 

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

435 self.setMenu(menuName, menu) 

436 label = self.getRealMenuName(menuName) 

437 amp_index = label.find("&") 

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

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

440 index_label = self.getRealMenuName(before) 

441 amp_index = index_label.find("&") 

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

443 index = parent.index(index_label) 

444 self.insert_cascade( 

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

446 else: 

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

448 return menu 

449 except Exception: 

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

451 g.es_exception() 

452 return None 

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

454 def createOpenWithMenuFromTable(self, table): 

455 """ 

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

457 

458 This menu code uses these keys: 

459 

460 'name': menu label. 

461 'shortcut': optional menu shortcut. 

462 

463 efc.open_temp_file uses these keys: 

464 

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

466 'ext': the file extension. 

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

468 """ 

469 k = self.c.k 

470 if not table: 

471 return 

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

473 # Delete the previous entry. 

474 parent = self.getMenu("File") 

475 if not parent: 

476 if not g.app.batchMode: 

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

478 return 

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

480 amp_index = label.find("&") 

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

482 try: 

483 index = parent.index(label) 

484 parent.delete(index) 

485 except Exception: 

486 try: 

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

488 parent.delete(index) 

489 except Exception: 

490 g.trace('unexpected exception') 

491 g.es_exception() 

492 return 

493 # Create the Open With menu. 

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

495 if not openWithMenu: 

496 g.trace('openWithMenu returns None') 

497 return 

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

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

500 self.createOpenWithMenuItemsFromTable(openWithMenu, table) 

501 for d in table: 

502 k.bindOpenWith(d) 

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

504 def createOpenWithMenuItemsFromTable(self, menu, table): 

505 """ 

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

507 

508 Each dictionary d has the following keys: 

509 

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

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

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

513 'name': menu label. 

514 'shortcut': optional menu shortcut. 

515 """ 

516 c = self.c 

517 if g.unitTesting: 

518 return 

519 for d in table: 

520 label = d.get('name') 

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

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

523 if label and args: 

524 realLabel = self.getRealMenuName(label) 

525 underline = realLabel.find("&") 

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

527 callback = self.defineOpenWithMenuCallback(d) 

528 c.add_command(menu, 

529 label=realLabel, 

530 accelerator=accel, 

531 command=callback, 

532 underline=underline) 

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

534 def defineOpenWithMenuCallback(self, d=None): 

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

536 

537 def openWithMenuCallback(event=None, self=self, d=d): 

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

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

540 

541 return openWithMenuCallback 

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

543 def deleteRecentFilesMenuItems(self, menu): 

544 """Delete recent file menu entries""" 

545 rf = g.app.recentFilesManager 

546 # Why not just delete all the entries? 

547 recentFiles = rf.getRecentFiles() 

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

549 self.delete_range(menu, 0, toDrop) 

550 for i in rf.groupedMenus: 

551 menu = self.getMenu(i) 

552 if menu: 

553 self.destroy(menu) 

554 self.destroyMenu(i) 

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

556 def deleteMenu(self, menuName): 

557 try: 

558 menu = self.getMenu(menuName) 

559 if menu: 

560 self.destroy(menu) 

561 self.destroyMenu(menuName) 

562 else: 

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

564 except Exception: 

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

566 g.es_exception() 

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

568 def deleteMenuItem(self, itemName, menuName="top"): 

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

570 try: 

571 menu = self.getMenu(menuName) 

572 if menu: 

573 realItemName = self.getRealMenuName(itemName) 

574 self.delete(menu, realItemName) 

575 else: 

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

577 except Exception: 

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

579 g.es_exception() 

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

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

582 

583 def getRealMenuName(self, menuName): 

584 cmn = self.canonicalizeTranslatedMenuName(menuName) 

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

586 

587 def setRealMenuName(self, untrans, trans): 

588 cmn = self.canonicalizeTranslatedMenuName(untrans) 

589 g.app.realMenuNameDict[cmn] = trans 

590 

591 def setRealMenuNamesFromTable(self, table): 

592 try: 

593 for untrans, trans in table: 

594 self.setRealMenuName(untrans, trans) 

595 except Exception: 

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

597 g.es_exception() 

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

599 def getMenu(self, menuName): 

600 cmn = self.canonicalizeMenuName(menuName) 

601 return self.menus.get(cmn) 

602 

603 def setMenu(self, menuName, menu): 

604 cmn = self.canonicalizeMenuName(menuName) 

605 self.menus[cmn] = menu 

606 

607 def destroyMenu(self, menuName): 

608 cmn = self.canonicalizeMenuName(menuName) 

609 del self.menus[cmn] 

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

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

612 def add_cascade(self, parent, label, menu, underline): 

613 self.oops() 

614 

615 def add_command(self, menu, **keys): 

616 self.oops() 

617 

618 def add_separator(self, menu): 

619 self.oops() 

620 

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

622 # self.oops() 

623 

624 def delete(self, menu, realItemName): 

625 self.oops() 

626 

627 def delete_range(self, menu, n1, n2): 

628 self.oops() 

629 

630 def destroy(self, menu): 

631 self.oops() 

632 

633 def insert( 

634 self, menuName, position, label, command, underline=None): # New in Leo 4.4.3 a1 

635 self.oops() 

636 

637 def insert_cascade(self, parent, index, label, menu, underline): 

638 self.oops() 

639 

640 def new_menu(self, parent, tearoff=0, label=''): 

641 # 2010: added label arg for pylint. 

642 self.oops() 

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

644 def activateMenu(self, menuName): # New in Leo 4.4b2. 

645 self.oops() 

646 

647 def clearAccel(self, menu, name): 

648 self.oops() 

649 

650 def createMenuBar(self, frame): 

651 self.oops() 

652 

653 def createOpenWithMenu(self, parent, label, index, amp_index): 

654 self.oops() 

655 

656 def disableMenu(self, menu, name): 

657 self.oops() 

658 

659 def enableMenu(self, menu, name, val): 

660 self.oops() 

661 

662 def getMacHelpMenu(self, table): 

663 return None 

664 

665 def getMenuLabel(self, menu, name): 

666 self.oops() 

667 

668 def setMenuLabel(self, menu, name, label, underline=-1): 

669 self.oops() 

670 #@-others 

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

672class NullMenu(LeoMenu): 

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

674 #@+others 

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

676 def __init__(self, frame): 

677 super().__init__(frame) 

678 self.isNull = True 

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

680 def oops(self): 

681 pass 

682 #@-others 

683#@-others 

684#@@language python 

685#@@tabwidth -4 

686#@@pagewidth 70 

687#@-leo