#@+leo-ver=4-thin #@+node:ekr.20040107092135.2:@thin searchbox.py #@<< doc string >> #@+node:ekr.20040108060748:<< doc string >> """Add a quick search to the toolbar in Leo A search box which behaves like a web site search is added, along with a "GO" button to do quick searches right from the main Leo window. All the current search options are retained except that "search body text" is explicitely set - mainly because this is by far the most common use case. Pressing while editing the text automatically does a search. Repeated searches can be done by clicking the "GO" button. The combo box also stores a list of previous searches, which can be selected to quickly repeat a search. When activating a previous search the original search mode is used. Still to do: - incremental search - reverse search - persist recent searches across Leo sessions - use INI file to set options for list size etc """ #@nonl #@-node:ekr.20040108060748:<< doc string >> #@nl #@@language python #@@tabwidth -4 __version__ = "0.5" #@<< version history >> #@+node:ekr.20040908094021.3:<< version history >> #@+at # # 0.4 EKR: Don't mess with button width on MacOS/darwin. # # 0.5 EKR: Create a separate SearchBox instance for each open window. # This eliminates problems when multiple windows are open. #@-at #@nonl #@-node:ekr.20040908094021.3:<< version history >> #@nl #@<< imports >> #@+node:ekr.20040908093511.3:<< imports >> import leoGlobals as g import leoPlugins import leoFind try: import Tkinter as Tk except ImportError: Tk = None import sys #@nonl #@-node:ekr.20040908093511.3:<< imports >> #@nl #@<< vars >> #@+node:ekr.20040108054555.14:<< vars >> # Set this to 0 if the sizing of the toolbar controls doesn't look good on # your platform USE_FIXED_SIZES = sys.platform != "darwin" # Set this to the number of previous searches you want to remember SEARCH_LIST_LENGTH = 10 # Define the search option/find option matrix - we define as a list first # so we can keep a nice order. You can easily add options here if you # know what you are doing ;) # If an option is supposed to zero a flag then start the name with a ! OPTION_LIST = [ ("Search text", ["search_body_flag", "search_headline_flag", "ignore_case_flag"]), ("Search word", ["search_body_flag", "search_headline_flag", "whole_word_flag", "ignore_case_flag"]), ("Search headlines", ["search_headline_flag","ignore_case_flag"]), ("Search body", ["search_body_flag", "ignore_case_flag"]), ("Case sensitive", ["search_body_flag", "search_headline_flag"]), ] # dict is new in Python 2.2. OPTION_DICT = dict(OPTION_LIST) #@nonl #@-node:ekr.20040108054555.14:<< vars >> #@nl #@+others #@+node:ekr.20040909132007:onCreate def onCreate(tag, keywords): c = keywords.get("c") search = SearchBox(c) search.addWidgets() #@nonl #@-node:ekr.20040909132007:onCreate #@+node:ekr.20040107092135.3:class SearchBox class SearchBox: """A search box for Leo""" #@ @+others #@+node:ekr.20040909132007.1:ctor def __init__ (self,c): self.c = c #@nonl #@-node:ekr.20040909132007.1:ctor #@+node:ekr.20040108054555.4:_getSizer def _getSizer(self, parent, height, width): """Return a sizer object to force a Tk widget to be the right size""" if USE_FIXED_SIZES: sizer = Tk.Frame(parent, height=height, width=width) sizer.pack_propagate(0) # don't shrink sizer.pack(side="right") return sizer else: return parent #@nonl #@-node:ekr.20040108054555.4:_getSizer #@+node:ekr.20040108054555.3:addWidgets def addWidgets(self): """Add the widgets to the navigation bar""" c = self.c ; toolbar = c.frame.iconFrame # Button. self.go = Tk.Button(self._getSizer(toolbar, 25, 32), text="GO", command=self.doSearch) self.go.pack(side="right", fill="both", expand=1) # Search options. options = [name for name, flags in OPTION_LIST] self.option_value = Tk.StringVar() self.options = Tk.OptionMenu( self._getSizer(toolbar, 29, 130), self.option_value, *options) self.option_value.set(options[0]) self.options.pack(side="right", fill="both", expand=1) # Text entry. self.search = Tk.Entry(self._getSizer(toolbar, 24, 130)) self.search.pack(side="right", padx=3, fill="both", expand=1) self.search.bind("", self.onKey) # Store a list of the last searches. self.search_list = [] #@-node:ekr.20040108054555.3:addWidgets #@+node:ekr.20040107092135.5:doSearch def doSearch(self,*args,**keys): """Do the actual search""" # import pdb; pdb.set_trace() text = self.search.get() # Remove the old find frame so its options don't compete with ours. search_mode = self.option_value.get() new_find = QuickFind(text,search_mode) old_find, g.app.findFrame = g.app.findFrame, new_find # Do the search. self.c.findNext() # Restore the find frame. g.app.findFrame = old_find # Remember this list self.updateRecentList(text, search_mode) if 0: # This doesn't work yet: the user can't see the match. self.search.focus_set() #@nonl #@-node:ekr.20040107092135.5:doSearch #@+node:ekr.20040107111307:onBackSpace def onBackSpace (self,event=None): g.trace() #@-node:ekr.20040107111307:onBackSpace #@+node:ekr.20040108054555.5:onKey def onKey (self,event=None): """Called when the user presses Return in the text entry box""" self.search.after_idle(self.doSearch) #@-node:ekr.20040108054555.5:onKey #@+node:ekr.20040108054555.8:searchRecent def searchRecent(self, *args, **kw): """Do a search on a recently used item""" # Find the item. name = self.option_value.get() for item_name, mode in self.search_list: if item_name == name: # Ok, so set mode and text and then do the search self.option_value.set(mode) self.search.delete(0, "end") self.search.insert(0, name) self.doSearch() break else: print name, self.search_list g.es("Recent search item not found! Looks like a bug ...", color="red") #@nonl #@-node:ekr.20040108054555.8:searchRecent #@+node:ekr.20040108054555.7:updateRecentList def updateRecentList(self, text, search_mode): """Update the list of recently searched items""" # First update the menu - delete all the options if there are any menu = self.options["menu"] if self.search_list: menu.delete(len(OPTION_LIST),"end") menu.add_command(label="-------------", command=lambda:0) # Update and prune list to remove a previous search for this text self.search_list = [(text, search_mode)] + [ (name, mode) for name, mode in self.search_list[:SEARCH_LIST_LENGTH] if name <> text] # Now update the menu for name, mode in self.search_list: menu.add_command( label=name,command=Tk._setit(self.option_value,name,self.searchRecent)) #@nonl #@-node:ekr.20040108054555.7:updateRecentList #@-others #@nonl #@-node:ekr.20040107092135.3:class SearchBox #@+node:ekr.20040107092135.6:class QuickFind class QuickFind(leoFind.leoFind): """A class for quick searching""" #@ @+others #@+node:ekr.20040107092135.7:__init__ def __init__(self, text, search_option=""): """Initialize the finder""" # Init the base class. leoFind.leoFind.__init__(self) self.s_text = Tk.Text() # Used by find.search() self.__find_text = text self.search_option = search_option #@-node:ekr.20040107092135.7:__init__ #@+node:ekr.20040107092135.8:set_ivars def set_ivars(self, c): """Set the ivars for the find""" # Modified from leoTkinterFind.set_ivars for key in self.intKeys: setattr(c, key + "_flag", 0) c.change_text = "" c.find_text = self.__find_text # Set options for flag_name in OPTION_DICT[self.search_option]: if flag_name.startswith("!"): value = 0 name = flag_name[1:] else: value = 1 name = flag_name setattr(c, name, value) #@-node:ekr.20040107092135.8:set_ivars #@+node:ekr.20040107103252:init_s_text def init_s_text (self,s): c = self.c ; t = self.s_text t.delete("1.0","end") t.insert("end",s) t.mark_set("insert",g.choose(c.reverse_flag,"end","1.0")) return t #@-node:ekr.20040107103252:init_s_text #@+node:ekr.20040107103339:gui_search def gui_search (self,t,*args,**keys): return t.search(*args,**keys) #@nonl #@-node:ekr.20040107103339:gui_search #@-others #@-node:ekr.20040107092135.6:class QuickFind #@-others if Tk and not g.app.unitTesting: if g.app.gui is None: g.app.createTkGui(__file__) if g.app.gui.guiName() == "tkinter": leoPlugins.registerHandler("after-create-leo-frame", onCreate) g.plugin_signon(__name__) #@nonl #@-node:ekr.20040107092135.2:@thin searchbox.py #@-leo