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. 7 8import tkinter 9 10 11## Define functions that query the Mac graphics type. 12## _tk_type and its initializer are private to this section. 13 14_tk_type = None 15 16def _init_tk_type(): 17 """ 18 Initializes OS X Tk variant values for 19 isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz(). 20 """ 21 global _tk_type 22 if platform == 'darwin': 23 root = tkinter.Tk() 24 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" 33 root.destroy() 34 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 """ 41 if not _tk_type: 42 _init_tk_type() 43 return _tk_type == "cocoa" or _tk_type == "carbon" 44 45def isCarbonTk(): 46 """ 47 Returns True if IDLE is using a Carbon Aqua Tk (instead of the 48 newer Cocoa Aqua Tk). 49 """ 50 if not _tk_type: 51 _init_tk_type() 52 return _tk_type == "carbon" 53 54def isCocoaTk(): 55 """ 56 Returns True if IDLE is using a Cocoa Aqua Tk. 57 """ 58 if not _tk_type: 59 _init_tk_type() 60 return _tk_type == "cocoa" 61 62def isXQuartz(): 63 """ 64 Returns True if IDLE is using an OS X X11 Tk. 65 """ 66 if not _tk_type: 67 _init_tk_type() 68 return _tk_type == "xquartz" 69 70 71def tkVersionWarning(root): 72 """ 73 Returns a string warning message if the Tk version in use appears to 74 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. 78 """ 79 80 if isCocoaTk(): 81 patchlevel = root.tk.call('info', 'patchlevel') 82 if patchlevel not in ('8.5.7', '8.5.9'): 83 return False 84 return ("WARNING: The version of Tcl/Tk ({0}) in use may" 85 " be unstable.\n" 86 "Visit https://www.python.org/download/mac/tcltk/" 87 " for current information.".format(patchlevel)) 88 else: 89 return False 90 91 92def 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 125## Fix the menu and related functions. 126 127def addOpenEventSupport(root, flist): 128 """ 129 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. 131 """ 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): 142 try: 143 root.tk.call('console', 'hide') 144 except tkinter.TclError: 145 # Some versions of the Tk framework don't have a console object 146 pass 147 148def overrideRootMenu(root, flist): 149 """ 150 Replace the Tk root menu by something that is more appropriate for 151 IDLE with an Aqua Tk. 152 """ 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. 164 from tkinter import Menu 165 from idlelib import mainmenu 166 from idlelib import window 167 168 closeItem = mainmenu.menudefs[0][1][-2] 169 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. 173 del mainmenu.menudefs[0][1][-3:] 174 mainmenu.menudefs[0][1].insert(6, closeItem) 175 176 # Remove the 'About' entry from the help menu, it is in the application 177 # menu 178 del mainmenu.menudefs[-1][1][0:2] 179 # Remove the 'Configure Idle' entry from the options menu, it is in the 180 # application menu as 'Preferences' 181 del mainmenu.menudefs[-3][1][0:2] 182 menubar = Menu(root) 183 root.configure(menu=menubar) 184 menudict = {} 185 186 menudict['window'] = menu = Menu(menubar, name='window', tearoff=0) 187 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) 196 window.add_windows_to_menu(menu) 197 window.register_callback(postwindowsmenu) 198 199 def about_dialog(event=None): 200 "Handle Help 'About IDLE' event." 201 # Synchronize with editor.EditorWindow.about_dialog. 202 from idlelib import help_about 203 help_about.AboutDialog(root) 204 205 def config_dialog(event=None): 206 "Handle Options 'Configure IDLE' event." 207 # Synchronize with editor.EditorWindow.config_dialog. 208 from idlelib import configdialog 209 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 215 configdialog.ConfigDialog(root, 'Settings') 216 217 def help_dialog(event=None): 218 "Handle Help 'IDLE Help' event." 219 # Synchronize with editor.EditorWindow.help_dialog. 220 from idlelib import help 221 help.show_idlehelp(root) 222 223 root.bind('<<about-idle>>', about_dialog) 224 root.bind('<<open-config-dialog>>', config_dialog) 225 root.createcommand('::tk::mac::ShowPreferences', config_dialog) 226 if flist: 227 root.bind('<<close-all-windows>>', flist.close_all_callback) 228 229 # The binding above doesn't reliably work on all versions of Tk 230 # on macOS. Adding command definition below does seem to do the 231 # right thing for now. 232 root.createcommand('exit', flist.close_all_callback) 233 234 if isCarbonTk(): 235 # for Carbon AquaTk, replace the default Tk apple menu 236 menudict['application'] = menu = Menu(menubar, name='apple', 237 tearoff=0) 238 menubar.add_cascade(label='IDLE', menu=menu) 239 mainmenu.menudefs.insert(0, 240 ('application', [ 241 ('About IDLE', '<<about-idle>>'), 242 None, 243 ])) 244 if isCocoaTk(): 245 # 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 250 del mainmenu.menudefs[-1][1][0] 251 252def 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 262def setupApp(root, flist): 263 """ 264 Perform initial OS X customizations if needed. 265 Called from pyshell.main() after initial calls to Tk() 266 267 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 """ 278 if isAquaTk(): 279 hideTkConsole(root) 280 overrideRootMenu(root, flist) 281 addOpenEventSupport(root, flist) 282 fixb2context(root) 283 284 285if __name__ == '__main__': 286 from unittest import main 287 main('idlelib.idle_test.test_macosx', verbosity=2) 288