1"""File selection dialog classes. 2 3Classes: 4 5- FileDialog 6- LoadFileDialog 7- SaveFileDialog 8 9""" 10 11from Tkinter import * 12from Dialog import Dialog 13 14import os 15import fnmatch 16 17 18dialogstates = {} 19 20 21class 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 220class 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 234class 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 262def 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 273if __name__ == '__main__': 274 test() 275