From b81e40e38b3d5214df41c2e7c1cf6a98d7d13a62 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Fri, 18 Jan 2019 13:33:50 -0500 Subject: [PATCH 1/3] Convert menudefs to dictionary --- Lib/idlelib/editor.py | 10 +++--- Lib/idlelib/idle_test/test_mainmenu.py | 2 +- Lib/idlelib/macosx.py | 22 +++++++------- Lib/idlelib/mainmenu.py | 42 +++++++++++++------------- Lib/idlelib/zzdummy.py | 8 ++--- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index e05b52a96dcc61..cc8e890d1b26b1 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -818,11 +818,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) @@ -1117,7 +1117,7 @@ def fill_menus(self, menudefs=None, keydefs=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_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 9be4ed2ec411d5..c83872c6f3bad8 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -165,20 +165,20 @@ 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[-2][1][0] + del mainmenu.menudefs['options'][0] menubar = Menu(root) root.configure(menu=menubar) menudict = {} @@ -236,18 +236,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 f834220fc2bb75..b2d84f09cb919a 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,15 +70,15 @@ ('New Indent Width', '<>'), ('F_ormat Paragraph', '<>'), ('S_trip Trailing Whitespace', '<>'), - ]), + ], - ('run', [ + 'run': [ ('Python Shell', '<>'), ('C_heck Module', '<>'), ('R_un Module', '<>'), - ]), + ], - ('shell', [ + 'shell': [ ('_View Last Restart', '<>'), ('_Restart Shell', '<>'), None, @@ -86,35 +86,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..dd66aa5f1be9ad 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 From 4607bd489eeb466431c6fb90499e1deff92cfb57 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 18 Jan 2019 16:13:57 -0500 Subject: [PATCH 2/3] zzdummy whitespace --- Lib/idlelib/zzdummy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/zzdummy.py b/Lib/idlelib/zzdummy.py index dd66aa5f1be9ad..5d4aa553195fe6 100644 --- a/Lib/idlelib/zzdummy.py +++ b/Lib/idlelib/zzdummy.py @@ -11,7 +11,7 @@ class ZzDummy: ## 'format': [ ## ('Z in', '<>'), ## ('Z out', '<>'), -## ] +## ] ## } def __init__(self, editwin): From ddd21eedeb97cf1ee1b9970545bdd834cc539025 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sun, 20 Jan 2019 13:30:01 -0500 Subject: [PATCH 3/3] Convert menudefs list to dictonary and add tests. * 3rd party extensions may define menudefs using the old list format, so, if it's a list, convert it to a dictionary. * Add tests for fill_menus. --- Lib/idlelib/editor.py | 36 ++++++++- Lib/idlelib/idle_test/test_editor.py | 107 ++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index cc8e890d1b26b1..01133c43bf8548 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1107,12 +1107,42 @@ 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 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)