(root)/
Python-3.11.7/
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      menudict = {}
     178  
     179      menudict['window'] = menu = Menu(menubar, name='window', tearoff=0)
     180      menubar.add_cascade(label='Window', menu=menu, underline=0)
     181  
     182      def postwindowsmenu(menu=menu):
     183          end = menu.index('end')
     184          if end is None:
     185              end = -1
     186  
     187          if end > 0:
     188              menu.delete(0, end)
     189          window.add_windows_to_menu(menu)
     190      window.register_callback(postwindowsmenu)
     191  
     192      def about_dialog(event=None):
     193          "Handle Help 'About IDLE' event."
     194          # Synchronize with editor.EditorWindow.about_dialog.
     195          from idlelib import help_about
     196          help_about.AboutDialog(root)
     197  
     198      def config_dialog(event=None):
     199          "Handle Options 'Configure IDLE' event."
     200          # Synchronize with editor.EditorWindow.config_dialog.
     201          from idlelib import configdialog
     202  
     203          # Ensure that the root object has an instance_dict attribute,
     204          # mirrors code in EditorWindow (although that sets the attribute
     205          # on an EditorWindow instance that is then passed as the first
     206          # argument to ConfigDialog)
     207          root.instance_dict = flist.inversedict
     208          configdialog.ConfigDialog(root, 'Settings')
     209  
     210      def help_dialog(event=None):
     211          "Handle Help 'IDLE Help' event."
     212          # Synchronize with editor.EditorWindow.help_dialog.
     213          from idlelib import help
     214          help.show_idlehelp(root)
     215  
     216      root.bind('<<about-idle>>', about_dialog)
     217      root.bind('<<open-config-dialog>>', config_dialog)
     218      root.createcommand('::tk::mac::ShowPreferences', config_dialog)
     219      if flist:
     220          root.bind('<<close-all-windows>>', flist.close_all_callback)
     221  
     222          # The binding above doesn't reliably work on all versions of Tk
     223          # on macOS. Adding command definition below does seem to do the
     224          # right thing for now.
     225          root.createcommand('exit', flist.close_all_callback)
     226  
     227      if isCarbonTk():
     228          # for Carbon AquaTk, replace the default Tk apple menu
     229          menudict['application'] = menu = Menu(menubar, name='apple',
     230                                                tearoff=0)
     231          menubar.add_cascade(label='IDLE', menu=menu)
     232          mainmenu.menudefs.insert(0,
     233              ('application', [
     234                  ('About IDLE', '<<about-idle>>'),
     235                      None,
     236                  ]))
     237      if isCocoaTk():
     238          # replace default About dialog with About IDLE one
     239          root.createcommand('tkAboutDialog', about_dialog)
     240          # replace default "Help" item in Help menu
     241          root.createcommand('::tk::mac::ShowHelp', help_dialog)
     242          # remove redundant "IDLE Help" from menu
     243          del mainmenu.menudefs[-1][1][0]
     244  
     245  def fixb2context(root):
     246      '''Removed bad AquaTk Button-2 (right) and Paste bindings.
     247  
     248      They prevent context menu access and seem to be gone in AquaTk8.6.
     249      See issue #24801.
     250      '''
     251      root.unbind_class('Text', '<B2>')
     252      root.unbind_class('Text', '<B2-Motion>')
     253      root.unbind_class('Text', '<<PasteSelection>>')
     254  
     255  def setupApp(root, flist):
     256      """
     257      Perform initial OS X customizations if needed.
     258      Called from pyshell.main() after initial calls to Tk()
     259  
     260      There are currently three major versions of Tk in use on OS X:
     261          1. Aqua Cocoa Tk (native default since OS X 10.6)
     262          2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
     263          3. X11 (supported by some third-party distributors, deprecated)
     264      There are various differences among the three that affect IDLE
     265      behavior, primarily with menus, mouse key events, and accelerators.
     266      Some one-time customizations are performed here.
     267      Others are dynamically tested throughout idlelib by calls to the
     268      isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
     269      are initialized here as well.
     270      """
     271      if isAquaTk():
     272          hideTkConsole(root)
     273          overrideRootMenu(root, flist)
     274          addOpenEventSupport(root, flist)
     275          fixb2context(root)
     276  
     277  
     278  if __name__ == '__main__':
     279      from unittest import main
     280      main('idlelib.idle_test.test_macosx', verbosity=2)