Coverage for macosx.py: 33%
127 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1"""
2A number of functions that enhance IDLE on macOS.
3"""
4from os.path import expanduser
5import plistlib
6from sys import platform # Used in _init_tk_type, changed by test.
8import tkinter
11## Define functions that query the Mac graphics type.
12## _tk_type and its initializer are private to this section.
14_tk_type = None
16def _init_tk_type():
17 """ Initialize _tk_type for isXyzTk functions.
19 This function is only called once, when _tk_type is still None.
20 """
21 global _tk_type
22 if platform == 'darwin': 22 ↛ 48line 22 didn't jump to line 48, because the condition on line 22 was never false1b
24 # When running IDLE, GUI is present, test/* may not be.
25 # When running tests, test/* is present, GUI may not be.
26 # If not, guess most common. Does not matter for testing.
27 from idlelib.__init__ import testing 1b
28 if testing: 28 ↛ 29line 28 didn't jump to line 29, because the condition on line 28 was never true1b
29 from test.support import requires, ResourceDenied
30 try:
31 requires('gui')
32 except ResourceDenied:
33 _tk_type = "cocoa"
34 return
36 root = tkinter.Tk() 1b
37 ws = root.tk.call('tk', 'windowingsystem') 1b
38 if 'x11' in ws: 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true1b
39 _tk_type = "xquartz"
40 elif 'aqua' not in ws: 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true1b
41 _tk_type = "other"
42 elif 'AppKit' in root.tk.call('winfo', 'server', '.'): 42 ↛ 45line 42 didn't jump to line 45, because the condition on line 42 was never false1b
43 _tk_type = "cocoa" 1b
44 else:
45 _tk_type = "carbon"
46 root.destroy() 1b
47 else:
48 _tk_type = "other"
49 return 1b
51def isAquaTk():
52 """
53 Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
54 """
55 if not _tk_type: 1cdb
56 _init_tk_type() 1cb
57 return _tk_type == "cocoa" or _tk_type == "carbon" 1cdb
59def isCarbonTk():
60 """
61 Returns True if IDLE is using a Carbon Aqua Tk (instead of the
62 newer Cocoa Aqua Tk).
63 """
64 if not _tk_type: 1cd
65 _init_tk_type() 1c
66 return _tk_type == "carbon" 1cd
68def isCocoaTk():
69 """
70 Returns True if IDLE is using a Cocoa Aqua Tk.
71 """
72 if not _tk_type: 1cd
73 _init_tk_type() 1c
74 return _tk_type == "cocoa" 1cd
76def isXQuartz():
77 """
78 Returns True if IDLE is using an OS X X11 Tk.
79 """
80 if not _tk_type: 1cd
81 _init_tk_type() 1c
82 return _tk_type == "xquartz" 1cd
85def readSystemPreferences():
86 """
87 Fetch the macOS system preferences.
88 """
89 if platform != 'darwin':
90 return None
92 plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist')
93 try:
94 with open(plist_path, 'rb') as plist_file:
95 return plistlib.load(plist_file)
96 except OSError:
97 return None
100def preferTabsPreferenceWarning():
101 """
102 Warn if "Prefer tabs when opening documents" is set to "Always".
103 """
104 if platform != 'darwin':
105 return None
107 prefs = readSystemPreferences()
108 if prefs and prefs.get('AppleWindowTabbingMode') == 'always':
109 return (
110 'WARNING: The system preference "Prefer tabs when opening'
111 ' documents" is set to "Always". This will cause various problems'
112 ' with IDLE. For the best experience, change this setting when'
113 ' running IDLE (via System Preferences -> Dock).'
114 )
115 return None
118## Fix the menu and related functions.
120def addOpenEventSupport(root, flist):
121 """
122 This ensures that the application will respond to open AppleEvents, which
123 makes is feasible to use IDLE as the default application for python files.
124 """
125 def doOpenFile(*args):
126 for fn in args:
127 flist.open(fn)
129 # The command below is a hook in aquatk that is called whenever the app
130 # receives a file open event. The callback can have multiple arguments,
131 # one for every file that should be opened.
132 root.createcommand("::tk::mac::OpenDocument", doOpenFile)
134def hideTkConsole(root):
135 try:
136 root.tk.call('console', 'hide')
137 except tkinter.TclError:
138 # Some versions of the Tk framework don't have a console object
139 pass
141def overrideRootMenu(root, flist):
142 """
143 Replace the Tk root menu by something that is more appropriate for
144 IDLE with an Aqua Tk.
145 """
146 # The menu that is attached to the Tk root (".") is also used by AquaTk for
147 # all windows that don't specify a menu of their own. The default menubar
148 # contains a number of menus, none of which are appropriate for IDLE. The
149 # Most annoying of those is an 'About Tck/Tk...' menu in the application
150 # menu.
151 #
152 # This function replaces the default menubar by a mostly empty one, it
153 # should only contain the correct application menu and the window menu.
154 #
155 # Due to a (mis-)feature of TkAqua the user will also see an empty Help
156 # menu.
157 from tkinter import Menu
158 from idlelib import mainmenu
159 from idlelib import window
161 closeItem = mainmenu.menudefs[0][1][-2]
163 # Remove the last 3 items of the file menu: a separator, close window and
164 # quit. Close window will be reinserted just above the save item, where
165 # it should be according to the HIG. Quit is in the application menu.
166 del mainmenu.menudefs[0][1][-3:]
167 mainmenu.menudefs[0][1].insert(6, closeItem)
169 # Remove the 'About' entry from the help menu, it is in the application
170 # menu
171 del mainmenu.menudefs[-1][1][0:2]
172 # Remove the 'Configure Idle' entry from the options menu, it is in the
173 # application menu as 'Preferences'
174 del mainmenu.menudefs[-3][1][0:2]
175 menubar = Menu(root)
176 root.configure(menu=menubar)
178 menu = Menu(menubar, name='window', tearoff=0)
179 menubar.add_cascade(label='Window', menu=menu, underline=0)
181 def postwindowsmenu(menu=menu):
182 end = menu.index('end')
183 if end is None:
184 end = -1
186 if end > 0:
187 menu.delete(0, end)
188 window.add_windows_to_menu(menu)
189 window.register_callback(postwindowsmenu)
191 def about_dialog(event=None):
192 "Handle Help 'About IDLE' event."
193 # Synchronize with editor.EditorWindow.about_dialog.
194 from idlelib import help_about
195 help_about.AboutDialog(root)
197 def config_dialog(event=None):
198 "Handle Options 'Configure IDLE' event."
199 # Synchronize with editor.EditorWindow.config_dialog.
200 from idlelib import configdialog
202 # Ensure that the root object has an instance_dict attribute,
203 # mirrors code in EditorWindow (although that sets the attribute
204 # on an EditorWindow instance that is then passed as the first
205 # argument to ConfigDialog)
206 root.instance_dict = flist.inversedict
207 configdialog.ConfigDialog(root, 'Settings')
209 def help_dialog(event=None):
210 "Handle Help 'IDLE Help' event."
211 # Synchronize with editor.EditorWindow.help_dialog.
212 from idlelib import help
213 help.show_idlehelp(root)
215 root.bind('<<about-idle>>', about_dialog)
216 root.bind('<<open-config-dialog>>', config_dialog)
217 root.createcommand('::tk::mac::ShowPreferences', config_dialog)
218 if flist:
219 root.bind('<<close-all-windows>>', flist.close_all_callback)
221 # The binding above doesn't reliably work on all versions of Tk
222 # on macOS. Adding command definition below does seem to do the
223 # right thing for now.
224 root.createcommand('exit', flist.close_all_callback)
226 if isCarbonTk():
227 # for Carbon AquaTk, replace the default Tk apple menu
228 menu = Menu(menubar, name='apple', tearoff=0)
229 menubar.add_cascade(label='IDLE', menu=menu)
230 mainmenu.menudefs.insert(0,
231 ('application', [
232 ('About IDLE', '<<about-idle>>'),
233 None,
234 ]))
235 if isCocoaTk():
236 # replace default About dialog with About IDLE one
237 root.createcommand('tkAboutDialog', about_dialog)
238 # replace default "Help" item in Help menu
239 root.createcommand('::tk::mac::ShowHelp', help_dialog)
240 # remove redundant "IDLE Help" from menu
241 del mainmenu.menudefs[-1][1][0]
243def fixb2context(root):
244 '''Removed bad AquaTk Button-2 (right) and Paste bindings.
246 They prevent context menu access and seem to be gone in AquaTk8.6.
247 See issue #24801.
248 '''
249 root.unbind_class('Text', '<B2>')
250 root.unbind_class('Text', '<B2-Motion>')
251 root.unbind_class('Text', '<<PasteSelection>>')
253def setupApp(root, flist):
254 """
255 Perform initial OS X customizations if needed.
256 Called from pyshell.main() after initial calls to Tk()
258 There are currently three major versions of Tk in use on OS X:
259 1. Aqua Cocoa Tk (native default since OS X 10.6)
260 2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
261 3. X11 (supported by some third-party distributors, deprecated)
262 There are various differences among the three that affect IDLE
263 behavior, primarily with menus, mouse key events, and accelerators.
264 Some one-time customizations are performed here.
265 Others are dynamically tested throughout idlelib by calls to the
266 isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
267 are initialized here as well.
268 """
269 if isAquaTk():
270 hideTkConsole(root)
271 overrideRootMenu(root, flist)
272 addOpenEventSupport(root, flist)
273 fixb2context(root)
276if __name__ == '__main__': 276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true
277 from unittest import main
278 main('idlelib.idle_test.test_macosx', verbosity=2)