Home | History | Annotate | Download | only in idlelib
      1 """
      2 A number of functions that enhance IDLE on Mac OSX.
      3 """
      4 from sys import platform  # Used in _init_tk_type, changed by test.
      5 import warnings
      6 
      7 import tkinter
      8 
      9 
     10 ## Define functions that query the Mac graphics type.
     11 ## _tk_type and its initializer are private to this section.
     12 
     13 _tk_type = None
     14 
     15 def _init_tk_type():
     16     """
     17     Initializes OS X Tk variant values for
     18     isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz().
     19     """
     20     global _tk_type
     21     if platform == 'darwin':
     22         root = tkinter.Tk()
     23         ws = root.tk.call('tk', 'windowingsystem')
     24         if 'x11' in ws:
     25             _tk_type = "xquartz"
     26         elif 'aqua' not in ws:
     27             _tk_type = "other"
     28         elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
     29             _tk_type = "cocoa"
     30         else:
     31             _tk_type = "carbon"
     32         root.destroy()
     33     else:
     34         _tk_type = "other"
     35 
     36 def isAquaTk():
     37     """
     38     Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
     39     """
     40     if not _tk_type:
     41         _init_tk_type()
     42     return _tk_type == "cocoa" or _tk_type == "carbon"
     43 
     44 def isCarbonTk():
     45     """
     46     Returns True if IDLE is using a Carbon Aqua Tk (instead of the
     47     newer Cocoa Aqua Tk).
     48     """
     49     if not _tk_type:
     50         _init_tk_type()
     51     return _tk_type == "carbon"
     52 
     53 def isCocoaTk():
     54     """
     55     Returns True if IDLE is using a Cocoa Aqua Tk.
     56     """
     57     if not _tk_type:
     58         _init_tk_type()
     59     return _tk_type == "cocoa"
     60 
     61 def isXQuartz():
     62     """
     63     Returns True if IDLE is using an OS X X11 Tk.
     64     """
     65     if not _tk_type:
     66         _init_tk_type()
     67     return _tk_type == "xquartz"
     68 
     69 
     70 def tkVersionWarning(root):
     71     """
     72     Returns a string warning message if the Tk version in use appears to
     73     be one known to cause problems with IDLE.
     74     1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable.
     75     2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but
     76         can still crash unexpectedly.
     77     """
     78 
     79     if isCocoaTk():
     80         patchlevel = root.tk.call('info', 'patchlevel')
     81         if patchlevel not in ('8.5.7', '8.5.9'):
     82             return False
     83         return (r"WARNING: The version of Tcl/Tk ({0}) in use may"
     84                 r" be unstable.\n"
     85                 r"Visit http://www.python.org/download/mac/tcltk/"
     86                 r" for current information.".format(patchlevel))
     87     else:
     88         return False
     89 
     90 
     91 ## Fix the menu and related functions.
     92 
     93 def addOpenEventSupport(root, flist):
     94     """
     95     This ensures that the application will respond to open AppleEvents, which
     96     makes is feasible to use IDLE as the default application for python files.
     97     """
     98     def doOpenFile(*args):
     99         for fn in args:
    100             flist.open(fn)
    101 
    102     # The command below is a hook in aquatk that is called whenever the app
    103     # receives a file open event. The callback can have multiple arguments,
    104     # one for every file that should be opened.
    105     root.createcommand("::tk::mac::OpenDocument", doOpenFile)
    106 
    107 def hideTkConsole(root):
    108     try:
    109         root.tk.call('console', 'hide')
    110     except tkinter.TclError:
    111         # Some versions of the Tk framework don't have a console object
    112         pass
    113 
    114 def overrideRootMenu(root, flist):
    115     """
    116     Replace the Tk root menu by something that is more appropriate for
    117     IDLE with an Aqua Tk.
    118     """
    119     # The menu that is attached to the Tk root (".") is also used by AquaTk for
    120     # all windows that don't specify a menu of their own. The default menubar
    121     # contains a number of menus, none of which are appropriate for IDLE. The
    122     # Most annoying of those is an 'About Tck/Tk...' menu in the application
    123     # menu.
    124     #
    125     # This function replaces the default menubar by a mostly empty one, it
    126     # should only contain the correct application menu and the window menu.
    127     #
    128     # Due to a (mis-)feature of TkAqua the user will also see an empty Help
    129     # menu.
    130     from tkinter import Menu
    131     from idlelib import mainmenu
    132     from idlelib import windows
    133 
    134     closeItem = mainmenu.menudefs[0][1][-2]
    135 
    136     # Remove the last 3 items of the file menu: a separator, close window and
    137     # quit. Close window will be reinserted just above the save item, where
    138     # it should be according to the HIG. Quit is in the application menu.
    139     del mainmenu.menudefs[0][1][-3:]
    140     mainmenu.menudefs[0][1].insert(6, closeItem)
    141 
    142     # Remove the 'About' entry from the help menu, it is in the application
    143     # menu
    144     del mainmenu.menudefs[-1][1][0:2]
    145     # Remove the 'Configure Idle' entry from the options menu, it is in the
    146     # application menu as 'Preferences'
    147     del mainmenu.menudefs[-2][1][0]
    148     menubar = Menu(root)
    149     root.configure(menu=menubar)
    150     menudict = {}
    151 
    152     menudict['windows'] = menu = Menu(menubar, name='windows', tearoff=0)
    153     menubar.add_cascade(label='Window', menu=menu, underline=0)
    154 
    155     def postwindowsmenu(menu=menu):
    156         end = menu.index('end')
    157         if end is None:
    158             end = -1
    159 
    160         if end > 0:
    161             menu.delete(0, end)
    162         windows.add_windows_to_menu(menu)
    163     windows.register_callback(postwindowsmenu)
    164 
    165     def about_dialog(event=None):
    166         "Handle Help 'About IDLE' event."
    167         # Synchronize with editor.EditorWindow.about_dialog.
    168         from idlelib import help_about
    169         help_about.AboutDialog(root, 'About IDLE')
    170 
    171     def config_dialog(event=None):
    172         "Handle Options 'Configure IDLE' event."
    173         # Synchronize with editor.EditorWindow.config_dialog.
    174         from idlelib import configdialog
    175 
    176         # Ensure that the root object has an instance_dict attribute,
    177         # mirrors code in EditorWindow (although that sets the attribute
    178         # on an EditorWindow instance that is then passed as the first
    179         # argument to ConfigDialog)
    180         root.instance_dict = flist.inversedict
    181         configdialog.ConfigDialog(root, 'Settings')
    182 
    183     def help_dialog(event=None):
    184         "Handle Help 'IDLE Help' event."
    185         # Synchronize with editor.EditorWindow.help_dialog.
    186         from idlelib import help
    187         help.show_idlehelp(root)
    188 
    189     root.bind('<<about-idle>>', about_dialog)
    190     root.bind('<<open-config-dialog>>', config_dialog)
    191     root.createcommand('::tk::mac::ShowPreferences', config_dialog)
    192     if flist:
    193         root.bind('<<close-all-windows>>', flist.close_all_callback)
    194 
    195         # The binding above doesn't reliably work on all versions of Tk
    196         # on MacOSX. Adding command definition below does seem to do the
    197         # right thing for now.
    198         root.createcommand('exit', flist.close_all_callback)
    199 
    200     if isCarbonTk():
    201         # for Carbon AquaTk, replace the default Tk apple menu
    202         menudict['application'] = menu = Menu(menubar, name='apple',
    203                                               tearoff=0)
    204         menubar.add_cascade(label='IDLE', menu=menu)
    205         mainmenu.menudefs.insert(0,
    206             ('application', [
    207                 ('About IDLE', '<<about-idle>>'),
    208                     None,
    209                 ]))
    210     if isCocoaTk():
    211         # replace default About dialog with About IDLE one
    212         root.createcommand('tkAboutDialog', about_dialog)
    213         # replace default "Help" item in Help menu
    214         root.createcommand('::tk::mac::ShowHelp', help_dialog)
    215         # remove redundant "IDLE Help" from menu
    216         del mainmenu.menudefs[-1][1][0]
    217 
    218 def fixb2context(root):
    219     '''Removed bad AquaTk Button-2 (right) and Paste bindings.
    220 
    221     They prevent context menu access and seem to be gone in AquaTk8.6.
    222     See issue #24801.
    223     '''
    224     root.unbind_class('Text', '<B2>')
    225     root.unbind_class('Text', '<B2-Motion>')
    226     root.unbind_class('Text', '<<PasteSelection>>')
    227 
    228 def setupApp(root, flist):
    229     """
    230     Perform initial OS X customizations if needed.
    231     Called from pyshell.main() after initial calls to Tk()
    232 
    233     There are currently three major versions of Tk in use on OS X:
    234         1. Aqua Cocoa Tk (native default since OS X 10.6)
    235         2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
    236         3. X11 (supported by some third-party distributors, deprecated)
    237     There are various differences among the three that affect IDLE
    238     behavior, primarily with menus, mouse key events, and accelerators.
    239     Some one-time customizations are performed here.
    240     Others are dynamically tested throughout idlelib by calls to the
    241     isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
    242     are initialized here as well.
    243     """
    244     if isAquaTk():
    245         hideTkConsole(root)
    246         overrideRootMenu(root, flist)
    247         addOpenEventSupport(root, flist)
    248         fixb2context(root)
    249 
    250 
    251 if __name__ == '__main__':
    252     from unittest import main
    253     main('idlelib.idle_test.test_macosx', verbosity=2)
    254