blob: eeaab59ae80295fbd6aef7d068f3dc460fd4b523 [file] [log] [blame]
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001"""
Terry Jan Reedyb65413b2018-11-15 13:15:13 -05002A number of functions that enhance IDLE on macOS.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00003"""
Tal Einat9ebe8792018-12-07 08:32:21 +02004from os.path import expanduser
5import plistlib
Terry Jan Reedy2518fa82016-06-12 15:49:20 -04006from sys import platform # Used in _init_tk_type, changed by test.
Ronald Oussoren6f6c5622009-12-24 14:03:19 +00007
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04008import tkinter
9
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040010
11## Define functions that query the Mac graphics type.
12## _tk_type and its initializer are private to this section.
13
Ned Deilyb7601672014-03-27 20:49:14 -070014_tk_type = None
15
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040016def _init_tk_type():
Ned Deilyb7601672014-03-27 20:49:14 -070017 """
18 Initializes OS X Tk variant values for
19 isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz().
20 """
21 global _tk_type
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040022 if platform == 'darwin':
23 root = tkinter.Tk()
Ned Deilyb7601672014-03-27 20:49:14 -070024 ws = root.tk.call('tk', 'windowingsystem')
25 if 'x11' in ws:
26 _tk_type = "xquartz"
27 elif 'aqua' not in ws:
28 _tk_type = "other"
29 elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
30 _tk_type = "cocoa"
31 else:
32 _tk_type = "carbon"
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040033 root.destroy()
Ned Deilyb7601672014-03-27 20:49:14 -070034 else:
35 _tk_type = "other"
36
37def isAquaTk():
38 """
39 Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
40 """
Terry Jan Reedyfb51e652016-06-08 18:09:22 -040041 if not _tk_type:
42 _init_tk_type()
Ned Deilyb7601672014-03-27 20:49:14 -070043 return _tk_type == "cocoa" or _tk_type == "carbon"
44
45def isCarbonTk():
Georg Brandlaedd2892010-12-19 10:10:32 +000046 """
47 Returns True if IDLE is using a Carbon Aqua Tk (instead of the
48 newer Cocoa Aqua Tk).
49 """
Terry Jan Reedyfb51e652016-06-08 18:09:22 -040050 if not _tk_type:
51 _init_tk_type()
Ned Deilyb7601672014-03-27 20:49:14 -070052 return _tk_type == "carbon"
53
54def isCocoaTk():
55 """
56 Returns True if IDLE is using a Cocoa Aqua Tk.
57 """
Terry Jan Reedyfb51e652016-06-08 18:09:22 -040058 if not _tk_type:
59 _init_tk_type()
Ned Deilyb7601672014-03-27 20:49:14 -070060 return _tk_type == "cocoa"
61
62def isXQuartz():
63 """
64 Returns True if IDLE is using an OS X X11 Tk.
65 """
Terry Jan Reedyfb51e652016-06-08 18:09:22 -040066 if not _tk_type:
67 _init_tk_type()
Ned Deilyb7601672014-03-27 20:49:14 -070068 return _tk_type == "xquartz"
Georg Brandlaedd2892010-12-19 10:10:32 +000069
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040070
Ned Deily4ce92b22011-01-15 04:37:12 +000071def tkVersionWarning(root):
72 """
73 Returns a string warning message if the Tk version in use appears to
Ned Deilyc556c642012-07-30 03:31:21 -070074 be one known to cause problems with IDLE.
75 1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable.
76 2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but
77 can still crash unexpectedly.
Ned Deily4ce92b22011-01-15 04:37:12 +000078 """
79
Ned Deilyb7601672014-03-27 20:49:14 -070080 if isCocoaTk():
Ned Deilyc556c642012-07-30 03:31:21 -070081 patchlevel = root.tk.call('info', 'patchlevel')
82 if patchlevel not in ('8.5.7', '8.5.9'):
83 return False
Tal Einat9ebe8792018-12-07 08:32:21 +020084 return ("WARNING: The version of Tcl/Tk ({0}) in use may"
85 " be unstable.\n"
86 "Visit http://www.python.org/download/mac/tcltk/"
87 " for current information.".format(patchlevel))
Ned Deily4ce92b22011-01-15 04:37:12 +000088 else:
89 return False
90
Terry Jan Reedy2518fa82016-06-12 15:49:20 -040091
Tal Einat9ebe8792018-12-07 08:32:21 +020092def readSystemPreferences():
93 """
94 Fetch the macOS system preferences.
95 """
96 if platform != 'darwin':
97 return None
98
99 plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist')
100 try:
101 with open(plist_path, 'rb') as plist_file:
102 return plistlib.load(plist_file)
103 except OSError:
104 return None
105
106
107def preferTabsPreferenceWarning():
108 """
109 Warn if "Prefer tabs when opening documents" is set to "Always".
110 """
111 if platform != 'darwin':
112 return None
113
114 prefs = readSystemPreferences()
115 if prefs and prefs.get('AppleWindowTabbingMode') == 'always':
116 return (
117 'WARNING: The system preference "Prefer tabs when opening'
118 ' documents" is set to "Always". This will cause various problems'
119 ' with IDLE. For the best experience, change this setting when'
120 ' running IDLE (via System Preferences -> Dock).'
121 )
122 return None
123
124
Terry Jan Reedy2518fa82016-06-12 15:49:20 -0400125## Fix the menu and related functions.
126
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127def addOpenEventSupport(root, flist):
128 """
Ezio Melotti13925002011-03-16 11:05:33 +0200129 This ensures that the application will respond to open AppleEvents, which
130 makes is feasible to use IDLE as the default application for python files.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000131 """
132 def doOpenFile(*args):
133 for fn in args:
134 flist.open(fn)
135
136 # The command below is a hook in aquatk that is called whenever the app
137 # receives a file open event. The callback can have multiple arguments,
138 # one for every file that should be opened.
139 root.createcommand("::tk::mac::OpenDocument", doOpenFile)
140
141def hideTkConsole(root):
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000142 try:
143 root.tk.call('console', 'hide')
Georg Brandl14fc4272008-05-17 18:39:55 +0000144 except tkinter.TclError:
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000145 # Some versions of the Tk framework don't have a console object
146 pass
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000147
148def overrideRootMenu(root, flist):
149 """
Ned Deilyb7601672014-03-27 20:49:14 -0700150 Replace the Tk root menu by something that is more appropriate for
151 IDLE with an Aqua Tk.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000152 """
153 # The menu that is attached to the Tk root (".") is also used by AquaTk for
154 # all windows that don't specify a menu of their own. The default menubar
155 # contains a number of menus, none of which are appropriate for IDLE. The
156 # Most annoying of those is an 'About Tck/Tk...' menu in the application
157 # menu.
158 #
159 # This function replaces the default menubar by a mostly empty one, it
160 # should only contain the correct application menu and the window menu.
161 #
162 # Due to a (mis-)feature of TkAqua the user will also see an empty Help
163 # menu.
Terry Jan Reedy038c16b2015-05-15 23:03:17 -0400164 from tkinter import Menu
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400165 from idlelib import mainmenu
Terry Jan Reedya361e892018-06-20 21:25:59 -0400166 from idlelib import window
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000167
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400168 closeItem = mainmenu.menudefs[0][1][-2]
Ned Deilyb7601672014-03-27 20:49:14 -0700169
170 # Remove the last 3 items of the file menu: a separator, close window and
171 # quit. Close window will be reinserted just above the save item, where
172 # it should be according to the HIG. Quit is in the application menu.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400173 del mainmenu.menudefs[0][1][-3:]
174 mainmenu.menudefs[0][1].insert(6, closeItem)
Ned Deilyb7601672014-03-27 20:49:14 -0700175
176 # Remove the 'About' entry from the help menu, it is in the application
177 # menu
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400178 del mainmenu.menudefs[-1][1][0:2]
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400179 # Remove the 'Configure Idle' entry from the options menu, it is in the
Ned Deilyb7601672014-03-27 20:49:14 -0700180 # application menu as 'Preferences'
Terry Jan Reedy2cf1dda2019-01-18 17:05:40 -0500181 del mainmenu.menudefs[-3][1][0:2]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000182 menubar = Menu(root)
183 root.configure(menu=menubar)
184 menudict = {}
185
Terry Jan Reedy33c74202018-06-20 22:49:55 -0400186 menudict['window'] = menu = Menu(menubar, name='window', tearoff=0)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000187 menubar.add_cascade(label='Window', menu=menu, underline=0)
188
189 def postwindowsmenu(menu=menu):
190 end = menu.index('end')
191 if end is None:
192 end = -1
193
194 if end > 0:
195 menu.delete(0, end)
Mark Roseman42397732018-06-25 18:19:40 -0700196 window.add_windows_to_menu(menu)
197 window.register_callback(postwindowsmenu)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000198
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000199 def about_dialog(event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400200 "Handle Help 'About IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400201 # Synchronize with editor.EditorWindow.about_dialog.
202 from idlelib import help_about
csabella18ede062017-06-23 20:00:58 -0400203 help_about.AboutDialog(root)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000204
205 def config_dialog(event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400206 "Handle Options 'Configure IDLE' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400207 # Synchronize with editor.EditorWindow.config_dialog.
208 from idlelib import configdialog
Ronald Oussoren220a9fb2009-05-26 18:41:00 +0000209
210 # Ensure that the root object has an instance_dict attribute,
211 # mirrors code in EditorWindow (although that sets the attribute
212 # on an EditorWindow instance that is then passed as the first
213 # argument to ConfigDialog)
214 root.instance_dict = flist.inversedict
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400215 configdialog.ConfigDialog(root, 'Settings')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000216
Georg Brandlaedd2892010-12-19 10:10:32 +0000217 def help_dialog(event=None):
Terry Jan Reedyb50c6372015-09-20 22:55:39 -0400218 "Handle Help 'IDLE Help' event."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400219 # Synchronize with editor.EditorWindow.help_dialog.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400220 from idlelib import help
221 help.show_idlehelp(root)
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000222
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000223 root.bind('<<about-idle>>', about_dialog)
224 root.bind('<<open-config-dialog>>', config_dialog)
Georg Brandlaedd2892010-12-19 10:10:32 +0000225 root.createcommand('::tk::mac::ShowPreferences', config_dialog)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000226 if flist:
227 root.bind('<<close-all-windows>>', flist.close_all_callback)
228
Ronald Oussoren10e05e12010-12-07 15:28:10 +0000229 # The binding above doesn't reliably work on all versions of Tk
Terry Jan Reedyb65413b2018-11-15 13:15:13 -0500230 # on macOS. Adding command definition below does seem to do the
Ronald Oussoren10e05e12010-12-07 15:28:10 +0000231 # right thing for now.
232 root.createcommand('exit', flist.close_all_callback)
233
Ned Deilyb7601672014-03-27 20:49:14 -0700234 if isCarbonTk():
Georg Brandlaedd2892010-12-19 10:10:32 +0000235 # for Carbon AquaTk, replace the default Tk apple menu
Terry Jan Reedy30f1f672015-07-30 16:44:22 -0400236 menudict['application'] = menu = Menu(menubar, name='apple',
237 tearoff=0)
Georg Brandlaedd2892010-12-19 10:10:32 +0000238 menubar.add_cascade(label='IDLE', menu=menu)
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400239 mainmenu.menudefs.insert(0,
Georg Brandlaedd2892010-12-19 10:10:32 +0000240 ('application', [
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000241 ('About IDLE', '<<about-idle>>'),
Georg Brandlaedd2892010-12-19 10:10:32 +0000242 None,
243 ]))
Ned Deilyb7601672014-03-27 20:49:14 -0700244 if isCocoaTk():
Georg Brandlaedd2892010-12-19 10:10:32 +0000245 # replace default About dialog with About IDLE one
246 root.createcommand('tkAboutDialog', about_dialog)
247 # replace default "Help" item in Help menu
248 root.createcommand('::tk::mac::ShowHelp', help_dialog)
249 # remove redundant "IDLE Help" from menu
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400250 del mainmenu.menudefs[-1][1][0]
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000251
Terry Jan Reedy24f3a182016-06-08 14:37:05 -0400252def fixb2context(root):
253 '''Removed bad AquaTk Button-2 (right) and Paste bindings.
254
255 They prevent context menu access and seem to be gone in AquaTk8.6.
256 See issue #24801.
257 '''
258 root.unbind_class('Text', '<B2>')
259 root.unbind_class('Text', '<B2-Motion>')
260 root.unbind_class('Text', '<<PasteSelection>>')
261
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000262def setupApp(root, flist):
263 """
Ned Deilyb7601672014-03-27 20:49:14 -0700264 Perform initial OS X customizations if needed.
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400265 Called from pyshell.main() after initial calls to Tk()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000266
Ned Deilyb7601672014-03-27 20:49:14 -0700267 There are currently three major versions of Tk in use on OS X:
268 1. Aqua Cocoa Tk (native default since OS X 10.6)
269 2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
270 3. X11 (supported by some third-party distributors, deprecated)
271 There are various differences among the three that affect IDLE
272 behavior, primarily with menus, mouse key events, and accelerators.
273 Some one-time customizations are performed here.
274 Others are dynamically tested throughout idlelib by calls to the
275 isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
276 are initialized here as well.
277 """
Ned Deilyb7601672014-03-27 20:49:14 -0700278 if isAquaTk():
279 hideTkConsole(root)
280 overrideRootMenu(root, flist)
281 addOpenEventSupport(root, flist)
Ned Deily139fb7c2016-06-11 02:57:56 -0400282 fixb2context(root)
Terry Jan Reedy2518fa82016-06-12 15:49:20 -0400283
284
285if __name__ == '__main__':
286 from unittest import main
287 main('idlelib.idle_test.test_macosx', verbosity=2)