17db96d56Sopenharmony_ci""" 27db96d56Sopenharmony_ciA number of functions that enhance IDLE on macOS. 37db96d56Sopenharmony_ci""" 47db96d56Sopenharmony_cifrom os.path import expanduser 57db96d56Sopenharmony_ciimport plistlib 67db96d56Sopenharmony_cifrom sys import platform # Used in _init_tk_type, changed by test. 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ciimport tkinter 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_ci## Define functions that query the Mac graphics type. 127db96d56Sopenharmony_ci## _tk_type and its initializer are private to this section. 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ci_tk_type = None 157db96d56Sopenharmony_ci 167db96d56Sopenharmony_cidef _init_tk_type(): 177db96d56Sopenharmony_ci """ Initialize _tk_type for isXyzTk functions. 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ci This function is only called once, when _tk_type is still None. 207db96d56Sopenharmony_ci """ 217db96d56Sopenharmony_ci global _tk_type 227db96d56Sopenharmony_ci if platform == 'darwin': 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci # When running IDLE, GUI is present, test/* may not be. 257db96d56Sopenharmony_ci # When running tests, test/* is present, GUI may not be. 267db96d56Sopenharmony_ci # If not, guess most common. Does not matter for testing. 277db96d56Sopenharmony_ci from idlelib.__init__ import testing 287db96d56Sopenharmony_ci if testing: 297db96d56Sopenharmony_ci from test.support import requires, ResourceDenied 307db96d56Sopenharmony_ci try: 317db96d56Sopenharmony_ci requires('gui') 327db96d56Sopenharmony_ci except ResourceDenied: 337db96d56Sopenharmony_ci _tk_type = "cocoa" 347db96d56Sopenharmony_ci return 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci root = tkinter.Tk() 377db96d56Sopenharmony_ci ws = root.tk.call('tk', 'windowingsystem') 387db96d56Sopenharmony_ci if 'x11' in ws: 397db96d56Sopenharmony_ci _tk_type = "xquartz" 407db96d56Sopenharmony_ci elif 'aqua' not in ws: 417db96d56Sopenharmony_ci _tk_type = "other" 427db96d56Sopenharmony_ci elif 'AppKit' in root.tk.call('winfo', 'server', '.'): 437db96d56Sopenharmony_ci _tk_type = "cocoa" 447db96d56Sopenharmony_ci else: 457db96d56Sopenharmony_ci _tk_type = "carbon" 467db96d56Sopenharmony_ci root.destroy() 477db96d56Sopenharmony_ci else: 487db96d56Sopenharmony_ci _tk_type = "other" 497db96d56Sopenharmony_ci return 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_cidef isAquaTk(): 527db96d56Sopenharmony_ci """ 537db96d56Sopenharmony_ci Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon). 547db96d56Sopenharmony_ci """ 557db96d56Sopenharmony_ci if not _tk_type: 567db96d56Sopenharmony_ci _init_tk_type() 577db96d56Sopenharmony_ci return _tk_type == "cocoa" or _tk_type == "carbon" 587db96d56Sopenharmony_ci 597db96d56Sopenharmony_cidef isCarbonTk(): 607db96d56Sopenharmony_ci """ 617db96d56Sopenharmony_ci Returns True if IDLE is using a Carbon Aqua Tk (instead of the 627db96d56Sopenharmony_ci newer Cocoa Aqua Tk). 637db96d56Sopenharmony_ci """ 647db96d56Sopenharmony_ci if not _tk_type: 657db96d56Sopenharmony_ci _init_tk_type() 667db96d56Sopenharmony_ci return _tk_type == "carbon" 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_cidef isCocoaTk(): 697db96d56Sopenharmony_ci """ 707db96d56Sopenharmony_ci Returns True if IDLE is using a Cocoa Aqua Tk. 717db96d56Sopenharmony_ci """ 727db96d56Sopenharmony_ci if not _tk_type: 737db96d56Sopenharmony_ci _init_tk_type() 747db96d56Sopenharmony_ci return _tk_type == "cocoa" 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_cidef isXQuartz(): 777db96d56Sopenharmony_ci """ 787db96d56Sopenharmony_ci Returns True if IDLE is using an OS X X11 Tk. 797db96d56Sopenharmony_ci """ 807db96d56Sopenharmony_ci if not _tk_type: 817db96d56Sopenharmony_ci _init_tk_type() 827db96d56Sopenharmony_ci return _tk_type == "xquartz" 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_cidef readSystemPreferences(): 867db96d56Sopenharmony_ci """ 877db96d56Sopenharmony_ci Fetch the macOS system preferences. 887db96d56Sopenharmony_ci """ 897db96d56Sopenharmony_ci if platform != 'darwin': 907db96d56Sopenharmony_ci return None 917db96d56Sopenharmony_ci 927db96d56Sopenharmony_ci plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist') 937db96d56Sopenharmony_ci try: 947db96d56Sopenharmony_ci with open(plist_path, 'rb') as plist_file: 957db96d56Sopenharmony_ci return plistlib.load(plist_file) 967db96d56Sopenharmony_ci except OSError: 977db96d56Sopenharmony_ci return None 987db96d56Sopenharmony_ci 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_cidef preferTabsPreferenceWarning(): 1017db96d56Sopenharmony_ci """ 1027db96d56Sopenharmony_ci Warn if "Prefer tabs when opening documents" is set to "Always". 1037db96d56Sopenharmony_ci """ 1047db96d56Sopenharmony_ci if platform != 'darwin': 1057db96d56Sopenharmony_ci return None 1067db96d56Sopenharmony_ci 1077db96d56Sopenharmony_ci prefs = readSystemPreferences() 1087db96d56Sopenharmony_ci if prefs and prefs.get('AppleWindowTabbingMode') == 'always': 1097db96d56Sopenharmony_ci return ( 1107db96d56Sopenharmony_ci 'WARNING: The system preference "Prefer tabs when opening' 1117db96d56Sopenharmony_ci ' documents" is set to "Always". This will cause various problems' 1127db96d56Sopenharmony_ci ' with IDLE. For the best experience, change this setting when' 1137db96d56Sopenharmony_ci ' running IDLE (via System Preferences -> Dock).' 1147db96d56Sopenharmony_ci ) 1157db96d56Sopenharmony_ci return None 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci## Fix the menu and related functions. 1197db96d56Sopenharmony_ci 1207db96d56Sopenharmony_cidef addOpenEventSupport(root, flist): 1217db96d56Sopenharmony_ci """ 1227db96d56Sopenharmony_ci This ensures that the application will respond to open AppleEvents, which 1237db96d56Sopenharmony_ci makes is feasible to use IDLE as the default application for python files. 1247db96d56Sopenharmony_ci """ 1257db96d56Sopenharmony_ci def doOpenFile(*args): 1267db96d56Sopenharmony_ci for fn in args: 1277db96d56Sopenharmony_ci flist.open(fn) 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci # The command below is a hook in aquatk that is called whenever the app 1307db96d56Sopenharmony_ci # receives a file open event. The callback can have multiple arguments, 1317db96d56Sopenharmony_ci # one for every file that should be opened. 1327db96d56Sopenharmony_ci root.createcommand("::tk::mac::OpenDocument", doOpenFile) 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_cidef hideTkConsole(root): 1357db96d56Sopenharmony_ci try: 1367db96d56Sopenharmony_ci root.tk.call('console', 'hide') 1377db96d56Sopenharmony_ci except tkinter.TclError: 1387db96d56Sopenharmony_ci # Some versions of the Tk framework don't have a console object 1397db96d56Sopenharmony_ci pass 1407db96d56Sopenharmony_ci 1417db96d56Sopenharmony_cidef overrideRootMenu(root, flist): 1427db96d56Sopenharmony_ci """ 1437db96d56Sopenharmony_ci Replace the Tk root menu by something that is more appropriate for 1447db96d56Sopenharmony_ci IDLE with an Aqua Tk. 1457db96d56Sopenharmony_ci """ 1467db96d56Sopenharmony_ci # The menu that is attached to the Tk root (".") is also used by AquaTk for 1477db96d56Sopenharmony_ci # all windows that don't specify a menu of their own. The default menubar 1487db96d56Sopenharmony_ci # contains a number of menus, none of which are appropriate for IDLE. The 1497db96d56Sopenharmony_ci # Most annoying of those is an 'About Tck/Tk...' menu in the application 1507db96d56Sopenharmony_ci # menu. 1517db96d56Sopenharmony_ci # 1527db96d56Sopenharmony_ci # This function replaces the default menubar by a mostly empty one, it 1537db96d56Sopenharmony_ci # should only contain the correct application menu and the window menu. 1547db96d56Sopenharmony_ci # 1557db96d56Sopenharmony_ci # Due to a (mis-)feature of TkAqua the user will also see an empty Help 1567db96d56Sopenharmony_ci # menu. 1577db96d56Sopenharmony_ci from tkinter import Menu 1587db96d56Sopenharmony_ci from idlelib import mainmenu 1597db96d56Sopenharmony_ci from idlelib import window 1607db96d56Sopenharmony_ci 1617db96d56Sopenharmony_ci closeItem = mainmenu.menudefs[0][1][-2] 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci # Remove the last 3 items of the file menu: a separator, close window and 1647db96d56Sopenharmony_ci # quit. Close window will be reinserted just above the save item, where 1657db96d56Sopenharmony_ci # it should be according to the HIG. Quit is in the application menu. 1667db96d56Sopenharmony_ci del mainmenu.menudefs[0][1][-3:] 1677db96d56Sopenharmony_ci mainmenu.menudefs[0][1].insert(6, closeItem) 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci # Remove the 'About' entry from the help menu, it is in the application 1707db96d56Sopenharmony_ci # menu 1717db96d56Sopenharmony_ci del mainmenu.menudefs[-1][1][0:2] 1727db96d56Sopenharmony_ci # Remove the 'Configure Idle' entry from the options menu, it is in the 1737db96d56Sopenharmony_ci # application menu as 'Preferences' 1747db96d56Sopenharmony_ci del mainmenu.menudefs[-3][1][0:2] 1757db96d56Sopenharmony_ci menubar = Menu(root) 1767db96d56Sopenharmony_ci root.configure(menu=menubar) 1777db96d56Sopenharmony_ci menudict = {} 1787db96d56Sopenharmony_ci 1797db96d56Sopenharmony_ci menudict['window'] = menu = Menu(menubar, name='window', tearoff=0) 1807db96d56Sopenharmony_ci menubar.add_cascade(label='Window', menu=menu, underline=0) 1817db96d56Sopenharmony_ci 1827db96d56Sopenharmony_ci def postwindowsmenu(menu=menu): 1837db96d56Sopenharmony_ci end = menu.index('end') 1847db96d56Sopenharmony_ci if end is None: 1857db96d56Sopenharmony_ci end = -1 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_ci if end > 0: 1887db96d56Sopenharmony_ci menu.delete(0, end) 1897db96d56Sopenharmony_ci window.add_windows_to_menu(menu) 1907db96d56Sopenharmony_ci window.register_callback(postwindowsmenu) 1917db96d56Sopenharmony_ci 1927db96d56Sopenharmony_ci def about_dialog(event=None): 1937db96d56Sopenharmony_ci "Handle Help 'About IDLE' event." 1947db96d56Sopenharmony_ci # Synchronize with editor.EditorWindow.about_dialog. 1957db96d56Sopenharmony_ci from idlelib import help_about 1967db96d56Sopenharmony_ci help_about.AboutDialog(root) 1977db96d56Sopenharmony_ci 1987db96d56Sopenharmony_ci def config_dialog(event=None): 1997db96d56Sopenharmony_ci "Handle Options 'Configure IDLE' event." 2007db96d56Sopenharmony_ci # Synchronize with editor.EditorWindow.config_dialog. 2017db96d56Sopenharmony_ci from idlelib import configdialog 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ci # Ensure that the root object has an instance_dict attribute, 2047db96d56Sopenharmony_ci # mirrors code in EditorWindow (although that sets the attribute 2057db96d56Sopenharmony_ci # on an EditorWindow instance that is then passed as the first 2067db96d56Sopenharmony_ci # argument to ConfigDialog) 2077db96d56Sopenharmony_ci root.instance_dict = flist.inversedict 2087db96d56Sopenharmony_ci configdialog.ConfigDialog(root, 'Settings') 2097db96d56Sopenharmony_ci 2107db96d56Sopenharmony_ci def help_dialog(event=None): 2117db96d56Sopenharmony_ci "Handle Help 'IDLE Help' event." 2127db96d56Sopenharmony_ci # Synchronize with editor.EditorWindow.help_dialog. 2137db96d56Sopenharmony_ci from idlelib import help 2147db96d56Sopenharmony_ci help.show_idlehelp(root) 2157db96d56Sopenharmony_ci 2167db96d56Sopenharmony_ci root.bind('<<about-idle>>', about_dialog) 2177db96d56Sopenharmony_ci root.bind('<<open-config-dialog>>', config_dialog) 2187db96d56Sopenharmony_ci root.createcommand('::tk::mac::ShowPreferences', config_dialog) 2197db96d56Sopenharmony_ci if flist: 2207db96d56Sopenharmony_ci root.bind('<<close-all-windows>>', flist.close_all_callback) 2217db96d56Sopenharmony_ci 2227db96d56Sopenharmony_ci # The binding above doesn't reliably work on all versions of Tk 2237db96d56Sopenharmony_ci # on macOS. Adding command definition below does seem to do the 2247db96d56Sopenharmony_ci # right thing for now. 2257db96d56Sopenharmony_ci root.createcommand('exit', flist.close_all_callback) 2267db96d56Sopenharmony_ci 2277db96d56Sopenharmony_ci if isCarbonTk(): 2287db96d56Sopenharmony_ci # for Carbon AquaTk, replace the default Tk apple menu 2297db96d56Sopenharmony_ci menudict['application'] = menu = Menu(menubar, name='apple', 2307db96d56Sopenharmony_ci tearoff=0) 2317db96d56Sopenharmony_ci menubar.add_cascade(label='IDLE', menu=menu) 2327db96d56Sopenharmony_ci mainmenu.menudefs.insert(0, 2337db96d56Sopenharmony_ci ('application', [ 2347db96d56Sopenharmony_ci ('About IDLE', '<<about-idle>>'), 2357db96d56Sopenharmony_ci None, 2367db96d56Sopenharmony_ci ])) 2377db96d56Sopenharmony_ci if isCocoaTk(): 2387db96d56Sopenharmony_ci # replace default About dialog with About IDLE one 2397db96d56Sopenharmony_ci root.createcommand('tkAboutDialog', about_dialog) 2407db96d56Sopenharmony_ci # replace default "Help" item in Help menu 2417db96d56Sopenharmony_ci root.createcommand('::tk::mac::ShowHelp', help_dialog) 2427db96d56Sopenharmony_ci # remove redundant "IDLE Help" from menu 2437db96d56Sopenharmony_ci del mainmenu.menudefs[-1][1][0] 2447db96d56Sopenharmony_ci 2457db96d56Sopenharmony_cidef fixb2context(root): 2467db96d56Sopenharmony_ci '''Removed bad AquaTk Button-2 (right) and Paste bindings. 2477db96d56Sopenharmony_ci 2487db96d56Sopenharmony_ci They prevent context menu access and seem to be gone in AquaTk8.6. 2497db96d56Sopenharmony_ci See issue #24801. 2507db96d56Sopenharmony_ci ''' 2517db96d56Sopenharmony_ci root.unbind_class('Text', '<B2>') 2527db96d56Sopenharmony_ci root.unbind_class('Text', '<B2-Motion>') 2537db96d56Sopenharmony_ci root.unbind_class('Text', '<<PasteSelection>>') 2547db96d56Sopenharmony_ci 2557db96d56Sopenharmony_cidef setupApp(root, flist): 2567db96d56Sopenharmony_ci """ 2577db96d56Sopenharmony_ci Perform initial OS X customizations if needed. 2587db96d56Sopenharmony_ci Called from pyshell.main() after initial calls to Tk() 2597db96d56Sopenharmony_ci 2607db96d56Sopenharmony_ci There are currently three major versions of Tk in use on OS X: 2617db96d56Sopenharmony_ci 1. Aqua Cocoa Tk (native default since OS X 10.6) 2627db96d56Sopenharmony_ci 2. Aqua Carbon Tk (original native, 32-bit only, deprecated) 2637db96d56Sopenharmony_ci 3. X11 (supported by some third-party distributors, deprecated) 2647db96d56Sopenharmony_ci There are various differences among the three that affect IDLE 2657db96d56Sopenharmony_ci behavior, primarily with menus, mouse key events, and accelerators. 2667db96d56Sopenharmony_ci Some one-time customizations are performed here. 2677db96d56Sopenharmony_ci Others are dynamically tested throughout idlelib by calls to the 2687db96d56Sopenharmony_ci isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which 2697db96d56Sopenharmony_ci are initialized here as well. 2707db96d56Sopenharmony_ci """ 2717db96d56Sopenharmony_ci if isAquaTk(): 2727db96d56Sopenharmony_ci hideTkConsole(root) 2737db96d56Sopenharmony_ci overrideRootMenu(root, flist) 2747db96d56Sopenharmony_ci addOpenEventSupport(root, flist) 2757db96d56Sopenharmony_ci fixb2context(root) 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci 2787db96d56Sopenharmony_ciif __name__ == '__main__': 2797db96d56Sopenharmony_ci from unittest import main 2807db96d56Sopenharmony_ci main('idlelib.idle_test.test_macosx', verbosity=2) 281