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