diff --git a/projects/lookuplet/src/.cvsignore b/projects/lookuplet/src/.cvsignore index db6dd331..0d20b648 100644 --- a/projects/lookuplet/src/.cvsignore +++ b/projects/lookuplet/src/.cvsignore @@ -1,5 +1 @@ -Makefile.in -Makefile -lookuplet -.deps -.libs +*.pyc diff --git a/projects/lookuplet/src/python/bindings.py b/projects/lookuplet/src/python/bindings.py new file mode 100644 index 00000000..aad478cc --- /dev/null +++ b/projects/lookuplet/src/python/bindings.py @@ -0,0 +1,141 @@ +# +# $Id: bindings.py,v 1.1 2002/03/17 09:03:06 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 posix +import re +import string +import urllib + +import gnome.config +import gnome.url + +class Binding: + "Contains the configuration for a particular key binding and its \ + associated query mapping." + + # constants defining which type of binding this is + URL = 0; + EXEC = 1; + + # the key combination associated with this binding + key = ""; + + # the type of binding (URL or Exec) + type = URL; + + # the human readable name of the binding + name = ""; + + # the URL or command line assosciated with the binding + argument = ""; + + # + # Constructs a binding with its requisite parameters. + # + def __init__ (self, key, type, name, argument): + self.key = key; + self.type = type; + self.name = name; + self.argument = argument; + + def invoke (self, terms): + command = re.sub("%T", terms, self.argument); + command = re.sub("%U", urllib.quote(terms), command); + if (self.type == self.EXEC): + posix.system(command) + else: + gnome.url.show(command) + + def update (self, key, type, name, argument): + modified = 0; + if (cmp(key, self.key)): + self.key = key; + modified = 1; + + if (type != self.type): + self.type = type; + modified = 1; + + if (cmp(name, self.name)): + self.name = name; + modified = 1; + + if (cmp(argument, self.argument)): + self.argument = argument; + modified = 1; + + return modified; + + def to_string (self): + return "[key=%s, type=%d, name=%s, arg=%s]" % ( + self.key, self.type, self.name, self.argument); + +class BindingSet: + "Maintains the set of bindings loading into the program." + + # the list of Binding objects + bindings = []; + + # + # The default constructor. + # + def __init__ (self): + gnome.config.push_prefix("/lookuplet/"); + count = gnome.config.get_int("lookuplet/bindings/count"); + for b in range(0, count): + key = gnome.config.get_string("lookuplet/bindings/key_%.2u" % b); + type = gnome.config.get_int("lookuplet/bindings/type_%.2u" % b); + name = gnome.config.get_string("lookuplet/bindings/name_%.2u" % b); + arg = gnome.config.get_string("lookuplet/bindings/arg_%.2u" % b); + self.bindings.append(Binding(key, type, name, arg)); + gnome.config.pop_prefix(); + return; + + # + # Returns the binding with a keysym matching the specified keysym or + # None if no binding matches. + # + def get_match (self, keysym): + for binding in self.bindings: + if (binding.key == keysym): + return binding + return None; + + # + # Stores our updated bindings to our GConf preferences. + # + def flush (self): + gnome.config.push_prefix("/lookuplet/"); + gnome.config.set_int("lookuplet/bindings/count", len(self.bindings)); + b = 0 + for binding in self.bindings: + gnome.config.set_string( + "lookuplet/bindings/key_%.2u" % b, binding.key); + gnome.config.set_int( + "lookuplet/bindings/type_%.2u" % b, binding.type); + gnome.config.set_string( + "lookuplet/bindings/name_%.2u" % b, binding.name); + gnome.config.set_string( + "lookuplet/bindings/arg_%.2u" % b, binding.argument); + b += 1; + gnome.config.sync(); + gnome.config.drop_all(); + gnome.config.pop_prefix(); + return; diff --git a/projects/lookuplet/src/python/edit_binding.py b/projects/lookuplet/src/python/edit_binding.py new file mode 100644 index 00000000..13533a71 --- /dev/null +++ b/projects/lookuplet/src/python/edit_binding.py @@ -0,0 +1,159 @@ +# +# $Id: edit_binding.py,v 1.1 2002/03/17 09:03:06 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 gtk +import GDK +import gnome.ui +import libglade +import string +import re + +import bindings +import keyval_util + +class BindingEditor: + # the properties instance for which we're editing bindings + props = None; + + # our bindings set + bindings = None; + + # the binding editor panel + bindPanel = None; + + # the grab window + grabWindow = None; + + # the index of the binding we're editing + index = -1; + + # the binding we're editing + binding = None; + + # the key entry field + keyField = None; + + # the name entry field + nameField = None; + + # the argument type selector + argTypeSel = None; + + # the URL menu item of the arg selection widget + urlItem = None; + + # the argument entry field + argField = None; + + def __init__ (self, xmlui, props, bindings): + # keep these around for later + self.props = props; + self.bindings = bindings; + + # get a reference to some widgets + self.bindPanel = xmlui.get_widget("binding"); + self.grabWindow = xmlui.get_widget("grab"); + self.keyField = xmlui.get_widget("key"); + self.nameField = xmlui.get_widget("name"); + self.argTypeSel = xmlui.get_widget("type"); + self.urlItem = self.argTypeSel.get_menu().get_active(); + self.argField = xmlui.get_widget("argument"); + + # wire up our handlers + nameFuncMap = {}; + for key in dir(self.__class__): + nameFuncMap[key] = getattr(self, key); + xmlui.signal_autoconnect(nameFuncMap); + + # + # Instructs the binding panel to display itself, configured for + # editing the supplied binding. + # + def editBinding (self, index): + # keep a reference to the binding + self.index = index; + self.binding = self.bindings.bindings[index]; + self.populateAndShow(self.binding); + + # + # Instructs the binding panel to create a blank binding and make that + # available for editing by the user. + # + def createBinding (self): + # create a blank binding and edit it + self.index = -1; + self.binding = bindings.Binding("", bindings.Binding.URL, "", ""); + self.populateAndShow(self.binding); + + # + # A helper function used to populate our widgets and show the dialog. + # + def populateAndShow (self, binding): + print "populating from binding " + binding.to_string(); + # populate our widgets with the binding values + self.keyField.set_text(binding.key); + self.nameField.set_text(binding.name); + self.argTypeSel.set_history(binding.type); + self.argField.set_text(binding.argument); + + # and show the bind dialog + self.bindPanel.show(); + + # + # A callback, called when the OK button is clicked. + # + def on_ok_clicked (self, button): + # figure out which type menu item is selected; what a hack + active = self.argTypeSel.get_menu().get_active(); + if (active == self.urlItem): + type = bindings.Binding.URL; + else: + type = bindings.Binding.EXEC; + + # repopulate our binding + modified = self.binding.update(self.keyField.get_text(), type, + self.nameField.get_text(), + self.argField.get_text()); + + # let the props panel know if there were any modifications + if (modified): + if (self.index >= 0): + self.props.updated(self.index); + else: + self.props.created(self.binding); + + # hide the binding panel + self.bindPanel.hide(); + + # + # A callback, called when the cancel button is clicked. + # + def on_cancel_clicked (self, button): + # hide the binding panel + self.bindPanel.hide(); + + def on_key_key_press_event (self, textfield, event): + # ignore plain or shifted keystrokes + if (event.state > 1): + # convert the key press into a string + keystr = keyval_util.convert_keyval_state_to_string( + event.keyval, event.state); + textfield.set_text(keystr); + return gtk.TRUE; diff --git a/projects/lookuplet/src/python/keyval_util.py b/projects/lookuplet/src/python/keyval_util.py new file mode 100644 index 00000000..d3435e4d --- /dev/null +++ b/projects/lookuplet/src/python/keyval_util.py @@ -0,0 +1,57 @@ +# +# $Id: keyval_util.py,v 1.1 2002/03/17 09:03:06 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 string +import GDK + +# these are used when doing our conversion +_CODES = [ GDK.CONTROL_MASK, GDK.LOCK_MASK, GDK.SHIFT_MASK, GDK.MOD1_MASK, + GDK.MOD2_MASK, GDK.MOD3_MASK ]; +_NAMES = [ "Control", "Lock", "Shift", "Mod1", "Mod2", "Mod3" ]; + +# +# Converts a (keyval, state) pair to a human readable string. +# +def convert_keyval_state_to_string (keyval, state): + # bail if we've got an invalid kesym + if (keyval == 0): + return "None"; + + # start with the empty string + modstr = ""; + + # we'd like to ask GDK to convert the key itself to a string, but they + # don't wrap those functions. dooh! + # key = GDK.keyval_name(keyval); + + # so we do this hack instead + if (keyval < 0 or keyval > 255): + return "None"; + key = string.lower(chr(keyval)); + + # we have to handle the modifiers ourselves + i = 0; + for code in _CODES: + if (state & code): + modstr += _NAMES[i]; + modstr += "-"; + i += 1; + + return modstr + key; diff --git a/projects/lookuplet/src/python/lookuplet.py b/projects/lookuplet/src/python/lookuplet.py new file mode 100644 index 00000000..3206695f --- /dev/null +++ b/projects/lookuplet/src/python/lookuplet.py @@ -0,0 +1,173 @@ +# +# $Id: lookuplet.py,v 1.1 2002/03/17 09:03:06 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.applet +import gtk +import GDK +import gnome.ui +import gnome.uiconsts +import string +import re + +import bindings +import keyval_util +import properties + +class Lookuplet: + "Handles the primary setup and operation of the lookuplet application." + + # in theory these should be defined by GDK but seem not to be + SELECTION_PRIMARY = 1; + CURRENT_TIME = 0; + + # a reference to our key bindings + bindings = None; + + # our about box + about = None; + + # a reference to the properties manager + props = None; + + # whether or not we're running as a Gnome applet + appletMode = 0; + + 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; + self.props = props; + self.appletMode = appletMode; + + # wire up our handlers + nameFuncMap = {}; + for key in dir(self.__class__): + nameFuncMap[key] = getattr(self, key); + xmlui.signal_autoconnect(nameFuncMap); + + # if we're in applet mode, extract the UI and stick it into an + # applet widget + if (appletMode): + applet = gnome.applet.AppletWidget("lookuplet"); + mainbox = xmlui.get_widget("mainbox"); + # lose the border + mainbox.set_border_width(0); + # and hide the prefs button + prefs = xmlui.get_widget("prefs"); + prefs.hide(); + # remove everything from the window + window.remove(mainbox); + # and add it to the applet + applet.add(mainbox); + # register our menu items + applet.register_stock_callback( + "properties", gnome.uiconsts.STOCK_MENU_PROP, + "Properties...", self.on_props_selected, None); + applet.register_stock_callback( + "about", gnome.uiconsts.STOCK_MENU_ABOUT, + "About...", self.on_about_selected, None); + applet.show(); + + else: + window.show(); + + # request the selection + if (self.string_atom == None): + self.string_atom = gtk.atom_intern("STRING", gtk.FALSE); + query.selection_convert(self.SELECTION_PRIMARY, + self.string_atom, + self.CURRENT_TIME); + + # put the focus in the query box + query.grab_focus(); + + def on_props_selected (self, one, two): + self.props.editProperties(); + + def on_about_selected (self, one, two): + self.about.show(); + + 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; + + # 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 (binding != None): + # if we found one, invoke it + binding.invoke(query.get_text()); + if (self.appletMode): + query.set_text(""); + else: + gtk.mainquit(); + return gtk.TRUE; + + else: + # otherwise, let GTK know that we didn't handle the key press + return gtk.FALSE; + + def on_query_selection_received (self, query, selection_data, data): + if (selection_data.length < 0): + # print "Selection retrieval failed."; + return; + + if (selection_data.type != GDK.SELECTION_TYPE_STRING): + print "Selection target not returned as a string."; + return; + + print "Got selection '%s'." % selection_data.data; + + # prune spaces from the end of the text + text = string.strip(selection_data.data); + # compress whitespace (and convert newlines to spaces) + text = re.sub("[ \r\n]+", " ", text); + + # 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); + + # if this is a URL, we want to eat whitespace + # if (is_url): + # text.tr!(" ", ""); + + if (cmp(text, selection_data.data)): + # print "Setting trimmed text '" + text + "'."; + query.set_text(text); + + query.select_region(0, len(text)); + # print "Selected from %d to %d." % (0, len(text)); + + def exit_lookuplet (self, button): + gtk.mainquit(); diff --git a/projects/lookuplet/src/python/properties.py b/projects/lookuplet/src/python/properties.py new file mode 100644 index 00000000..4a6a793b --- /dev/null +++ b/projects/lookuplet/src/python/properties.py @@ -0,0 +1,133 @@ +# +# $Id: properties.py,v 1.1 2002/03/17 09:03:06 mdb Exp $ +# +# lookuplet - a utility for quickly looking up information +# Copyright (C) 2001-2002 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 gtk +import GDK +import gnome.ui +import libglade +import string +import re + +import edit_binding + +class Properties: + # our configured key bindings + bindings = None; + + # the properties panel + propsPanel = None; + + # our binding editor + bindEditor = None; + + # the bindings list + bindList = None; + + # the edit button + editButton = None; + + # the delete button + deleteButton = None; + + # the selected binding index + selection = -1; + + def __init__ (self, xmlui, bindings): + # keep a handle on our bindings + self.bindings = bindings; + + # create our binding editor + self.bindEditor = edit_binding.BindingEditor(xmlui, self, bindings); + + # get a reference to some widgets + self.propsPanel = xmlui.get_widget("properties"); + self.editButton = xmlui.get_widget("edit"); + self.deleteButton = xmlui.get_widget("delete"); + self.bindList = xmlui.get_widget("bindings"); + + # wire up our handlers + nameFuncMap = {}; + for key in dir(self.__class__): + nameFuncMap[key] = getattr(self, key); + xmlui.signal_autoconnect(nameFuncMap); + + # configure our properties panel with the loaded bindings + for binding in bindings.bindings: + self.bindList.append([binding.key, binding.name]); + + def editProperties (self): + self.propsPanel.show(); + return; + + def updated (self, index): + binding = self.bindings.bindings[index]; + # refresh the display for the specified index + self.bindList.set_text(index, 0, binding.key); + self.bindList.set_text(index, 1, binding.name); + # and make a note that we've updated ourselves + self.propsPanel.changed(); + + def created (self, binding): + # add the new binding to our list + self.bindings.bindings.append(binding); + # add it to the display + self.bindList.append([binding.key, binding.name]); + # and make a note that we've updated ourselves + self.propsPanel.changed(); + + def on_properties_apply (self, panel, page): + if (page == 0): + self.bindings.flush(); + + def on_bindings_select_row (self, clist, row, column, event): + # enable our buttons + self.editButton.set_sensitive(gtk.TRUE); + self.deleteButton.set_sensitive(gtk.TRUE); + + # make a note of the selected binding index + self.selection = row; + + # make like they pressed the edit button on a double click + if (event.type == GDK._2BUTTON_PRESS): + self.on_edit_clicked(None); + + def on_bindings_unselect_row (self, clist, row, column, event): + # disable our buttons + self.editButton.set_sensitive(gtk.FALSE); + self.deleteButton.set_sensitive(gtk.FALSE); + + def on_add_clicked (self, button): + # show the binding creation dialog + self.bindEditor.createBinding(); + return; + + def on_edit_clicked (self, button): + # tell the binding editor to edit this binding + self.bindEditor.editBinding(self.selection); + return; + + def on_delete_clicked (self, button): + # remove the binding from the display and our bindings list + binding = self.bindings.bindings[self.selection]; + self.bindList.remove(self.selection) + self.bindings.bindings.remove(binding); + self.selection = -1; + self.propsPanel.changed(); + return;