(root)/
Python-3.12.0/
Lib/
idlelib/
macosx.py
       1  """
       2  A number of functions that enhance IDLE on macOS.
       3  """
       4  from os.path import expanduser
       5  import plistlib
       6  from sys import platform  # Used in _init_tk_type, changed by test.
       7  
       8  import 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  
      16  def _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':
      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
      28          if testing:
      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()
      37          ws = root.tk.call('tk', 'windowingsystem')
      38          if 'x11' in ws:
      39              _tk_type = "xquartz"
      40          elif 'aqua' not in ws:
      41              _tk_type = "other"
      42          elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
      43              _tk_type = "cocoa"
      44          else:
      45              _tk_type = "carbon"
      46          root.destroy()
      47      else:
      48          _tk_type = "other"
      49      return
      50  
      51  def isAquaTk():
      52      """
      53      Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
      54      """
      55      if not _tk_type:
      56          _init_tk_type()
      57      return _tk_type == "cocoa" or _tk_type == "carbon"
      58  
      59  def 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:
      65          _init_tk_type()
      66      return _tk_type == "carbon"
      67  
      68  def isCocoaTk():
      69      """
      70      Returns True if IDLE is using a Cocoa Aqua Tk.
      71      """
      72      if not _tk_type:
      73          _init_tk_type()
      74      return _tk_type == "cocoa"
      75  
      76  def isXQuartz():
      77      """
      78      Returns True if IDLE is using an OS X X11 Tk.
      79      """
      80      if not _tk_type:
      81          _init_tk_type()
      82      return _tk_type == "xquartz"
      83  
      84  
      85  def 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  
     100  def 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  
     120  def 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  
     134  def 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  
     141  def 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  
     243  def 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  
     253  def 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  
     276  if __name__ == '__main__':
     277      from unittest import main
     278      main('idlelib.idle_test.test_macosx', verbosity=2)