patch-nad0014 missing/extra menu options on OS X IDLE.app and bin/idle **THIS IS THE trunk/2.6 VERSION OF THE PATCH** IDLE is supposed to have various menu customizations when running on OS X. But currently they do not all work and the menus vary if IDLE is launched via IDLE.app versus via command line bin/idle. The most noticeable issue is the lack of a Preferences menu item in IDLE.app to access the IDLE configuration settings. ANALYSIS There are inconsistent OS X execution checks scattered throughout idlelib. Also, the bootstrap mechanism for IDLE.app causes sys.executable to be set incorrectly. Most importantly, idlemain imports idlelib before it has fixed up the execution environment. This can cause the menu fixup in idlelib.Bindings to be skipped. SOLUTION Change idlemain to set up the execution environment consistently and defer idlelib imports until it has done so. Modify several OS X checks in idlelib for consistency. APPLIES py3k, 3.0 -> patch-nad0014-py3k-30.txt trunk, 2.6 -> patch-nad0014-trunk-26.txt DELETE Mac/IDLE/idlemain.py (py3k and 3.0 only!) NOTE In 3.x idlemain.py was copied down into the app bundle: 2.x -> Mac/IDLE/idlemain.py 3.x -> Mac/IDLE/IDLE.app/Contents/Resources/idlemain.py 3.x -> Mac/IDLE/idlemain.py -> unused / delete so there are two versions of this patch. However, the 2.x location was not deleted in 3.x svn so there are two identical files in 3.x prior to this patch. diff -r 610fbb84993b Lib/idlelib/EditorWindow.py --- Lib/idlelib/EditorWindow.py Mon Feb 09 12:10:27 2009 -0800 +++ Lib/idlelib/EditorWindow.py Mon Feb 09 12:12:46 2009 -0800 @@ -368,7 +368,7 @@ menudict[name] = menu = Menu(mbar, name=name) mbar.add_cascade(label=label, menu=menu, underline=underline) - if sys.platform == 'darwin' and '.framework' in sys.executable: + if sys.platform == 'darwin' and '.app' in sys.executable: # Insert the application menu menudict['application'] = menu = Menu(mbar, name='apple') mbar.add_cascade(label='IDLE', menu=menu) diff -r 610fbb84993b Lib/idlelib/MultiCall.py --- Lib/idlelib/MultiCall.py Mon Feb 09 12:10:27 2009 -0800 +++ Lib/idlelib/MultiCall.py Mon Feb 09 12:12:46 2009 -0800 @@ -45,7 +45,7 @@ MC_OPTION = 1<<6; MC_COMMAND = 1<<7 # define the list of modifiers, to be used in complex event types. -if sys.platform == "darwin" and sys.executable.count(".app"): +if sys.platform == "darwin" and '.app' in sys.executable: _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",)) _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND) else: diff -r 610fbb84993b Lib/idlelib/keybindingDialog.py --- Lib/idlelib/keybindingDialog.py Mon Feb 09 12:10:27 2009 -0800 +++ Lib/idlelib/keybindingDialog.py Mon Feb 09 12:12:46 2009 -0800 @@ -133,7 +133,7 @@ config-keys.def must use the same ordering. """ import sys - if sys.platform == 'darwin' and sys.argv[0].count('.app'): + if sys.platform == 'darwin' and '.app' in sys.executable: self.modifiers = ['Shift', 'Control', 'Option', 'Command'] else: self.modifiers = ['Control', 'Alt', 'Shift'] diff -r 610fbb84993b Lib/idlelib/macosxSupport.py --- Lib/idlelib/macosxSupport.py Mon Feb 09 12:10:27 2009 -0800 +++ Lib/idlelib/macosxSupport.py Mon Feb 09 12:12:46 2009 -0800 @@ -6,8 +6,12 @@ import Tkinter def runningAsOSXApp(): - """ Returns True iff running from the IDLE.app bundle on OSX """ - return (sys.platform == 'darwin' and 'IDLE.app' in sys.argv[0]) + """ + Returns True if Python is running from within an app on OSX. + If so, assume that Python was built with Aqua Tcl/Tk rather than + X11 Tck/Tk. + """ + return (sys.platform == 'darwin' and '.app' in sys.executable) def addOpenEventSupport(root, flist): """ diff -r 610fbb84993b Mac/IDLE/idlemain.py --- Mac/IDLE/idlemain.py Mon Feb 09 12:10:27 2009 -0800 +++ Mac/IDLE/idlemain.py Mon Feb 09 12:12:46 2009 -0800 @@ -3,8 +3,6 @@ """ import sys, os -from idlelib.PyShell import main - # Change the current directory the user's home directory, that way we'll get # a more useful default location in the open/save dialogs. os.chdir(os.path.expanduser('~/Documents')) @@ -13,10 +11,54 @@ # Make sure sys.executable points to the python interpreter inside the # framework, instead of at the helper executable inside the application # bundle (the latter works, but doesn't allow access to the window server) -if sys.executable.endswith('-32'): - sys.executable = os.path.join(sys.prefix, 'bin', 'python-32') -else: - sys.executable = os.path.join(sys.prefix, 'bin', 'python') +# +# .../IDLE.app/ +# Contents/ +# MacOS/ +# IDLE (a python script) +# Python{-32} (symlink) +# Resources/ +# idlemain.py (this module) +# ... +# +# ../IDLE.app/Contents/MacOS/Python{-32} is symlinked to +# ..Library/Frameworks/Python.framework/Versions/m.n +# /Resources/Python.app/Contents/MacOS/Python{-32} +# which is the Python interpreter executable +# +# The flow of control is as follows: +# 1. IDLE.app is launched which starts python running the IDLE script +# 2. IDLE script exports +# PYTHONEXECUTABLE = .../IDLE.app/Contents/MacOS/Python{-32} +# (the symlink to the framework python) +# 3. IDLE script alters sys.argv and uses os.execve to replace itself with +# idlemain.py running under the symlinked python. +# This is the magic step. +# 4. During interpreter initialization, because PYTHONEXECUTABLE is defined, +# sys.executable may get set to an unuseful value. +# +# (Note that the IDLE script and the setting of PYTHONEXECUTABLE is +# generated automatically by bundlebuilder in the Python 2.x build. +# Also, IDLE invoked via command line, i.e. bin/idle, bypasses all of +# this.) +# +# Now fix up the execution environment before importing idlelib. + +# Reset sys.executable to its normal value, the actual path of +# the interpreter in the framework, by following the symlink +# exported in PYTHONEXECUTABLE. +pyex = os.environ['PYTHONEXECUTABLE'] +sys.executable = os.path.join(os.path.dirname(pyex), os.readlink(pyex)) + +# Remove any sys.path entries for the Resources dir in the IDLE.app bundle. +p = pyex.partition('.app') +if p[2].startswith('/Contents/MacOS/Python'): + sys.path = [value for value in sys.path if + value.partition('.app') != (p[0], p[1], '/Contents/Resources')] + +# Unexport PYTHONEXECUTABLE so that the other Python processes started +# by IDLE have a normal sys.executable. +del os.environ['PYTHONEXECUTABLE'] # Look for the -psn argument that the launcher adds and remove it, it will # only confuse the IDLE startup code. @@ -25,6 +67,7 @@ del sys.argv[idx] break -#argvemulator.ArgvCollector().mainloop() +# Now it is safe to import idlelib. +from idlelib.PyShell import main if __name__ == '__main__': main()