# HG changeset patch # User Erik Grinaker # Date 1090955641 0 # Node ID 3794d9b384465462491f9ea3fa821f7572c9ba43 # Parent 9b3c9903350d6654128a459470db753e4a05b5d7 cleaned up widget code, and added docstrings diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 ChangeLog --- a/ChangeLog Tue Jul 27 17:54:30 2004 +0000 +++ b/ChangeLog Tue Jul 27 19:14:01 2004 +0000 @@ -6,6 +6,9 @@ * rewrote the app configuration handling + * cleaned up the widget code, and added docstrings to all + classes, methods and functions + 2004-07-15 Erik Grinaker * when adding an entry the default type is Generic (not Folder) diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 TODO --- a/TODO Tue Jul 27 17:54:30 2004 +0000 +++ b/TODO Tue Jul 27 19:14:01 2004 +0000 @@ -7,7 +7,6 @@ - string cleanups - program-launchers for misc account types (announce on rvl-list when done in svn) -- add docstrings to all objects, methods and functions - export to XHTML/CSS files 0.4.x: diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/data.py --- a/src/lib/data.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/data.py Tue Jul 27 19:14:01 2004 +0000 @@ -117,8 +117,6 @@ def __cb_notify(self, client, id, entry, data): "Callback for handling notifications" - print "Config callbacks: ", len(self.callbacks), id - # get the value contents value = entry.get_value() diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/dialog.py --- a/src/lib/dialog.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/dialog.py Tue Jul 27 19:14:01 2004 +0000 @@ -31,6 +31,7 @@ # first we define a few base classes class Dialog(gtk.Dialog): + "Base class for dialogs" def __init__(self, parent, title, buttons, default = None): gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR) @@ -50,23 +51,34 @@ def __cb_keypress(self, widget, data): + "Callback for handling keypresses" + + # close the dialog on Escape if data.keyval == 65307: self.response(gtk.RESPONSE_CLOSE) def get_button(self, index): + "Get one of the dialogs buttons" + buttons = self.action_area.get_children() - return index < len(buttons) and buttons[index] or None + + if index < len(buttons): + return buttons[index] + + else: + return None class Hig(Dialog): + "A HIG-ified message dialog" def __init__(self, parent, pritext, sectext, stockimage, buttons, default = None): Dialog.__init__(self, parent, "", buttons, default) # hbox separating dialog image and contents - hbox = gtk.HBox() + hbox = revelation.widget.HBox() hbox.set_spacing(12) hbox.set_border_width(6) self.vbox.pack_start(hbox) @@ -78,7 +90,7 @@ hbox.pack_start(image, gtk.FALSE, gtk.FALSE) # set up main content area - self.contents = gtk.VBox() + self.contents = revelation.widget.VBox() self.contents.set_spacing(10) hbox.pack_start(self.contents) @@ -88,6 +100,8 @@ def run(self): + "Display the dialog" + self.show_all() response = gtk.Dialog.run(self) self.destroy() @@ -97,6 +111,7 @@ class Property(Dialog): + "A property dialog" def __init__(self, parent, title, buttons, default = None): Dialog.__init__(self, parent, title, buttons, default) @@ -109,6 +124,8 @@ def add_section(self, title, description = None): + "Adds an input section to the dialog" + section = revelation.widget.InputSection(title, self.sizegroup, description) self.vbox.pack_start(section) return section @@ -118,6 +135,7 @@ # simple message dialogs class Error(Hig): + "Displays an error message" def __init__(self, parent, pritext, sectext): Hig.__init__( @@ -152,6 +170,7 @@ class FileOverwrite(Hig): + "Asks for file overwrite confirmation" def __init__(self, parent, file): Hig.__init__( @@ -163,6 +182,8 @@ def run(self): + "Displays the dialog" + response = Hig.run(self) if response == gtk.RESPONSE_OK: @@ -174,6 +195,7 @@ class RemoveEntry(Hig): + "Asks for confirmation when removing entries" def __init__(self, parent, pritext, sectext): Hig.__init__( @@ -184,11 +206,14 @@ def run(self): + "Displays the dialog" + return Hig.run(self) == gtk.RESPONSE_OK class SaveChanges(Hig): + "Asks the user if she wants to save her changes" def __init__(self, parent, pritext, sectext): Hig.__init__( @@ -196,7 +221,10 @@ [ [ revelation.stock.STOCK_DISCARD, gtk.RESPONSE_CLOSE ], [ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL ], [ gtk.STOCK_SAVE, gtk.RESPONSE_OK ] ] ) + def run(self): + "Displays the dialog" + response = Hig.run(self) if response == gtk.RESPONSE_CANCEL: @@ -220,8 +248,7 @@ def add_widget(self, title, widget): "Adds a widget to the file selector" - hbox = gtk.HBox() - hbox.set_spacing(5) + hbox = revelation.widget.HBox() self.main_vbox.pack_start(hbox) if title is not None: @@ -320,6 +347,7 @@ # more complex dialogs class About(gnome.ui.About): + "An about dialog" def __init__(self, parent): gnome.ui.About.__init__( @@ -334,11 +362,14 @@ def run(self): + "Displays the dialog" + self.show_all() class EditEntry(Property): + "A dialog for editing entries" def __init__(self, parent, title, entry = None): Property.__init__( @@ -375,15 +406,26 @@ def __cb_entry_description_changed(self, widget, data = None): + "Updates the description data" + self.entry.description = widget.get_text() + def __cb_entry_name_changed(self, widget, data = None): + "Updates the name data" + self.entry.name = widget.get_text() + def __cb_entry_field_changed(self, widget, id): + "Updates field data" + self.entry.set_field(id, widget.get_text()) + def __cb_dropdown_changed(self, object): + "Updates the entry type data" + type = self.dropdown.get_active_item().type if type != self.entry.type: @@ -392,6 +434,8 @@ def run(self): + "Displays the dialog" + if Property.run(self) == gtk.RESPONSE_OK: if self.entry.name == "": @@ -409,10 +453,14 @@ def set_typechange_allowed(self, allow): + "Sets whether to allow type changes" + self.dropdown.set_sensitive(allow) def update(self, type = None): + "Updates the dialog to a given type" + if len(self.vbox.get_children()) > 2: self.vbox.get_children().pop(1).destroy() @@ -434,14 +482,12 @@ self.tooltips.set_tip(entry, field.description) if field.id == revelation.entry.FIELD_GENERIC_PASSWORD: - hbox = gtk.HBox() - hbox.set_spacing(6) + hbox = revelation.widget.HBox() section.add_inputrow(field.name, hbox) hbox.pack_start(entry) - button = gtk.Button("Generate") - button.connect("clicked", lambda w: entry.set_text(revelation.misc.generate_password())) + button = revelation.widget.Button("Generate", lambda w: entry.set_text(revelation.misc.generate_password())) hbox.pack_start(button, gtk.FALSE, gtk.FALSE) @@ -501,18 +547,23 @@ def __cb_entry_changed(self, widget, data = None): + "Sets the Find button sensitivity based on entry contents" + active = len(self.entry_phrase.get_text()) > 0 self.get_button(0).set_sensitive(active) self.get_button(1).set_sensitive(active) def run(self): + "Displays the dialog" + self.show_all() return Property.run(self) class Password(Hig): + "A dialog which asks for passwords" def __init__(self, parent, title, text, current = gtk.TRUE, new = gtk.FALSE): Hig.__init__( @@ -548,6 +599,8 @@ def __cb_entry_changed(self, widget, data = None): + "Sets the OK button sensitivity based on the entries" + if ( (self.entry_password is None or self.entry_password.get_text() != "") and (self.entry_new is None or self.entry_new.get_text() != "") @@ -559,6 +612,8 @@ def run(self): + "Displays the dialog" + while 1: self.show_all() diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/entry.py --- a/src/lib/entry.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/entry.py Tue Jul 27 19:14:01 2004 +0000 @@ -259,6 +259,7 @@ class Entry(gobject.GObject): + "An entry object" def __init__(self, type = ENTRY_FOLDER): gobject.GObject.__init__(self) @@ -276,10 +277,14 @@ def copy(self): + "Create a copy of the entry" + return copy.deepcopy(self) def get_field(self, id): + "Get one of the entrys fields" + try: return self.fields[id] @@ -288,6 +293,8 @@ def get_fields(self): + "Get all the entrys fields" + fields = [] for id in ENTRYDATA[self.type]["fields"]: fields.append(self.get_field(id)) @@ -296,18 +303,26 @@ def get_updated_age(self): + "Get the age of an entry" + return revelation.misc.timediff_simple(self.updated) def get_updated_iso(self): + "Get the update time in ISO format" + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.updated)) def has_field(self, id): + "Checks if the entry has a particular field" + return self.fields.has_key(id) def set_field(self, id, value): + "Sets one of the entries fields to a value" + if not self.fields.has_key(id): raise EntryFieldError @@ -315,6 +330,7 @@ def set_type(self, type): + "Sets the type of entry" # backwards-compatability if type == "usenet": @@ -343,6 +359,7 @@ class Field(gobject.GObject): + "An entry field object" def __init__(self, id = None, value = ""): gobject.GObject.__init__(self) @@ -353,3 +370,13 @@ self.name = FIELDDATA[id]["name"] self.description = FIELDDATA[id]["description"] + + +def get_entry_list(): + "Returns a sorted list of all available entry types" + + typelist = ENTRYDATA.keys() + typelist.sort() + + return typelist + diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/misc.py --- a/src/lib/misc.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/misc.py Tue Jul 27 19:14:01 2004 +0000 @@ -27,14 +27,18 @@ def escape_markup(string): + "Escapes a string so it can be placed in a markup string" + string = string.replace("&", "&") string = string.replace("<", "<") string = string.replace(">", ">") + return string def generate_password(): + "Generates a random password" def get_random_item(list): return list[int(random.random() * len(list))] @@ -100,6 +104,8 @@ def timediff_simple(start, end = None): + "Returns an approximate time difference in human-readable format" + if end is None: end = time.time() diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/stock.py --- a/src/lib/stock.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/stock.py Tue Jul 27 19:14:01 2004 +0000 @@ -80,6 +80,7 @@ class IconFactory(gtk.IconFactory): + "A stock icon factory" def __init__(self, widget): gtk.IconFactory.__init__(self) diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/ui.py --- a/src/lib/ui.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/ui.py Tue Jul 27 19:14:01 2004 +0000 @@ -26,10 +26,11 @@ import gobject, gtk, gtk.gdk, gnome.ui, revelation, time, os, gconf -class DataView(gtk.VBox): +class DataView(revelation.widget.VBox): + "An UI component for displaying an entry" def __init__(self): - gtk.VBox.__init__(self) + revelation.widget.VBox.__init__(self) self.set_spacing(15) self.set_border_width(10) @@ -40,13 +41,20 @@ def clear(self, force = gtk.FALSE): + "Clears the data view" + + # only clear if containing an entry, or if forced if force == gtk.TRUE or self.entry is not None: + self.entry = None + for child in self.get_children(): child.destroy() def display_entry(self, entry): + "Displays an entry" + if entry is None: self.clear() return @@ -55,8 +63,7 @@ self.entry = entry # set up metadata display - metabox = gtk.VBox() - metabox.set_spacing(4) + metabox = revelation.widget.VBox() self.pack_start(metabox) metabox.pack_start(revelation.widget.ImageLabel( @@ -73,8 +80,7 @@ if field.value == "": continue - row = gtk.HBox() - row.set_spacing(5) + row = revelation.widget.HBox() rows.append(row) label = revelation.widget.Label("" + revelation.misc.escape_markup(field.name) + ":", gtk.JUSTIFY_RIGHT) @@ -100,7 +106,7 @@ if len(rows) > 0: - fieldlist = gtk.VBox() + fieldlist = revelation.widget.VBox() fieldlist.set_spacing(2) self.pack_start(fieldlist) @@ -115,6 +121,8 @@ def display_info(self): + "Displays info about the application" + self.clear(gtk.TRUE) self.pack_start(revelation.widget.ImageLabel( @@ -133,13 +141,16 @@ def pack_start(self, widget): + "Adds a widget to the data view" + alignment = gtk.Alignment(0.5, 0.5, 0, 0) alignment.add(widget) - gtk.VBox.pack_start(self, alignment) + revelation.widget.VBox.pack_start(self, alignment) class Tree(revelation.widget.TreeView): + "The entry tree" def __init__(self, datastore = None): revelation.widget.TreeView.__init__(self, datastore) @@ -161,12 +172,18 @@ def __cb_row_collapsed(self, object, iter, extra): + "Updates folder icons when collapsed" + self.model.set_folder_state(iter, gtk.FALSE) def __cb_row_expanded(self, object, iter, extra): + "Updates folder icons when expanded" + + # make sure all children are collapsed (some may have lingering expand icons) for i in range(self.model.iter_n_children(iter)): child = self.model.iter_nth_child(iter, i) + if self.row_expanded(self.model.get_path(child)) == gtk.FALSE: self.model.set_folder_state(child, gtk.FALSE) @@ -174,6 +191,8 @@ def set_model(self, model): + "Sets the model displayed by the tree view" + revelation.widget.TreeView.set_model(self, model) if model is not None: diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/lib/widget.py --- a/src/lib/widget.py Tue Jul 27 17:54:30 2004 +0000 +++ b/src/lib/widget.py Tue Jul 27 19:14:01 2004 +0000 @@ -26,69 +26,21 @@ import gobject, gtk, gtk.gdk, gnome.ui, revelation, os.path, gconf -# first, some simple subclasses - replacements for gtk widgets -class App(gnome.ui.App): +# simple subclasses for gtk widgets - def __init__(self, appname): - gnome.ui.App.__init__(self, appname, appname) - self.appname = appname +class Button(gtk.Button): + "A normal button" - self.toolbar = Toolbar() - self.toolbar.connect("hide", self.__cb_toolbar_hide) - self.toolbar.connect("show", self.__cb_toolbar_show) - self.set_toolbar(self.toolbar) + def __init__(self, label, callback = None): + gtk.Button.__init__(self, label) - self.statusbar = gnome.ui.AppBar(gtk.FALSE, gtk.TRUE, gnome.ui.PREFERENCES_USER) - self.set_statusbar(self.statusbar) - - self.accelgroup = gtk.AccelGroup() - self.add_accel_group(self.accelgroup) - - - def __cb_toolbar_hide(self, object, data = None): - self.get_dock_item_by_name("Toolbar").hide() - - - def __cb_toolbar_show(self, object, data = None): - self.get_dock_item_by_name("Toolbar").show() - - - def __cb_menudesc(self, object, item, show): - if show: - self.statusbar.set_status(item.get_data("description")) - else: - self.statusbar.set_status("") - - - def __create_itemfactory(self, widget, accelgroup, items): - itemfactory = MenuFactory(widget, accelgroup) - itemfactory.create_items(items) - itemfactory.connect("item-selected", self.__cb_menudesc, gtk.TRUE) - itemfactory.connect("item-deselected", self.__cb_menudesc, gtk.FALSE) - return itemfactory - - - def create_menu(self, menuitems): - self.if_menu = self.__create_itemfactory(gtk.MenuBar, self.accelgroup, menuitems) - self.set_menus(self.if_menu.get_widget("
")) - - - def popup(self, menuitems, x, y, button, time): - itemfactory = self.__create_itemfactory(gtk.Menu, self.accelgroup, menuitems) - itemfactory.popup(x, y, button, time) - - - def run(self): - self.show_all() - gtk.main() - - - def set_title(self, title): - gnome.ui.App.set_title(self, title + " - " + self.appname) + if callback is not None: + self.connect("clicked", callback) class CheckButton(gtk.CheckButton): + "A checkbutton" def __init__(self, label = None): gtk.CheckButton.__init__(self, label) @@ -96,14 +48,18 @@ class Entry(gtk.Entry): + "An input entry" def __init__(self, text = None): gtk.Entry.__init__(self) + self.set_activates_default(gtk.TRUE) self.set_text(text) def set_text(self, text): + "Sets the entry contents" + if text == None: text = "" @@ -111,49 +67,8 @@ -class FileEntry(gtk.HBox): - - def __init__(self, title, filename = None): - gtk.HBox.__init__(self) - self.set_spacing(5) - self.title = title - - self.entry = Entry() - self.pack_start(self.entry) - - self.button = gtk.Button("Browse...") - self.button.connect("clicked", self.__cb_filesel) - self.pack_start(self.button, gtk.FALSE, gtk.FALSE) - - if filename is not None: - self.set_filename(filename) - - - def __cb_filesel(self, object, data = None): - fsel = gtk.FileSelection(self.title) - fsel.set_modal(gtk.TRUE) - fsel.set_filename(self.get_filename()) - - fsel.show_all() - response = fsel.run() - - if response == gtk.RESPONSE_OK: - self.set_filename(fsel.get_filename()) - - fsel.destroy() - - - def get_filename(self): - return self.entry.get_text() - - - def set_filename(self, filename): - self.entry.set_text(os.path.normpath(filename)) - self.entry.set_position(-1) - - - class HPaned(gtk.HPaned): + "Horizontal pane" def __init__(self, content_left = None, content_right = None, position = None): gtk.HPaned.__init__(self) @@ -170,7 +85,21 @@ +class HBox(gtk.HBox): + "A horizontal container" + + def __init__(self, *args): + gtk.HBox.__init__(self) + self.set_spacing(6) + self.set_border_width(0) + + for widget in args: + self.pack_start(widget) + + + class HRef(gnome.ui.HRef): + "A button containing a link" def __init__(self, url, text): gnome.ui.HRef.__init__(self, url, text) @@ -179,6 +108,7 @@ class Image(gtk.Image): + "A widget for displaying an image" def __init__(self, stock = None, size = None): gtk.Image.__init__(self) @@ -189,6 +119,7 @@ class ImageMenuItem(gtk.ImageMenuItem): + "A menuitem with a stock icon" def __init__(self, stock, text = None): gtk.ImageMenuItem.__init__(self, stock) @@ -199,24 +130,30 @@ if text is not None: self.set_text(text) + def set_stock(self, stock): + "Set the stock item to use as icon" + self.image.set_from_stock(stock, gtk.ICON_SIZE_MENU) + def set_text(self, text): + "Set the item text" + self.label.set_text(text) class Label(gtk.Label): + "A text label" def __init__(self, text = None, justify = gtk.JUSTIFY_LEFT): gtk.Label.__init__(self) + self.set_text(text) + self.set_justify(justify) self.set_use_markup(gtk.TRUE) self.set_line_wrap(gtk.TRUE) - self.set_text(text) - - self.set_justify(justify) if justify == gtk.JUSTIFY_LEFT: self.set_alignment(0, 0.5) @@ -227,48 +164,17 @@ elif justify == gtk.JUSTIFY_RIGHT: self.set_alignment(1, 0.5) + def set_text(self, text): + "Sets the text of the label" + if text is not None: gtk.Label.set_markup(self, text) -class MenuFactory(gtk.ItemFactory): - - def __init__(self, widget, accelgroup): - gtk.ItemFactory.__init__(self, widget, "
", accelgroup) - - def __cb_select(self, object): - self.emit("item-selected", object) - - def __cb_deselect(self, object): - self.emit("item-deselected", object) - - - def create_items(self, items): - - # strip description from items, and create the items - ifitems = [] - for item in items: - ifitems.append(item[0:2] + item[3:]) - - gtk.ItemFactory.create_items(self, ifitems) - - # set up description for items - for item in items: - if item[5] in ["", "", ""]: - widget = self.get_widget("
" + item[0].replace("_", "")) - widget.set_data("description", item[2]) - widget.connect("select", self.__cb_select) - widget.connect("deselect", self.__cb_deselect) - - -gobject.signal_new("item-selected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) -gobject.signal_new("item-deselected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) - - - class OptionMenu(gtk.OptionMenu): + "An option menu (dropdown)" def __init__(self, menu = None): gtk.OptionMenu.__init__(self) @@ -278,35 +184,59 @@ self.set_menu(menu) + def append_item(self, item): + "Appends an item to the dropdown menu" + self.menu.append(item) + if len(self.menu.get_children()) == 1: self.set_history(0) + def get_active_item(self): + "Returns the currently active menu item" + return self.menu.get_children()[self.get_history()] + def get_item(self, index): + "Get an item from the menu" + items = self.menu.get_children() - return index < len(items) and items[index] or None + + if index > len(items): + return items[index] + + else: + return None + def set_active_item(self, activeitem): + "Set a menu item as the currently active item" + items = self.get_menu().get_children() for i, item in zip(range(len(items)), items): if activeitem == item: self.set_history(i) + def set_menu(self, menu): + "Set the menu of the dropdown" + self.menu = menu gtk.OptionMenu.set_menu(self, menu) + class ScrolledWindow(gtk.ScrolledWindow): + "A scrolled window which partially displays a different widget" def __init__(self, contents = None): gtk.ScrolledWindow.__init__(self) + self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) if contents is not None: @@ -315,14 +245,39 @@ class SpinButton(gtk.SpinButton): + "An entry for numbers" def __init__(self, adjustment = None, climb_rate = 0.0, digits = 0): gtk.SpinButton.__init__(self, adjustment, climb_rate, digits) + self.set_increments(1, 1) self.set_numeric(gtk.TRUE) +class Statusbar(gtk.Statusbar): + "A window statusbar" + + def __init__(self): + gtk.Statusbar.__init__(self) + + self.contextid = self.get_context_id("statusbar") + + + def clear(self): + "Clears the statusbar" + + self.pop(self.contextid) + + + def set_status(self, text): + "Displays a text in the statusbar" + + self.clear() + self.push(self.contextid, text) + + + class TreeStore(gtk.TreeStore): "An enhanced gtk.TreeStore" @@ -428,6 +383,7 @@ class TreeView(gtk.TreeView): + "A widget for displaying a tree" def __init__(self, model = None): gtk.TreeView.__init__(self, model) @@ -442,6 +398,9 @@ def __cb_buttonpress(self, object, data): + "Callback for handling mouse clicks" + + # handle doubleclick if data.button == 1 and data.type == gtk.gdk._2BUTTON_PRESS: path = self.get_path_at_pos(int(data.x), int(data.y)) @@ -452,70 +411,98 @@ def __cb_keypress(self, object, data): + "Callback for handling key presses" + + # expand/collapse an item when space is pressed if data.keyval == 32: self.toggle_expanded(self.get_active()) def collapse_row(self, iter): + "Collapse a tree row" + gtk.TreeView.collapse_row(self, self.model.get_path(iter)) def expand_row(self, iter): + "Expand a tree row" + if iter is not None and self.model.iter_n_children(iter) > 0: gtk.TreeView.expand_row(self, self.model.get_path(iter), gtk.FALSE) def expand_to_iter(self, iter): + "Expand all items up to and including a given iter" + path = self.model.get_path(iter) + for i in range(len(path)): iter = self.model.get_iter(path[0:i]) self.expand_row(iter) def get_active(self): + "Get the currently active row" + iter = self.model.get_iter(self.get_cursor()[0]) - if iter == None or self.selection.iter_is_selected(iter) == gtk.FALSE: + if iter is None or self.selection.iter_is_selected(iter) == gtk.FALSE: return None return iter def get_selected(self): + "Get a list of currently selected rows" + list = [] self.selection.selected_foreach(lambda model, path, iter: list.append(iter)) + return list def select(self, iter): + "Select a particular row" + if iter == None: self.unselect_all() + else: self.expand_to_iter(iter) self.set_cursor(self.model.get_path(iter)) def select_all(self): + "Select all rows in the tree" + self.selection.select_all() self.selection.emit("changed") self.emit("cursor_changed") def set_model(self, model): + "Change the tree model which is being displayed" + gtk.TreeView.set_model(self, model) self.model = model def toggle_expanded(self, iter): - if iter == None: + "Toggle the expanded state of a row" + + if iter is None: return + elif self.row_expanded(self.model.get_path(iter)): self.collapse_row(iter) + else: self.expand_row(iter) def unselect_all(self): + "Unselect all rows in the tree" + self.selection.unselect_all() self.selection.emit("changed") self.emit("cursor_changed") @@ -538,14 +525,108 @@ return self.insert_stock(stock, tooltip, None, callback, "", -1) + +class VBox(gtk.VBox): + "A vertical container" + + def __init__(self, *args): + gtk.VBox.__init__(self) + self.set_spacing(6) + self.set_border_width(0) + + for widget in args: + self.pack_start(widget) + + + # more extensive custom widgets + +class App(gnome.ui.App): + "An application window" + + def __init__(self, appname): + gnome.ui.App.__init__(self, appname, appname) + self.appname = appname + + self.toolbar = Toolbar() + self.set_toolbar(self.toolbar) + self.toolbar.connect("hide", self.__cb_toolbar_hide) + self.toolbar.connect("show", self.__cb_toolbar_show) + + self.statusbar = Statusbar() + self.set_statusbar(self.statusbar) + + self.accelgroup = gtk.AccelGroup() + self.add_accel_group(self.accelgroup) + + + def __cb_toolbar_hide(self, object, data = None): + "Hides the toolbar dock when the toolbar is hidden" + + self.get_dock_item_by_name("Toolbar").hide() + + + def __cb_toolbar_show(self, object, data = None): + "Shows the toolbar dock when the toolbar is hidden" + + self.get_dock_item_by_name("Toolbar").show() + + + def __cb_menudesc(self, object, item, show): + "Displays menu descriptions in the statusbar" + + if show: + self.statusbar.set_status(item.get_data("description")) + else: + self.statusbar.set_status("") + + + def __create_itemfactory(self, widget, accelgroup, items): + "Creates an item factory" + + itemfactory = MenuFactory(widget, accelgroup) + itemfactory.create_items(items) + itemfactory.connect("item-selected", self.__cb_menudesc, gtk.TRUE) + itemfactory.connect("item-deselected", self.__cb_menudesc, gtk.FALSE) + + return itemfactory + + + def create_menu(self, menuitems): + "Creates an application menu" + + self.if_menu = self.__create_itemfactory(gtk.MenuBar, self.accelgroup, menuitems) + self.set_menus(self.if_menu.get_widget("
")) + + + def popup(self, menuitems, x, y, button, time): + "Displays a popup menu" + + itemfactory = self.__create_itemfactory(gtk.Menu, self.accelgroup, menuitems) + itemfactory.popup(x, y, button, time) + + + def run(self): + "Runs the application" + + self.show_all() + gtk.main() + + + def set_title(self, title): + "Sets the window title" + + gnome.ui.App.set_title(self, title + " - " + self.appname) + + + class EntryDropdown(OptionMenu): + "A dropdown menu with all available entry types" def __init__(self): revelation.widget.OptionMenu.__init__(self) - typelist = revelation.entry.ENTRYDATA.keys() - typelist.sort() + typelist = revelation.entry.get_entry_list() typelist.remove(revelation.entry.ENTRY_FOLDER) typelist.insert(0, revelation.entry.ENTRY_FOLDER) @@ -559,24 +640,83 @@ def get_type(self): - item = self.get_active_item() - return hasattr(item, "type") and item.type or None + "Get the currently active type" + + try: + return self.get_active_item().type + + except AttributeError: + return None def set_type(self, type): + "Set the active type" + for item in self.get_menu().get_children(): - if hasattr(item, "type") and item.type == type: - self.set_active_item(item) + + try: + if item.type == type: + self.set_active_item(item) + + except AttributeError: + pass + + + +class FileEntry(HBox): + "An entry for file names with a Browse button" + + def __init__(self, title, filename = None): + HBox.__init__(self) + self.title = title + + self.entry = Entry() + self.pack_start(self.entry) + + self.button = gtk.Button("Browse...", self.__cb_filesel) + self.pack_start(self.button, gtk.FALSE, gtk.FALSE) + + if filename is not None: + self.set_filename(filename) + + + def __cb_filesel(self, object, data = None): + "Displays a file selector when Browse is pressed" + + fsel = gtk.FileSelection(self.title) + fsel.set_modal(gtk.TRUE) + fsel.set_filename(self.get_filename()) + + fsel.show_all() + response = fsel.run() + + if response == gtk.RESPONSE_OK: + self.set_filename(fsel.get_filename()) + + fsel.destroy() + + + def get_filename(self): + "Gets the current filename" + + return self.entry.get_text() + + + def set_filename(self, filename): + "Sets the filename of the entry" + + self.entry.set_text(os.path.normpath(filename)) + self.entry.set_position(-1) class ImageLabel(gtk.Alignment): + "A label with an image" def __init__(self, stock, size, text): gtk.Alignment.__init__(self, 0.5, 0.5, 0, 0) - self.hbox = gtk.HBox() - self.hbox.set_spacing(5) + self.hbox = HBox() self.add(self.hbox) self.image = Image() @@ -586,24 +726,34 @@ self.label = Label(text, gtk.JUSTIFY_CENTER) self.hbox.pack_start(self.label) + def set_stock(self, stock, size): + "Sets the label icon" + self.image.set_from_stock(stock, size) + def set_text(self, text): + "Sets the label text" + self.label.set_text(text) -class InputSection(gtk.VBox): +class InputSection(VBox): + "A section of input fields" def __init__(self, title = None, sizegroup = None, description = None): - gtk.VBox.__init__(self) - self.set_border_width(0) - self.set_spacing(6) + VBox.__init__(self) self.title = None self.description = None - self.sizegroup = sizegroup is not None and sizegroup or gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + if sizegroup is None: + self.sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + else: + self.sizegroup = sizegroup if title is not None: self.title = Label("" + revelation.misc.escape_markup(title) + "") @@ -615,8 +765,9 @@ def add_inputrow(self, title, widget): - row = gtk.HBox() - row.set_spacing(6) + "Adds an input row to the section" + + row = HBox() self.pack_start(row) if self.title is not None: @@ -633,12 +784,57 @@ def clear(self): + "Clears the input section" + for child in self.get_children(): if child not in [ self.label, self.description ]: child.destroy() +class MenuFactory(gtk.ItemFactory): + "A factory for menus" + + def __init__(self, widget, accelgroup): + gtk.ItemFactory.__init__(self, widget, "
", accelgroup) + + + def __cb_select(self, object): + "Emits the item-selected signal when a menu item is selected" + + self.emit("item-selected", object) + + + def __cb_deselect(self, object): + "Emits the item-deselected signal when a menu item is deselected" + + self.emit("item-deselected", object) + + + def create_items(self, items): + "Create a menu from a data structure" + + # strip description from items, and create the items + ifitems = [] + for item in items: + ifitems.append(item[0:2] + item[3:]) + + gtk.ItemFactory.create_items(self, ifitems) + + # set up description for items + for item in items: + if item[5] in ["", "", ""]: + widget = self.get_widget("
" + item[0].replace("_", "")) + widget.set_data("description", item[2]) + widget.connect("select", self.__cb_select) + widget.connect("deselect", self.__cb_deselect) + + +gobject.signal_new("item-selected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) +gobject.signal_new("item-deselected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) + + + class PasswordEntry(Entry): "An entry which edits a password (follows the 'show passwords' preference" diff -r 9b3c9903350d6654128a459470db753e4a05b5d7 -r 3794d9b384465462491f9ea3fa821f7572c9ba43 src/revelation --- a/src/revelation Tue Jul 27 17:54:30 2004 +0000 +++ b/src/revelation Tue Jul 27 19:14:01 2004 +0000 @@ -383,7 +383,7 @@ if self.file is None or self.password is None: return - if not self.client.get("file/autosave"): + if not self.config.get("file/autosave"): return self.file_save(self.file, self.password) @@ -517,6 +517,8 @@ # public methods def change_password(self): + "Changes the password of the current data file" + try: dialog = revelation.dialog.Password( self, "Enter new password", @@ -544,11 +546,15 @@ def clip_copy(self): + "Copies selected entries to the clipboard" + iters = self.data.filter_parents(self.tree.get_selected()) self.clipboard.copy(self.data, iters) def clip_cut(self): + "Cuts selected entries to the clipboard" + iters = self.data.filter_parents(self.tree.get_selected()) self.undoqueue.add_action(revelation.data.UNDO_ACTION_CUT, iters) self.clipboard.cut(self.data, iters) @@ -556,6 +562,8 @@ def clip_paste(self): + "Pastes entries from the clipboard" + if not self.clipboard.has_contents(): return @@ -565,6 +573,8 @@ def entry_add(self): + "Adds an entry" + try: entry = revelation.dialog.EditEntry(self, "Add entry").run() iter = self.data.add_entry(self.tree.get_active(), entry) @@ -579,6 +589,8 @@ def entry_edit(self): + "Edits an entry" + iter = self.tree.get_active() if iter is None: @@ -603,6 +615,8 @@ def entry_find(self): + "Searches for an entry" + dialog = revelation.dialog.Find(self, self.config) dialog.entry_phrase.set_text(self.finder.string) dialog.dropdown.set_type(self.finder.type) @@ -624,6 +638,8 @@ def entry_remove(self): + "Removes one or more entries" + iters = self.tree.get_selected() if len(iters) == 0: @@ -809,6 +825,8 @@ def save_changes(self, pritext, sectext): + "Asks the user if she wants to save her changes" + if self.data.changed == gtk.FALSE: return gtk.TRUE @@ -823,6 +841,8 @@ def quit(self): + "Quits the application" + if not self.save_changes("Save changes before quitting?", "You have made changes which have not been saved. If you quit without saving, then these changes will be discarded."): self.statusbar.set_status("Quit cancelled") return gtk.FALSE @@ -854,6 +874,8 @@ def run(self, file = None): + "Run the application" + if file is not None: self.file_open(file) elif self.config.get("file/autoload"):