123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- """
- A number of functions that enhance IDLE on macOS.
- """
- from os.path import expanduser
- import plistlib
- from sys import platform # Used in _init_tk_type, changed by test.
- import tkinter
- ## Define functions that query the Mac graphics type.
- ## _tk_type and its initializer are private to this section.
- _tk_type = None
- def _init_tk_type():
- """ Initialize _tk_type for isXyzTk functions.
- This function is only called once, when _tk_type is still None.
- """
- global _tk_type
- if platform == 'darwin':
- # When running IDLE, GUI is present, test/* may not be.
- # When running tests, test/* is present, GUI may not be.
- # If not, guess most common. Does not matter for testing.
- from idlelib.__init__ import testing
- if testing:
- from test.support import requires, ResourceDenied
- try:
- requires('gui')
- except ResourceDenied:
- _tk_type = "cocoa"
- return
- root = tkinter.Tk()
- ws = root.tk.call('tk', 'windowingsystem')
- if 'x11' in ws:
- _tk_type = "xquartz"
- elif 'aqua' not in ws:
- _tk_type = "other"
- elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
- _tk_type = "cocoa"
- else:
- _tk_type = "carbon"
- root.destroy()
- else:
- _tk_type = "other"
- return
- def isAquaTk():
- """
- Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
- """
- if not _tk_type:
- _init_tk_type()
- return _tk_type == "cocoa" or _tk_type == "carbon"
- def isCarbonTk():
- """
- Returns True if IDLE is using a Carbon Aqua Tk (instead of the
- newer Cocoa Aqua Tk).
- """
- if not _tk_type:
- _init_tk_type()
- return _tk_type == "carbon"
- def isCocoaTk():
- """
- Returns True if IDLE is using a Cocoa Aqua Tk.
- """
- if not _tk_type:
- _init_tk_type()
- return _tk_type == "cocoa"
- def isXQuartz():
- """
- Returns True if IDLE is using an OS X X11 Tk.
- """
- if not _tk_type:
- _init_tk_type()
- return _tk_type == "xquartz"
- def readSystemPreferences():
- """
- Fetch the macOS system preferences.
- """
- if platform != 'darwin':
- return None
- plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist')
- try:
- with open(plist_path, 'rb') as plist_file:
- return plistlib.load(plist_file)
- except OSError:
- return None
- def preferTabsPreferenceWarning():
- """
- Warn if "Prefer tabs when opening documents" is set to "Always".
- """
- if platform != 'darwin':
- return None
- prefs = readSystemPreferences()
- if prefs and prefs.get('AppleWindowTabbingMode') == 'always':
- return (
- 'WARNING: The system preference "Prefer tabs when opening'
- ' documents" is set to "Always". This will cause various problems'
- ' with IDLE. For the best experience, change this setting when'
- ' running IDLE (via System Preferences -> Dock).'
- )
- return None
- ## Fix the menu and related functions.
- def addOpenEventSupport(root, flist):
- """
- This ensures that the application will respond to open AppleEvents, which
- makes is feasible to use IDLE as the default application for python files.
- """
- def doOpenFile(*args):
- for fn in args:
- flist.open(fn)
- # The command below is a hook in aquatk that is called whenever the app
- # receives a file open event. The callback can have multiple arguments,
- # one for every file that should be opened.
- root.createcommand("::tk::mac::OpenDocument", doOpenFile)
- def hideTkConsole(root):
- try:
- root.tk.call('console', 'hide')
- except tkinter.TclError:
- # Some versions of the Tk framework don't have a console object
- pass
- def overrideRootMenu(root, flist):
- """
- Replace the Tk root menu by something that is more appropriate for
- IDLE with an Aqua Tk.
- """
- # The menu that is attached to the Tk root (".") is also used by AquaTk for
- # all windows that don't specify a menu of their own. The default menubar
- # contains a number of menus, none of which are appropriate for IDLE. The
- # Most annoying of those is an 'About Tck/Tk...' menu in the application
- # menu.
- #
- # This function replaces the default menubar by a mostly empty one, it
- # should only contain the correct application menu and the window menu.
- #
- # Due to a (mis-)feature of TkAqua the user will also see an empty Help
- # menu.
- from tkinter import Menu
- from idlelib import mainmenu
- from idlelib import window
- closeItem = mainmenu.menudefs[0][1][-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)
- # Remove the 'About' entry from the help menu, it is in the application
- # menu
- del mainmenu.menudefs[-1][1][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]
- menubar = Menu(root)
- root.configure(menu=menubar)
- menu = Menu(menubar, name='window', tearoff=0)
- menubar.add_cascade(label='Window', menu=menu, underline=0)
- def postwindowsmenu(menu=menu):
- end = menu.index('end')
- if end is None:
- end = -1
- if end > 0:
- menu.delete(0, end)
- window.add_windows_to_menu(menu)
- window.register_callback(postwindowsmenu)
- def about_dialog(event=None):
- "Handle Help 'About IDLE' event."
- # Synchronize with editor.EditorWindow.about_dialog.
- from idlelib import help_about
- help_about.AboutDialog(root)
- def config_dialog(event=None):
- "Handle Options 'Configure IDLE' event."
- # Synchronize with editor.EditorWindow.config_dialog.
- from idlelib import configdialog
- # Ensure that the root object has an instance_dict attribute,
- # mirrors code in EditorWindow (although that sets the attribute
- # on an EditorWindow instance that is then passed as the first
- # argument to ConfigDialog)
- root.instance_dict = flist.inversedict
- configdialog.ConfigDialog(root, 'Settings')
- def help_dialog(event=None):
- "Handle Help 'IDLE Help' event."
- # Synchronize with editor.EditorWindow.help_dialog.
- from idlelib import help
- help.show_idlehelp(root)
- root.bind('<<about-idle>>', about_dialog)
- root.bind('<<open-config-dialog>>', config_dialog)
- root.createcommand('::tk::mac::ShowPreferences', config_dialog)
- if flist:
- root.bind('<<close-all-windows>>', flist.close_all_callback)
- # The binding above doesn't reliably work on all versions of Tk
- # on macOS. Adding command definition below does seem to do the
- # right thing for now.
- root.createcommand('exit', flist.close_all_callback)
- if isCarbonTk():
- # for Carbon AquaTk, replace the default Tk apple menu
- menu = Menu(menubar, name='apple', tearoff=0)
- menubar.add_cascade(label='IDLE', menu=menu)
- mainmenu.menudefs.insert(0,
- ('application', [
- ('About IDLE', '<<about-idle>>'),
- None,
- ]))
- 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]
- def fixb2context(root):
- '''Removed bad AquaTk Button-2 (right) and Paste bindings.
- They prevent context menu access and seem to be gone in AquaTk8.6.
- See issue #24801.
- '''
- root.unbind_class('Text', '<B2>')
- root.unbind_class('Text', '<B2-Motion>')
- root.unbind_class('Text', '<<PasteSelection>>')
- def setupApp(root, flist):
- """
- Perform initial OS X customizations if needed.
- Called from pyshell.main() after initial calls to Tk()
- There are currently three major versions of Tk in use on OS X:
- 1. Aqua Cocoa Tk (native default since OS X 10.6)
- 2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
- 3. X11 (supported by some third-party distributors, deprecated)
- There are various differences among the three that affect IDLE
- behavior, primarily with menus, mouse key events, and accelerators.
- Some one-time customizations are performed here.
- Others are dynamically tested throughout idlelib by calls to the
- isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
- are initialized here as well.
- """
- if isAquaTk():
- hideTkConsole(root)
- overrideRootMenu(root, flist)
- addOpenEventSupport(root, flist)
- fixb2context(root)
- if __name__ == '__main__':
- from unittest import main
- main('idlelib.idle_test.test_macosx', verbosity=2)
|