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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

382 statements  

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 # #528: Don't create duplicate menu items. 

152 # Create top-level menu. 

153 menu = self.createNewMenu(name) 

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 # Create submenu of parent menu. 

196 menu = self.createNewMenu(name, parentName) 

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 # #848 

230 self.createNewMenu(alt_name or name, parentName) 

231 return True 

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

233 helpMenu = self.getMacHelpMenu(table) 

234 return helpMenu is not None 

235 return False 

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

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

238 

239 def hasSelection(self): 

240 c = self.c 

241 w = c.frame.body.wrapper 

242 if c.frame.body: 

243 first, last = w.getSelectionRange() 

244 return first != last 

245 return False 

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

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

248 def canonicalizeMenuName(self, name): 

249 

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

251 if g.isascii(name): 

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

253 return name 

254 

255 def canonicalizeTranslatedMenuName(self, name): 

256 

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

258 if g.isascii(name): 

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

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

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

262 def createMenuEntries(self, menu, table): 

263 """ 

264 Create a menu entry from the table. 

265 

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

267 """ 

268 c = self.c 

269 if g.unitTesting: 

270 return 

271 if not menu: 

272 return 

273 self.traceMenuTable(table) 

274 for data in table: 

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

276 if done: 

277 continue 

278 commandName = self.getMenuEntryBindings(command, label) 

279 if not commandName: 

280 continue 

281 masterMenuCallback = self.createMasterMenuCallback(command, commandName) 

282 realLabel = self.getRealMenuName(label) 

283 amp_index = realLabel.find("&") 

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

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

286 c.add_command(menu, label=realLabel, 

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

288 command=masterMenuCallback, 

289 commandName=commandName, 

290 underline=amp_index) 

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

292 def createMasterMenuCallback(self, command, commandName): 

293 """ 

294 Create a callback for the given args. 

295 

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

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

298 """ 

299 c = self.c 

300 

301 def getWidget(): 

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

303 w = c.frame.getFocus() 

304 if w and g.isMac: 

305 # Redirect (MacOS only). 

306 wname = c.widget_name(w) 

307 if wname.startswith('head'): 

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

309 # Return a wrapper if possible. 

310 if not g.isTextWrapper(w): 

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

312 return w 

313 

314 if isinstance(command, str): 

315 

316 def static_menu_callback(): 

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

318 c.doCommandByName(commandName, event) 

319 

320 return static_menu_callback 

321 

322 # The command must be a callable. 

323 if not callable(command): 

324 

325 def dummy_menu_callback(event=None): 

326 pass 

327 

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

329 return dummy_menu_callback 

330 

331 # Create a command dynamically. 

332 

333 def dynamic_menu_callback(): 

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

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

336 

337 return dynamic_menu_callback 

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

339 def getMenuEntryBindings(self, command, label): 

340 """Compute commandName from command.""" 

341 c = self.c 

342 if isinstance(command, str): 

343 # Command is really a command name. 

344 commandName = command 

345 else: 

346 # First, get the old-style name. 

347 # #1121: Allow Chinese characters in command names 

348 commandName = label.strip() 

349 command = c.commandsDict.get(commandName) 

350 return commandName 

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

352 def getMenuEntryInfo(self, data, menu): 

353 """ 

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

355 

356 Table entries have the following formats: 

357 

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

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

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

361 

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

363 """ 

364 done = False 

365 if isinstance(data, str): 

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

367 s = data 

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

369 if removeHyphens: 

370 s = s[1:] 

371 label = self.capitalizeMinibufferMenuName(s, removeHyphens) 

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

373 if label == '-': 

374 self.add_separator(menu) 

375 done = True # That's all. 

376 else: 

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

378 if ok: 

379 if len(data) == 2: 

380 # Command can be a minibuffer-command name. 

381 label, command = data 

382 else: 

383 # Ignore shortcuts bound in menu tables. 

384 label, junk, command = data 

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

386 self.add_separator(menu) 

387 done = True # That's all. 

388 else: 

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

390 done = True # Ignore bad data 

391 return label, command, done 

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

393 def traceMenuTable(self, table): 

394 

395 trace = False and not g.unitTesting 

396 if not trace: 

397 return 

398 format = '%40s %s' 

399 g.trace('*' * 40) 

400 for data in table: 

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

402 n = len(data) 

403 if n == 2: 

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

405 elif n == 3: 

406 name, junk, func = data 

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

408 else: 

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

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

411 def createMenuItemsFromTable(self, menuName, table): 

412 

413 if g.app.gui.isNullGui: 

414 return 

415 try: 

416 menu = self.getMenu(menuName) 

417 if menu is None: 

418 return 

419 self.createMenuEntries(menu, table) 

420 except Exception: 

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

422 g.es_exception() 

423 g.app.menuWarningsGiven = True 

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

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

426 try: 

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

428 menu = self.getMenu(menuName) 

429 if menu: 

430 # Not an error. 

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

432 return None # Fix #528. 

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

434 self.setMenu(menuName, menu) 

435 label = self.getRealMenuName(menuName) 

436 amp_index = label.find("&") 

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

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

439 index_label = self.getRealMenuName(before) 

440 amp_index = index_label.find("&") 

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

442 index = parent.index(index_label) 

443 self.insert_cascade( 

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

445 else: 

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

447 return menu 

448 except Exception: 

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

450 g.es_exception() 

451 return None 

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

453 def createOpenWithMenuFromTable(self, table): 

454 """ 

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

456 

457 This menu code uses these keys: 

458 

459 'name': menu label. 

460 'shortcut': optional menu shortcut. 

461 

462 efc.open_temp_file uses these keys: 

463 

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

465 'ext': the file extension. 

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

467 """ 

468 k = self.c.k 

469 if not table: 

470 return 

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

472 # Delete the previous entry. 

473 parent = self.getMenu("File") 

474 if not parent: 

475 if not g.app.batchMode: 

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

477 return 

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

479 amp_index = label.find("&") 

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

481 try: 

482 index = parent.index(label) 

483 parent.delete(index) 

484 except Exception: 

485 try: 

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

487 parent.delete(index) 

488 except Exception: 

489 g.trace('unexpected exception') 

490 g.es_exception() 

491 return 

492 # Create the Open With menu. 

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

494 if not openWithMenu: 

495 g.trace('openWithMenu returns None') 

496 return 

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

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

499 self.createOpenWithMenuItemsFromTable(openWithMenu, table) 

500 for d in table: 

501 k.bindOpenWith(d) 

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

503 def createOpenWithMenuItemsFromTable(self, menu, table): 

504 """ 

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

506 

507 Each dictionary d has the following keys: 

508 

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

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

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

512 'name': menu label. 

513 'shortcut': optional menu shortcut. 

514 """ 

515 c = self.c 

516 if g.unitTesting: 

517 return 

518 for d in table: 

519 label = d.get('name') 

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

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

522 if label and args: 

523 realLabel = self.getRealMenuName(label) 

524 underline = realLabel.find("&") 

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

526 callback = self.defineOpenWithMenuCallback(d) 

527 c.add_command(menu, 

528 label=realLabel, 

529 accelerator=accel, 

530 command=callback, 

531 underline=underline) 

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

533 def defineOpenWithMenuCallback(self, d=None): 

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

535 

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

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

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

539 

540 return openWithMenuCallback 

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

542 def deleteRecentFilesMenuItems(self, menu): 

543 """Delete recent file menu entries""" 

544 rf = g.app.recentFilesManager 

545 # Why not just delete all the entries? 

546 recentFiles = rf.getRecentFiles() 

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

548 self.delete_range(menu, 0, toDrop) 

549 for i in rf.groupedMenus: 

550 menu = self.getMenu(i) 

551 if menu: 

552 self.destroy(menu) 

553 self.destroyMenu(i) 

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

555 def deleteMenu(self, menuName): 

556 try: 

557 menu = self.getMenu(menuName) 

558 if menu: 

559 self.destroy(menu) 

560 self.destroyMenu(menuName) 

561 else: 

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

563 except Exception: 

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

565 g.es_exception() 

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

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

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

569 try: 

570 menu = self.getMenu(menuName) 

571 if menu: 

572 realItemName = self.getRealMenuName(itemName) 

573 self.delete(menu, realItemName) 

574 else: 

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

576 except Exception: 

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

578 g.es_exception() 

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

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

581 

582 def getRealMenuName(self, menuName): 

583 cmn = self.canonicalizeTranslatedMenuName(menuName) 

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

585 

586 def setRealMenuName(self, untrans, trans): 

587 cmn = self.canonicalizeTranslatedMenuName(untrans) 

588 g.app.realMenuNameDict[cmn] = trans 

589 

590 def setRealMenuNamesFromTable(self, table): 

591 try: 

592 for untrans, trans in table: 

593 self.setRealMenuName(untrans, trans) 

594 except Exception: 

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

596 g.es_exception() 

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

598 def getMenu(self, menuName): 

599 cmn = self.canonicalizeMenuName(menuName) 

600 return self.menus.get(cmn) 

601 

602 def setMenu(self, menuName, menu): 

603 cmn = self.canonicalizeMenuName(menuName) 

604 self.menus[cmn] = menu 

605 

606 def destroyMenu(self, menuName): 

607 cmn = self.canonicalizeMenuName(menuName) 

608 del self.menus[cmn] 

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

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

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

612 self.oops() 

613 

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

615 self.oops() 

616 

617 def add_separator(self, menu): 

618 self.oops() 

619 

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

621 # self.oops() 

622 

623 def delete(self, menu, realItemName): 

624 self.oops() 

625 

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

627 self.oops() 

628 

629 def destroy(self, menu): 

630 self.oops() 

631 

632 def insert( 

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

634 self.oops() 

635 

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

637 self.oops() 

638 

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

640 # 2010: added label arg for pylint. 

641 self.oops() 

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

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

644 self.oops() 

645 

646 def clearAccel(self, menu, name): 

647 self.oops() 

648 

649 def createMenuBar(self, frame): 

650 self.oops() 

651 

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

653 self.oops() 

654 

655 def disableMenu(self, menu, name): 

656 self.oops() 

657 

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

659 self.oops() 

660 

661 def getMacHelpMenu(self, table): 

662 return None 

663 

664 def getMenuLabel(self, menu, name): 

665 self.oops() 

666 

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

668 self.oops() 

669 #@-others 

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

671class NullMenu(LeoMenu): 

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

673 #@+others 

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

675 def __init__(self, frame): 

676 super().__init__(frame) 

677 self.isNull = True 

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

679 def oops(self): 

680 pass 

681 #@-others 

682#@-others 

683#@@language python 

684#@@tabwidth -4 

685#@@pagewidth 70 

686#@-leo