(root)/
Python-3.11.7/
Lib/
tkinter/
filedialog.py
       1  """File selection dialog classes.
       2  
       3  Classes:
       4  
       5  - FileDialog
       6  - LoadFileDialog
       7  - SaveFileDialog
       8  
       9  This module also presents tk common file dialogues, it provides interfaces
      10  to the native file dialogues available in Tk 4.2 and newer, and the
      11  directory dialogue available in Tk 8.3 and newer.
      12  These interfaces were written by Fredrik Lundh, May 1997.
      13  """
      14  __all__ = ["FileDialog", "LoadFileDialog", "SaveFileDialog",
      15             "Open", "SaveAs", "Directory",
      16             "askopenfilename", "asksaveasfilename", "askopenfilenames",
      17             "askopenfile", "askopenfiles", "asksaveasfile", "askdirectory"]
      18  
      19  import fnmatch
      20  import os
      21  from tkinter import (
      22      Frame, LEFT, YES, BOTTOM, Entry, TOP, Button, Tk, X,
      23      Toplevel, RIGHT, Y, END, Listbox, BOTH, Scrollbar,
      24  )
      25  from tkinter.dialog import Dialog
      26  from tkinter import commondialog
      27  from tkinter.simpledialog import _setup_dialog
      28  
      29  
      30  dialogstates = {}
      31  
      32  
      33  class ESC[4;38;5;81mFileDialog:
      34  
      35      """Standard file selection dialog -- no checks on selected file.
      36  
      37      Usage:
      38  
      39          d = FileDialog(master)
      40          fname = d.go(dir_or_file, pattern, default, key)
      41          if fname is None: ...canceled...
      42          else: ...open file...
      43  
      44      All arguments to go() are optional.
      45  
      46      The 'key' argument specifies a key in the global dictionary
      47      'dialogstates', which keeps track of the values for the directory
      48      and pattern arguments, overriding the values passed in (it does
      49      not keep track of the default argument!).  If no key is specified,
      50      the dialog keeps no memory of previous state.  Note that memory is
      51      kept even when the dialog is canceled.  (All this emulates the
      52      behavior of the Macintosh file selection dialogs.)
      53  
      54      """
      55  
      56      title = "File Selection Dialog"
      57  
      58      def __init__(self, master, title=None):
      59          if title is None: title = self.title
      60          self.master = master
      61          self.directory = None
      62  
      63          self.top = Toplevel(master)
      64          self.top.title(title)
      65          self.top.iconname(title)
      66          _setup_dialog(self.top)
      67  
      68          self.botframe = Frame(self.top)
      69          self.botframe.pack(side=BOTTOM, fill=X)
      70  
      71          self.selection = Entry(self.top)
      72          self.selection.pack(side=BOTTOM, fill=X)
      73          self.selection.bind('<Return>', self.ok_event)
      74  
      75          self.filter = Entry(self.top)
      76          self.filter.pack(side=TOP, fill=X)
      77          self.filter.bind('<Return>', self.filter_command)
      78  
      79          self.midframe = Frame(self.top)
      80          self.midframe.pack(expand=YES, fill=BOTH)
      81  
      82          self.filesbar = Scrollbar(self.midframe)
      83          self.filesbar.pack(side=RIGHT, fill=Y)
      84          self.files = Listbox(self.midframe, exportselection=0,
      85                               yscrollcommand=(self.filesbar, 'set'))
      86          self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
      87          btags = self.files.bindtags()
      88          self.files.bindtags(btags[1:] + btags[:1])
      89          self.files.bind('<ButtonRelease-1>', self.files_select_event)
      90          self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
      91          self.filesbar.config(command=(self.files, 'yview'))
      92  
      93          self.dirsbar = Scrollbar(self.midframe)
      94          self.dirsbar.pack(side=LEFT, fill=Y)
      95          self.dirs = Listbox(self.midframe, exportselection=0,
      96                              yscrollcommand=(self.dirsbar, 'set'))
      97          self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
      98          self.dirsbar.config(command=(self.dirs, 'yview'))
      99          btags = self.dirs.bindtags()
     100          self.dirs.bindtags(btags[1:] + btags[:1])
     101          self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
     102          self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
     103  
     104          self.ok_button = Button(self.botframe,
     105                                   text="OK",
     106                                   command=self.ok_command)
     107          self.ok_button.pack(side=LEFT)
     108          self.filter_button = Button(self.botframe,
     109                                      text="Filter",
     110                                      command=self.filter_command)
     111          self.filter_button.pack(side=LEFT, expand=YES)
     112          self.cancel_button = Button(self.botframe,
     113                                      text="Cancel",
     114                                      command=self.cancel_command)
     115          self.cancel_button.pack(side=RIGHT)
     116  
     117          self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
     118          # XXX Are the following okay for a general audience?
     119          self.top.bind('<Alt-w>', self.cancel_command)
     120          self.top.bind('<Alt-W>', self.cancel_command)
     121  
     122      def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
     123          if key and key in dialogstates:
     124              self.directory, pattern = dialogstates[key]
     125          else:
     126              dir_or_file = os.path.expanduser(dir_or_file)
     127              if os.path.isdir(dir_or_file):
     128                  self.directory = dir_or_file
     129              else:
     130                  self.directory, default = os.path.split(dir_or_file)
     131          self.set_filter(self.directory, pattern)
     132          self.set_selection(default)
     133          self.filter_command()
     134          self.selection.focus_set()
     135          self.top.wait_visibility() # window needs to be visible for the grab
     136          self.top.grab_set()
     137          self.how = None
     138          self.master.mainloop()          # Exited by self.quit(how)
     139          if key:
     140              directory, pattern = self.get_filter()
     141              if self.how:
     142                  directory = os.path.dirname(self.how)
     143              dialogstates[key] = directory, pattern
     144          self.top.destroy()
     145          return self.how
     146  
     147      def quit(self, how=None):
     148          self.how = how
     149          self.master.quit()              # Exit mainloop()
     150  
     151      def dirs_double_event(self, event):
     152          self.filter_command()
     153  
     154      def dirs_select_event(self, event):
     155          dir, pat = self.get_filter()
     156          subdir = self.dirs.get('active')
     157          dir = os.path.normpath(os.path.join(self.directory, subdir))
     158          self.set_filter(dir, pat)
     159  
     160      def files_double_event(self, event):
     161          self.ok_command()
     162  
     163      def files_select_event(self, event):
     164          file = self.files.get('active')
     165          self.set_selection(file)
     166  
     167      def ok_event(self, event):
     168          self.ok_command()
     169  
     170      def ok_command(self):
     171          self.quit(self.get_selection())
     172  
     173      def filter_command(self, event=None):
     174          dir, pat = self.get_filter()
     175          try:
     176              names = os.listdir(dir)
     177          except OSError:
     178              self.master.bell()
     179              return
     180          self.directory = dir
     181          self.set_filter(dir, pat)
     182          names.sort()
     183          subdirs = [os.pardir]
     184          matchingfiles = []
     185          for name in names:
     186              fullname = os.path.join(dir, name)
     187              if os.path.isdir(fullname):
     188                  subdirs.append(name)
     189              elif fnmatch.fnmatch(name, pat):
     190                  matchingfiles.append(name)
     191          self.dirs.delete(0, END)
     192          for name in subdirs:
     193              self.dirs.insert(END, name)
     194          self.files.delete(0, END)
     195          for name in matchingfiles:
     196              self.files.insert(END, name)
     197          head, tail = os.path.split(self.get_selection())
     198          if tail == os.curdir: tail = ''
     199          self.set_selection(tail)
     200  
     201      def get_filter(self):
     202          filter = self.filter.get()
     203          filter = os.path.expanduser(filter)
     204          if filter[-1:] == os.sep or os.path.isdir(filter):
     205              filter = os.path.join(filter, "*")
     206          return os.path.split(filter)
     207  
     208      def get_selection(self):
     209          file = self.selection.get()
     210          file = os.path.expanduser(file)
     211          return file
     212  
     213      def cancel_command(self, event=None):
     214          self.quit()
     215  
     216      def set_filter(self, dir, pat):
     217          if not os.path.isabs(dir):
     218              try:
     219                  pwd = os.getcwd()
     220              except OSError:
     221                  pwd = None
     222              if pwd:
     223                  dir = os.path.join(pwd, dir)
     224                  dir = os.path.normpath(dir)
     225          self.filter.delete(0, END)
     226          self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
     227  
     228      def set_selection(self, file):
     229          self.selection.delete(0, END)
     230          self.selection.insert(END, os.path.join(self.directory, file))
     231  
     232  
     233  class ESC[4;38;5;81mLoadFileDialog(ESC[4;38;5;149mFileDialog):
     234  
     235      """File selection dialog which checks that the file exists."""
     236  
     237      title = "Load File Selection Dialog"
     238  
     239      def ok_command(self):
     240          file = self.get_selection()
     241          if not os.path.isfile(file):
     242              self.master.bell()
     243          else:
     244              self.quit(file)
     245  
     246  
     247  class ESC[4;38;5;81mSaveFileDialog(ESC[4;38;5;149mFileDialog):
     248  
     249      """File selection dialog which checks that the file may be created."""
     250  
     251      title = "Save File Selection Dialog"
     252  
     253      def ok_command(self):
     254          file = self.get_selection()
     255          if os.path.exists(file):
     256              if os.path.isdir(file):
     257                  self.master.bell()
     258                  return
     259              d = Dialog(self.top,
     260                         title="Overwrite Existing File Question",
     261                         text="Overwrite existing file %r?" % (file,),
     262                         bitmap='questhead',
     263                         default=1,
     264                         strings=("Yes", "Cancel"))
     265              if d.num != 0:
     266                  return
     267          else:
     268              head, tail = os.path.split(file)
     269              if not os.path.isdir(head):
     270                  self.master.bell()
     271                  return
     272          self.quit(file)
     273  
     274  
     275  # For the following classes and modules:
     276  #
     277  # options (all have default values):
     278  #
     279  # - defaultextension: added to filename if not explicitly given
     280  #
     281  # - filetypes: sequence of (label, pattern) tuples.  the same pattern
     282  #   may occur with several patterns.  use "*" as pattern to indicate
     283  #   all files.
     284  #
     285  # - initialdir: initial directory.  preserved by dialog instance.
     286  #
     287  # - initialfile: initial file (ignored by the open dialog).  preserved
     288  #   by dialog instance.
     289  #
     290  # - parent: which window to place the dialog on top of
     291  #
     292  # - title: dialog title
     293  #
     294  # - multiple: if true user may select more than one file
     295  #
     296  # options for the directory chooser:
     297  #
     298  # - initialdir, parent, title: see above
     299  #
     300  # - mustexist: if true, user must pick an existing directory
     301  #
     302  
     303  
     304  class ESC[4;38;5;81m_Dialog(ESC[4;38;5;149mcommondialogESC[4;38;5;149m.ESC[4;38;5;149mDialog):
     305  
     306      def _fixoptions(self):
     307          try:
     308              # make sure "filetypes" is a tuple
     309              self.options["filetypes"] = tuple(self.options["filetypes"])
     310          except KeyError:
     311              pass
     312  
     313      def _fixresult(self, widget, result):
     314          if result:
     315              # keep directory and filename until next time
     316              # convert Tcl path objects to strings
     317              try:
     318                  result = result.string
     319              except AttributeError:
     320                  # it already is a string
     321                  pass
     322              path, file = os.path.split(result)
     323              self.options["initialdir"] = path
     324              self.options["initialfile"] = file
     325          self.filename = result # compatibility
     326          return result
     327  
     328  
     329  #
     330  # file dialogs
     331  
     332  class ESC[4;38;5;81mOpen(ESC[4;38;5;149m_Dialog):
     333      "Ask for a filename to open"
     334  
     335      command = "tk_getOpenFile"
     336  
     337      def _fixresult(self, widget, result):
     338          if isinstance(result, tuple):
     339              # multiple results:
     340              result = tuple([getattr(r, "string", r) for r in result])
     341              if result:
     342                  path, file = os.path.split(result[0])
     343                  self.options["initialdir"] = path
     344                  # don't set initialfile or filename, as we have multiple of these
     345              return result
     346          if not widget.tk.wantobjects() and "multiple" in self.options:
     347              # Need to split result explicitly
     348              return self._fixresult(widget, widget.tk.splitlist(result))
     349          return _Dialog._fixresult(self, widget, result)
     350  
     351  
     352  class ESC[4;38;5;81mSaveAs(ESC[4;38;5;149m_Dialog):
     353      "Ask for a filename to save as"
     354  
     355      command = "tk_getSaveFile"
     356  
     357  
     358  # the directory dialog has its own _fix routines.
     359  class ESC[4;38;5;81mDirectory(ESC[4;38;5;149mcommondialogESC[4;38;5;149m.ESC[4;38;5;149mDialog):
     360      "Ask for a directory"
     361  
     362      command = "tk_chooseDirectory"
     363  
     364      def _fixresult(self, widget, result):
     365          if result:
     366              # convert Tcl path objects to strings
     367              try:
     368                  result = result.string
     369              except AttributeError:
     370                  # it already is a string
     371                  pass
     372              # keep directory until next time
     373              self.options["initialdir"] = result
     374          self.directory = result # compatibility
     375          return result
     376  
     377  #
     378  # convenience stuff
     379  
     380  
     381  def askopenfilename(**options):
     382      "Ask for a filename to open"
     383  
     384      return Open(**options).show()
     385  
     386  
     387  def asksaveasfilename(**options):
     388      "Ask for a filename to save as"
     389  
     390      return SaveAs(**options).show()
     391  
     392  
     393  def askopenfilenames(**options):
     394      """Ask for multiple filenames to open
     395  
     396      Returns a list of filenames or empty list if
     397      cancel button selected
     398      """
     399      options["multiple"]=1
     400      return Open(**options).show()
     401  
     402  # FIXME: are the following  perhaps a bit too convenient?
     403  
     404  
     405  def askopenfile(mode = "r", **options):
     406      "Ask for a filename to open, and returned the opened file"
     407  
     408      filename = Open(**options).show()
     409      if filename:
     410          return open(filename, mode)
     411      return None
     412  
     413  
     414  def askopenfiles(mode = "r", **options):
     415      """Ask for multiple filenames and return the open file
     416      objects
     417  
     418      returns a list of open file objects or an empty list if
     419      cancel selected
     420      """
     421  
     422      files = askopenfilenames(**options)
     423      if files:
     424          ofiles=[]
     425          for filename in files:
     426              ofiles.append(open(filename, mode))
     427          files=ofiles
     428      return files
     429  
     430  
     431  def asksaveasfile(mode = "w", **options):
     432      "Ask for a filename to save as, and returned the opened file"
     433  
     434      filename = SaveAs(**options).show()
     435      if filename:
     436          return open(filename, mode)
     437      return None
     438  
     439  
     440  def askdirectory (**options):
     441      "Ask for a directory, and return the file name"
     442      return Directory(**options).show()
     443  
     444  
     445  # --------------------------------------------------------------------
     446  # test stuff
     447  
     448  def test():
     449      """Simple test program."""
     450      root = Tk()
     451      root.withdraw()
     452      fd = LoadFileDialog(root)
     453      loadfile = fd.go(key="test")
     454      fd = SaveFileDialog(root)
     455      savefile = fd.go(key="test")
     456      print(loadfile, savefile)
     457  
     458      # Since the file name may contain non-ASCII characters, we need
     459      # to find an encoding that likely supports the file name, and
     460      # displays correctly on the terminal.
     461  
     462      # Start off with UTF-8
     463      enc = "utf-8"
     464      import sys
     465  
     466      # See whether CODESET is defined
     467      try:
     468          import locale
     469          locale.setlocale(locale.LC_ALL,'')
     470          enc = locale.nl_langinfo(locale.CODESET)
     471      except (ImportError, AttributeError):
     472          pass
     473  
     474      # dialog for opening files
     475  
     476      openfilename=askopenfilename(filetypes=[("all files", "*")])
     477      try:
     478          fp=open(openfilename,"r")
     479          fp.close()
     480      except:
     481          print("Could not open File: ")
     482          print(sys.exc_info()[1])
     483  
     484      print("open", openfilename.encode(enc))
     485  
     486      # dialog for saving files
     487  
     488      saveasfilename=asksaveasfilename()
     489      print("saveas", saveasfilename.encode(enc))
     490  
     491  
     492  if __name__ == '__main__':
     493      test()