blob: 4dbe92a13b62477a6309997ec66b501aa07d91a6 [file] [log] [blame]
Just van Rossum2d564fd2001-11-02 19:30:21 +00001# copyright 1997-2001 Just van Rossum, Letterror. just@letterror.com
Just van Rossum40f9b7b1999-01-30 22:39:17 +00002
3import Splash
4
5import FrameWork
Just van Rossum40f9b7b1999-01-30 22:39:17 +00006import Wapplication
7import W
8import os
Jack Jansene0ba0872002-03-29 21:23:47 +00009import sys
Jack Jansen815d2bf2002-01-21 23:00:52 +000010import MacOS
Jack Jansenfd0b00e2003-01-26 22:15:48 +000011import EasyDialogs
Jack Jansene7ee17c2003-02-06 22:32:35 +000012from Carbon import File
13from Carbon import Files
Just van Rossum40f9b7b1999-01-30 22:39:17 +000014
Jack Jansen815d2bf2002-01-21 23:00:52 +000015if MacOS.runtimemodel == 'macho':
Tim Peters182b5ac2004-07-18 06:16:08 +000016 ELLIPSIS = '...'
Jack Jansen815d2bf2002-01-21 23:00:52 +000017else:
Tim Peters182b5ac2004-07-18 06:16:08 +000018 ELLIPSIS = '\xc9'
Just van Rossum40f9b7b1999-01-30 22:39:17 +000019
Just van Rossumbf0a9082002-02-04 12:48:06 +000020def runningOnOSX():
Tim Peters182b5ac2004-07-18 06:16:08 +000021 from gestalt import gestalt
22 gestaltMenuMgrAquaLayoutBit = 1 # menus have the Aqua 1.0 layout
23 gestaltMenuMgrAquaLayoutMask = (1L << gestaltMenuMgrAquaLayoutBit)
24 value = gestalt("menu") & gestaltMenuMgrAquaLayoutMask
25 return not not value
Just van Rossumbf0a9082002-02-04 12:48:06 +000026
Jack Jansene7ee17c2003-02-06 22:32:35 +000027def getmodtime(file):
Tim Peters182b5ac2004-07-18 06:16:08 +000028 file = File.FSRef(file)
29 catinfo, d1, d2, d3 = file.FSGetCatalogInfo(Files.kFSCatInfoContentMod)
30 return catinfo.contentModDate
Just van Rossumbf0a9082002-02-04 12:48:06 +000031
Just van Rossum40f9b7b1999-01-30 22:39:17 +000032class PythonIDE(Wapplication.Application):
Tim Peters182b5ac2004-07-18 06:16:08 +000033
34 def __init__(self):
35 if sys.platform == "darwin":
36 if len(sys.argv) > 1 and sys.argv[1].startswith("-psn"):
37 home = os.getenv("HOME")
38 if home:
39 os.chdir(home)
40 self.preffilepath = os.path.join("Python", "PythonIDE preferences")
41 Wapplication.Application.__init__(self, 'Pide')
42 from Carbon import AE
43 from Carbon import AppleEvents
44
45 AE.AEInstallEventHandler(AppleEvents.kCoreEventClass, AppleEvents.kAEOpenApplication,
46 self.ignoreevent)
47 AE.AEInstallEventHandler(AppleEvents.kCoreEventClass, AppleEvents.kAEReopenApplication,
48 self.ignoreevent)
49 AE.AEInstallEventHandler(AppleEvents.kCoreEventClass, AppleEvents.kAEPrintDocuments,
50 self.ignoreevent)
51 AE.AEInstallEventHandler(AppleEvents.kCoreEventClass, AppleEvents.kAEOpenDocuments,
52 self.opendocsevent)
53 AE.AEInstallEventHandler(AppleEvents.kCoreEventClass, AppleEvents.kAEQuitApplication,
54 self.quitevent)
55 import PyConsole, PyEdit
56 Splash.wait()
57 # With -D option (OSX command line only) keep stderr, for debugging the IDE
58 # itself.
59 debug_stderr = None
60 if len(sys.argv) >= 2 and sys.argv[1] == '-D':
61 debug_stderr = sys.stderr
62 del sys.argv[1]
63 PyConsole.installoutput()
64 PyConsole.installconsole()
65 if debug_stderr:
66 sys.stderr = debug_stderr
67 for path in sys.argv[1:]:
68 if path.startswith("-p"):
69 # process number added by the OS
70 continue
71 self.opendoc(path)
72 self.mainloop()
73
74 def makeusermenus(self):
75 m = Wapplication.Menu(self.menubar, "File")
76 newitem = FrameWork.MenuItem(m, "New", "N", 'new')
77 openitem = FrameWork.MenuItem(m, "Open"+ELLIPSIS, "O", 'open')
78 openbynameitem = FrameWork.MenuItem(m, "Open File by Name"+ELLIPSIS, "D", 'openbyname')
79 self.openrecentmenu = FrameWork.SubMenu(m, "Open Recent")
80 self.makeopenrecentmenu()
81 FrameWork.Separator(m)
82 closeitem = FrameWork.MenuItem(m, "Close", "W", 'close')
83 saveitem = FrameWork.MenuItem(m, "Save", "S", 'save')
84 saveasitem = FrameWork.MenuItem(m, "Save as"+ELLIPSIS, None, 'save_as')
85 FrameWork.Separator(m)
86 saveasappletitem = FrameWork.MenuItem(m, "Save as Applet"+ELLIPSIS, None, 'save_as_applet')
87 FrameWork.Separator(m)
88 instmgritem = FrameWork.MenuItem(m, "Package Manager", None, 'openpackagemanager')
89 gensuiteitem = FrameWork.MenuItem(m, "Generate OSA Suite...", None, 'gensuite')
90 if not runningOnOSX():
91 # On OSX there's a special "magic" quit menu, so we shouldn't add
92 # it to the File menu.
93 FrameWork.Separator(m)
94 quititem = FrameWork.MenuItem(m, "Quit", "Q", 'quit')
95
96 m = Wapplication.Menu(self.menubar, "Edit")
97 undoitem = FrameWork.MenuItem(m, "Undo", 'Z', "undo")
98 FrameWork.Separator(m)
99 cutitem = FrameWork.MenuItem(m, "Cut", 'X', "cut")
100 copyitem = FrameWork.MenuItem(m, "Copy", "C", "copy")
101 pasteitem = FrameWork.MenuItem(m, "Paste", "V", "paste")
102 FrameWork.MenuItem(m, "Clear", None, "clear")
103 FrameWork.Separator(m)
104 selallitem = FrameWork.MenuItem(m, "Select all", "A", "selectall")
105 sellineitem = FrameWork.MenuItem(m, "Select line", "L", "selectline")
106 FrameWork.Separator(m)
107 finditem = FrameWork.MenuItem(m, "Find"+ELLIPSIS, "F", "find")
108 findagainitem = FrameWork.MenuItem(m, "Find again", 'G', "findnext")
109 enterselitem = FrameWork.MenuItem(m, "Enter search string", "E", "entersearchstring")
110 replaceitem = FrameWork.MenuItem(m, "Replace", None, "replace")
111 replacefinditem = FrameWork.MenuItem(m, "Replace & find again", 'T', "replacefind")
112 FrameWork.Separator(m)
113 shiftleftitem = FrameWork.MenuItem(m, "Shift left", "[", "shiftleft")
114 shiftrightitem = FrameWork.MenuItem(m, "Shift right", "]", "shiftright")
115
116 m = Wapplication.Menu(self.menubar, "Python")
117 runitem = FrameWork.MenuItem(m, "Run window", "R", 'run')
118 runselitem = FrameWork.MenuItem(m, "Run selection", None, 'runselection')
119 FrameWork.Separator(m)
120 moditem = FrameWork.MenuItem(m, "Module browser"+ELLIPSIS, "M", self.domenu_modulebrowser)
121 FrameWork.Separator(m)
122 mm = FrameWork.SubMenu(m, "Preferences")
123 FrameWork.MenuItem(mm, "Set Scripts folder"+ELLIPSIS, None, self.do_setscriptsfolder)
124 FrameWork.MenuItem(mm, "Editor default settings"+ELLIPSIS, None, self.do_editorprefs)
125 FrameWork.MenuItem(mm, "Set default window font"+ELLIPSIS, None, self.do_setwindowfont)
126
127 self.openwindowsmenu = Wapplication.Menu(self.menubar, 'Windows')
128 self.makeopenwindowsmenu()
129 self._menustocheck = [closeitem, saveitem, saveasitem, saveasappletitem,
130 undoitem, cutitem, copyitem, pasteitem,
131 selallitem, sellineitem,
132 finditem, findagainitem, enterselitem, replaceitem, replacefinditem,
133 shiftleftitem, shiftrightitem,
134 runitem, runselitem]
135
136 prefs = self.getprefs()
137 try:
138 fsr, d = File.Alias(rawdata=prefs.scriptsfolder).FSResolveAlias(None)
139 self.scriptsfolder = fsr.FSNewAliasMinimal()
140 except:
141 path = os.path.join(os.getcwd(), "Mac", "IDE scripts")
142 if not os.path.exists(path):
143 if sys.platform == "darwin":
144 path = os.path.join(os.getenv("HOME"), "Library", "Python", "IDE-Scripts")
145 else:
146 path = os.path.join(os.getcwd(), "Scripts")
147 if not os.path.exists(path):
148 os.makedirs(path)
149 f = open(os.path.join(path, "Place your scripts here"+ELLIPSIS), "w")
150 f.close()
151 fsr = File.FSRef(path)
152 self.scriptsfolder = fsr.FSNewAliasMinimal()
153 self.scriptsfoldermodtime = getmodtime(fsr)
154 else:
155 self.scriptsfoldermodtime = getmodtime(fsr)
156 prefs.scriptsfolder = self.scriptsfolder.data
157 self._scripts = {}
158 self.scriptsmenu = None
159 self.makescriptsmenu()
160 self.makehelpmenu()
161
162 def quitevent(self, theAppleEvent, theReply):
163 self._quit()
164
165 def suspendresume(self, onoff):
166 if onoff:
167 fsr, changed = self.scriptsfolder.FSResolveAlias(None)
168 modtime = getmodtime(fsr)
169 if self.scriptsfoldermodtime <> modtime or changed:
170 self.scriptsfoldermodtime = modtime
171 W.SetCursor('watch')
172 self.makescriptsmenu()
173
174 def ignoreevent(self, theAppleEvent, theReply):
175 pass
176
177 def opendocsevent(self, theAppleEvent, theReply):
178 W.SetCursor('watch')
179 import aetools
180 parameters, args = aetools.unpackevent(theAppleEvent)
181 docs = parameters['----']
182 if type(docs) <> type([]):
183 docs = [docs]
184 for doc in docs:
185 fsr, a = doc.FSResolveAlias(None)
186 path = fsr.as_pathname()
187 self.opendoc(path)
188
189 def opendoc(self, path):
190 fcreator, ftype = MacOS.GetCreatorAndType(path)
191 if ftype == 'TEXT':
192 self.openscript(path)
193 elif ftype == '\0\0\0\0' and path[-3:] == '.py':
194 self.openscript(path)
195 else:
196 W.Message("Can't open file of type '%s'." % ftype)
197
198 def getabouttext(self):
199 return "About Python IDE"+ELLIPSIS
200
201 def do_about(self, id, item, window, event):
202 Splash.about()
203
204 def do_setscriptsfolder(self, *args):
205 fsr = EasyDialogs.AskFolder(message="Select Scripts Folder",
206 wanted=File.FSRef)
207 if fsr:
208 prefs = self.getprefs()
209 alis = fsr.FSNewAliasMinimal()
210 prefs.scriptsfolder = alis.data
211 self.scriptsfolder = alis
212 self.makescriptsmenu()
213 prefs.save()
214
215 def domenu_modulebrowser(self, *args):
216 W.SetCursor('watch')
217 import ModuleBrowser
218 ModuleBrowser.ModuleBrowser()
219
220 def domenu_open(self, *args):
221 filename = EasyDialogs.AskFileForOpen(typeList=("TEXT",))
222 if filename:
223 self.openscript(filename)
224
225 def domenu_openbyname(self, *args):
226 # Open a file by name. If the clipboard contains a filename
227 # use that as the default.
228 from Carbon import Scrap
229 try:
230 sc = Scrap.GetCurrentScrap()
231 dft = sc.GetScrapFlavorData("TEXT")
232 except Scrap.Error:
233 dft = ""
234 else:
235 if not os.path.exists(dft):
236 dft = ""
237 filename = EasyDialogs.AskString("Open File Named:", default=dft, ok="Open")
238 if filename:
239 self.openscript(filename)
240
241 def domenu_new(self, *args):
242 W.SetCursor('watch')
243 import PyEdit
244 return PyEdit.Editor()
245
246 def makescriptsmenu(self):
247 W.SetCursor('watch')
248 if self._scripts:
249 for id, item in self._scripts.keys():
250 if self.menubar.menus.has_key(id):
251 m = self.menubar.menus[id]
252 m.delete()
253 self._scripts = {}
254 if self.scriptsmenu:
255 if hasattr(self.scriptsmenu, 'id') and self.menubar.menus.has_key(self.scriptsmenu.id):
256 self.scriptsmenu.delete()
257 self.scriptsmenu = FrameWork.Menu(self.menubar, "Scripts")
258 #FrameWork.MenuItem(self.scriptsmenu, "New script", None, self.domenu_new)
259 #self.scriptsmenu.addseparator()
260 fsr, d1 = self.scriptsfolder.FSResolveAlias(None)
261 self.scriptswalk(fsr.as_pathname(), self.scriptsmenu)
262
263 def makeopenwindowsmenu(self):
264 for i in range(len(self.openwindowsmenu.items)):
265 self.openwindowsmenu.menu.DeleteMenuItem(1)
266 self.openwindowsmenu.items = []
267 windows = []
268 self._openwindows = {}
269 for window in self._windows.keys():
270 title = window.GetWTitle()
271 if not title:
272 title = "<no title>"
273 windows.append((title, window))
274 windows.sort()
275 for title, window in windows:
276 if title == "Python Interactive": # ugly but useful hack by Joe Strout
277 shortcut = '0'
278 else:
279 shortcut = None
280 item = FrameWork.MenuItem(self.openwindowsmenu, title, shortcut, callback = self.domenu_openwindows)
281 self._openwindows[item.item] = window
282 self._openwindowscheckmark = 0
283 self.checkopenwindowsmenu()
284
285 def makeopenrecentmenu(self):
286 for i in range(len(self.openrecentmenu.items)):
287 self.openrecentmenu.menu.DeleteMenuItem(1)
288 self.openrecentmenu.items = []
289 prefs = self.getprefs()
290 filelist = prefs.recentfiles
291 if not filelist:
292 self.openrecentmenu.enable(0)
293 return
294 self.openrecentmenu.enable(1)
295 for filename in filelist:
296 item = FrameWork.MenuItem(self.openrecentmenu, filename, None, callback = self.domenu_openrecent)
297
298 def addrecentfile(self, file):
299 prefs = self.getprefs()
300 filelist = prefs.recentfiles
301 if not filelist:
302 filelist = []
303
304 if file in filelist:
305 if file == filelist[0]:
306 return
307 filelist.remove(file)
308 filelist.insert(0, file)
309 filelist = filelist[:10]
310 prefs.recentfiles = filelist
311 prefs.save()
312 self.makeopenrecentmenu()
313
314 def domenu_openwindows(self, id, item, window, event):
315 w = self._openwindows[item]
316 w.ShowWindow()
317 w.SelectWindow()
318
319 def domenu_openrecent(self, id, item, window, event):
320 prefs = self.getprefs()
321 filelist = prefs.recentfiles
322 if not filelist:
323 filelist = []
324 item = item - 1
325 filename = filelist[item]
326 self.openscript(filename)
327
328 def domenu_quit(self):
329 self._quit()
330
331 def domenu_save(self, *args):
332 print "Save"
333
334 def _quit(self):
335 import PyConsole, PyEdit
336 for window in self._windows.values():
337 try:
338 rv = window.close() # ignore any errors while quitting
339 except:
340 rv = 0 # (otherwise, we can get stuck!)
341 if rv and rv > 0:
342 return
343 try:
344 PyConsole.console.writeprefs()
345 PyConsole.output.writeprefs()
346 PyEdit.searchengine.writeprefs()
347 except:
348 # Write to __stderr__ so the msg end up in Console.app and has
349 # at least _some_ chance of getting read...
350 # But: this is a workaround for way more serious problems with
351 # the Python 2.2 Jaguar addon.
352 sys.__stderr__.write("*** PythonIDE: Can't write preferences ***\n")
353 self.quitting = 1
354
355 def domenu_openpackagemanager(self):
356 import PackageManager
357 PackageManager.PackageBrowser()
358
359 def domenu_gensuite(self):
360 import gensuitemodule
361 gensuitemodule.main_interactive()
362
363 def makehelpmenu(self):
364 hashelp, hasdocs = self.installdocumentation()
365 self.helpmenu = m = self.gethelpmenu()
366 helpitem = FrameWork.MenuItem(m, "MacPython Help", None, self.domenu_localhelp)
367 helpitem.enable(hashelp)
368 docitem = FrameWork.MenuItem(m, "Python Documentation", None, self.domenu_localdocs)
369 docitem.enable(hasdocs)
370 finditem = FrameWork.MenuItem(m, "Lookup in Python Documentation", None, 'lookuppython')
371 finditem.enable(hasdocs)
372 if runningOnOSX():
373 FrameWork.Separator(m)
374 doc2item = FrameWork.MenuItem(m, "Apple Developer Documentation", None, self.domenu_appledocs)
375 find2item = FrameWork.MenuItem(m, "Lookup in Carbon Documentation", None, 'lookupcarbon')
376 FrameWork.Separator(m)
377 webitem = FrameWork.MenuItem(m, "Python Documentation on the Web", None, self.domenu_webdocs)
378 web2item = FrameWork.MenuItem(m, "Python on the Web", None, self.domenu_webpython)
379 web3item = FrameWork.MenuItem(m, "MacPython on the Web", None, self.domenu_webmacpython)
380
381 def domenu_localdocs(self, *args):
382 from Carbon import AH
383 AH.AHGotoPage("Python Documentation", None, None)
384
385 def domenu_localhelp(self, *args):
386 from Carbon import AH
387 AH.AHGotoPage("MacPython Help", None, None)
388
389 def domenu_appledocs(self, *args):
390 from Carbon import AH, AppleHelp
391 try:
392 AH.AHGotoMainTOC(AppleHelp.kAHTOCTypeDeveloper)
393 except AH.Error, arg:
394 if arg[0] == -50:
395 W.Message("Developer documentation not installed")
396 else:
397 W.Message("AppleHelp Error: %r" % (arg,))
398
399 def domenu_lookuppython(self, *args):
400 from Carbon import AH
401 searchstring = self._getsearchstring()
402 if not searchstring:
403 return
404 try:
405 AH.AHSearch("Python Documentation", searchstring)
406 except AH.Error, arg:
407 W.Message("AppleHelp Error: %r" % (arg,))
408
409 def domenu_lookupcarbon(self, *args):
410 from Carbon import AH
411 searchstring = self._getsearchstring()
412 if not searchstring:
413 return
414 try:
415 AH.AHSearch("Carbon", searchstring)
416 except AH.Error, arg:
417 W.Message("AppleHelp Error: %r" % (arg,))
418
419 def _getsearchstring(self):
420 # First we get the frontmost window
421 front = self.getfrontwindow()
422 if front and hasattr(front, 'getselectedtext'):
423 text = front.getselectedtext()
424 if text:
425 return text
426 # This is a cop-out. We should have disabled the menus
427 # if there is no selection, but the can_ methods only seem
428 # to work for Windows. Or not for the Help menu, maybe?
429 text = EasyDialogs.AskString("Search documentation for", ok="Search")
430 return text
431
432 def domenu_webdocs(self, *args):
433 import webbrowser
434 major, minor, micro, state, nano = sys.version_info
435 if state in ('alpha', 'beta'):
436 docversion = 'dev/doc/devel'
437 elif micro == 0:
438 docversion = 'doc/%d.%d' % (major, minor)
439 else:
440 docversion = 'doc/%d.%d.%d' % (major, minor, micro)
441 webbrowser.open("http://www.python.org/%s" % docversion)
442
443 def domenu_webpython(self, *args):
444 import webbrowser
445 webbrowser.open("http://www.python.org/")
446
447 def domenu_webmacpython(self, *args):
448 import webbrowser
449 webbrowser.open("http://www.cwi.nl/~jack/macpython.html")
450
451 def installdocumentation(self):
452 # This is rather much of a hack. Someone has to tell the Help Viewer
453 # about the Python documentation, so why not us. The documentation
454 # is located in the framework, but there's a symlink in Python.app.
455 # And as AHRegisterHelpBook wants a bundle (with the right bits in
456 # the plist file) we refer it to Python.app
457 #
458 # To make matters worse we have to look in two places: first in the IDE
459 # itself, then in the Python application inside the framework.
460 has_help = False
461 has_doc = False
462 ide_path_components = sys.argv[0].split("/")
463 if ide_path_components[-3:] == ["Contents", "Resources", "PythonIDE.py"]:
464 ide_app = "/".join(ide_path_components[:-3])
465 help_source = os.path.join(ide_app, 'Contents/Resources/English.lproj/Documentation')
466 doc_source = os.path.join(ide_app, 'Contents/Resources/English.lproj/PythonDocumentation')
467 has_help = os.path.isdir(help_source)
468 has_doc = os.path.isdir(doc_source)
469 if has_help or has_doc:
470 try:
471 from Carbon import AH
472 AH.AHRegisterHelpBook(ide_app)
473 except (ImportError, MacOS.Error), arg:
474 pass # W.Message("Cannot register Python Documentation: %s" % str(arg))
475 python_app = os.path.join(sys.prefix, 'Resources/Python.app')
476 if not has_help:
477 help_source = os.path.join(python_app, 'Contents/Resources/English.lproj/Documentation')
478 has_help = os.path.isdir(help_source)
479 if not has_doc:
480 doc_source = os.path.join(python_app, 'Contents/Resources/English.lproj/PythonDocumentation')
481 has_doc = os.path.isdir(doc_source)
482 if has_help or has_doc:
483 try:
484 from Carbon import AH
485 AH.AHRegisterHelpBook(python_app)
486 except (ImportError, MacOS.Error), arg:
487 pass # W.Message("Cannot register Python Documentation: %s" % str(arg))
488 return has_help, has_doc