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

Reply via email to