1"""
2A number of function that enhance IDLE on MacOSX when it used as a normal
3GUI application (as opposed to an X11 application).
4"""
5import sys
6import Tkinter
7from os import path
8
9
10_appbundle = None
11
12def runningAsOSXApp():
13    """
14    Returns True if Python is running from within an app on OSX.
15    If so, assume that Python was built with Aqua Tcl/Tk rather than
16    X11 Tcl/Tk.
17    """
18    global _appbundle
19    if _appbundle is None:
20        _appbundle = (sys.platform == 'darwin' and '.app' in sys.executable)
21    return _appbundle
22
23_carbonaquatk = None
24
25def isCarbonAquaTk(root):
26    """
27    Returns True if IDLE is using a Carbon Aqua Tk (instead of the
28    newer Cocoa Aqua Tk).
29    """
30    global _carbonaquatk
31    if _carbonaquatk is None:
32        _carbonaquatk = (runningAsOSXApp() and
33                         'aqua' in root.tk.call('tk', 'windowingsystem') and
34                         'AppKit' not in root.tk.call('winfo', 'server', '.'))
35    return _carbonaquatk
36
37def tkVersionWarning(root):
38    """
39    Returns a string warning message if the Tk version in use appears to
40    be one known to cause problems with IDLE.
41    1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable.
42    2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but
43        can still crash unexpectedly.
44    """
45
46    if (runningAsOSXApp() and
47            ('AppKit' in root.tk.call('winfo', 'server', '.')) ):
48        patchlevel = root.tk.call('info', 'patchlevel')
49        if patchlevel not in ('8.5.7', '8.5.9'):
50            return False
51        return (r"WARNING: The version of Tcl/Tk ({0}) in use may"
52                r" be unstable.\n"
53                r"Visit http://www.python.org/download/mac/tcltk/"
54                r" for current information.".format(patchlevel))
55    else:
56        return False
57
58def addOpenEventSupport(root, flist):
59    """
60    This ensures that the application will respond to open AppleEvents, which
61    makes is feasible to use IDLE as the default application for python files.
62    """
63    def doOpenFile(*args):
64        for fn in args:
65            flist.open(fn)
66
67    # The command below is a hook in aquatk that is called whenever the app
68    # receives a file open event. The callback can have multiple arguments,
69    # one for every file that should be opened.
70    root.createcommand("::tk::mac::OpenDocument", doOpenFile)
71
72def hideTkConsole(root):
73    try:
74        root.tk.call('console', 'hide')
75    except Tkinter.TclError:
76        # Some versions of the Tk framework don't have a console object
77        pass
78
79def overrideRootMenu(root, flist):
80    """
81    Replace the Tk root menu by something that's more appropriate for
82    IDLE.
83    """
84    # The menu that is attached to the Tk root (".") is also used by AquaTk for
85    # all windows that don't specify a menu of their own. The default menubar
86    # contains a number of menus, none of which are appropriate for IDLE. The
87    # Most annoying of those is an 'About Tck/Tk...' menu in the application
88    # menu.
89    #
90    # This function replaces the default menubar by a mostly empty one, it
91    # should only contain the correct application menu and the window menu.
92    #
93    # Due to a (mis-)feature of TkAqua the user will also see an empty Help
94    # menu.
95    from Tkinter import Menu, Text, Text
96    from idlelib.EditorWindow import prepstr, get_accelerator
97    from idlelib import Bindings
98    from idlelib import WindowList
99    from idlelib.MultiCall import MultiCallCreator
100
101    menubar = Menu(root)
102    root.configure(menu=menubar)
103    menudict = {}
104
105    menudict['windows'] = menu = Menu(menubar, name='windows')
106    menubar.add_cascade(label='Window', menu=menu, underline=0)
107
108    def postwindowsmenu(menu=menu):
109        end = menu.index('end')
110        if end is None:
111            end = -1
112
113        if end > 0:
114            menu.delete(0, end)
115        WindowList.add_windows_to_menu(menu)
116    WindowList.register_callback(postwindowsmenu)
117
118    def about_dialog(event=None):
119        from idlelib import aboutDialog
120        aboutDialog.AboutDialog(root, 'About IDLE')
121
122    def config_dialog(event=None):
123        from idlelib import configDialog
124        root.instance_dict = flist.inversedict
125        configDialog.ConfigDialog(root, 'Settings')
126
127    def help_dialog(event=None):
128        from idlelib import textView
129        fn = path.join(path.abspath(path.dirname(__file__)), 'help.txt')
130        textView.view_file(root, 'Help', fn)
131
132    root.bind('<<about-idle>>', about_dialog)
133    root.bind('<<open-config-dialog>>', config_dialog)
134    root.createcommand('::tk::mac::ShowPreferences', config_dialog)
135    if flist:
136        root.bind('<<close-all-windows>>', flist.close_all_callback)
137
138        # The binding above doesn't reliably work on all versions of Tk
139        # on MacOSX. Adding command definition below does seem to do the
140        # right thing for now.
141        root.createcommand('exit', flist.close_all_callback)
142
143    if isCarbonAquaTk(root):
144        # for Carbon AquaTk, replace the default Tk apple menu
145        menudict['application'] = menu = Menu(menubar, name='apple')
146        menubar.add_cascade(label='IDLE', menu=menu)
147        Bindings.menudefs.insert(0,
148            ('application', [
149                ('About IDLE', '<<about-idle>>'),
150                    None,
151                ]))
152        tkversion = root.tk.eval('info patchlevel')
153        if tuple(map(int, tkversion.split('.'))) < (8, 4, 14):
154            # for earlier AquaTk versions, supply a Preferences menu item
155            Bindings.menudefs[0][1].append(
156                    ('_Preferences....', '<<open-config-dialog>>'),
157                )
158    else:
159        # assume Cocoa AquaTk
160        # replace default About dialog with About IDLE one
161        root.createcommand('tkAboutDialog', about_dialog)
162        # replace default "Help" item in Help menu
163        root.createcommand('::tk::mac::ShowHelp', help_dialog)
164        # remove redundant "IDLE Help" from menu
165        del Bindings.menudefs[-1][1][0]
166
167def setupApp(root, flist):
168    """
169    Perform setup for the OSX application bundle.
170    """
171    if not runningAsOSXApp(): return
172
173    hideTkConsole(root)
174    overrideRootMenu(root, flist)
175    addOpenEventSupport(root, flist)
176