diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 606de71a6add88..9ae8758f566ebd 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -815,11 +815,11 @@ def ApplyKeybindings(self): self.apply_bindings(xkeydefs) #update menu accelerators menuEventDict = {} - for menu in self.mainmenu.menudefs: - menuEventDict[menu[0]] = {} - for item in menu[1]: + for menu, items in self.mainmenu.menudefs.items(): + menuEventDict[menu] = {} + for item in items: if item: - menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1] + menuEventDict[menu][prepstr(item[0])[1]] = item[1] for menubarItem in self.menudict: menu = self.menudict[menubarItem] end = menu.index(END) @@ -1104,17 +1104,47 @@ def apply_bindings(self, keydefs=None): text.event_add(event, *keylist) def fill_menus(self, menudefs=None, keydefs=None): - """Add appropriate entries to the menus and submenus - - Menus that are absent or None in self.menudict are ignored. + """Add appropriate entries to the menus and submenus. + + The default menudefs and keydefs are loaded from idlelib.mainmenu. + Menus that are absent or None in self.menudict are ignored. The + default menu type created for submenus from menudefs is `command`. + A submenu item of None results in a `separator` menu type. + A submenu name beginning with ! represents a `checkbutton` type. + + The menus are stored in self.menudict. + + Args: + menudefs: Menu and submenu names, underlines (shortcuts), + and events which is a dictionary of the form: + {menu1: [(submenu1a, '<>'), + (submenu1b, '<>'), ...], + menu2: [(submenu2a, '<>'), + (submenu2b, '<>'), ...], + } + Alternate format (may have been used in extensions): + [(menu1, [(submenu1a, '<>'), + (submenu1b, '<>'), ...]), + (menu2, [(submenu2a, '<>'), + (submenu2b, '<>'), ...]), + ] + keydefs: Virtual events and keybinding definitions. Used for + the 'accelerator' text on the menu. Stored as a + dictionary of + {'<>': ['', ''],} """ if menudefs is None: menudefs = self.mainmenu.menudefs + # menudefs was changed from a list of tuples to a dictionary. + # This conversion is needed for backward-compatibility for + # existing extensions that use the list format. + if isinstance(menudefs, list): + menudefs = dict(menudefs) if keydefs is None: keydefs = self.mainmenu.default_keydefs menudict = self.menudict text = self.text - for mname, entrylist in menudefs: + for mname, entrylist in menudefs.items(): menu = menudict.get(mname) if not menu: continue diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 12bc8473668334..d203b111f97e64 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -2,8 +2,10 @@ from idlelib import editor import unittest +from unittest import mock from test.support import requires -from tkinter import Tk +import tkinter as tk +from functools import partial Editor = editor.EditorWindow @@ -13,7 +15,7 @@ class EditorWindowTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.root = Tk() + cls.root = tk.Tk() cls.root.withdraw() @classmethod @@ -42,5 +44,106 @@ class dummy(): self.assertEqual(func(dummy, inp), out) +class MenubarTest(unittest.TestCase): + """Test functions involved with creating the menubar.""" + + @classmethod + def setUpClass(cls): + requires('gui') + root = cls.root = tk.Tk() + cls.root.withdraw() + # Test the functions called during the __init__ for + # EditorWindow that create the menubar and submenus. + # The class is mocked in order to prevent the functions + # from being called automatically. + w = cls.mock_editwin = mock.Mock(editor.EditorWindow) + w.menubar = tk.Menu(root, tearoff=False) + w.text = tk.Text(root) + w.tkinter_vars = {} + + @classmethod + def tearDownClass(cls): + w = cls.mock_editwin + w.text.destroy() + w.menubar.destroy() + del w.menubar, w.text, w + cls.root.update_idletasks() + for id in cls.root.tk.call('after', 'info'): + cls.root.after_cancel(id) + cls.root.destroy() + del cls.root + + def test_fill_menus(self): + eq = self.assertEqual + ed = editor.EditorWindow + w = self.mock_editwin + # Call real functions instead of mock. + fm = partial(editor.EditorWindow.fill_menus, w) + w.get_var_obj = ed.get_var_obj.__get__(w) + + # Initialize top level menubar. + w.menudict = {} + edit = w.menudict['edit'] = tk.Menu(w.menubar, name='edit', tearoff=False) + win = w.menudict['windows'] = tk.Menu(w.menubar, name='windows', tearoff=False) + form = w.menudict['format'] = tk.Menu(w.menubar, name='format', tearoff=False) + + # Submenus. + dict_menudefs = {'edit': [('_New', '<>'), + None, + ('!Deb_ug', '<>')], + 'shell': [('_View', '<>'), ], + 'windows': [('Zoom Height', '<>')], + } + list_menudefs = [('edit', [('_New', '<>'), + None, + ('!Deb_ug', '<>')]), + ('shell', [('_View', '<>'), ]), + ('windows', [('Zoom Height', '<>')]), + ] + keydefs = {'<>': ['']} + for menudefs in (dict_menudefs, list_menudefs): + with self.subTest(menudefs=menudefs): + fm(menudefs, keydefs) + + eq(edit.index('end'), 2) + eq(edit.type(0), tk.COMMAND) + eq(edit.entrycget(0, 'label'), 'New') + eq(edit.entrycget(0, 'underline'), 0) + self.assertIsNotNone(edit.entrycget(0, 'command')) + with self.assertRaises(tk.TclError): + self.assertIsNone(edit.entrycget(0, 'var')) + + eq(edit.type(1), tk.SEPARATOR) + with self.assertRaises(tk.TclError): + self.assertIsNone(edit.entrycget(1, 'label')) + + eq(edit.type(2), tk.CHECKBUTTON) + # Strip !. + eq(edit.entrycget(2, 'label'), 'Debug') + # Check that underline ignores !. + eq(edit.entrycget(2, 'underline'), 3) + self.assertIsNotNone(edit.entrycget(2, 'var')) + self.assertIn('<>', w.tkinter_vars) + + eq(win.index('end'), 0) + eq(win.entrycget(0, 'underline'), -1) + eq(win.entrycget(0, 'accelerator'), 'Alt+9') + + eq(form.index('end'), None) + self.assertNotIn('shell', w.menudict) + + # Cleanup menus by deleting all menu items. + edit.delete(0, 2) + win.delete(0) + form.delete(0) + + # Test defaults. + w.mainmenu.menudefs = ed.mainmenu.menudefs + w.mainmenu.default_keydefs = ed.mainmenu.default_keydefs + fm() + eq(form.index('end'), 9) # Default Format menu has 10 items. + self.assertNotIn('run', w.menudict) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_mainmenu.py b/Lib/idlelib/idle_test/test_mainmenu.py index 7ec0368371c7df..147355d51a1868 100644 --- a/Lib/idlelib/idle_test/test_mainmenu.py +++ b/Lib/idlelib/idle_test/test_mainmenu.py @@ -8,7 +8,7 @@ class MainMenuTest(unittest.TestCase): def test_menudefs(self): - actual = [item[0] for item in mainmenu.menudefs] + actual = list(mainmenu.menudefs.keys()) expect = ['file', 'edit', 'format', 'run', 'shell', 'debug', 'options', 'window', 'help'] self.assertEqual(actual, expect) diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index eeaab59ae80295..7894f2e5b55cc2 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -165,20 +165,21 @@ def overrideRootMenu(root, flist): from idlelib import mainmenu from idlelib import window - closeItem = mainmenu.menudefs[0][1][-2] + closeItem = mainmenu.menudefs['file'][-2] # Remove the last 3 items of the file menu: a separator, close window and # quit. Close window will be reinserted just above the save item, where # it should be according to the HIG. Quit is in the application menu. - del mainmenu.menudefs[0][1][-3:] - mainmenu.menudefs[0][1].insert(6, closeItem) + del mainmenu.menudefs['file'][-3:] + mainmenu.menudefs['file'].insert(6, closeItem) # Remove the 'About' entry from the help menu, it is in the application # menu - del mainmenu.menudefs[-1][1][0:2] + del mainmenu.menudefs['help'][0:2] # Remove the 'Configure Idle' entry from the options menu, it is in the # application menu as 'Preferences' - del mainmenu.menudefs[-3][1][0:2] + del mainmenu.menudefs['options'][0:2] + menubar = Menu(root) root.configure(menu=menubar) menudict = {} @@ -236,18 +237,18 @@ def help_dialog(event=None): menudict['application'] = menu = Menu(menubar, name='apple', tearoff=0) menubar.add_cascade(label='IDLE', menu=menu) - mainmenu.menudefs.insert(0, - ('application', [ - ('About IDLE', '<>'), - None, - ])) + appmenu = {'application': [ + ('About IDLE', '<>'), + None, + ]} + mainmenu.menudefs = {**appmenu, **mainmenu.menudefs} if isCocoaTk(): # replace default About dialog with About IDLE one root.createcommand('tkAboutDialog', about_dialog) # replace default "Help" item in Help menu root.createcommand('::tk::mac::ShowHelp', help_dialog) # remove redundant "IDLE Help" from menu - del mainmenu.menudefs[-1][1][0] + del mainmenu.menudefs['help'][0] def fixb2context(root): '''Removed bad AquaTk Button-2 (right) and Paste bindings. diff --git a/Lib/idlelib/mainmenu.py b/Lib/idlelib/mainmenu.py index 1b8dc475650d8d..96c4bcd4c9ae71 100644 --- a/Lib/idlelib/mainmenu.py +++ b/Lib/idlelib/mainmenu.py @@ -19,9 +19,9 @@ # without altering overrideRootMenu() as well. # TODO: Make this more robust -menudefs = [ +menudefs = { # underscore prefixes character to underscore - ('file', [ + 'file': [ ('_New File', '<>'), ('_Open...', '<>'), ('Open _Module...', '<>'), @@ -36,9 +36,9 @@ None, ('_Close', '<>'), ('E_xit', '<>'), - ]), + ], - ('edit', [ + 'edit': [ ('_Undo', '<>'), ('_Redo', '<>'), None, @@ -57,9 +57,9 @@ ('E_xpand Word', '<>'), ('Show C_all Tip', '<>'), ('Show Surrounding P_arens', '<>'), - ]), + ], - ('format', [ + 'format': [ ('_Indent Region', '<>'), ('_Dedent Region', '<>'), ('Comment _Out Region', '<>'), @@ -70,16 +70,16 @@ ('New Indent Width', '<>'), ('F_ormat Paragraph', '<>'), ('S_trip Trailing Whitespace', '<>'), - ]), + ], - ('run', [ + 'run': [ ('Python Shell', '<>'), ('C_heck Module', '<>'), ('R_un Module', '<>'), ('Run... _Customized', '<>'), ]), - ('shell', [ + 'shell': [ ('_View Last Restart', '<>'), ('_Restart Shell', '<>'), None, @@ -87,35 +87,35 @@ ('_Next History', '<>'), None, ('_Interrupt Execution', '<>'), - ]), + ], - ('debug', [ + 'debug': [ ('_Go to File/Line', '<>'), ('!_Debugger', '<>'), ('_Stack Viewer', '<>'), ('!_Auto-open Stack Viewer', '<>'), - ]), + ], - ('options', [ + 'options': [ ('Configure _IDLE', '<>'), None, ('Show _Code Context', '<>'), ('Zoom Height', '<>'), - ]), + ], - ('window', [ - ]), + 'window': [ + ], - ('help', [ + 'help': [ ('_About IDLE', '<>'), None, ('_IDLE Help', '<>'), ('Python _Docs', '<>'), - ]), -] + ], +} if find_spec('turtledemo'): - menudefs[-1][1].append(('Turtle Demo', '<>')) + menudefs['help'].append(('Turtle Demo', '<>')) default_keydefs = idleConf.GetCurrentKeySet() diff --git a/Lib/idlelib/zzdummy.py b/Lib/idlelib/zzdummy.py index 8084499646653d..5d4aa553195fe6 100644 --- a/Lib/idlelib/zzdummy.py +++ b/Lib/idlelib/zzdummy.py @@ -7,12 +7,12 @@ class ZzDummy: -## menudefs = [ -## ('format', [ +## menudefs = { +## 'format': [ ## ('Z in', '<>'), ## ('Z out', '<>'), -## ] ) -## ] +## ] +## } def __init__(self, editwin): self.text = editwin.text pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy