From be6c4511ee167f6d5f2a92a9d2ef0fead92f0878 Mon Sep 17 00:00:00 2001 From: mdb Date: Sun, 17 Mar 2002 21:25:20 +0000 Subject: [PATCH] Persistent history and tab completion (from the history) support. git-svn-id: https://samskivert.googlecode.com/svn/trunk@669 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- projects/lookuplet/ChangeLog | 3 + projects/lookuplet/lookuplet.glade | 2 +- projects/lookuplet/src/python/history.py | 90 ++++++++++++++ projects/lookuplet/src/python/lookuplet.py | 136 ++++++++++++++++++--- 4 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 projects/lookuplet/src/python/history.py diff --git a/projects/lookuplet/ChangeLog b/projects/lookuplet/ChangeLog index 3aae107f..3f657770 100644 --- a/projects/lookuplet/ChangeLog +++ b/projects/lookuplet/ChangeLog @@ -1,5 +1,8 @@ 2002-03-17 Michael Bayne + * src/history.py: Added persistent history support and tab + completion (from the history) to the query box text field. Yay! + * various: Rewrote the whole business in Python, using Glade to create the user interface. Oh, the joy of being rid of C. diff --git a/projects/lookuplet/lookuplet.glade b/projects/lookuplet/lookuplet.glade index 8aba7156..305e2aea 100644 --- a/projects/lookuplet/lookuplet.glade +++ b/projects/lookuplet/lookuplet.glade @@ -67,6 +67,7 @@ selection_received on_query_selection_received + True Sat, 16 Mar 2002 21:46:37 GMT True @@ -83,7 +84,6 @@ GtkButton prefs - True clicked on_prefs_clicked diff --git a/projects/lookuplet/src/python/history.py b/projects/lookuplet/src/python/history.py new file mode 100644 index 00000000..e4bbec5a --- /dev/null +++ b/projects/lookuplet/src/python/history.py @@ -0,0 +1,90 @@ +# +# $Id: history.py,v 1.1 2002/03/17 21:25:20 mdb Exp $ +# +# lookuplet - a utility for quickly looking up information +# Copyright (C) 2001 Michael Bayne +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2.1 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import gnome.config + +class History: + "Used to persistently maintain a history of entries for the \ + lookuplet query text field." + + # the maximum history size + HISTORY_SIZE = 10; + + # the actual history entries + history = []; + + # + # Loads up the history from persistent preferences and prepares for + # operation. + # + def __init__ (self): + gnome.config.push_prefix("/lookuplet/"); + count = gnome.config.get_int("lookuplet/history/count"); + for h in range(0, count): + self.history.append(gnome.config.get_string( + "lookuplet/history/entry_%.2u" % h)); + # print "loaded %d: %s" % (h, self.history[h]); + gnome.config.pop_prefix(); + return; + + # + # Adds an entry to the history. + # + def append (self, entry): + # if this entry is already in the history list, we simply want to + # move it to the end + if (entry in self.history): + self.history.remove(entry); + + # add our entry + self.history.append(entry); + + # trim the history if it's too big + if (len(self.history) > self.HISTORY_SIZE): + self.history.pop(0); + + # flush our config to the configuration repository + gnome.config.push_prefix("/lookuplet/"); + count = len(self.history); + gnome.config.set_int("lookuplet/history/count", count); + for h in range(0, count): + gnome.config.set_string( + "lookuplet/history/entry_%.2u" % h, self.history[h]); + # print "wrote %d: %s" % (h, self.history[h]); + gnome.config.sync(); + gnome.config.drop_all(); + gnome.config.pop_prefix(); + + # + # Scans back from one before the specified index looking for a history + # entry that matches the specified prefix, returning its index if one + # is found or -1 if one is not. + # + def match (self, prefix, index): + count = len(self.history); + for offset in range(1, count+1): + # do some funny math to always ensure a positive value + pos = (index - offset + count) % count; + # print "scanning %s for %s." % (self.history[pos], prefix); + if (self.history[pos].find(prefix) == 0): + # print "found it at %d." % pos; + return pos; + # print "didn't find it."; + return -1; diff --git a/projects/lookuplet/src/python/lookuplet.py b/projects/lookuplet/src/python/lookuplet.py index 0b0ba5bc..38c96b7c 100644 --- a/projects/lookuplet/src/python/lookuplet.py +++ b/projects/lookuplet/src/python/lookuplet.py @@ -1,5 +1,5 @@ # -# $Id: lookuplet.py,v 1.2 2002/03/17 10:13:25 mdb Exp $ +# $Id: lookuplet.py,v 1.3 2002/03/17 21:25:20 mdb Exp $ # # lookuplet - a utility for quickly looking up information # Copyright (C) 2001 Michael Bayne @@ -27,6 +27,7 @@ import string import re import bindings +import history import keyval_util import properties @@ -37,6 +38,11 @@ class Lookuplet: SELECTION_PRIMARY = 1; CURRENT_TIME = 0; + # our special key definitions + PREV_HISTORY_KEY = GDK.Up + NEXT_HISTORY_KEY = GDK.Down + AUTO_COMPLETE_KEY = GDK.Tab + # a reference to our key bindings bindings = None; @@ -49,9 +55,17 @@ class Lookuplet: # whether or not we're running as a Gnome applet appletMode = 0; + # our query box history + history = None; + + # where we are if we're scanning the history + hisidx = -1; + + # the prefix we're using to match if we're matching on a prefix + hispref = None; + def __init__ (self, xmlui, bindings, props, appletMode): window = xmlui.get_widget("lookuplet"); - query = xmlui.get_widget("query"); self.about = xmlui.get_widget("about"); self.string_atom = None; self.bindings = bindings; @@ -64,6 +78,9 @@ class Lookuplet: nameFuncMap[key] = getattr(self, key); xmlui.signal_autoconnect(nameFuncMap); + # create our query history + self.history = history.History(); + # if we're in applet mode, extract the UI and stick it into an # applet widget if (appletMode): @@ -93,6 +110,7 @@ class Lookuplet: # request the selection if (self.string_atom == None): self.string_atom = gtk.atom_intern("STRING", gtk.FALSE); + query = xmlui.get_widget("query"); query.selection_convert(self.SELECTION_PRIMARY, self.string_atom, self.CURRENT_TIME); @@ -109,24 +127,100 @@ class Lookuplet: def on_prefs_clicked (self, button): self.props.editProperties(); - def on_query_key_press_event (self, query, event): - # handle special keystrokes such as history and auto-complete - # if (handle_special_keys(widget, ek)): - # return true; + def handle_special_keys (self, query, event): + # we'll need this later + count = len(self.history.history); - # ignore plain or shifted keystrokes - if (event.state <= 1): + # handle special keystrokes such as history and auto-complete + if (event.keyval == self.NEXT_HISTORY_KEY): + if (self.hisidx >= 0 and self.hisidx < count): + self.hisidx += 1; + else: + return gtk.TRUE; + + elif (event.keyval == self.PREV_HISTORY_KEY): + if (self.hisidx > 0): + self.hisidx -= 1; + elif (self.hisidx == -1): + # if we haven't grabbed a prefix as of yet, grab what we've got + if (self.hispref == None): + self.hispref = query.get_text(); + # print "set prefix: %s" % self.hispref; + self.hisidx = count-1; + else: + return gtk.TRUE; + + elif (event.keyval == self.AUTO_COMPLETE_KEY): + # if we haven't grabbed a prefix as of yet, grab what we've got + if (self.hispref == None): + self.hispref = query.get_text(); + # print "set prefix: %s" % self.hispref; + + # if our history prefix has not yet been set, set it and start + # scanning through the history + if (self.hisidx == -1): + self.hisidx = count; + self.hisidx = self.history.match(self.hispref, self.hisidx); + + else: return gtk.FALSE; - # convert the key press into a string - keystr = keyval_util.convert_keyval_state_to_string( - event.keyval, event.state); + # if we found something scrolling or autocompleting, show it + if (self.hisidx > -1 and self.hisidx < count): + # print "displaying history entry %d." % self.hisidx; + text = self.history.history[self.hisidx]; + self.set_query(query, text); + return gtk.TRUE; - # look up a binding for that key string - binding = self.bindings.get_match(keystr); + # otherwise go back to whatever we were editing + if (self.hispref != None): + # print "restoring prefix %s" % self.hispref; + self.set_query(query, self.hispref); + + # we always want to claim to have handled the keypress if it was + # the autocomplete key, otherwise GTK will move the focus from our + # query box widget + if (event.keyval == self.AUTO_COMPLETE_KEY): + return gtk.TRUE + else: + return gtk.FALSE; + + def set_query (self, query, text): + # display text in the query box and select it for easy erasal + query.set_text(text); + query.select_region(0, len(text)); + + def on_query_key_press_event (self, query, event): + # handle history browsing and auto-completion + if (self.handle_special_keys(query, event) == gtk.TRUE): + return gtk.TRUE; + + # clear out our history prefix because it's in the text field now + # and we'll grab it again if we need it + self.hispref = None; + self.hisidx = -1; + + # if they pressed return, map that to a special binding + if (event.keyval == GDK.Return): + binding = bindings.Binding("", bindings.Binding.URL, "", "%T"); + + else: + # ignore plain or shifted keystrokes + if (event.state <= 1): + return gtk.FALSE; + # convert the key press into a string + keystr = keyval_util.convert_keyval_state_to_string( + event.keyval, event.state); + # look up a binding for that key string + binding = self.bindings.get_match(keystr); + + # if we found one, invoke it if (binding != None): - # if we found one, invoke it - binding.invoke(query.get_text()); + text = query.get_text(); + binding.invoke(text); + # append this entry to our history + self.history.append(text); + # and either bail or get ready for the next go if (self.appletMode): query.set_text(""); else: @@ -137,6 +231,12 @@ class Lookuplet: # otherwise, let GTK know that we didn't handle the key press return gtk.FALSE; + def url_p (self, text): + return (re.match("^http:", text) or + re.match("^ftp:", text) or + re.match("^https:", text) or + re.match("^file:", text)); + def on_query_selection_received (self, query, selection_data, data): if (selection_data.length < 0): # print "Selection retrieval failed."; @@ -156,11 +256,11 @@ class Lookuplet: # make sure some selection was provided if (len(text) > 0): # check to see if we're looking at a url here - # is_url = url_p(text); + is_url = self.url_p(text); # if this is a URL, we want to eat whitespace - # if (is_url): - # text.tr!(" ", ""); + if (is_url): + text = re.sub(" ", "", text); if (cmp(text, selection_data.data)): # print "Setting trimmed text '" + text + "'.";