diff --git a/po/POTFILES.in b/po/POTFILES.in index af2d8c976..49b35c18f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -80,6 +80,7 @@ src/gen/plug/report/utils.py src/gen/proxy/private.py # gui - GUI code +src/gui/bottombar.py src/gui/columnorder.py src/gui/configure.py src/gui/dbloader.py @@ -255,12 +256,16 @@ src/plugins/export/ExportXml.py src/plugins/gramplet/AgeOnDateGramplet.py src/plugins/gramplet/AgeStats.py src/plugins/gramplet/AttributesGramplet.py +src/plugins/gramplet/bottombar.gpr.py src/plugins/gramplet/CalendarGramplet.py src/plugins/gramplet/DescendGramplet.py src/plugins/gramplet/FanChartGramplet.py src/plugins/gramplet/GivenNameGramplet.py src/plugins/gramplet/gramplet.gpr.py src/plugins/gramplet/PedigreeGramplet.py +src/plugins/gramplet/PersonAttributes.py +src/plugins/gramplet/PersonDetails.py +src/plugins/gramplet/PersonResidence.py src/plugins/gramplet/QuickViewGramplet.py src/plugins/gramplet/RelativeGramplet.py src/plugins/gramplet/SessionLogGramplet.py diff --git a/po/POTFILES.skip b/po/POTFILES.skip index 508e7d0bf..bf6cdcb38 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -296,6 +296,7 @@ src/plugins/gramplet/DeepConnections.py src/plugins/gramplet/FaqGramplet.py src/plugins/gramplet/HeadlineNewsGramplet.py src/plugins/gramplet/NoteGramplet.py +src/plugins/gramplet/PersonGallery.py src/plugins/gramplet/PluginManagerGramplet.py src/plugins/gramplet/PythonGramplet.py diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index 45f208040..ef300baeb 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -15,6 +15,7 @@ pkgdatadir = $(datadir)/@PACKAGE@/gui pkgdata_PYTHON = \ __init__.py \ basesidebar.py \ + bottombar.py \ columnorder.py \ configure.py \ dbguielement.py \ diff --git a/src/gui/bottombar.py b/src/gui/bottombar.py new file mode 100644 index 000000000..d51fa5cfa --- /dev/null +++ b/src/gui/bottombar.py @@ -0,0 +1,421 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# GNOME modules +# +#------------------------------------------------------------------------- +import gtk +import time +import os + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +import ConfigParser +import const +from gui.widgets.grampletpane import (AVAILABLE_GRAMPLETS, + GET_AVAILABLE_GRAMPLETS, + get_gramplet_opts, + get_gramplet_options_by_tname, + get_gramplet_options_by_name, + make_requested_gramplet) +from ListModel import ListModel, NOSORT + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +NL = "\n" + +#------------------------------------------------------------------------- +# +# Bottombar class +# +#------------------------------------------------------------------------- +class Bottombar(object): + """ + A class which defines the graphical representation of the Gramps bottom bar. + """ + def __init__(self, dbstate, uistate, configfile, close_callback, defaults): + + self.dbstate = dbstate + self.uistate = uistate + self.configfile = os.path.join(const.VERSION_DIR, "%s.ini" % configfile) + self.close_callback = close_callback + self.gramplet_map = {} # title->gramplet + + self.top = gtk.HBox() + self.notebook = gtk.Notebook() + self.notebook.set_show_border(False) + self.notebook.set_scrollable(True) + self.notebook.connect('switch_page', self.__switch_page) + + vbox = gtk.VBox() + + close_button = gtk.Button() + img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) + close_button.set_image(img) + close_button.set_relief(gtk.RELIEF_NONE) + close_button.connect('clicked', self.__close_clicked) + vbox.pack_start(close_button, False) + + delete_button = gtk.Button() + img = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + delete_button.set_image(img) + delete_button.set_relief(gtk.RELIEF_NONE) + delete_button.connect('clicked', self.__delete_clicked) + vbox.pack_end(delete_button, False) + + add_button = gtk.Button() + img = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU) + add_button.set_image(img) + add_button.set_relief(gtk.RELIEF_NONE) + add_button.connect('clicked', self.__add_clicked) + vbox.pack_end(add_button, False) + + self.top.pack_start(self.notebook, True) + self.top.pack_start(vbox, False) + + vbox.show_all() + self.notebook.show() + + self.default_gramplets = defaults + config_settings = self.load_gramplets() + + for (name, opts) in config_settings[1]: + all_opts = get_gramplet_opts(name, opts) + gramplet = make_requested_gramplet(self, name, all_opts, + self.dbstate, self.uistate) + self.gramplet_map[all_opts["title"]] = gramplet + + gramplets = [g for g in self.gramplet_map.itervalues() + if g is not None] + gramplets.sort(key=lambda x: x.page) + for gramplet in gramplets: + gramplet.scrolledwindow.set_size_request(-1, 200) + self.notebook.append_page(gramplet.mainframe, + gtk.Label(gramplet.title)) + self.notebook.set_tab_reorderable(gramplet.mainframe, True) + + if config_settings[0][0]: + self.top.show() + self.notebook.set_current_page(config_settings[0][1]) + + def load_gramplets(self): + """ + Load the gramplets from the configuration file. + """ + retval = [] + visible = False + default_page = 0 + filename = self.configfile + if filename and os.path.exists(filename): + cp = ConfigParser.ConfigParser() + cp.read(filename) + for sec in cp.sections(): + if sec == "Bar Options": + if "visible" in cp.options(sec): + visible = cp.get(sec, "visible") == "True" + if "page" in cp.options(sec): + default_page = int(cp.get(sec, "page")) + else: + data = {"title": sec} + for opt in cp.options(sec): + if opt.startswith("data["): + temp = data.get("data", {}) + #temp.append(cp.get(sec, opt).strip()) + pos = int(opt[5:-1]) + temp[pos] = cp.get(sec, opt).strip() + data["data"] = temp + else: + data[opt] = cp.get(sec, opt).strip() + if "data" in data: + data["data"] = [data["data"][key] + for key in sorted(data["data"].keys())] + if "name" not in data: + data["name"] = "Unnamed Gramplet" + data["tname"]= _("Unnamed Gramplet") + retval.append((data["name"], data)) # name, opts + else: + # give defaults as currently known + for name in self.default_gramplets: + if name in AVAILABLE_GRAMPLETS(): + retval.append((name, GET_AVAILABLE_GRAMPLETS(name))) + return ((visible, default_page), retval) + + def save(self): + """ + Save the gramplet configuration. + """ + if len(self.gramplet_map) == 0: + return # something is the matter + filename = self.configfile + try: + fp = open(filename, "w") + except: + print "Failed writing '%s'; gramplets not saved" % filename + return + fp.write(";; Gramps bar configuration file" + NL) + fp.write((";; Automatically created at %s" % + time.strftime("%Y/%m/%d %H:%M:%S")) + NL + NL) + fp.write("[Bar Options]" + NL) + fp.write(("visible=%s" + NL) % self.top.get_property('visible')) + fp.write(("page=%d" + NL) % self.notebook.get_current_page()) + fp.write(NL) + + for page_num in range(self.notebook.get_n_pages()): + title = get_title(self.notebook, page_num) + gramplet = self.gramplet_map[title] + + opts = get_gramplet_options_by_name(gramplet.name) + if opts is not None: + base_opts = opts.copy() + for key in base_opts: + if key in gramplet.__dict__: + base_opts[key] = gramplet.__dict__[key] + fp.write(("[%s]" + NL) % gramplet.title) + for key in base_opts: + if key == "content": continue + elif key == "title": continue + elif key == "row": continue + elif key == "column": continue + elif key == "page": continue + elif key == "version": continue # code, don't save + elif key == "gramps": continue # code, don't save + elif key == "data": + if not isinstance(base_opts["data"], (list, tuple)): + fp.write(("data[0]=%s" + NL) % base_opts["data"]) + else: + cnt = 0 + for item in base_opts["data"]: + fp.write(("data[%d]=%s" + NL) % (cnt, item)) + cnt += 1 + else: + fp.write(("%s=%s" + NL)% (key, base_opts[key])) + fp.write(("page=%d" + NL) % page_num) + fp.write(NL) + + fp.close() + + def set_active(self): + """ + Called with the view is set as active. + """ + page = self.notebook.get_current_page() + title = get_title(self.notebook, page) + if self.gramplet_map[title].pui: + self.gramplet_map[title].pui.active = True + if self.gramplet_map[title].pui.dirty: + self.gramplet_map[title].pui.update() + + def set_inactive(self): + """ + Called with the view is set as inactive. + """ + page = self.notebook.get_current_page() + title = get_title(self.notebook, page) + if self.gramplet_map[title].pui: + if self.gramplet_map[title].state != "detached": + self.gramplet_map[title].pui.active = False + + def on_delete(self): + """ + Called when the view is closed. + """ + gramplets = (g for g in self.gramplet_map.itervalues() + if g is not None) + for gramplet in gramplets: + # this is the only place where the gui runs user code directly + if gramplet.pui: + gramplet.pui.on_save() + self.save() + + def get_display(self): + """ + Return the top container widget for the GUI. + """ + return self.top + + def show(self): + """ + Show the bottom bar. + """ + return self.top.show() + + def hide(self): + """ + Hide the bottom bar. + """ + return self.top.hide() + + def __close_clicked(self, button): + """ + Called when the sidebar is closed. + """ + self.close_callback() + + def __add_clicked(self, button): + """ + Called when the add button is clicked. + """ + names = [GET_AVAILABLE_GRAMPLETS(key)["tname"] for key + in AVAILABLE_GRAMPLETS()] + skip = [gramplet.tname for gramplet in self.gramplet_map.values()] + gramplet_list = [name for name in names if name not in skip] + gramplet_list.sort() + dialog = ChooseGrampletDialog(_("Select Gramplet"), gramplet_list) + tname = dialog.run() + if not tname: + return + + all_opts = get_gramplet_options_by_tname(tname) + gramplet = make_requested_gramplet(self, all_opts["name"], all_opts, + self.dbstate, self.uistate) + if not gramplet: + print "Problem creating ", tname + return + + title = all_opts["title"] + self.gramplet_map[title] = gramplet + gramplet.scrolledwindow.set_size_request(-1, gramplet.height) + page_num = self.notebook.append_page(gramplet.mainframe, + gtk.Label(title)) + self.notebook.set_tab_reorderable(gramplet.mainframe, True) + self.notebook.set_current_page(page_num) + + def __delete_clicked(self, button): + """ + Called when the delete button is clicked. + """ + page_num = self.notebook.get_current_page() + title = get_title(self.notebook, page_num) + del self.gramplet_map[title] + self.notebook.remove_page(page_num) + + def __switch_page(self, notebook, unused, new_page): + """ + Called when the user has switched to a new bottombar page. + """ + old_page = notebook.get_current_page() + #print "switch from", old_page, "to", new_page + if old_page >= 0: + title = get_title(notebook, old_page) + if self.gramplet_map[title].pui: + if self.gramplet_map[title].state != "detached": + self.gramplet_map[title].pui.active = False + + title = get_title(notebook, new_page) + if self.gramplet_map[title].pui: + self.gramplet_map[title].pui.active = True + if self.gramplet_map[title].pui.dirty: + self.gramplet_map[title].pui.update() + +def get_title(notebook, page_num): + """ + Reurn the title of a given page in a notebook. + """ + return notebook.get_tab_label_text(notebook.get_nth_page(page_num)) + +#------------------------------------------------------------------------- +# +# Choose Gramplet Dialog +# +#------------------------------------------------------------------------- +class ChooseGrampletDialog(object): + """ + A dialog to choose a gramplet + """ + def __init__(self, title, names): + self.title = title + self.names = names + self.namelist = None + self.namemodel = None + self.top = self._create_dialog() + + def run(self): + """ + Run the dialog and return the result. + """ + self._populate_model() + response = self.top.run() + result = None + if response == gtk.RESPONSE_OK: + store, iter_ = self.namemodel.get_selected() + if iter_: + result = store.get_value(iter_, 0) + self.top.destroy() + return result + + def _populate_model(self): + """ + Populate the model. + """ + self.namemodel.clear() + for name in self.names: + self.namemodel.add([name]) + + def _create_dialog(self): + """ + Create a dialog box to organize tags. + """ + # pylint: disable-msg=E1101 + title = _("%(title)s - Gramps") % {'title': self.title} + top = gtk.Dialog(title) + top.set_default_size(400, 350) + top.set_modal(True) + top.set_has_separator(False) + top.vbox.set_spacing(5) + label = gtk.Label('%s' + % self.title) + label.set_use_markup(True) + top.vbox.pack_start(label, 0, 0, 5) + box = gtk.HBox() + top.vbox.pack_start(box, 1, 1, 5) + + name_titles = [(_('Name'), NOSORT, 200)] + self.namelist = gtk.TreeView() + self.namemodel = ListModel(self.namelist, name_titles) + + slist = gtk.ScrolledWindow() + slist.add_with_viewport(self.namelist) + slist.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + box.pack_start(slist, 1, 1, 5) + bbox = gtk.VButtonBox() + bbox.set_layout(gtk.BUTTONBOX_START) + bbox.set_spacing(6) + top.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + top.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + box.pack_start(bbox, 0, 0, 5) + top.show_all() + return top + diff --git a/src/gui/views/pageview.py b/src/gui/views/pageview.py index 413af9655..6dbdd6c86 100644 --- a/src/gui/views/pageview.py +++ b/src/gui/views/pageview.py @@ -50,6 +50,7 @@ import Errors from gui.dbguielement import DbGUIElement from gui.widgets.menutoolbuttonaction import MenuToolButtonAction from gui.sidebar import Sidebar +from gui.bottombar import Bottombar from gui.widgets.grampletpane import GrampletPane from gui.configure import ConfigureDialog from config import config @@ -116,6 +117,7 @@ class PageView(DbGUIElement):
@@ -150,6 +152,7 @@ class PageView(DbGUIElement): self.top = None self.gramplet_pane = None self.sidebar = None + self.bottombar = None DbGUIElement.__init__(self, dbstate.db) @@ -159,16 +162,22 @@ class PageView(DbGUIElement): Returns a gtk container widget. """ self.sidebar = Sidebar(self.sidebar_changed, self.sidebar_closed) + defaults = self.get_default_gramplets()[1] + self.bottombar = Bottombar(self.dbstate, self.uistate, + self.ident + "_bottombar", + self.bottombar_closed, + defaults) hpane = gtk.HPaned() vpane = gtk.VPaned() - hpane.pack1(vpane, resize=True, shrink=True) + hpane.pack1(vpane, resize=True, shrink=False) hpane.pack2(self.sidebar.get_display(), resize=False, shrink=False) hpane.show() vpane.show() widget = self.build_widget() widget.show_all() - vpane.add1(widget) + vpane.pack1(widget, resize=True, shrink=False) + vpane.pack2(self.bottombar.get_display(), resize=False, shrink=False) initial_page = self._config.get('sidebar.page') self.gramplet_pane = self.__create_gramplet_pane() @@ -205,9 +214,11 @@ class PageView(DbGUIElement): """ Create a gramplet pane. """ + defaults = self.get_default_gramplets()[0] gramplet_pane = GrampletPane(self.ident + "_sidebar", self, self.dbstate, self.uistate, - column_count=1) + column_count=1, + default_gramplets=defaults) gramplet_pane.show_all() self.sidebar.add(_('Gramplets'), gramplet_pane, GRAMPLET_PAGE) return gramplet_pane @@ -232,6 +243,17 @@ class PageView(DbGUIElement): self.sidebar_changed(None, False, None) self._config.set('sidebar.visible', active) + def __bottombar_toggled(self, action): + """ + Called when the bottombar is toggled. + """ + active = action.get_active() + if active: + self.bottombar.show() + else: + self.bottombar.hide() + self._config.set('bottombar.visible', active) + def sidebar_changed(self, page_type, active, index): """ Called when the sidebar page is changed. @@ -246,6 +268,23 @@ class PageView(DbGUIElement): uimanager = self.uistate.uimanager uimanager.get_action('/MenuBar/ViewMenu/Bars/Sidebar').activate() + def bottombar_closed(self): + """ + Called when the bottombar close button is clicked. + """ + uimanager = self.uistate.uimanager + uimanager.get_action('/MenuBar/ViewMenu/Bars/Bottombar').activate() + + def get_default_gramplets(self): + """ + Get the default gramplets for the Gramps sidebar and bottombar. + Returns a 2-tuple. The first element is a tuple of sidebar gramplets + and the second element is a tuple of bottombar gramplets. + + Views should override this method to define default gramplets. + """ + return ((), ()) + def key_press_handler(self, widget, event): """ A general keypress handler. Override if you want to handle @@ -337,6 +376,7 @@ class PageView(DbGUIElement): then we rebuild the data. """ self.gramplet_pane.set_active() + self.bottombar.set_active() self.active = True if self.dirty: self.uistate.set_busy_cursor(True) @@ -348,6 +388,7 @@ class PageView(DbGUIElement): Marks page as being inactive (not currently displayed) """ self.gramplet_pane.set_inactive() + self.bottombar.set_inactive() self.active = False def build_tree(self): @@ -443,6 +484,9 @@ class PageView(DbGUIElement): self._add_toggle_action('Sidebar', None, _('_Sidebar'), None, None, self.__sidebar_toggled, self._config.get('sidebar.visible')) + self._add_toggle_action('Bottombar', None, _('_Bottombar'), + None, None, self.__bottombar_toggled, + self._config.get('bottombar.visible')) self._add_action("AddGramplet", gtk.STOCK_ADD, _("Add a gramplet")) self._add_action("RestoreGramplet", None, _("Restore a gramplet")) @@ -562,6 +606,7 @@ class PageView(DbGUIElement): that should be called when quiting the main application. """ self.gramplet_pane.on_delete() + self.bottombar.on_delete() self._config.save() def init_config(self): @@ -584,8 +629,10 @@ class PageView(DbGUIElement): use_config_path=True) for section, value in self.CONFIGSETTINGS: self._config.register(section, value) - self._config.register('sidebar.visible', True) + self._config.register('sidebar.visible', False) self._config.register('sidebar.page', 0) + self._config.register('bottombar.visible', False) + self._config.register('bottombar.page', 0) self._config.init() self.config_connect() diff --git a/src/gui/widgets/grampletpane.py b/src/gui/widgets/grampletpane.py index 0902a8aa1..6b9f77657 100644 --- a/src/gui/widgets/grampletpane.py +++ b/src/gui/widgets/grampletpane.py @@ -332,6 +332,7 @@ class GuiGramplet(object): self.detached_height = int(kwargs.get("detached_height", 300)) self.detached_width = int(kwargs.get("detached_width", 400)) self.row = int(kwargs.get("row", -1)) + self.page = int(kwargs.get("page", -1)) self.state = kwargs.get("state", "maximized") self.data = kwargs.get("data", []) self.help_url = kwargs.get("help_url", WIKI_HELP_PAGE) @@ -360,12 +361,16 @@ class GuiGramplet(object): self.titlelabel.get_children()[0].set_use_markup(True) self.titlelabel.connect("clicked", self.edit_title) self.titlelabel_entry = None + self.titlelabel.hide() self.gvclose = self.xml.get_object('gvclose') self.gvclose.connect('clicked', self.close) + self.gvclose.hide() self.gvstate = self.xml.get_object('gvstate') self.gvstate.connect('clicked', self.change_state) + self.gvstate.hide() self.gvproperties = self.xml.get_object('gvproperties') self.gvproperties.connect('clicked', self.set_properties) + self.gvproperties.hide() self.xml.get_object('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, diff --git a/src/plugins/gramplet/Makefile.am b/src/plugins/gramplet/Makefile.am index 57ea36a0b..362b5c8f3 100644 --- a/src/plugins/gramplet/Makefile.am +++ b/src/plugins/gramplet/Makefile.am @@ -9,6 +9,7 @@ pkgdata_PYTHON = \ AgeOnDateGramplet.py \ AgeStats.py \ AttributesGramplet.py \ + bottombar.gpr.py \ CalendarGramplet.py \ DescendGramplet.py \ FanChartGramplet.py \ @@ -16,6 +17,10 @@ pkgdata_PYTHON = \ GivenNameGramplet.py \ gramplet.gpr.py \ PedigreeGramplet.py \ + PersonAttributes.py \ + PersonDetails.py \ + PersonGallery.py \ + PersonResidence.py \ PluginManagerGramplet.py\ QuickViewGramplet.py \ RelativeGramplet.py \ diff --git a/src/plugins/gramplet/PersonAttributes.py b/src/plugins/gramplet/PersonAttributes.py new file mode 100644 index 000000000..109ac71d3 --- /dev/null +++ b/src/plugins/gramplet/PersonAttributes.py @@ -0,0 +1,74 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ +# + +from ListModel import ListModel, NOSORT +from QuickReports import run_quick_report_by_name +from gen.plug import Gramplet +import gtk + +class PersonAttributes(Gramplet): + """ + Displays the attributes of a person. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + top = gtk.TreeView() + titles = [(_('Key'), 1, 100), + (_('Value'), 2, 100)] + self.model = ListModel(top, titles, event_func=self.display_report) + return top + + def db_changed(self): + self.dbstate.db.connect('person-update', self.update) + self.update() + + def active_changed(self, handle): + self.update() + + def main(self): # return false finishes + active_handle = self.get_active('Person') + active_person = self.dbstate.db.get_person_from_handle(active_handle) + if not active_person: + return + + self.model.clear() + for attr in active_person.get_attribute_list(): + self.model.add((attr.get_type(), attr.get_value())) + + def display_report(self, treeview): + """ + Display the quick report for matching attribute key. + """ + model, iter_ = treeview.get_selection().get_selected() + if iter_: + key = model.get_value(iter_, 0) + run_quick_report_by_name(self.dbstate, + self.uistate, + 'attribute_match', + key) diff --git a/src/plugins/gramplet/PersonDetails.py b/src/plugins/gramplet/PersonDetails.py new file mode 100644 index 000000000..d12e947df --- /dev/null +++ b/src/plugins/gramplet/PersonDetails.py @@ -0,0 +1,232 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ +# + +from gen.lib import EventType, EventRoleType +from gen.plug import Gramplet +from gen.display.name import displayer as name_displayer +from gen.ggettext import gettext as _ +import DateHandler +import Utils +import gtk +import pango + +class PersonDetails(Gramplet): + """ + Displays details for a person. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + self.load_obj = None + self.load_rect = None + top = gtk.HBox() + vbox = gtk.VBox() + self.obj_photo = gtk.Image() + self.name = gtk.Label() + self.name.set_alignment(0, 0) + self.name.modify_font(pango.FontDescription('sans bold 12')) + vbox.pack_start(self.name, fill=True, expand=False, padding=10) + table = gtk.Table(2, 2) + self.father = self.make_row(table, 0, _('Father')) + self.mother = self.make_row(table, 1, _('Mother')) + vbox.pack_start(table, fill=True, expand=False, padding=5) + table = gtk.Table(4, 2) + self.birth = self.make_row(table, 0, _('Birth')) + self.baptism = self.make_row(table, 1, _('Baptism')) + self.death = self.make_row(table, 2, _('Death')) + self.burial = self.make_row(table, 3, _('Burial')) + vbox.pack_start(table, fill=True, expand=False, padding=5) + table = gtk.Table(1, 2) + self.occupation = self.make_row(table, 0, _('Occupation')) + vbox.pack_start(table, fill=True, expand=False, padding=5) + vbox.show_all() + top.pack_start(self.obj_photo, fill=True, expand=False, padding=5) + top.pack_start(vbox, fill=True, expand=True, padding=10) + return top + + def make_row(self, table, row, title): + """ + Make a row in a table. + """ + label = gtk.Label(title + ':') + label.set_alignment(1, 0) + widget = gtk.Label() + widget.set_alignment(0, 0) + table.attach(label, 0, 1, row, row + 1, xoptions=gtk.FILL, xpadding=10) + table.attach(widget, 1, 2, row, row + 1) + return (label, widget) + + def db_changed(self): + self.dbstate.db.connect('person-update', self.update) + self.update() + + def active_changed(self, handle): + self.update() + + def main(self): # return false finishes + active_handle = self.get_active('Person') + active_person = self.dbstate.db.get_person_from_handle(active_handle) + if not active_person: + return + + self.load_person_image(active_person) + self.name.set_text(name_displayer.display(active_person)) + self.display_parents(active_person) + self.display_type(active_person, self.birth, EventType.BIRTH) + self.display_type(active_person, self.baptism, EventType.BAPTISM) + self.display_type(active_person, self.death, EventType.DEATH) + self.display_type(active_person, self.burial, EventType.BURIAL) + occupation_text = self.get_attribute(active_person, 'Occupation') + self.occupation[1].set_text(occupation_text) + + def display_parents(self, active_person): + """ + Display the parents of the active person. + """ + family_handle = active_person.get_main_parents_family_handle() + if family_handle: + family = self.dbstate.db.get_family_from_handle(family_handle) + handle = family.get_father_handle() + if handle: + father = self.dbstate.db.get_person_from_handle(handle) + self.father[1].set_text(name_displayer.display(father)) + else: + self.father[1].set_text(_('Unknown')) + handle = family.get_mother_handle() + if handle: + mother = self.dbstate.db.get_person_from_handle(handle) + self.mother[1].set_text(name_displayer.display(mother)) + else: + self.mother[1].set_text(_('Unknown')) + else: + self.father[1].set_text(_('Unknown')) + self.mother[1].set_text(_('Unknown')) + + def get_attribute(self, person, attr_key): + """ + Return an attribute with the given key. + """ + for attr in person.get_attribute_list(): + if attr.get_type() == attr_key: + return attr.get_value() + return _('Unknown') + + def display_type(self, active_person, widget, event_type): + """ + Display an event type row. + """ + event = self.get_event(active_person, event_type) + if event: + widget[1].set_text(self.format_event(event)) + widget[0].show() + widget[1].show() + else: + widget[1].set_text('') + widget[0].hide() + widget[1].hide() + + def get_event(self, person, event_type): + """ + Return an event of the given type. + """ + for event_ref in person.get_event_ref_list(): + if int(event_ref.get_role()) == EventRoleType.PRIMARY: + event = self.dbstate.db.get_event_from_handle(event_ref.ref) + if int(event.get_type()) == event_type: + return event + return None + + def format_event(self, event): + """ + Format the event for display. + """ + date = DateHandler.get_date(event) + handle = event.get_place_handle() + if handle: + place = self.dbstate.db.get_place_from_handle(handle).get_title() + retval = _('%s - %s.') % (date, place) + else: + retval = _('%s.') % date + return retval + + def load_person_image(self, person): + """ + Load the primary image if it exists. + """ + media_list = person.get_media_list() + if media_list: + photo = media_list[0] + object_handle = photo.get_reference_handle() + obj = self.dbstate.db.get_object_from_handle(object_handle) + full_path = Utils.media_path_full(self.dbstate.db, obj.get_path()) + #reload if different media, or different rectangle + if self.load_obj != full_path or \ + self.load_rect != photo.get_rectangle(): + mime_type = obj.get_mime_type() + if mime_type and mime_type.startswith("image"): + self.load_photo(full_path, photo.get_rectangle()) + else: + self.load_photo(None) + else: + self.load_photo(None) + + def load_photo(self, path, rectangle=None): + """ + Load, scale and display the person's main photo from the path. + """ + self.load_obj = path + self.load_rect = rectangle + if path is None: + self.obj_photo.hide() + else: + try: + i = gtk.gdk.pixbuf_new_from_file(path) + width = i.get_width() + height = i.get_height() + + if rectangle is not None: + upper_x = min(rectangle[0], rectangle[2])/100. + lower_x = max(rectangle[0], rectangle[2])/100. + upper_y = min(rectangle[1], rectangle[3])/100. + lower_y = max(rectangle[1], rectangle[3])/100. + sub_x = int(upper_x * width) + sub_y = int(upper_y * height) + sub_width = int((lower_x - upper_x) * width) + sub_height = int((lower_y - upper_y) * height) + if sub_width > 0 and sub_height > 0: + i = i.subpixbuf(sub_x, sub_y, sub_width, sub_height) + + ratio = float(max(i.get_height(), i.get_width())) + scale = float(190.0)/ratio + x = int(scale*(i.get_width())) + y = int(scale*(i.get_height())) + i = i.scale_simple(x, y, gtk.gdk.INTERP_BILINEAR) + self.obj_photo.set_from_pixbuf(i) + self.obj_photo.show() + except: + self.obj_photo.hide() diff --git a/src/plugins/gramplet/PersonGallery.py b/src/plugins/gramplet/PersonGallery.py new file mode 100644 index 000000000..be710bcc5 --- /dev/null +++ b/src/plugins/gramplet/PersonGallery.py @@ -0,0 +1,125 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ +# + +from gui.utils import open_file_with_default_application +from gen.plug import Gramplet +import Utils +import gtk + +class PersonGallery(Gramplet): + """ + Displays details for a person. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + self.image_list = [] + self.top = gtk.HBox(False, 3) + return self.top + + def db_changed(self): + self.dbstate.db.connect('person-update', self.update) + self.update() + + def active_changed(self, handle): + self.update() + + def main(self): # return false finishes + active_handle = self.get_active('Person') + active_person = self.dbstate.db.get_person_from_handle(active_handle) + if not active_person: + return + + self.clear_images() + self.load_person_images(active_person) + + def clear_images(self): + """ + Remove all images from the Gramplet. + """ + for image in self.image_list: + self.top.remove(image) + self.image_list = [] + + def load_person_images(self, person): + """ + Load the primary image into the main form if it exists. + """ + media_list = person.get_media_list() + for photo in media_list: + object_handle = photo.get_reference_handle() + obj = self.dbstate.db.get_object_from_handle(object_handle) + full_path = Utils.media_path_full(self.dbstate.db, obj.get_path()) + mime_type = obj.get_mime_type() + if mime_type and mime_type.startswith("image"): + pb = self.get_pixbuf(full_path, photo.get_rectangle()) + eb = gtk.EventBox() + eb.connect('button-press-event', self.display_image, full_path) + image = gtk.Image() + eb.add(image) + self.image_list.append(eb) + image.set_from_pixbuf(pb) + eb.show_all() + self.top.pack_start(eb, expand=False, fill=False) + + def display_image(self, widget, event, path): + """ + Display the image with the default application. + """ + if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1: + open_file_with_default_application(path) + + def get_pixbuf(self, path, rectangle=None): + """ + Load, scale and display the person's main photo from the path. + """ + try: + i = gtk.gdk.pixbuf_new_from_file(path) + width = i.get_width() + height = i.get_height() + + if rectangle is not None: + upper_x = min(rectangle[0], rectangle[2])/100. + lower_x = max(rectangle[0], rectangle[2])/100. + upper_y = min(rectangle[1], rectangle[3])/100. + lower_y = max(rectangle[1], rectangle[3])/100. + sub_x = int(upper_x * width) + sub_y = int(upper_y * height) + sub_width = int((lower_x - upper_x) * width) + sub_height = int((lower_y - upper_y) * height) + if sub_width > 0 and sub_height > 0: + i = i.subpixbuf(sub_x, sub_y, sub_width, sub_height) + + ratio = float(max(i.get_height(), i.get_width())) + scale = float(180.0)/ratio + x = int(scale*(i.get_width())) + y = int(scale*(i.get_height())) + i = i.scale_simple(x, y, gtk.gdk.INTERP_BILINEAR) + return i + except: + return None diff --git a/src/plugins/gramplet/PersonResidence.py b/src/plugins/gramplet/PersonResidence.py new file mode 100644 index 000000000..14e869063 --- /dev/null +++ b/src/plugins/gramplet/PersonResidence.py @@ -0,0 +1,92 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ +# + +from gen.lib import EventType, EventRoleType +from gui.editors import EditEvent +from ListModel import ListModel, NOSORT +from gen.plug import Gramplet +import DateHandler +import gtk + +class PersonResidence(Gramplet): + """ + Displays residence events for a person. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + top = gtk.TreeView() + titles = [('', NOSORT, 50,), + (_('Date'), 1, 200), + (_('Place'), 2, 200)] + self.model = ListModel(top, titles, event_func=self.edit_event) + return top + + def db_changed(self): + self.dbstate.db.connect('person-update', self.update) + self.update() + + def active_changed(self, handle): + self.update() + + def main(self): # return false finishes + active_handle = self.get_active('Person') + active_person = self.dbstate.db.get_person_from_handle(active_handle) + if not active_person: + return + + self.model.clear() + for event_ref in active_person.get_event_ref_list(): + if int(event_ref.get_role()) == EventRoleType.PRIMARY: + event = self.dbstate.db.get_event_from_handle(event_ref.ref) + if int(event.get_type()) == EventType.RESIDENCE: + self.add_residence(event) + + def add_residence(self, event): + """ + Add a residence event to the model. + """ + date = DateHandler.get_date(event) + place = '' + handle = event.get_place_handle() + if handle: + place = self.dbstate.db.get_place_from_handle(handle).get_title() + self.model.add((event.get_handle(), date, place)) + + def edit_event(self, treeview): + """ + Edit the selected event. + """ + model, iter_ = treeview.get_selection().get_selected() + if iter_: + handle = model.get_value(iter_, 0) + try: + event = self.dbstate.db.get_event_from_handle(handle) + EditEvent(self.dbstate, self.uistate, [], event) + except Errors.WindowActiveError: + pass diff --git a/src/plugins/gramplet/bottombar.gpr.py b/src/plugins/gramplet/bottombar.gpr.py new file mode 100644 index 000000000..f08c87b66 --- /dev/null +++ b/src/plugins/gramplet/bottombar.gpr.py @@ -0,0 +1,78 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Nick Hall +# +# 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$ + +#------------------------------------------------------------------------ +# +# Register Gramplet +# +#------------------------------------------------------------------------ +register(GRAMPLET, + id="Person Details Gramplet", + name=_("Person Details Gramplet"), + description = _("Gramplet showing details of a person"), + version="1.0.0", + gramps_target_version="3.3", + status = STABLE, + fname="PersonDetails.py", + height=200, + gramplet = 'PersonDetails', + gramplet_title=_("Details"), + ) + +register(GRAMPLET, + id="Person Gallery Gramplet", + name=_("Person Gallery Gramplet"), + description = _("Gramplet showing media objects for a person"), + version="1.0.0", + gramps_target_version="3.3", + status = STABLE, + fname="PersonGallery.py", + height=200, + gramplet = 'PersonGallery', + gramplet_title=_("Gallery"), + ) + +register(GRAMPLET, + id="Person Residence Gramplet", + name=_("Person Residence Gramplet"), + description = _("Gramplet showing residence events for a person"), + version="1.0.0", + gramps_target_version="3.3", + status = STABLE, + fname="PersonResidence.py", + height=200, + gramplet = 'PersonResidence', + gramplet_title=_("Residence"), + ) + +register(GRAMPLET, + id="Person Attributes Gramplet", + name=_("Person Attributes Gramplet"), + description = _("Gramplet showing the attributes of a person"), + version="1.0.0", + gramps_target_version="3.3", + status = STABLE, + fname="PersonAttributes.py", + height=200, + gramplet = 'PersonAttributes', + gramplet_title=_("Attributes"), + ) diff --git a/src/plugins/lib/libpersonview.py b/src/plugins/lib/libpersonview.py index cbfde2d3c..735179441 100644 --- a/src/plugins/lib/libpersonview.py +++ b/src/plugins/lib/libpersonview.py @@ -440,3 +440,13 @@ class BasePersonView(ListView): person = self.dbstate.db.get_person_from_handle(person_handle) person.add_tag(tag_handle) self.dbstate.db.commit_person(person, transaction) + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Welcome Gramplet",), + ("Person Details Gramplet", + "Person Gallery Gramplet", + "Person Residence Gramplet", + "Person Attributes Gramplet"))