Hello tutors!
My file search dialog is working now! I attach a copy of the module. It
would be nice, if someone could look over it and make suggestions to improve
it. There are at least two points I find not very elegant in the program:
1. The dialog is ended by raising a SystemExit exception. (I found the idea
searching www.koders.com for Tkinter programs.) Here is a small example of
the technique:
--- snip ---
import Tix
from Tkconstants import *
class YesNo:
def __init__(self, root, title="", question=""):
self.top= Tix.Toplevel(root)
self.top.title(title)
self.top.withdraw() # don't show
Tix.Label(self.top, text=question).pack()
Tix.Button(self.top, text="Yes", command=self.yes_cmd).pack(side=LEFT)
Tix.Button(self.top, text="No", command=self.no_cmd).pack(side=RIGHT)
def ask(self):
self.top.deiconify()
self.top.tkraise()
self.top.grab_set()
try:
self.top.mainloop()
except SystemExit, answer:
self.top.grab_release()
self.top.withdraw()
return answer
def yes_cmd(self, event=None):
raise SystemExit, True
def no_cmd(self, event=None):
raise SystemExit, False
if __name__=="__main__":
def set_label():
global yes_no_dlg, yes_no_label
answer= yes_no_dlg.ask()
yes_no_label["text"]= str(answer)
yes_no_label.update_idletasks()
root = Tix.Tk()
root.title("Modal Dialog Demo")
yes_no_dlg = YesNo(root, "Question", "Do you find this approach elegant?")
yes_no_label= Tix.Label(root, text="Unknown.")
yes_no_label.pack()
Tix.Button(root, text = 'Question..', command = set_label).pack()
Tix.Button(root, text = 'Exit', command = root.destroy).pack()
root.mainloop()
--- snip ---
This is deceiving. A SystemExit exception should, in my opinion, exit the
program. Is there an other way to do this?
2. The Listbox in the file search dialog seems to have two selection modes.
When scrolling with the page-up/page-down keys, only the underlined
selection moves, while the other selection (indicated by a different
background color) remains where it is unless I press the space key. That is
imo too complicated. Is there a Listbox with only one selection type (or an
option to Tix.ScrolledListbox)?
Kind regards,
Karsten.
--
10 GB Mailbox, 100 FreeSMS/Monat http://www.gmx.net/de/go/topmail
+++ GMX - die erste Adresse für Mail, Message, More +++import Tix
from Tkconstants import *
from tkFileDialog import askdirectory
from tkMessageBox import showerror
from grep_mod import grep
import os, re, thread, time
class FileSearchBase:
def __init__(self, root, search_func, func_caller):
"""root... parent window
search_func... a "worker" function which performs the search
func_caller... the call to the search_func is wrapped to allow
threading etc."""
self.root = root
self.top = None
self.title= "File Search"
self.subdirs= Tix.BooleanVar()
self.is_regex= Tix.BooleanVar()
self.is_stopped= False
self.search_func= search_func
self.func_caller= func_caller
self.time= 0
self.dir= None
self.file_combo= None
self.search_combo= None
self.startstop= None
self.statusbar= None
def ask_file_by_search(self):
self._open()
try:
self.top.mainloop()
except SystemExit, file_name:
self._close()
return file_name
def _open(self):
"""Window management: displays the File search dialog window"""
if not self.top:
self._create_widgets()
else:
self.top.deiconify()
self.top.tkraise()
self.search_combo.entry.focus_set()
self.search_combo.entry.selection_range(0, "end")
self.search_combo.entry.icursor(0)
self.top.grab_set()
def _close(self, event=None):
"""Window management: closes the File search dialog"""
if self.top is not None:
self.top.grab_release()
self.top.withdraw()
def _create_widgets(self):
"""Window management: called by _open() to create the dialog widgets."""
top = Tix.Toplevel(self.root)
top.resizable(0,0)
top.bind("<Return>", self._start_search_cmd)
top.bind("<Escape>", self._cancel_cmd)
top.protocol("WM_DELETE_WINDOW", self._cancel_cmd)
top.wm_title(self.title)
#top.wm_iconname(self.icon)
self.top = top
self.top.grid_columnconfigure(0, pad=2, weight=0)
self.top.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
# first row: folder information
## first column: Folder entry + pick folder button
frame= Tix.Frame(top)
self.dir = Tix.LabelEntry(frame, label="Folder:",
options='entry.width 20')
self.dir.entry.insert("end", os.getcwd()) # use os.getcwdu() for unicode
self.dir.pack(side=LEFT)
w= Tix.Button(frame, text = 'Pick..', command = self._pick_dir_cmd)
w.pack(side=RIGHT)
frame.grid(row=0, column=0)
## second column: checkbox
w= Tix.Checkbutton(top,
text = 'Look in subfolders, too', # Label
variable=self.subdirs # Variable
)
w.select() # set default to "on"
w.grid(row = 0, column = 1, sticky= W)
# second row: File information
self.file_combo = Tix.ComboBox(top, label="Files: ", dropdown=1,
editable=1, options='listbox.height 4 label.padY 5 label.width 13
label.anchor ne')
self.file_combo.entry.insert("end", "*")
self.file_combo.grid(row=1, column=0)
# third row: Search text
## first column: Combobox
self.search_combo = Tix.ComboBox(top, label="Search for text: ", dropdown=1,
editable=1, options='listbox.height 4 label.padY 5 label.width 13
label.anchor ne')
self.search_combo.grid(row=2, column=0)
## second column: checkbox
w= Tix.Checkbutton(top, text = "it's a regex", variable=self.is_regex)
w.grid(row = 2, column = 1, sticky= W)
# fourth row: Buttons
## first column is empty
## second column: start+cancel button
frame= Tix.Frame(top)
self.startstop= Tix.Button(frame, text = 'Search!', command =
self._start_search_cmd)
self.startstop.pack(side=LEFT)
w= Tix.Button(frame, text = 'Cancel', command = self._cancel_cmd)
w.pack(side=RIGHT)
frame.grid(row=3, column=1)
# next rows: the result box
w= Tix.ScrolledListBox(top, scrollbar= "auto")
self.flist= w.listbox # access to the list
self.flist.bind("<Double-Button-1>",self._ok_cmd)
w.grid(row=4, column=0, rowspan=3, columnspan=2, sticky=E+W)
# last row: status bar
self.statusbar = Tix.Label(top, bd=1, relief=SUNKEN, anchor=W)
self.statusbar.grid(row=7, column=0, columnspan=2, sticky=E+W)
def _pick_dir_cmd(self):
"""Button Function: Let the user choose a directory"""
dir= askdirectory(parent= self.top)
if (dir!=""):
self.dir.entry.delete(0,"end")
self.dir.entry.insert("end", dir.replace("/", os.sep)) # portable
print "New starting directory: ", dir, " = ", self.dir.entry.get()
def _cancel_search_cmd(self, event=None):
"""Button Function: Stop a started search."""
self.is_stopped= True
def _start_search_cmd(self, event=None):
"""Button Function. Starts a search."""
if self._is_valid():
self.time= time.time()
self.flist.delete(0, Tix.END)
self.startstop.configure(text= "Stop", command= self._cancel_search_cmd)
self.is_stopped= False
self.file_combo.insert(0, self.file_combo.entry.get())
self.search_combo.insert(0, self.search_combo.entry.get())
self.file_combo.update_idletasks()
self.search_combo.update_idletasks()
self.statusbar["text"]= "Searching..."
self.func_caller(self.search_func,
dict(dir=self.dir.entry.get(),
do_subdirs=self.subdirs.get(),
filemask= self.file_combo.entry.get(),
search_text= self.search_combo.entry.get(),
is_regex= self.is_regex.get(),
update_callback= self._add_to_list,
cancel_callback= lambda : self.is_stopped),
self._end_search)
def _cancel_cmd(self, event=None):
"""Button function: Cancels the whole dialog."""
raise SystemExit, ("", -1)
def _ok_cmd(self, event= None):
"""Button function: End the dialog, return file_name."""
idx = self.flist.curselection()
s= self.flist.get(idx)
if self.search_combo.entry.get()=="": # s has no line information
raise SystemExit, (s,-1)
else:
idx= s.rfind(" (")
raise SystemExit, (s[:idx], int(s[idx+2:-1]))
def _is_valid(self):
"""Validate the dialog data. Returns True if it defines a valid search
request,
otherwise displays an error message and returns False."""
if self.dir.entry.get()=="":
showerror("Error", "Folder name needed", parent=self.top)
if self.file_combo.entry.get()=="":
showerror("Error", "File mask needed, (e.g. '*')", parent=self.top)
return False
if self.is_regex:
try:
re.compile(self.search_combo.entry.get())
except re.error:
showerror("Error", "Invalid regular expression", parent=self.top)
return False
return True
def _add_to_list(self, f_name, line):
"""Callback Function. Updates the search result list."""
if (line==-1): # no search expression provided
self.flist.insert(Tix.END, f_name)
else:
self.flist.insert(Tix.END, "%s (%d)" % (f_name, line))
def _end_search(self, ret_val):
"""Callback Function. It is called when the search thread finished.
Updates the statusbar and the start/stop button."""
self.startstop.configure(text= "Search!", command= self._start_search_cmd)
self.statusbar["text"]= "Elapsed time: %ds" % (time.time()-self.time)
def as_thread(func, kwargs, callback=None):
"""calls the function func with keyword arguments kwargs
as a new thread. If callback is not None, it is expected to be a
callable with one parameter. It is called with the return value of func."""
if callback is None:
thread.start_new_thread(func, (), kwargs)
else:
thread.start_new_thread(lambda : callback(func(**kwargs)), ())
if __name__ == '__main__':
def set_label():
global file_search_dlg, file_label
(name, line) = file_search_dlg.ask_file_by_search()
print "name='%s', line=%d" % (name,line)
if (str(name)== ""):
name= "No file selected."
file_label["text"]= name
file_label.update_idletasks()
if True:
root = Tix.Tk()
root.title("File Search Demo")
file_search_dlg = FileSearchBase(root, grep, as_thread)
file_label= Tix.Label(root, text="No file selected.")
file_label.pack()
Tix.Button(root, text = 'Search dialog', command = set_label).pack()
Tix.Button(root, text = 'Exit', command = root.destroy).pack()
root.mainloop()
import re, os
from os.path import join, isfile
from fnmatch import fnmatch
def tree_iter(dir):
for root, dirs, files in os.walk(dir):
for f in files:
yield (root,f)
def dir_iter(dir):
for p in os.listdir(dir):
if isfile(join(dir,p)):
yield (dir,p)
def grep(
dir, # string, where to start the search
do_subdirs, # boolean, whether or not dwelve into
subdirs
filemask, # string, a filemask like *.py
search_text, # string, the search expression
is_regex, # boolean, whether or not search_text is a
regular expression
update_callback, # a function which takes a string of a
filename
cancel_callback): # if this parameterless function returns
True,
# the search is canceled.
"""Calls update_callback for every file satisfying the search request
specified
by the arguments."""
no_search= (search_text=="")
if is_regex:
search_pat= re.compile(search_text) # may throw
match_func= lambda s: search_pat.search(s) is not None
else:
match_func= lambda s: s.find(search_text) != -1
if do_subdirs:
iter= tree_iter
else:
iter= dir_iter
for (d,f) in iter(dir): # (dirname, filename)
if fnmatch(f, filemask):
if cancel_callback(): return
if no_search:
update_callback(join(d,f),-1)
continue # next f
try:
fp = open(join(d,f), 'r')
except IOError:
continue # next f
lineno = 0
for line in fp:
lineno += 1
if match_func(line):
update_callback(join(d,f), lineno)
break
fp.close()
if __name__=="__main__":
import sys
grep(dir="d:\\active\\workflow\\tkinter\\searchdlg\\",
do_subdirs=True, filemask="*.py", search_text="Tix", is_regex=False,
update_callback= lambda s: sys.stdout.write(s+"\n"),
cancel_callback= lambda: False)
_______________________________________________
Tutor maillist - [email protected]
http://mail.python.org/mailman/listinfo/tutor