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 """ Initialize _tk_type for isXyzTk functions. 18 19 This function is only called once, when _tk_type is still None. 20 """ 21 global _tk_type 22 if platform == 'darwin': 23 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 28 if testing: 29 from test.support import requires, ResourceDenied 30 try: 31 requires('gui') 32 except ResourceDenied: 33 _tk_type = "cocoa" 34 return 35 36 root = tkinter.Tk() 37 ws = root.tk.call('tk', 'windowingsystem') 38 if 'x11' in ws: 39 _tk_type = "xquartz" 40 elif 'aqua' not in ws: 41 _tk_type = "other" 42 elif 'AppKit' in root.tk.call('winfo', 'server', '.'): 43 _tk_type = "cocoa" 44 else: 45 _tk_type = "carbon" 46 root.destroy() 47 else: 48 _tk_type = "other" 49 return 50 51def isAquaTk(): 52 """ 53 Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon). 54 """ 55 if not _tk_type: 56 _init_tk_type() 57 return _tk_type == "cocoa" or _tk_type == "carbon" 58 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: 65 _init_tk_type() 66 return _tk_type == "carbon" 67 68def isCocoaTk(): 69 """ 70 Returns True if IDLE is using a Cocoa Aqua Tk. 71 """ 72 if not _tk_type: 73 _init_tk_type() 74 return _tk_type == "cocoa" 75 76def isXQuartz(): 77 """ 78 Returns True if IDLE is using an OS X X11 Tk. 79 """ 80 if not _tk_type: 81 _init_tk_type() 82 return _tk_type == "xquartz" 83 84 85def readSystemPreferences(): 86 """ 87 Fetch the macOS system preferences. 88 """ 89 if platform != 'darwin': 90 return None 91 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 98 99 100def preferTabsPreferenceWarning(): 101 """ 102 Warn if "Prefer tabs when opening documents" is set to "Always". 103 """ 104 if platform != 'darwin': 105 return None 106 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 116 117 118## Fix the menu and related functions. 119 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) 128 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) 133 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 140 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 160 161 closeItem = mainmenu.menudefs[0][1][-2] 162 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) 168 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) 177 menudict = {} 178 179 menudict['window'] = menu = Menu(menubar, name='window', tearoff=0) 180 menubar.add_cascade(label='Window', menu=menu, underline=0) 181 182 def postwindowsmenu(menu=menu): 183 end = menu.index('end') 184 if end is None: 185 end = -1 186 187 if end > 0: 188 menu.delete(0, end) 189 window.add_windows_to_menu(menu) 190 window.register_callback(postwindowsmenu) 191 192 def about_dialog(event=None): 193 "Handle Help 'About IDLE' event." 194 # Synchronize with editor.EditorWindow.about_dialog. 195 from idlelib import help_about 196 help_about.AboutDialog(root) 197 198 def config_dialog(event=None): 199 "Handle Options 'Configure IDLE' event." 200 # Synchronize with editor.EditorWindow.config_dialog. 201 from idlelib import configdialog 202 203 # Ensure that the root object has an instance_dict attribute, 204 # mirrors code in EditorWindow (although that sets the attribute 205 # on an EditorWindow instance that is then passed as the first 206 # argument to ConfigDialog) 207 root.instance_dict = flist.inversedict 208 configdialog.ConfigDialog(root, 'Settings') 209 210 def help_dialog(event=None): 211 "Handle Help 'IDLE Help' event." 212 # Synchronize with editor.EditorWindow.help_dialog. 213 from idlelib import help 214 help.show_idlehelp(root) 215 216 root.bind('<<about-idle>>', about_dialog) 217 root.bind('<<open-config-dialog>>', config_dialog) 218 root.createcommand('::tk::mac::ShowPreferences', config_dialog) 219 if flist: 220 root.bind('<<close-all-windows>>', flist.close_all_callback) 221 222 # The binding above doesn't reliably work on all versions of Tk 223 # on macOS. Adding command definition below does seem to do the 224 # right thing for now. 225 root.createcommand('exit', flist.close_all_callback) 226 227 if isCarbonTk(): 228 # for Carbon AquaTk, replace the default Tk apple menu 229 menudict['application'] = menu = Menu(menubar, name='apple', 230 tearoff=0) 231 menubar.add_cascade(label='IDLE', menu=menu) 232 mainmenu.menudefs.insert(0, 233 ('application', [ 234 ('About IDLE', '<<about-idle>>'), 235 None, 236 ])) 237 if isCocoaTk(): 238 # replace default About dialog with About IDLE one 239 root.createcommand('tkAboutDialog', about_dialog) 240 # replace default "Help" item in Help menu 241 root.createcommand('::tk::mac::ShowHelp', help_dialog) 242 # remove redundant "IDLE Help" from menu 243 del mainmenu.menudefs[-1][1][0] 244 245def fixb2context(root): 246 '''Removed bad AquaTk Button-2 (right) and Paste bindings. 247 248 They prevent context menu access and seem to be gone in AquaTk8.6. 249 See issue #24801. 250 ''' 251 root.unbind_class('Text', '<B2>') 252 root.unbind_class('Text', '<B2-Motion>') 253 root.unbind_class('Text', '<<PasteSelection>>') 254 255def setupApp(root, flist): 256 """ 257 Perform initial OS X customizations if needed. 258 Called from pyshell.main() after initial calls to Tk() 259 260 There are currently three major versions of Tk in use on OS X: 261 1. Aqua Cocoa Tk (native default since OS X 10.6) 262 2. Aqua Carbon Tk (original native, 32-bit only, deprecated) 263 3. X11 (supported by some third-party distributors, deprecated) 264 There are various differences among the three that affect IDLE 265 behavior, primarily with menus, mouse key events, and accelerators. 266 Some one-time customizations are performed here. 267 Others are dynamically tested throughout idlelib by calls to the 268 isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which 269 are initialized here as well. 270 """ 271 if isAquaTk(): 272 hideTkConsole(root) 273 overrideRootMenu(root, flist) 274 addOpenEventSupport(root, flist) 275 fixb2context(root) 276 277 278if __name__ == '__main__': 279 from unittest import main 280 main('idlelib.idle_test.test_macosx', verbosity=2) 281