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
« 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.
24 def finishCreate(self) -> None:
25 self.define_enable_dict()
26 #@+node:ekr.20120124042346.12937: *4* LeoMenu.define_enable_table
27 #@@nobeautify
29 def define_enable_dict(self) -> None:
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 = {
38 # File menu...
39 # 'revert': True, # Revert is always enabled.
40 # 'open-with': True, # Open-With is always enabled.
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,
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,
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(),
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(),
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(),
92 # Outline:Mark menu...
93 'mark-subheads': lambda: c.p.hasChildren(),
94 # too slow...
95 # 'mark-changed-items': c.canMarkChangedHeadlines,
96 }
98 for i in range(1,9):
99 d [f"expand-to-level-{i}"] = lambda: c.p.hasChildren()
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")
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
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
172 List entries are either:
174 ['@item', 'command-name', 'optional-view-name']
176 or:
178 ['@menu Submenu name', <nested list>, None]
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.
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:
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
260 def canonicalizeTranslatedMenuName(self, name: str) -> str:
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.
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.
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
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
319 if isinstance(command, str):
321 def static_menu_callback() -> None:
322 event = g.app.gui.create_key_event(c, w=getWidget())
323 c.doCommandByName(commandName, event)
325 return static_menu_callback
327 # The command must be a callable.
328 if not callable(command):
330 def dummy_menu_callback(event: Event=None) -> None:
331 pass
333 g.trace(f"bad command: {command!r}", color='red')
334 return dummy_menu_callback
336 # Create a command dynamically.
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
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.
361 Table entries have the following formats:
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)
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:
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:
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.
462 This menu code uses these keys:
464 'name': menu label.
465 'shortcut': optional menu shortcut.
467 efc.open_temp_file uses these keys:
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.
512 Each dictionary d has the following keys:
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.
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)
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.
587 def getRealMenuName(self, menuName: str) -> str:
588 cmn = self.canonicalizeTranslatedMenuName(menuName)
589 return g.app.realMenuNameDict.get(cmn, menuName)
591 def setRealMenuName(self, untrans: str, trans: List) -> None:
592 cmn = self.canonicalizeTranslatedMenuName(untrans)
593 g.app.realMenuNameDict[cmn] = trans
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)
607 def setMenu(self, menuName: str, menu: str) -> None:
608 cmn = self.canonicalizeMenuName(menuName)
609 self.menus[cmn] = menu
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()
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()
624 def add_separator(self, menu: str) -> None:
625 self.oops()
627 # def bind (self,bind_shortcut,callback):
628 # self.oops()
630 def delete(self, menu: Any, realItemName: str) -> None:
631 self.oops()
633 def delete_range(self, menu: Any, n1: int, n2: int) -> None:
634 self.oops()
636 def destroy(self, menu: Any) -> None:
637 self.oops()
639 def insert(self, menuName: str, position: int, label: str, command: Callable, underline: int=None) -> None:
640 self.oops()
642 def insert_cascade(self, parent: Widget, index: int, label: str, menu: Any, underline: int) -> Widget:
643 self.oops()
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()
652 def clearAccel(self, menu: str, name: str) -> None:
653 self.oops()
655 def createMenuBar(self, frame: Widget) -> None:
656 self.oops()
658 def createOpenWithMenu(self, parent: Any, label: str, index: int, amp_index: int) -> Any:
659 self.oops()
661 def disableMenu(self, menu: str, name: str) -> None:
662 self.oops()
664 def enableMenu(self, menu: Widget, name: str, val: bool) -> None:
665 self.oops()
667 def getMacHelpMenu(self, table: List) -> Any:
668 self.oops()
670 def getMenuLabel(self, menu: str, name: str) -> str:
671 self.oops()
672 return ''
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