Persistent history and tab completion (from the history) support.
git-svn-id: https://samskivert.googlecode.com/svn/trunk@669 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
2002-03-17 Michael Bayne <mdb@samskivert.com>
|
2002-03-17 Michael Bayne <mdb@samskivert.com>
|
||||||
|
|
||||||
|
* 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
|
* various: Rewrote the whole business in Python, using Glade to
|
||||||
create the user interface. Oh, the joy of being rid of C.
|
create the user interface. Oh, the joy of being rid of C.
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
<signal>
|
<signal>
|
||||||
<name>selection_received</name>
|
<name>selection_received</name>
|
||||||
<handler>on_query_selection_received</handler>
|
<handler>on_query_selection_received</handler>
|
||||||
|
<after>True</after>
|
||||||
<last_modification_time>Sat, 16 Mar 2002 21:46:37 GMT</last_modification_time>
|
<last_modification_time>Sat, 16 Mar 2002 21:46:37 GMT</last_modification_time>
|
||||||
</signal>
|
</signal>
|
||||||
<editable>True</editable>
|
<editable>True</editable>
|
||||||
@@ -83,7 +84,6 @@
|
|||||||
<widget>
|
<widget>
|
||||||
<class>GtkButton</class>
|
<class>GtkButton</class>
|
||||||
<name>prefs</name>
|
<name>prefs</name>
|
||||||
<can_focus>True</can_focus>
|
|
||||||
<signal>
|
<signal>
|
||||||
<name>clicked</name>
|
<name>clicked</name>
|
||||||
<handler>on_prefs_clicked</handler>
|
<handler>on_prefs_clicked</handler>
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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
|
# lookuplet - a utility for quickly looking up information
|
||||||
# Copyright (C) 2001 Michael Bayne
|
# Copyright (C) 2001 Michael Bayne
|
||||||
@@ -27,6 +27,7 @@ import string
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import bindings
|
import bindings
|
||||||
|
import history
|
||||||
import keyval_util
|
import keyval_util
|
||||||
import properties
|
import properties
|
||||||
|
|
||||||
@@ -37,6 +38,11 @@ class Lookuplet:
|
|||||||
SELECTION_PRIMARY = 1;
|
SELECTION_PRIMARY = 1;
|
||||||
CURRENT_TIME = 0;
|
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
|
# a reference to our key bindings
|
||||||
bindings = None;
|
bindings = None;
|
||||||
|
|
||||||
@@ -49,9 +55,17 @@ class Lookuplet:
|
|||||||
# whether or not we're running as a Gnome applet
|
# whether or not we're running as a Gnome applet
|
||||||
appletMode = 0;
|
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):
|
def __init__ (self, xmlui, bindings, props, appletMode):
|
||||||
window = xmlui.get_widget("lookuplet");
|
window = xmlui.get_widget("lookuplet");
|
||||||
query = xmlui.get_widget("query");
|
|
||||||
self.about = xmlui.get_widget("about");
|
self.about = xmlui.get_widget("about");
|
||||||
self.string_atom = None;
|
self.string_atom = None;
|
||||||
self.bindings = bindings;
|
self.bindings = bindings;
|
||||||
@@ -64,6 +78,9 @@ class Lookuplet:
|
|||||||
nameFuncMap[key] = getattr(self, key);
|
nameFuncMap[key] = getattr(self, key);
|
||||||
xmlui.signal_autoconnect(nameFuncMap);
|
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
|
# if we're in applet mode, extract the UI and stick it into an
|
||||||
# applet widget
|
# applet widget
|
||||||
if (appletMode):
|
if (appletMode):
|
||||||
@@ -93,6 +110,7 @@ class Lookuplet:
|
|||||||
# request the selection
|
# request the selection
|
||||||
if (self.string_atom == None):
|
if (self.string_atom == None):
|
||||||
self.string_atom = gtk.atom_intern("STRING", gtk.FALSE);
|
self.string_atom = gtk.atom_intern("STRING", gtk.FALSE);
|
||||||
|
query = xmlui.get_widget("query");
|
||||||
query.selection_convert(self.SELECTION_PRIMARY,
|
query.selection_convert(self.SELECTION_PRIMARY,
|
||||||
self.string_atom,
|
self.string_atom,
|
||||||
self.CURRENT_TIME);
|
self.CURRENT_TIME);
|
||||||
@@ -109,24 +127,100 @@ class Lookuplet:
|
|||||||
def on_prefs_clicked (self, button):
|
def on_prefs_clicked (self, button):
|
||||||
self.props.editProperties();
|
self.props.editProperties();
|
||||||
|
|
||||||
def on_query_key_press_event (self, query, event):
|
def handle_special_keys (self, query, event):
|
||||||
# handle special keystrokes such as history and auto-complete
|
# we'll need this later
|
||||||
# if (handle_special_keys(widget, ek)):
|
count = len(self.history.history);
|
||||||
# return true;
|
|
||||||
|
|
||||||
# ignore plain or shifted keystrokes
|
# handle special keystrokes such as history and auto-complete
|
||||||
if (event.state <= 1):
|
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;
|
return gtk.FALSE;
|
||||||
|
|
||||||
# convert the key press into a string
|
# if we found something scrolling or autocompleting, show it
|
||||||
keystr = keyval_util.convert_keyval_state_to_string(
|
if (self.hisidx > -1 and self.hisidx < count):
|
||||||
event.keyval, event.state);
|
# 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
|
# otherwise go back to whatever we were editing
|
||||||
binding = self.bindings.get_match(keystr);
|
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 (binding != None):
|
||||||
# if we found one, invoke it
|
text = query.get_text();
|
||||||
binding.invoke(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):
|
if (self.appletMode):
|
||||||
query.set_text("");
|
query.set_text("");
|
||||||
else:
|
else:
|
||||||
@@ -137,6 +231,12 @@ class Lookuplet:
|
|||||||
# otherwise, let GTK know that we didn't handle the key press
|
# otherwise, let GTK know that we didn't handle the key press
|
||||||
return gtk.FALSE;
|
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):
|
def on_query_selection_received (self, query, selection_data, data):
|
||||||
if (selection_data.length < 0):
|
if (selection_data.length < 0):
|
||||||
# print "Selection retrieval failed.";
|
# print "Selection retrieval failed.";
|
||||||
@@ -156,11 +256,11 @@ class Lookuplet:
|
|||||||
# make sure some selection was provided
|
# make sure some selection was provided
|
||||||
if (len(text) > 0):
|
if (len(text) > 0):
|
||||||
# check to see if we're looking at a url here
|
# 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 this is a URL, we want to eat whitespace
|
||||||
# if (is_url):
|
if (is_url):
|
||||||
# text.tr!(" ", "");
|
text = re.sub(" ", "", text);
|
||||||
|
|
||||||
if (cmp(text, selection_data.data)):
|
if (cmp(text, selection_data.data)):
|
||||||
# print "Setting trimmed text '" + text + "'.";
|
# print "Setting trimmed text '" + text + "'.";
|
||||||
|
|||||||
Reference in New Issue
Block a user