Coverage for macosx.py: 33%

127 statements  

« 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. 

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': 22 ↛ 48line 22 didn't jump to line 48, because the condition on line 22 was never false1b

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 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 

35 

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

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: 1cdb

56 _init_tk_type() 1cb

57 return _tk_type == "cocoa" or _tk_type == "carbon" 1cdb

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: 1cd

65 _init_tk_type() 1c

66 return _tk_type == "carbon" 1cd

67 

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

75 

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

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 

178 menu = Menu(menubar, name='window', tearoff=0) 

179 menubar.add_cascade(label='Window', menu=menu, underline=0) 

180 

181 def postwindowsmenu(menu=menu): 

182 end = menu.index('end') 

183 if end is None: 

184 end = -1 

185 

186 if end > 0: 

187 menu.delete(0, end) 

188 window.add_windows_to_menu(menu) 

189 window.register_callback(postwindowsmenu) 

190 

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) 

196 

197 def config_dialog(event=None): 

198 "Handle Options 'Configure IDLE' event." 

199 # Synchronize with editor.EditorWindow.config_dialog. 

200 from idlelib import configdialog 

201 

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') 

208 

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) 

214 

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) 

220 

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) 

225 

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] 

242 

243def fixb2context(root): 

244 '''Removed bad AquaTk Button-2 (right) and Paste bindings. 

245 

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>>') 

252 

253def setupApp(root, flist): 

254 """ 

255 Perform initial OS X customizations if needed. 

256 Called from pyshell.main() after initial calls to Tk() 

257 

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) 

274 

275 

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)