From 50492711e84891fd9a23c93620a0f02c590c4c32 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Mon, 24 Dec 2007 14:56:15 +0000 Subject: [PATCH] 2007-12-24 Douglas S. Blank * src/DataViews/_MyGrampsView.py: new DataView for Gadgets * src/DataViews/__init__.py: import things from _MyGrampsView * src/Config/_GrampsConfigKeys.py: added data-views * src/glade/gramps.glade: added My Gramps Gadget gui * src/plugins/DefaultGadgets.py: sample gadgets svn: r9573 --- ChangeLog | 7 + src/Config/_GrampsConfigKeys.py | 4 + src/DataViews/_MyGrampsView.py | 381 ++++++++++++++++++++++++++++++++ src/DataViews/__init__.py | 20 +- src/glade/gramps.glade | 171 ++++++++++++++ src/plugins/DefaultGadgets.py | 262 ++++++++++++++++++++++ 6 files changed, 840 insertions(+), 5 deletions(-) create mode 100644 src/DataViews/_MyGrampsView.py create mode 100644 src/plugins/DefaultGadgets.py diff --git a/ChangeLog b/ChangeLog index 855179e73..9820c345c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2007-12-24 Douglas S. Blank + * src/DataViews/_MyGrampsView.py: new DataView for Gadgets + * src/DataViews/__init__.py: import things from _MyGrampsView + * src/Config/_GrampsConfigKeys.py: added data-views + * src/glade/gramps.glade: added My Gramps Gadget gui + * src/plugins/DefaultGadgets.py: sample gadgets + 2007-12-24 Brian Matherly * src/ReportBase/Makefile.am: Add _DocReportDialog.py to the Makefile diff --git a/src/Config/_GrampsConfigKeys.py b/src/Config/_GrampsConfigKeys.py index 0463369ce..2fc71ded6 100644 --- a/src/Config/_GrampsConfigKeys.py +++ b/src/Config/_GrampsConfigKeys.py @@ -160,6 +160,7 @@ MAX_SIB_AGE_DIFF = ('behavior', 'max-sib-age-diff', 1) MIN_GENERATION_YEARS = ('behavior', 'min-generation-years', 1) AVG_GENERATION_GAP = ('behavior', 'avg-generation-gap', 1) GENERATION_DEPTH = ('behavior', 'generation-depth', 1) +DATA_VIEWS = ('interface','data-views', 2) default_value = { DEFAULT_SOURCE : False, @@ -276,4 +277,7 @@ default_value = { MIN_GENERATION_YEARS : 13, AVG_GENERATION_GAP : 20, GENERATION_DEPTH : 15, + DATA_VIEWS: ('PersonView,RelationshipView,FamilyListView,PedigreeView,' + 'EventView,SourceView,PlaceView,MediaView,RepositoryView,' + 'NoteView'), } diff --git a/src/DataViews/_MyGrampsView.py b/src/DataViews/_MyGrampsView.py new file mode 100644 index 000000000..307e04461 --- /dev/null +++ b/src/DataViews/_MyGrampsView.py @@ -0,0 +1,381 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# +# 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 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 +# + +# $Id: _MyGrampsView.py $ + +""" +MyGrampsView interface +""" + +__author__ = "Doug Blank" +__revision__ = "$Revision: $" + +import gtk +import PageView +import const +import gobject +import traceback +import time + +AVAILABLE_GADGETS = [] + +debug = False + +def register_gadget(data_dict): + global AVAILABLE_GADGETS + AVAILABLE_GADGETS.append(data_dict) + +def register(**data): + if "type" in data: + if data["type"].lower() == "gadget": + register_gadget(data) + +def get_gadget_opts(name, opts): + for data in AVAILABLE_GADGETS: + if data.get("name", None) == name: + my_data = data.copy() + my_data.update(opts) + return my_data + return {} + +def make_requested_gadget(viewpage, name, opts, dbstate): + for data in AVAILABLE_GADGETS: + if data.get("name", None) == name: + gui = GuiGadget(viewpage, dbstate, **opts) + if opts.get("content", None): + opts["content"](gui) + return gui + return None + +class Gadget(object): + def __init__(self, gui): + self._idle_id = 0 + self._generator = None + self._need_to_update = False + self.gui = gui + self.dbstate = gui.dbstate + self.init() + self.dbstate.connect('database-changed', self._db_changed) + self.dbstate.connect('active-changed', self.active_changed) + + def active_changed(self, handle): + pass + + def _db_changed(self, dbstate): + if debug: print "%s is _connecting" % self.gui.title + self.dbstate = dbstate + self.gui.dbstate = dbstate + self.db_changed() + self.update() + + def db_changed(self): + if debug: print "%s is connecting" % self.gui.title + pass + + def init(self): # once, constructor + pass + + def main(self): # once per db open + pass + + def update(self, *handles): + self.main() + if self._idle_id != 0: + if debug: print "%s interrupt!" % self.gui.title + self.interrupt() + if debug: print "%s creating generator" % self.gui.title + self._generator = self.background() + if debug: print "%s adding to gobject" % self.gui.title + self._idle_id = gobject.idle_add(self._updater, + priority=gobject.PRIORITY_LOW) + + def background(self): # return false finishes + if debug: print "%s dummy" % self.gui.title + yield False + + def interrupt(self): + """ + Force the generator to stop running. + """ + if self._idle_id == 0: + if debug: print "%s removing from gobject" % self.gui.title + gobject.source_remove(self._idle_id) + self._idle_id = 0 + + def _updater(self): + if debug: print "%s _updater" % self.gui.title + try: + retval = self._generator.next() + if retval == False: + self._idle_id = 0 + return retval + except StopIteration: + self._idle_id = 0 + return False + except Exception, e: + #self._error = e + traceback.print_exc() + self._idle_id = 0 + return False + except: + self._idle_id = 0 + return False + + def append_text(self, text): + self.gui.buffer.insert_at_cursor(text) + + def clear_text(self): + self.gui.buffer.set_text('') + + def set_text(self, text): + self.gui.buffer.set_text(text) + + +class GuiGadget: + """ + Class that handles the plugin interfaces for the MyGrampsView. + """ + TARGET_TYPE_FRAME = 80 + LOCAL_DRAG_TYPE = 'GADGET' + LOCAL_DRAG_TARGET = (LOCAL_DRAG_TYPE, 0, TARGET_TYPE_FRAME) + def __init__(self, viewpage, dbstate, title, **kwargs): + self.viewpage = viewpage + self.dbstate = dbstate + self.title = title + ########## Set defaults + self.expand = kwargs.get("expand", False) + self.height = kwargs.get("height", 200) + self.column = kwargs.get("column", -1) + self.row = kwargs.get("row", -1) + self.state = kwargs.get("state", "maximized") + ########## + self.xml = gtk.glade.XML(const.GLADE_FILE, 'gvgadget', "gramps") + self.mainframe = self.xml.get_widget('gvgadget') + self.textview = self.xml.get_widget('gvtextview') + self.buffer = self.textview.get_buffer() + self.scrolledwindow = self.xml.get_widget('gvscrolledwindow') + self.titlelabel = self.xml.get_widget('gvtitle') + self.titlelabel.set_text("%s" % self.title) + self.titlelabel.set_use_markup(True) + self.xml.get_widget('gvclose').connect('clicked', self.close) + self.xml.get_widget('gvstate').connect('clicked', self.change_state) + self.xml.get_widget('gvproperties').connect('clicked', + self.set_properties) + self.xml.get_widget('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, + gtk.ICON_SIZE_MENU) + self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, + gtk.ICON_SIZE_MENU) + self.xml.get_widget('gvpropertiesimage').set_from_stock(gtk.STOCK_PROPERTIES, + gtk.ICON_SIZE_MENU) + + # source: + drag = self.xml.get_widget('gvproperties') + drag.drag_source_set(gtk.gdk.BUTTON1_MASK, + [GuiGadget.LOCAL_DRAG_TARGET], + gtk.gdk.ACTION_COPY) + + def close(self, obj): + del self.viewpage.gadget_map[self.title] + del self.viewpage.frame_map[str(self.mainframe)] + self.mainframe.destroy() + + def change_state(self, obj): + if self.state == "maximized": + self.scrolledwindow.hide() + self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_ADD, + gtk.ICON_SIZE_MENU) + self.state = "minimized" + else: + self.scrolledwindow.show() + self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, + gtk.ICON_SIZE_MENU) + self.state = "maximized" + if self.expand: + column = self.mainframe.get_parent() # column + expand,fill,padding,pack = column.query_child_packing(self.mainframe) + column.set_child_packing(self.mainframe,(not expand),fill,padding,pack) + + def set_properties(self, obj): + self.expand = not self.expand + if self.state == "maximized": + column = self.mainframe.get_parent() # column + expand,fill,padding,pack = column.query_child_packing(self.mainframe) + column.set_child_packing(self.mainframe,self.expand,fill,padding,pack) + + def append_text(self, text): + self.buffer.insert_at_cursor(text) + + def clear_text(self): + self.buffer.set_text('') + + def set_text(self, text): + self.buffer.set_text(text) + + + +class MyGrampsView(PageView.PageView): + """ + MyGrampsView interface + """ + + def __init__(self, dbstate, uistate): + """ + Creates a MyGrampsView, with the current dbstate and uistate + """ + PageView.PageView.__init__(self, _('My Gramps'), dbstate, uistate) + self.column_count = 3 + + def change_db(self, event): + """ + """ + # FIXME: remove/add widgets from new db ini file + pass + + def build_widget(self): + """ + Builds the container widget for the interface. Must be overridden by the + the base class. Returns a gtk container widget. + """ + frame = gtk.ScrolledWindow() + frame.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + hbox = gtk.HBox(homogeneous=True) + # Set up drag and drop + frame.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + [('GADGET', 0, 80)], + gtk.gdk.ACTION_COPY) + frame.connect('drag_drop', self.drop_widget) + + frame.add_with_viewport(hbox) + # Create the columns: + self.columns = [] + for i in range(self.column_count): + self.columns.append(gtk.VBox()) + hbox.pack_start(self.columns[-1],expand=True) + # Load the gadgets + self.gadget_map = {} # title->gadget + self.frame_map = {} # frame->gadget + # FIXME + # get the user's gadgets from .gramps + # and/or from open database + # Load the user's gadgets: + for (name, opts) in [('Stats Gadget', {}), + ('Top Surnames Gadget', {}), + #('Families Gadget', {}), + #('Families Gadget', {"title": "My Peeps"}), + ('Hello World Gadget', {}), + ('Log Gadget', {}), + #('Events Gadget', {}), + ]: + all_opts = get_gadget_opts(name, opts) + if "title" not in all_opts: + all_opts["title"] = "Untitled Gadget" + # uniqify titles: + unique = all_opts["title"] + cnt = 1 + while unique in self.gadget_map: + unique = all_opts["title"] + ("-%d" % cnt) + cnt += 1 + all_opts["title"] = unique + if all_opts["title"] not in self.gadget_map: + g = make_requested_gadget(self, name, all_opts, self.dbstate) + if g: + self.gadget_map[all_opts["title"]] = g + self.frame_map[str(g.mainframe)] = g + else: + print "Can't make gadget of type '%s'." % name + else: + print "Ignoring duplicate named gadget '%s'." % all_opts["title"] + + # put the gadgets where they go: + cnt = 0 + for gadget in self.gadget_map.values(): + # see if the user wants this in a particular location: + # and if there are that many columns + if gadget.column >= 0 and gadget.column < len(self.columns): + pos = gadget.column + else: + # else, spread them out: + pos = cnt % len(self.columns) + if gadget.state == "minimized": # starts max, change to min it + gadget.state = "maximized" + gadget.change_state(gadget) # minimize it + # to make as big as possible, set to True: + self.columns[pos].pack_start(gadget.mainframe, expand=gadget.expand) + # set height on gadget.scrolledwindow here: + gadget.scrolledwindow.set_size_request(-1, gadget.height) + cnt += 1 + return frame + + def drop_widget(self, source, context, x, y, timedata): + button = context.get_source_widget() + hbox = button.get_parent() + mainframe = hbox.get_parent() + rect = source.get_allocation() + sx, sy = rect.width, rect.height + # first, find column: + col = 0 + for i in range(len(self.columns)): + if x < (sx/len(self.columns) * (i + 1)): + col = i + break + fromcol = mainframe.get_parent() + fromcol.remove(mainframe) + # now find where to insert in column: + stack = [] + for gframe in self.columns[col]: + rect = gframe.get_allocation() + if y < (rect.y + 15): # starts at 0, this allows insert before + self.columns[col].remove(gframe) + stack.append(gframe) + maingadget = self.frame_map[str(mainframe)] + if maingadget.state == "maximized": + expand = maingadget.expand + else: + expand = False + self.columns[col].pack_start(mainframe, expand=expand) + for gframe in stack: + gadget = self.frame_map[str(gframe)] + if gadget.state == "maximized": + expand = gadget.expand + else: + expand = False + self.columns[col].pack_start(gframe, expand=expand) + return True + + def define_actions(self): + """ + Defines the UIManager actions. Called by the ViewManager to set up the + View. The user typically defines self.action_list and + self.action_toggle_list in this function. + """ + return '' + + def get_stock(self): + """ + Returns image associated with the view, which is used for the + icon for the button. + """ + return 'gtk-home' + + def build_tree(self): + return + diff --git a/src/DataViews/__init__.py b/src/DataViews/__init__.py index a8aa5d8f7..0c342e677 100644 --- a/src/DataViews/__init__.py +++ b/src/DataViews/__init__.py @@ -26,6 +26,7 @@ Package init for the DataView package __author__ = "Don Allingham" __revision__ = "$Revision: $" +from _MyGrampsView import MyGrampsView, register, Gadget from _PersonView import PersonView from _RelationView import RelationshipView from _FamilyList import FamilyListView @@ -37,11 +38,14 @@ from _MediaView import MediaView from _RepositoryView import RepositoryView from _NoteView import NoteView -def get_views(): - """ - Returns a list of PageView instances - """ - return [ +try: + import Config + DATA_VIEWS = eval("["+Config.get(Config.DATA_VIEWS)+"]") +except: + # Fallback if bad config line, or if no Config system + print "Ignoring malformed 'data-views' entry" + DATA_VIEWS = [ + #MyGrampsView, PersonView, RelationshipView, FamilyListView, @@ -54,3 +58,9 @@ def get_views(): RepositoryView, NoteView, ] + +def get_views(): + """ + Returns a list of PageView instances, in order + """ + return DATA_VIEWS diff --git a/src/glade/gramps.glade b/src/glade/gramps.glade index 89d993500..1cb044bf3 100644 --- a/src/glade/gramps.glade +++ b/src/glade/gramps.glade @@ -16202,4 +16202,175 @@ Very High + + True + window2 + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + 10 + True + 0 + 0.5 + GTK_SHADOW_IN + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_WORD + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + True + False + 0 + + + + True + True + GTK_RELIEF_NORMAL + True + + + + True + gtk-properties + 4 + 0.5 + 0.5 + 0 + 0 + + + + + 0 + False + False + + + + + + True + True + GTK_RELIEF_NORMAL + True + + + + True + gtk-remove + 4 + 0.5 + 0.5 + 0 + 0 + + + + + 0 + False + False + + + + + + True + True + GTK_RELIEF_NORMAL + True + + + + True + gtk-close + 4 + 0.5 + 0.5 + 0 + 0 + + + + + 0 + False + False + + + + + + True + <b><i>Gramplet</i></b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0 + 7 + 9 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + True + True + + + + + label_item + + + + + + diff --git a/src/plugins/DefaultGadgets.py b/src/plugins/DefaultGadgets.py new file mode 100644 index 000000000..f834a2fea --- /dev/null +++ b/src/plugins/DefaultGadgets.py @@ -0,0 +1,262 @@ +from DataViews import register, Gadget +from BasicUtils import name_displayer +import DateHandler +import gen.lib + +# +# Hello World, in Gramps Gadgets +# +# First, you need a function or class that takes a single argument +# a GuiGadget: + +def init(gui): + gui.set_text("Hello world!") + +# In this function, you can do some things to update the gadget, +# like set text of the main scroll window. + +# Then, you need to register the gadget: + +register(type="gadget", # case in-senstitive keyword "gadget" + name="Hello World Gadget", # gadget name, unique among gadgets + height = 20, + content = init, # function/class; takes state and container + title="Sample Gadget", # default title, user changeable, unique + ) + +# There are a number of arguments that you can provide, including: +# name, height, content, title, expand, minimized + +# Hereare a couple of other examples, with their register lines at the +# bottom: + +def make_family_content(gui): + gui.set_text("Families:") + +def make_event_content(gui): + gui.set_text("Events:") + +# Here is a Gadget object. It has a number of methods possibilities: +# init- run once, on construction +# db_changed- run when db-changed is triggered +# main- run once per db, main process (for fast code) +# background- run once per db, main process (for slow code) +# active_changed- run when active-changed is triggered + +# You can also call update to run main and background + +class LogGadget(Gadget): + def db_changed(self): + self.dbstate.connect('person-add', self.log_person_add) + self.dbstate.connect('person-delete', self.log_person_delete) + self.dbstate.connect('person-update', self.log_person_update) + self.dbstate.connect('family-add', self.log_family_add) + self.dbstate.connect('family-delete', self.log_family_delete) + self.dbstate.connect('family-update', self.log_family_update) + + def active_changed(self, handle): + self.log_active_changed(handle) + + def init(self): + self.set_text("Log for this Session\n--------------------\n") + + def log_person_add(self, handles): + self.append_text("person-add: ") + self.get_person(handles) + def log_person_delete(self, handles): + self.append_text("person-delete: ") + self.get_person(handles) + def log_person_update(self, handles): + self.append_text("person-update: ") + self.get_person(handles) + def log_family_add(self, handles): + self.append_text("family-add: %s" % handles) + def log_family_delete(self, handles): + self.append_text("family-delete: %s" % handles) + def log_family_update(self, handles): + self.append_text("family-update: %s" % handles) + def log_active_changed(self, handles): + self.append_text("active-changed: ") + self.get_person([handles]) + + def get_person(self, handles): + for person_handle in handles: + person = self.dbstate.get_person_from_handle(person_handle) + if person: + self.append_text(name_displayer.display(person)) + else: + self.append_text(person_handle) + self.append_text("\n") + +class TopSurnamesGadget(Gadget): + def main(self): + self.set_text("Processing...\n") + + def background(self): + people = self.dbstate.get_person_handles(sort_handles=False) + surnames = {} + cnt = 0 + for person_handle in people: + person = self.dbstate.get_person_from_handle(person_handle) + if person: + surname = person.get_primary_name().get_surname().strip() + surnames[surname] = surnames.get(surname, 0) + 1 + if cnt % 500 == 0: + yield True + cnt += 1 + total_people = cnt + surname_sort = [] + total = 0 + cnt = 0 + for surname in surnames: + surname_sort.append( (surnames[surname], surname) ) + total += surnames[surname] + if cnt % 500 == 0: + yield True + cnt += 1 + total_surnames = cnt + surname_sort.sort(lambda a,b: -cmp(a,b)) + line = 0 + ### All done! + self.set_text("") + for (count, surname) in surname_sort: + self.append_text(" %s, %d%%\n" % + (surname, int((float(count)/total) * 100))) + line += 1 + if line >= 10: + break + self.append_text("\nTotal unique surnames: %d\n" % total_surnames) + self.append_text("Total people: %d" % total_people) + +class StatsGadget(Gadget): + def db_changed(self): + self.dbstate.connect('person-add', self.update) + self.dbstate.connect('person-delete', self.update) + self.dbstate.connect('family-add', self.update) + self.dbstate.connect('family-delete', self.update) + + def background(self): + self.set_text("Processing...") + database = self.dbstate + personList = database.get_person_handles(sort_handles=False) + familyList = database.get_family_handles() + + with_photos = 0 + total_photos = 0 + incomp_names = 0 + disconnected = 0 + missing_bday = 0 + males = 0 + females = 0 + unknowns = 0 + bytes = 0 + namelist = [] + notfound = [] + + pobjects = len(database.get_media_object_handles()) + for photo_id in database.get_media_object_handles(): + photo = database.get_object_from_handle(photo_id) + try: + bytes = bytes + posixpath.getsize(photo.get_path()) + except: + notfound.append(photo.get_path()) + + cnt = 0 + for person_handle in personList: + person = database.get_person_from_handle(person_handle) + if not person: + continue + length = len(person.get_media_list()) + if length > 0: + with_photos = with_photos + 1 + total_photos = total_photos + length + + person = database.get_person_from_handle(person_handle) + name = person.get_primary_name() + if name.get_first_name() == "" or name.get_surname() == "": + incomp_names = incomp_names + 1 + if (not person.get_main_parents_family_handle()) and (not len(person.get_family_handle_list())): + disconnected = disconnected + 1 + birth_ref = person.get_birth_ref() + if birth_ref: + birth = database.get_event_from_handle(birth_ref.ref) + if not DateHandler.get_date(birth): + missing_bday = missing_bday + 1 + else: + missing_bday = missing_bday + 1 + if person.get_gender() == gen.lib.Person.FEMALE: + females = females + 1 + elif person.get_gender() == gen.lib.Person.MALE: + males = males + 1 + else: + unknowns += 1 + if name.get_surname() not in namelist: + namelist.append(name.get_surname()) + if cnt % 500 == 0: + yield True + cnt += 1 + + text = _("Individuals") + "\n" + text = text + "----------------------------\n" + text = text + "%s: %d\n" % (_("Number of individuals"),len(personList)) + text = text + "%s: %d\n" % (_("Males"),males) + text = text + "%s: %d\n" % (_("Females"),females) + text = text + "%s: %d\n" % (_("Individuals with unknown gender"),unknowns) + text = text + "%s: %d\n" % (_("Individuals with incomplete names"),incomp_names) + text = text + "%s: %d\n" % (_("Individuals missing birth dates"),missing_bday) + text = text + "%s: %d\n" % (_("Disconnected individuals"),disconnected) + text = text + "\n%s\n" % _("Family Information") + text = text + "----------------------------\n" + text = text + "%s: %d\n" % (_("Number of families"),len(familyList)) + text = text + "%s: %d\n" % (_("Unique surnames"),len(namelist)) + text = text + "\n%s\n" % _("Media Objects") + text = text + "----------------------------\n" + text = text + "%s: %d\n" % (_("Individuals with media objects"),with_photos) + text = text + "%s: %d\n" % (_("Total number of media object references"),total_photos) + text = text + "%s: %d\n" % (_("Number of unique media objects"),pobjects) + text = text + "%s: %d %s\n" % (_("Total size of media objects"),bytes,\ + _("bytes")) + + if len(notfound) > 0: + text = text + "\n%s\n" % _("Missing Media Objects") + text = text + "----------------------------\n" + for p in notfound: + text = text + "%s\n" % p + self.set_text(text) + + +register(type="gadget", + name="Families Gadget", + height=300, + content = make_family_content, + title="Favorite Families", + ) + +register(type="gadget", + name="Events Gadget", + height=100, + content = make_event_content, + title="Favorite Events", + ) + +register(type="gadget", + name="Top Surnames Gadget", + height=230, + content = TopSurnamesGadget, + title="Top 10 Surnames", + ) + +register(type="gadget", + name="Stats Gadget", + height=230, + content = StatsGadget, + title="Stats", + ) + +register(type="gadget", + name="Log Gadget", + height=230, + content = LogGadget, + title="Session Log", + ) +