Major overhaul:

- Support ~[user] expansion.

- Remember last directory and pattern; optional 'key' argument
specifies different memory locations.

- Absolutify pathnames if possible.

- WM close event cancels the dialog.

- First arg to go() can be either a directory or a file (renamed to
dir_of_file); defaults to current directory.
This commit is contained in:
Guido van Rossum 1996-05-28 23:14:36 +00:00
parent 51b708ab61
commit 0978f993e3
2 changed files with 202 additions and 96 deletions

View File

@ -6,13 +6,6 @@ Classes:
- LoadFileDialog
- SaveFileDialog
XXX Bugs:
- The fields are not labeled
- Default doesn't have absolute pathname
- Each FileDialog instance can be used only once
- There is no easy way for an application to add widgets of its own
"""
from Tkinter import *
@ -24,6 +17,9 @@ import os
import fnmatch
dialogstates = {}
class FileDialog:
"""Standard file selection dialog -- no checks on selected file.
@ -31,36 +27,65 @@ class FileDialog:
Usage:
d = FileDialog(master)
file = d.go(directory, pattern, default)
file = d.go(dir_or_file, pattern, default, key)
if file is None: ...canceled...
else: ...open file...
All arguments to go() are optional.
The 'key' argument specifies a key in the global dictionary
'dialogstates', which keeps track of the values for the directory
and pattern arguments, overriding the values passed in (it does
not keep track of the default argument!). If no key is specified,
the dialog keeps no memory of previous state. Note that memory is
kept even when the dialog is cancelled. (All this emulates the
behavior of the Macintosh file selection dialogs.)
"""
title = "File Selection Dialog"
def __init__(self, master):
def __init__(self, master, title=None):
if title is None: title = self.title
self.master = master
self.directory = None
self.top = Toplevel(master)
self.top.title(self.title)
self.top.title(title)
self.top.iconname(title)
self.botframe = Frame(self.top)
self.botframe.pack(side=BOTTOM, fill=X)
self.selection = Entry(self.top)
self.selection.pack(side=BOTTOM, fill=X)
self.selection.bind('<Return>', self.ok_event)
self.filter = Entry(self.top)
self.filter.pack(fill=X)
self.filter.pack(side=TOP, fill=X)
self.filter.bind('<Return>', self.filter_command)
self.midframe = Frame(self.top)
self.midframe.pack(expand=YES, fill=BOTH)
self.dirs = Listbox(self.midframe, exportselection=0)
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
self.files = Listbox(self.midframe, exportselection=0)
self.filesbar = Scrollbar(self.midframe)
self.filesbar.pack(side=RIGHT, fill=Y)
self.files = Listbox(self.midframe, exportselection=0,
yscrollcommand=(self.filesbar, 'set'))
self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
self.files.bind('<ButtonRelease-1>', self.files_select_event)
self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
self.selection = Entry(self.top)
self.selection.pack(fill=X)
self.selection.bind('<Return>', self.ok_event)
self.botframe = Frame(self.top)
self.botframe.pack(fill=X)
self.filesbar.config(command=(self.files, 'yview'))
self.dirsbar = Scrollbar(self.midframe)
self.dirsbar.pack(side=LEFT, fill=Y)
self.dirs = Listbox(self.midframe, exportselection=0,
yscrollcommand=(self.dirsbar, 'set'))
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
self.dirsbar.config(command=(self.dirs, 'yview'))
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
self.ok_button = Button(self.botframe,
text="OK",
command=self.ok_command)
@ -74,21 +99,36 @@ class FileDialog:
command=self.cancel_command)
self.cancel_button.pack(side=RIGHT)
def go(self, directory=os.curdir, pattern="*", default=""):
self.directory = directory
self.set_filter(directory, pattern)
self.filter_command()
self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
# XXX Are the following okay for a general audience?
self.top.bind('<Alt-w>', self.cancel_command)
self.top.bind('<Alt-W>', self.cancel_command)
def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
if key and dialogstates.has_key(key):
self.directory, pattern = dialogstates[key]
else:
dir_or_file = os.path.expanduser(dir_or_file)
if os.path.isdir(dir_or_file):
self.directory = dir_or_file
else:
self.directory, default = os.path.split(dir_or_file)
self.set_filter(self.directory, pattern)
self.set_selection(default)
self.filter_command()
self.selection.focus_set()
self.top.grab_set()
try:
self.master.mainloop()
except SystemExit, how:
self.top.destroy()
return how
self.how = None
self.master.mainloop() # Exited by self.quit(how)
if key: dialogstates[key] = self.get_filter()
self.top.destroy()
return self.how
def quit(self, how=None):
self.how = how
self.master.quit() # Exit mainloop()
def dirs_double_event(self, event):
## self.dirs_select_event(event)
self.filter_command()
def dirs_select_event(self, event):
@ -98,8 +138,6 @@ class FileDialog:
self.set_filter(dir, pat)
def files_double_event(self, event):
## self.files_select_event(event)
## self.master.update_idletasks()
self.ok_command()
def files_select_event(self, event):
@ -110,7 +148,7 @@ class FileDialog:
self.ok_command()
def ok_command(self):
raise SystemExit, self.selection.get()
self.quit(self.get_selection())
def filter_command(self, event=None):
dir, pat = self.get_filter()
@ -136,20 +174,34 @@ class FileDialog:
self.files.delete(0, END)
for name in matchingfiles:
self.files.insert(END, name)
head, tail = os.path.split(self.selection.get())
head, tail = os.path.split(self.get_selection())
if tail == os.curdir: tail = ''
self.set_selection(tail)
def get_filter(self):
filter = self.filter.get()
if filter[-1:] == os.sep:
filter = filter + "*"
filter = os.path.expanduser(filter)
if filter[-1:] == os.sep or os.path.isdir(filter):
filter = os.path.join(filter, "*")
return os.path.split(filter)
def cancel_command(self):
raise SystemExit, None
def get_selection(self):
file = self.selection.get()
file = os.path.expanduser(file)
return file
def cancel_command(self, event=None):
self.quit()
def set_filter(self, dir, pat):
if not os.path.isabs(dir):
try:
pwd = os.getcwd()
except os.error:
pwd = None
if pwd:
dir = os.path.join(pwd, dir)
dir = os.path.normpath(dir)
self.filter.delete(0, END)
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog):
title = "Load File Selection Dialog"
def ok_command(self):
file = self.selection.get()
file = self.get_selection()
if not os.path.isfile(file):
self.master.bell()
else:
raise SystemExit, file
self.quit(file)
class SaveFileDialog(FileDialog):
@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog):
title = "Save File Selection Dialog"
def ok_command(self):
file = self.selection.get()
file = self.get_selection()
if os.path.exists(file):
if os.path.isdir(file):
self.master.bell()
return
d = Dialog(self.master,
d = Dialog(self.top,
title="Overwrite Existing File Question",
text="Overwrite existing file %s?" % `file`,
bitmap='questhead',
default=0,
default=1,
strings=("Yes", "Cancel"))
if d.num != 0: file = None
if d.num != 0:
return
else:
head, tail = os.path.split(file)
if not os.path.isdir(head):
self.master.bell()
return
raise SystemExit, file
self.quit(file)
def test():
@ -204,9 +257,9 @@ def test():
root = Tk()
root.withdraw()
fd = LoadFileDialog(root)
loadfile = fd.go()
loadfile = fd.go(key="test")
fd = SaveFileDialog(root)
savefile = fd.go()
savefile = fd.go(key="test")
print loadfile, savefile

View File

@ -6,13 +6,6 @@ Classes:
- LoadFileDialog
- SaveFileDialog
XXX Bugs:
- The fields are not labeled
- Default doesn't have absolute pathname
- Each FileDialog instance can be used only once
- There is no easy way for an application to add widgets of its own
"""
from Tkinter import *
@ -24,6 +17,9 @@ import os
import fnmatch
dialogstates = {}
class FileDialog:
"""Standard file selection dialog -- no checks on selected file.
@ -31,36 +27,65 @@ class FileDialog:
Usage:
d = FileDialog(master)
file = d.go(directory, pattern, default)
file = d.go(dir_or_file, pattern, default, key)
if file is None: ...canceled...
else: ...open file...
All arguments to go() are optional.
The 'key' argument specifies a key in the global dictionary
'dialogstates', which keeps track of the values for the directory
and pattern arguments, overriding the values passed in (it does
not keep track of the default argument!). If no key is specified,
the dialog keeps no memory of previous state. Note that memory is
kept even when the dialog is cancelled. (All this emulates the
behavior of the Macintosh file selection dialogs.)
"""
title = "File Selection Dialog"
def __init__(self, master):
def __init__(self, master, title=None):
if title is None: title = self.title
self.master = master
self.directory = None
self.top = Toplevel(master)
self.top.title(self.title)
self.top.title(title)
self.top.iconname(title)
self.botframe = Frame(self.top)
self.botframe.pack(side=BOTTOM, fill=X)
self.selection = Entry(self.top)
self.selection.pack(side=BOTTOM, fill=X)
self.selection.bind('<Return>', self.ok_event)
self.filter = Entry(self.top)
self.filter.pack(fill=X)
self.filter.pack(side=TOP, fill=X)
self.filter.bind('<Return>', self.filter_command)
self.midframe = Frame(self.top)
self.midframe.pack(expand=YES, fill=BOTH)
self.dirs = Listbox(self.midframe, exportselection=0)
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
self.files = Listbox(self.midframe, exportselection=0)
self.filesbar = Scrollbar(self.midframe)
self.filesbar.pack(side=RIGHT, fill=Y)
self.files = Listbox(self.midframe, exportselection=0,
yscrollcommand=(self.filesbar, 'set'))
self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
self.files.bind('<ButtonRelease-1>', self.files_select_event)
self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
self.selection = Entry(self.top)
self.selection.pack(fill=X)
self.selection.bind('<Return>', self.ok_event)
self.botframe = Frame(self.top)
self.botframe.pack(fill=X)
self.filesbar.config(command=(self.files, 'yview'))
self.dirsbar = Scrollbar(self.midframe)
self.dirsbar.pack(side=LEFT, fill=Y)
self.dirs = Listbox(self.midframe, exportselection=0,
yscrollcommand=(self.dirsbar, 'set'))
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
self.dirsbar.config(command=(self.dirs, 'yview'))
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
self.ok_button = Button(self.botframe,
text="OK",
command=self.ok_command)
@ -74,21 +99,36 @@ class FileDialog:
command=self.cancel_command)
self.cancel_button.pack(side=RIGHT)
def go(self, directory=os.curdir, pattern="*", default=""):
self.directory = directory
self.set_filter(directory, pattern)
self.filter_command()
self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
# XXX Are the following okay for a general audience?
self.top.bind('<Alt-w>', self.cancel_command)
self.top.bind('<Alt-W>', self.cancel_command)
def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
if key and dialogstates.has_key(key):
self.directory, pattern = dialogstates[key]
else:
dir_or_file = os.path.expanduser(dir_or_file)
if os.path.isdir(dir_or_file):
self.directory = dir_or_file
else:
self.directory, default = os.path.split(dir_or_file)
self.set_filter(self.directory, pattern)
self.set_selection(default)
self.filter_command()
self.selection.focus_set()
self.top.grab_set()
try:
self.master.mainloop()
except SystemExit, how:
self.top.destroy()
return how
self.how = None
self.master.mainloop() # Exited by self.quit(how)
if key: dialogstates[key] = self.get_filter()
self.top.destroy()
return self.how
def quit(self, how=None):
self.how = how
self.master.quit() # Exit mainloop()
def dirs_double_event(self, event):
## self.dirs_select_event(event)
self.filter_command()
def dirs_select_event(self, event):
@ -98,8 +138,6 @@ class FileDialog:
self.set_filter(dir, pat)
def files_double_event(self, event):
## self.files_select_event(event)
## self.master.update_idletasks()
self.ok_command()
def files_select_event(self, event):
@ -110,7 +148,7 @@ class FileDialog:
self.ok_command()
def ok_command(self):
raise SystemExit, self.selection.get()
self.quit(self.get_selection())
def filter_command(self, event=None):
dir, pat = self.get_filter()
@ -136,20 +174,34 @@ class FileDialog:
self.files.delete(0, END)
for name in matchingfiles:
self.files.insert(END, name)
head, tail = os.path.split(self.selection.get())
head, tail = os.path.split(self.get_selection())
if tail == os.curdir: tail = ''
self.set_selection(tail)
def get_filter(self):
filter = self.filter.get()
if filter[-1:] == os.sep:
filter = filter + "*"
filter = os.path.expanduser(filter)
if filter[-1:] == os.sep or os.path.isdir(filter):
filter = os.path.join(filter, "*")
return os.path.split(filter)
def cancel_command(self):
raise SystemExit, None
def get_selection(self):
file = self.selection.get()
file = os.path.expanduser(file)
return file
def cancel_command(self, event=None):
self.quit()
def set_filter(self, dir, pat):
if not os.path.isabs(dir):
try:
pwd = os.getcwd()
except os.error:
pwd = None
if pwd:
dir = os.path.join(pwd, dir)
dir = os.path.normpath(dir)
self.filter.delete(0, END)
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog):
title = "Load File Selection Dialog"
def ok_command(self):
file = self.selection.get()
file = self.get_selection()
if not os.path.isfile(file):
self.master.bell()
else:
raise SystemExit, file
self.quit(file)
class SaveFileDialog(FileDialog):
@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog):
title = "Save File Selection Dialog"
def ok_command(self):
file = self.selection.get()
file = self.get_selection()
if os.path.exists(file):
if os.path.isdir(file):
self.master.bell()
return
d = Dialog(self.master,
d = Dialog(self.top,
title="Overwrite Existing File Question",
text="Overwrite existing file %s?" % `file`,
bitmap='questhead',
default=0,
default=1,
strings=("Yes", "Cancel"))
if d.num != 0: file = None
if d.num != 0:
return
else:
head, tail = os.path.split(file)
if not os.path.isdir(head):
self.master.bell()
return
raise SystemExit, file
self.quit(file)
def test():
@ -204,9 +257,9 @@ def test():
root = Tk()
root.withdraw()
fd = LoadFileDialog(root)
loadfile = fd.go()
loadfile = fd.go(key="test")
fd = SaveFileDialog(root)
savefile = fd.go()
savefile = fd.go(key="test")
print loadfile, savefile