Home | History | Annotate | Download | only in lib-tk
      1 """File selection dialog classes.
      2 
      3 Classes:
      4 
      5 - FileDialog
      6 - LoadFileDialog
      7 - SaveFileDialog
      8 
      9 """
     10 
     11 from Tkinter import *
     12 from Dialog import Dialog
     13 
     14 import os
     15 import fnmatch
     16 
     17 
     18 dialogstates = {}
     19 
     20 
     21 class FileDialog:
     22 
     23     """Standard file selection dialog -- no checks on selected file.
     24 
     25     Usage:
     26 
     27         d = FileDialog(master)
     28         fname = d.go(dir_or_file, pattern, default, key)
     29         if fname is None: ...canceled...
     30         else: ...open file...
     31 
     32     All arguments to go() are optional.
     33 
     34     The 'key' argument specifies a key in the global dictionary
     35     'dialogstates', which keeps track of the values for the directory
     36     and pattern arguments, overriding the values passed in (it does
     37     not keep track of the default argument!).  If no key is specified,
     38     the dialog keeps no memory of previous state.  Note that memory is
     39     kept even when the dialog is canceled.  (All this emulates the
     40     behavior of the Macintosh file selection dialogs.)
     41 
     42     """
     43 
     44     title = "File Selection Dialog"
     45 
     46     def __init__(self, master, title=None):
     47         if title is None: title = self.title
     48         self.master = master
     49         self.directory = None
     50 
     51         self.top = Toplevel(master)
     52         self.top.title(title)
     53         self.top.iconname(title)
     54 
     55         self.botframe = Frame(self.top)
     56         self.botframe.pack(side=BOTTOM, fill=X)
     57 
     58         self.selection = Entry(self.top)
     59         self.selection.pack(side=BOTTOM, fill=X)
     60         self.selection.bind('<Return>', self.ok_event)
     61 
     62         self.filter = Entry(self.top)
     63         self.filter.pack(side=TOP, fill=X)
     64         self.filter.bind('<Return>', self.filter_command)
     65 
     66         self.midframe = Frame(self.top)
     67         self.midframe.pack(expand=YES, fill=BOTH)
     68 
     69         self.filesbar = Scrollbar(self.midframe)
     70         self.filesbar.pack(side=RIGHT, fill=Y)
     71         self.files = Listbox(self.midframe, exportselection=0,
     72                              yscrollcommand=(self.filesbar, 'set'))
     73         self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
     74         btags = self.files.bindtags()
     75         self.files.bindtags(btags[1:] + btags[:1])
     76         self.files.bind('<ButtonRelease-1>', self.files_select_event)
     77         self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
     78         self.filesbar.config(command=(self.files, 'yview'))
     79 
     80         self.dirsbar = Scrollbar(self.midframe)
     81         self.dirsbar.pack(side=LEFT, fill=Y)
     82         self.dirs = Listbox(self.midframe, exportselection=0,
     83                             yscrollcommand=(self.dirsbar, 'set'))
     84         self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
     85         self.dirsbar.config(command=(self.dirs, 'yview'))
     86         btags = self.dirs.bindtags()
     87         self.dirs.bindtags(btags[1:] + btags[:1])
     88         self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
     89         self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
     90 
     91         self.ok_button = Button(self.botframe,
     92                                  text="OK",
     93                                  command=self.ok_command)
     94         self.ok_button.pack(side=LEFT)
     95         self.filter_button = Button(self.botframe,
     96                                     text="Filter",
     97                                     command=self.filter_command)
     98         self.filter_button.pack(side=LEFT, expand=YES)
     99         self.cancel_button = Button(self.botframe,
    100                                     text="Cancel",
    101                                     command=self.cancel_command)
    102         self.cancel_button.pack(side=RIGHT)
    103 
    104         self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
    105         # XXX Are the following okay for a general audience?
    106         self.top.bind('<Alt-w>', self.cancel_command)
    107         self.top.bind('<Alt-W>', self.cancel_command)
    108 
    109     def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
    110         if key and key in dialogstates:
    111             self.directory, pattern = dialogstates[key]
    112         else:
    113             dir_or_file = os.path.expanduser(dir_or_file)
    114             if os.path.isdir(dir_or_file):
    115                 self.directory = dir_or_file
    116             else:
    117                 self.directory, default = os.path.split(dir_or_file)
    118         self.set_filter(self.directory, pattern)
    119         self.set_selection(default)
    120         self.filter_command()
    121         self.selection.focus_set()
    122         self.top.wait_visibility() # window needs to be visible for the grab
    123         self.top.grab_set()
    124         self.how = None
    125         self.master.mainloop()          # Exited by self.quit(how)
    126         if key:
    127             directory, pattern = self.get_filter()
    128             if self.how:
    129                 directory = os.path.dirname(self.how)
    130             dialogstates[key] = directory, pattern
    131         self.top.destroy()
    132         return self.how
    133 
    134     def quit(self, how=None):
    135         self.how = how
    136         self.master.quit()              # Exit mainloop()
    137 
    138     def dirs_double_event(self, event):
    139         self.filter_command()
    140 
    141     def dirs_select_event(self, event):
    142         dir, pat = self.get_filter()
    143         subdir = self.dirs.get('active')
    144         dir = os.path.normpath(os.path.join(self.directory, subdir))
    145         self.set_filter(dir, pat)
    146 
    147     def files_double_event(self, event):
    148         self.ok_command()
    149 
    150     def files_select_event(self, event):
    151         file = self.files.get('active')
    152         self.set_selection(file)
    153 
    154     def ok_event(self, event):
    155         self.ok_command()
    156 
    157     def ok_command(self):
    158         self.quit(self.get_selection())
    159 
    160     def filter_command(self, event=None):
    161         dir, pat = self.get_filter()
    162         try:
    163             names = os.listdir(dir)
    164         except os.error:
    165             self.master.bell()
    166             return
    167         self.directory = dir
    168         self.set_filter(dir, pat)
    169         names.sort()
    170         subdirs = [os.pardir]
    171         matchingfiles = []
    172         for name in names:
    173             fullname = os.path.join(dir, name)
    174             if os.path.isdir(fullname):
    175                 subdirs.append(name)
    176             elif fnmatch.fnmatch(name, pat):
    177                 matchingfiles.append(name)
    178         self.dirs.delete(0, END)
    179         for name in subdirs:
    180             self.dirs.insert(END, name)
    181         self.files.delete(0, END)
    182         for name in matchingfiles:
    183             self.files.insert(END, name)
    184         head, tail = os.path.split(self.get_selection())
    185         if tail == os.curdir: tail = ''
    186         self.set_selection(tail)
    187 
    188     def get_filter(self):
    189         filter = self.filter.get()
    190         filter = os.path.expanduser(filter)
    191         if filter[-1:] == os.sep or os.path.isdir(filter):
    192             filter = os.path.join(filter, "*")
    193         return os.path.split(filter)
    194 
    195     def get_selection(self):
    196         file = self.selection.get()
    197         file = os.path.expanduser(file)
    198         return file
    199 
    200     def cancel_command(self, event=None):
    201         self.quit()
    202 
    203     def set_filter(self, dir, pat):
    204         if not os.path.isabs(dir):
    205             try:
    206                 pwd = os.getcwd()
    207             except os.error:
    208                 pwd = None
    209             if pwd:
    210                 dir = os.path.join(pwd, dir)
    211                 dir = os.path.normpath(dir)
    212         self.filter.delete(0, END)
    213         self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
    214 
    215     def set_selection(self, file):
    216         self.selection.delete(0, END)
    217         self.selection.insert(END, os.path.join(self.directory, file))
    218 
    219 
    220 class LoadFileDialog(FileDialog):
    221 
    222     """File selection dialog which checks that the file exists."""
    223 
    224     title = "Load File Selection Dialog"
    225 
    226     def ok_command(self):
    227         file = self.get_selection()
    228         if not os.path.isfile(file):
    229             self.master.bell()
    230         else:
    231             self.quit(file)
    232 
    233 
    234 class SaveFileDialog(FileDialog):
    235 
    236     """File selection dialog which checks that the file may be created."""
    237 
    238     title = "Save File Selection Dialog"
    239 
    240     def ok_command(self):
    241         file = self.get_selection()
    242         if os.path.exists(file):
    243             if os.path.isdir(file):
    244                 self.master.bell()
    245                 return
    246             d = Dialog(self.top,
    247                        title="Overwrite Existing File Question",
    248                        text="Overwrite existing file %r?" % (file,),
    249                        bitmap='questhead',
    250                        default=1,
    251                        strings=("Yes", "Cancel"))
    252             if d.num != 0:
    253                 return
    254         else:
    255             head, tail = os.path.split(file)
    256             if not os.path.isdir(head):
    257                 self.master.bell()
    258                 return
    259         self.quit(file)
    260 
    261 
    262 def test():
    263     """Simple test program."""
    264     root = Tk()
    265     root.withdraw()
    266     fd = LoadFileDialog(root)
    267     loadfile = fd.go(key="test")
    268     fd = SaveFileDialog(root)
    269     savefile = fd.go(key="test")
    270     print loadfile, savefile
    271 
    272 
    273 if __name__ == '__main__':
    274     test()
    275