Kaydet (Commit) 0978f993 authored tarafından Guido van Rossum's avatar Guido van Rossum

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.
üst 51b708ab
...@@ -6,13 +6,6 @@ Classes: ...@@ -6,13 +6,6 @@ Classes:
- LoadFileDialog - LoadFileDialog
- SaveFileDialog - 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 * from Tkinter import *
...@@ -24,6 +17,9 @@ import os ...@@ -24,6 +17,9 @@ import os
import fnmatch import fnmatch
dialogstates = {}
class FileDialog: class FileDialog:
"""Standard file selection dialog -- no checks on selected file. """Standard file selection dialog -- no checks on selected file.
...@@ -31,36 +27,65 @@ class FileDialog: ...@@ -31,36 +27,65 @@ class FileDialog:
Usage: Usage:
d = FileDialog(master) d = FileDialog(master)
file = d.go(directory, pattern, default) file = d.go(dir_or_file, pattern, default, key)
if file is None: ...canceled... 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" 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.master = master
self.directory = None self.directory = None
self.top = Toplevel(master) 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 = Entry(self.top)
self.filter.pack(fill=X) self.filter.pack(side=TOP, fill=X)
self.filter.bind('<Return>', self.filter_command) self.filter.bind('<Return>', self.filter_command)
self.midframe = Frame(self.top) self.midframe = Frame(self.top)
self.midframe.pack(expand=YES, fill=BOTH) self.midframe.pack(expand=YES, fill=BOTH)
self.dirs = Listbox(self.midframe, exportselection=0)
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH) self.filesbar = Scrollbar(self.midframe)
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event) self.filesbar.pack(side=RIGHT, fill=Y)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event) self.files = Listbox(self.midframe, exportselection=0,
self.files = Listbox(self.midframe, exportselection=0) yscrollcommand=(self.filesbar, 'set'))
self.files.pack(side=RIGHT, expand=YES, fill=BOTH) self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
self.files.bind('<ButtonRelease-1>', self.files_select_event) self.files.bind('<ButtonRelease-1>', self.files_select_event)
self.files.bind('<Double-ButtonRelease-1>', self.files_double_event) self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
self.selection = Entry(self.top) self.filesbar.config(command=(self.files, 'yview'))
self.selection.pack(fill=X)
self.selection.bind('<Return>', self.ok_event) self.dirsbar = Scrollbar(self.midframe)
self.botframe = Frame(self.top) self.dirsbar.pack(side=LEFT, fill=Y)
self.botframe.pack(fill=X) 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, self.ok_button = Button(self.botframe,
text="OK", text="OK",
command=self.ok_command) command=self.ok_command)
...@@ -74,21 +99,36 @@ class FileDialog: ...@@ -74,21 +99,36 @@ class FileDialog:
command=self.cancel_command) command=self.cancel_command)
self.cancel_button.pack(side=RIGHT) self.cancel_button.pack(side=RIGHT)
def go(self, directory=os.curdir, pattern="*", default=""): self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
self.directory = directory # XXX Are the following okay for a general audience?
self.set_filter(directory, pattern) self.top.bind('<Alt-w>', self.cancel_command)
self.filter_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.set_selection(default)
self.filter_command()
self.selection.focus_set() self.selection.focus_set()
self.top.grab_set() self.top.grab_set()
try: self.how = None
self.master.mainloop() self.master.mainloop() # Exited by self.quit(how)
except SystemExit, how: if key: dialogstates[key] = self.get_filter()
self.top.destroy() self.top.destroy()
return how return self.how
def quit(self, how=None):
self.how = how
self.master.quit() # Exit mainloop()
def dirs_double_event(self, event): def dirs_double_event(self, event):
## self.dirs_select_event(event)
self.filter_command() self.filter_command()
def dirs_select_event(self, event): def dirs_select_event(self, event):
...@@ -98,8 +138,6 @@ class FileDialog: ...@@ -98,8 +138,6 @@ class FileDialog:
self.set_filter(dir, pat) self.set_filter(dir, pat)
def files_double_event(self, event): def files_double_event(self, event):
## self.files_select_event(event)
## self.master.update_idletasks()
self.ok_command() self.ok_command()
def files_select_event(self, event): def files_select_event(self, event):
...@@ -110,7 +148,7 @@ class FileDialog: ...@@ -110,7 +148,7 @@ class FileDialog:
self.ok_command() self.ok_command()
def ok_command(self): def ok_command(self):
raise SystemExit, self.selection.get() self.quit(self.get_selection())
def filter_command(self, event=None): def filter_command(self, event=None):
dir, pat = self.get_filter() dir, pat = self.get_filter()
...@@ -136,20 +174,34 @@ class FileDialog: ...@@ -136,20 +174,34 @@ class FileDialog:
self.files.delete(0, END) self.files.delete(0, END)
for name in matchingfiles: for name in matchingfiles:
self.files.insert(END, name) 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 = '' if tail == os.curdir: tail = ''
self.set_selection(tail) self.set_selection(tail)
def get_filter(self): def get_filter(self):
filter = self.filter.get() filter = self.filter.get()
if filter[-1:] == os.sep: filter = os.path.expanduser(filter)
filter = filter + "*" if filter[-1:] == os.sep or os.path.isdir(filter):
filter = os.path.join(filter, "*")
return os.path.split(filter) return os.path.split(filter)
def cancel_command(self): def get_selection(self):
raise SystemExit, None 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): 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.delete(0, END)
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*")) self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
...@@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog): ...@@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog):
title = "Load File Selection Dialog" title = "Load File Selection Dialog"
def ok_command(self): def ok_command(self):
file = self.selection.get() file = self.get_selection()
if not os.path.isfile(file): if not os.path.isfile(file):
self.master.bell() self.master.bell()
else: else:
raise SystemExit, file self.quit(file)
class SaveFileDialog(FileDialog): class SaveFileDialog(FileDialog):
...@@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog): ...@@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog):
title = "Save File Selection Dialog" title = "Save File Selection Dialog"
def ok_command(self): def ok_command(self):
file = self.selection.get() file = self.get_selection()
if os.path.exists(file): if os.path.exists(file):
if os.path.isdir(file): if os.path.isdir(file):
self.master.bell() self.master.bell()
return return
d = Dialog(self.master, d = Dialog(self.top,
title="Overwrite Existing File Question", title="Overwrite Existing File Question",
text="Overwrite existing file %s?" % `file`, text="Overwrite existing file %s?" % `file`,
bitmap='questhead', bitmap='questhead',
default=0, default=1,
strings=("Yes", "Cancel")) strings=("Yes", "Cancel"))
if d.num != 0: file = None if d.num != 0:
return
else: else:
head, tail = os.path.split(file) head, tail = os.path.split(file)
if not os.path.isdir(head): if not os.path.isdir(head):
self.master.bell() self.master.bell()
return return
raise SystemExit, file self.quit(file)
def test(): def test():
...@@ -204,9 +257,9 @@ def test(): ...@@ -204,9 +257,9 @@ def test():
root = Tk() root = Tk()
root.withdraw() root.withdraw()
fd = LoadFileDialog(root) fd = LoadFileDialog(root)
loadfile = fd.go() loadfile = fd.go(key="test")
fd = SaveFileDialog(root) fd = SaveFileDialog(root)
savefile = fd.go() savefile = fd.go(key="test")
print loadfile, savefile print loadfile, savefile
......
...@@ -6,13 +6,6 @@ Classes: ...@@ -6,13 +6,6 @@ Classes:
- LoadFileDialog - LoadFileDialog
- SaveFileDialog - 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 * from Tkinter import *
...@@ -24,6 +17,9 @@ import os ...@@ -24,6 +17,9 @@ import os
import fnmatch import fnmatch
dialogstates = {}
class FileDialog: class FileDialog:
"""Standard file selection dialog -- no checks on selected file. """Standard file selection dialog -- no checks on selected file.
...@@ -31,36 +27,65 @@ class FileDialog: ...@@ -31,36 +27,65 @@ class FileDialog:
Usage: Usage:
d = FileDialog(master) d = FileDialog(master)
file = d.go(directory, pattern, default) file = d.go(dir_or_file, pattern, default, key)
if file is None: ...canceled... 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" 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.master = master
self.directory = None self.directory = None
self.top = Toplevel(master) 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 = Entry(self.top)
self.filter.pack(fill=X) self.filter.pack(side=TOP, fill=X)
self.filter.bind('<Return>', self.filter_command) self.filter.bind('<Return>', self.filter_command)
self.midframe = Frame(self.top) self.midframe = Frame(self.top)
self.midframe.pack(expand=YES, fill=BOTH) self.midframe.pack(expand=YES, fill=BOTH)
self.dirs = Listbox(self.midframe, exportselection=0)
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH) self.filesbar = Scrollbar(self.midframe)
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event) self.filesbar.pack(side=RIGHT, fill=Y)
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event) self.files = Listbox(self.midframe, exportselection=0,
self.files = Listbox(self.midframe, exportselection=0) yscrollcommand=(self.filesbar, 'set'))
self.files.pack(side=RIGHT, expand=YES, fill=BOTH) self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
self.files.bind('<ButtonRelease-1>', self.files_select_event) self.files.bind('<ButtonRelease-1>', self.files_select_event)
self.files.bind('<Double-ButtonRelease-1>', self.files_double_event) self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
self.selection = Entry(self.top) self.filesbar.config(command=(self.files, 'yview'))
self.selection.pack(fill=X)
self.selection.bind('<Return>', self.ok_event) self.dirsbar = Scrollbar(self.midframe)
self.botframe = Frame(self.top) self.dirsbar.pack(side=LEFT, fill=Y)
self.botframe.pack(fill=X) 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, self.ok_button = Button(self.botframe,
text="OK", text="OK",
command=self.ok_command) command=self.ok_command)
...@@ -74,21 +99,36 @@ class FileDialog: ...@@ -74,21 +99,36 @@ class FileDialog:
command=self.cancel_command) command=self.cancel_command)
self.cancel_button.pack(side=RIGHT) self.cancel_button.pack(side=RIGHT)
def go(self, directory=os.curdir, pattern="*", default=""): self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
self.directory = directory # XXX Are the following okay for a general audience?
self.set_filter(directory, pattern) self.top.bind('<Alt-w>', self.cancel_command)
self.filter_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.set_selection(default)
self.filter_command()
self.selection.focus_set() self.selection.focus_set()
self.top.grab_set() self.top.grab_set()
try: self.how = None
self.master.mainloop() self.master.mainloop() # Exited by self.quit(how)
except SystemExit, how: if key: dialogstates[key] = self.get_filter()
self.top.destroy() self.top.destroy()
return how return self.how
def quit(self, how=None):
self.how = how
self.master.quit() # Exit mainloop()
def dirs_double_event(self, event): def dirs_double_event(self, event):
## self.dirs_select_event(event)
self.filter_command() self.filter_command()
def dirs_select_event(self, event): def dirs_select_event(self, event):
...@@ -98,8 +138,6 @@ class FileDialog: ...@@ -98,8 +138,6 @@ class FileDialog:
self.set_filter(dir, pat) self.set_filter(dir, pat)
def files_double_event(self, event): def files_double_event(self, event):
## self.files_select_event(event)
## self.master.update_idletasks()
self.ok_command() self.ok_command()
def files_select_event(self, event): def files_select_event(self, event):
...@@ -110,7 +148,7 @@ class FileDialog: ...@@ -110,7 +148,7 @@ class FileDialog:
self.ok_command() self.ok_command()
def ok_command(self): def ok_command(self):
raise SystemExit, self.selection.get() self.quit(self.get_selection())
def filter_command(self, event=None): def filter_command(self, event=None):
dir, pat = self.get_filter() dir, pat = self.get_filter()
...@@ -136,20 +174,34 @@ class FileDialog: ...@@ -136,20 +174,34 @@ class FileDialog:
self.files.delete(0, END) self.files.delete(0, END)
for name in matchingfiles: for name in matchingfiles:
self.files.insert(END, name) 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 = '' if tail == os.curdir: tail = ''
self.set_selection(tail) self.set_selection(tail)
def get_filter(self): def get_filter(self):
filter = self.filter.get() filter = self.filter.get()
if filter[-1:] == os.sep: filter = os.path.expanduser(filter)
filter = filter + "*" if filter[-1:] == os.sep or os.path.isdir(filter):
filter = os.path.join(filter, "*")
return os.path.split(filter) return os.path.split(filter)
def cancel_command(self): def get_selection(self):
raise SystemExit, None 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): 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.delete(0, END)
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*")) self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
...@@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog): ...@@ -165,11 +217,11 @@ class LoadFileDialog(FileDialog):
title = "Load File Selection Dialog" title = "Load File Selection Dialog"
def ok_command(self): def ok_command(self):
file = self.selection.get() file = self.get_selection()
if not os.path.isfile(file): if not os.path.isfile(file):
self.master.bell() self.master.bell()
else: else:
raise SystemExit, file self.quit(file)
class SaveFileDialog(FileDialog): class SaveFileDialog(FileDialog):
...@@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog): ...@@ -179,24 +231,25 @@ class SaveFileDialog(FileDialog):
title = "Save File Selection Dialog" title = "Save File Selection Dialog"
def ok_command(self): def ok_command(self):
file = self.selection.get() file = self.get_selection()
if os.path.exists(file): if os.path.exists(file):
if os.path.isdir(file): if os.path.isdir(file):
self.master.bell() self.master.bell()
return return
d = Dialog(self.master, d = Dialog(self.top,
title="Overwrite Existing File Question", title="Overwrite Existing File Question",
text="Overwrite existing file %s?" % `file`, text="Overwrite existing file %s?" % `file`,
bitmap='questhead', bitmap='questhead',
default=0, default=1,
strings=("Yes", "Cancel")) strings=("Yes", "Cancel"))
if d.num != 0: file = None if d.num != 0:
return
else: else:
head, tail = os.path.split(file) head, tail = os.path.split(file)
if not os.path.isdir(head): if not os.path.isdir(head):
self.master.bell() self.master.bell()
return return
raise SystemExit, file self.quit(file)
def test(): def test():
...@@ -204,9 +257,9 @@ def test(): ...@@ -204,9 +257,9 @@ def test():
root = Tk() root = Tk()
root.withdraw() root.withdraw()
fd = LoadFileDialog(root) fd = LoadFileDialog(root)
loadfile = fd.go() loadfile = fd.go(key="test")
fd = SaveFileDialog(root) fd = SaveFileDialog(root)
savefile = fd.go() savefile = fd.go(key="test")
print loadfile, savefile print loadfile, savefile
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment