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