diff --git a/apply b/apply new file mode 100755 index 0000000..83ab14e --- /dev/null +++ b/apply @@ -0,0 +1,20 @@ +#!/bin/sh + +# Person Thumbnails in Family Editor +patch /usr/lib/python3/dist-packages/gramps/gui/editors/editfamily.py person-view-age-calculator/gramps/gui/editors/editfamily.py.patch +patch /usr/lib/python3/dist-packages/gramps/gui/glade/editfamily.glade person-view-age-calculator/gramps/gui/glade/editfamily.glade.patch + +# Person View Age Calculator +patch /usr/lib/python3/dist-packages/gramps/gui/editors/editperson.py person-view-age-calculator/gramps/gui/editors/editperson.py.patch +patch /usr/lib/python3/dist-packages/gramps/gui/glade/editperson.glade person-view-age-calculator/gramps/gui/glade/editperson.glade.patch + +# Hyperlink Person Associations +patch /usr/lib/python3/dist-packages/gramps/gui/editors/editpersonref.py hyperlink-person-associations/gramps/gu/editors/editpersonref.py.patch + +# Enhanced TODO Gramplet +patch /usr/lib/python3/dist-packages/gramps/plugins/gramplet/todogramplet.py enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.patch + +# GeoClose, GeoFamClose +patch /usr/lib/python3/dist-packages/gramps/plugins/view/geoclose.py geo-view-select-active-person/gramps/plugins/view/geoclose.py.patch +patch /usr/lib/python3/dist-packages/gramps/plugins/view/geofamclose.py geo-view-select-active-person/gramps/plugins/view/geofamclose.py.patch + diff --git a/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py new file mode 100644 index 0000000..c04f1d3 --- /dev/null +++ b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py @@ -0,0 +1,248 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2009 Douglas S. Blank +# Copyright (C) 2013 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# Gtk modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.plug import Gramplet +from gramps.gui.widgets.styledtexteditor import StyledTextEditor +from gramps.gui.widgets import SimpleButton +from gramps.gen.lib import StyledText, Note, NoteType +from gramps.gen.filters import GenericFilterFactory, rules +from gramps.gen.utils.db import navigation_label +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +class ToDoGramplet(Gramplet): + """ + Displays all the To Do notes in the database. + """ + def init(self): + self.note_type = "To Do" + self.show_toolbar = True + + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add(self.gui.WIDGET) + self.gui.WIDGET.show() + + + def build_options(self): + from gramps.gen.plug.menu import StringOption, BooleanOption + + self.add_option(StringOption(_("Note type"), + self.note_type)) + self.add_option(BooleanOption(_("Show toolbar"), + self.show_toolbar)) + + def build_gui(self): + """ + Build the GUI interface. + """ + top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + hbox = Gtk.Box() + self.left = SimpleButton('go-previous', self.left_clicked) + self.left.set_tooltip_text(_('Previous To Do note')) + self.left.set_sensitive(False) + pack_start(self.left, False, False, 0) + self.right = SimpleButton('go-next', self.right_clicked) + self.right.set_tooltip_text(_('Next To Do note')) + self.right.set_sensitive(False) + hbox.pack_start(self.right, False, False, 0) + self.edit = SimpleButton('gtk-edit', self.edit_clicked) + self.edit.set_tooltip_text(_('Edit the selected To Do note')) + self.edit.set_sensitive(False) + hbox.pack_start(self.edit, False, False, 0) + self.new = SimpleButton('document-new', self.new_clicked) + self.new.set_tooltip_text(_('Add a new To Do note')) + hbox.pack_start(self.new, False, False, 0) + self.page = Gtk.Label() + hbox.pack_end(self.page, False, False, 10) + + self.hbox = hbox + + self.title = Gtk.Label(halign=Gtk.Align.START) + self.title.set_line_wrap(True) + + scrolledwindow = Gtk.ScrolledWindow() + scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) + self.texteditor = StyledTextEditor() + self.texteditor.set_editable(False) + self.texteditor.set_wrap_mode(Gtk.WrapMode.WORD) + scrolledwindow.add(self.texteditor) + + top.pack_start(hbox, False, False, 0) + top.pack_start(self.title, False, False, 4) + top.pack_start(scrolledwindow, True, True, 0) + top.show_all() + return top + + def save_options(self): + self.note_type = self.get_option(_("Note type")).get_value() + self.show_toolbar = self.get_option(_("Show toolbar")).get_value() + + + def on_load(self): + if len(self.gui.data) == 2: + self.note_type = self.gui.data[0] + self.show_toolbar = bool(self.gui.data[1]) + + def save_update_options(self, widget=None): + self.note_type = self.get_option(_("Note type")).get_value() + self.show_toolbar = self.get_option(_("Show toolbar")).get_value() + self.gui.data = [self.note_type, self.show_toolbar] + + self.update() + + def main(self): + self.get_notes() + + def get_note_list(self): + """ + Get a list of all To Do notes. + """ + all_notes = self.dbstate.db.get_note_handles() + FilterClass = GenericFilterFactory('Note') + filter = FilterClass() + filter.add_rule(rules.note.HasType([self.note_type])) + note_list = filter.apply(self.dbstate.db, all_notes) + return note_list + + def get_notes(self): + """ + Display all the To Do notes. + """ + self.left.set_sensitive(False) + self.right.set_sensitive(False) + self.edit.set_sensitive(False) + self.texteditor.set_text(StyledText()) + self.note_list = self.get_note_list() + self.page.set_text('') + self.title.set_text('') + if len(self.note_list) > 0: + self.set_has_data(True) + self.edit.set_sensitive(True) + if len(self.note_list) > 1: + self.right.set_sensitive(True) + self.current = 0 + self.display_note() + else: + self.set_has_data(False) + + def clear_text(self): + self.left.set_sensitive(False) + self.right.set_sensitive(False) + self.edit.set_sensitive(False) + self.texteditor.set_text(StyledText()) + self.page.set_text('') + self.title.set_text('') + self.current = 0 + + def display_note(self): + """ + Display the current note. + """ + note_handle = self.note_list[self.current] + note = self.dbstate.db.get_note_from_handle(note_handle) + obj = [x for x in self.dbstate.db.find_backlink_handles(note_handle)] + if obj: + name, obj = navigation_label(self.dbstate.db, obj[0][0], obj[0][1]) + self.title.set_text(name) + else: + self.title.set_text(_("Unattached")) + self.texteditor.set_text(note.get_styledtext()) + self.page.set_text(_('%(current)d of %(total)d') % + {'current': self.current + 1, + 'total': len(self.note_list)}) + + self.hbox.set_visible(self.show_toolbar) + + def left_clicked(self, button): + """ + Display the previous note. + """ + if self.current > 0: + self.current -= 1 + self.right.set_sensitive(True) + if self.current == 0: + self.left.set_sensitive(False) + self.display_note() + + def right_clicked(self, button): + """ + Display the next note. + """ + if self.current < len(self.note_list) - 1: + self.current += 1 + self.left.set_sensitive(True) + if self.current == len(self.note_list) - 1: + self.right.set_sensitive(False) + self.display_note() + + def get_has_data(self): + """ + Return True if the gramplet has data, else return False. + """ + if self.get_note_list(): + return True + return False + + def edit_clicked(self, obj): + """ + Edit current To Do note. + """ + from gramps.gui.editors import EditNote + note_handle = self.note_list[self.current] + note = self.dbstate.db.get_note_from_handle(note_handle) + try: + EditNote(self.gui.dbstate, self.gui.uistate, [], note) + except AttributeError: + pass + + def new_clicked(self, obj): + """ + Create a new To Do note. + """ + from gramps.gui.editors import EditNote + note = Note() + note.set_type(self.note_type) + try: + EditNote(self.gui.dbstate, self.gui.uistate, [], note) + except AttributeError: + pass + + def update_has_data(self): + self.set_has_data(self.get_has_data()) + + def db_changed(self): + self.connect(self.dbstate.db, 'note-add', self.update) + self.connect(self.dbstate.db, 'note-delete', self.update) + self.connect(self.dbstate.db, 'note-update', self.update) diff --git a/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.0 b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.0 new file mode 100644 index 0000000..24b8863 --- /dev/null +++ b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.0 @@ -0,0 +1,215 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2009 Douglas S. Blank +# Copyright (C) 2013 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# Gtk modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.plug import Gramplet +from gramps.gui.widgets.styledtexteditor import StyledTextEditor +from gramps.gui.widgets import SimpleButton +from gramps.gen.lib import StyledText, Note, NoteType +from gramps.gen.filters import GenericFilterFactory, rules +from gramps.gen.utils.db import navigation_label +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +class ToDoGramplet(Gramplet): + """ + Displays all the To Do notes in the database. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + hbox = Gtk.Box() + self.left = SimpleButton('go-previous', self.left_clicked) + self.left.set_tooltip_text(_('Previous To Do note')) + self.left.set_sensitive(False) + hbox.pack_start(self.left, False, False, 0) + self.right = SimpleButton('go-next', self.right_clicked) + self.right.set_tooltip_text(_('Next To Do note')) + self.right.set_sensitive(False) + hbox.pack_start(self.right, False, False, 0) + self.edit = SimpleButton('gtk-edit', self.edit_clicked) + self.edit.set_tooltip_text(_('Edit the selected To Do note')) + self.edit.set_sensitive(False) + hbox.pack_start(self.edit, False, False, 0) + self.new = SimpleButton('document-new', self.new_clicked) + self.new.set_tooltip_text(_('Add a new To Do note')) + hbox.pack_start(self.new, False, False, 0) + self.page = Gtk.Label() + hbox.pack_end(self.page, False, False, 10) + + self.title = Gtk.Label(halign=Gtk.Align.START) + self.title.set_line_wrap(True) + + scrolledwindow = Gtk.ScrolledWindow() + scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, + Gtk.PolicyType.AUTOMATIC) + self.texteditor = StyledTextEditor() + self.texteditor.set_editable(False) + self.texteditor.set_wrap_mode(Gtk.WrapMode.WORD) + scrolledwindow.add(self.texteditor) + + top.pack_start(hbox, False, False, 0) + top.pack_start(self.title, False, False, 4) + top.pack_start(scrolledwindow, True, True, 0) + top.show_all() + return top + + def main(self): + self.get_notes() + + def get_note_list(self): + """ + Get a list of all To Do notes. + """ + all_notes = self.dbstate.db.get_note_handles() + FilterClass = GenericFilterFactory('Note') + filter = FilterClass() + filter.add_rule(rules.note.HasType(["To Do"])) + note_list = filter.apply(self.dbstate.db, all_notes) + return note_list + + def get_notes(self): + """ + Display all the To Do notes. + """ + self.left.set_sensitive(False) + self.right.set_sensitive(False) + self.edit.set_sensitive(False) + self.texteditor.set_text(StyledText()) + self.note_list = self.get_note_list() + self.page.set_text('') + self.title.set_text('') + if len(self.note_list) > 0: + self.set_has_data(True) + self.edit.set_sensitive(True) + if len(self.note_list) > 1: + self.right.set_sensitive(True) + self.current = 0 + self.display_note() + else: + self.set_has_data(False) + + def clear_text(self): + self.left.set_sensitive(False) + self.right.set_sensitive(False) + self.edit.set_sensitive(False) + self.texteditor.set_text(StyledText()) + self.page.set_text('') + self.title.set_text('') + self.current = 0 + + def display_note(self): + """ + Display the current note. + """ + note_handle = self.note_list[self.current] + note = self.dbstate.db.get_note_from_handle(note_handle) + obj = [x for x in self.dbstate.db.find_backlink_handles(note_handle)] + if obj: + name, obj = navigation_label(self.dbstate.db, obj[0][0], obj[0][1]) + self.title.set_text(name) + else: + self.title.set_text(_("Unattached")) + self.texteditor.set_text(note.get_styledtext()) + self.page.set_text(_('%(current)d of %(total)d') % + {'current': self.current + 1, + 'total': len(self.note_list)}) + + def left_clicked(self, button): + """ + Display the previous note. + """ + if self.current > 0: + self.current -= 1 + self.right.set_sensitive(True) + if self.current == 0: + self.left.set_sensitive(False) + self.display_note() + + def right_clicked(self, button): + """ + Display the next note. + """ + if self.current < len(self.note_list) - 1: + self.current += 1 + self.left.set_sensitive(True) + if self.current == len(self.note_list) - 1: + self.right.set_sensitive(False) + self.display_note() + + def get_has_data(self): + """ + Return True if the gramplet has data, else return False. + """ + if self.get_note_list(): + return True + return False + + def edit_clicked(self, obj): + """ + Edit current To Do note. + """ + from gramps.gui.editors import EditNote + note_handle = self.note_list[self.current] + note = self.dbstate.db.get_note_from_handle(note_handle) + try: + EditNote(self.gui.dbstate, self.gui.uistate, [], note) + except AttributeError: + pass + + def new_clicked(self, obj): + """ + Create a new To Do note. + """ + from gramps.gui.editors import EditNote + note = Note() + note.set_type(NoteType.TODO) + try: + EditNote(self.gui.dbstate, self.gui.uistate, [], note) + except AttributeError: + pass + + def update_has_data(self): + self.set_has_data(self.get_has_data()) + + def db_changed(self): + self.connect(self.dbstate.db, 'note-add', self.update) + self.connect(self.dbstate.db, 'note-delete', self.update) + self.connect(self.dbstate.db, 'note-update', self.update) diff --git a/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.patch b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.patch new file mode 100644 index 0000000..d6ada71 --- /dev/null +++ b/enhanced-todo-gramplet/gramps/plugins/gramplet/todogramplet.py.patch @@ -0,0 +1,50 @@ +46a47,49 +> self.note_type = "To Do" +> self.show_toolbar = True +> +51a55,63 +> +> def build_options(self): +> from gramps.gen.plug.menu import StringOption, BooleanOption +> +> self.add_option(StringOption(_("Note type"), +> self.note_type)) +> self.add_option(BooleanOption(_("Show toolbar"), +> self.show_toolbar)) +> +62c74 +< hbox.pack_start(self.left, False, False, 0) +--- +> pack_start(self.left, False, False, 0) +76a89,90 +> self.hbox = hbox +> +93a108,124 +> def save_options(self): +> self.note_type = self.get_option(_("Note type")).get_value() +> self.show_toolbar = self.get_option(_("Show toolbar")).get_value() +> +> +> def on_load(self): +> if len(self.gui.data) == 2: +> self.note_type = self.gui.data[0] +> self.show_toolbar = bool(self.gui.data[1]) +> +> def save_update_options(self, widget=None): +> self.note_type = self.get_option(_("Note type")).get_value() +> self.show_toolbar = self.get_option(_("Show toolbar")).get_value() +> self.gui.data = [self.note_type, self.show_toolbar] +> +> self.update() +> +104c135 +< filter.add_rule(rules.note.HasType(["To Do"])) +--- +> filter.add_rule(rules.note.HasType([self.note_type])) +154a186,187 +> self.hbox.set_visible(self.show_toolbar) +> +203c236 +< note.set_type(NoteType.TODO) +--- +> note.set_type(self.note_type) diff --git a/geo-view-select-active-person/gramps/plugins/view/geoclose.py b/geo-view-select-active-person/gramps/plugins/view/geoclose.py new file mode 100644 index 0000000..01bca67 --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geoclose.py @@ -0,0 +1,715 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011-2016 Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Geography for two persons +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +import operator +from gi.repository import Gtk +from math import hypot +from html import escape + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger("GeoGraphy.geoclose") + +#------------------------------------------------------------------------- +# +# Gramps Modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import EventRoleType, EventType +from gramps.gen.config import config +from gramps.gen.datehandler import displayer, get_date +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.gen.utils.place import conv_lat_lon +from gramps.gui.views.bookmarks import PersonBookmarks +from gramps.plugins.lib.maps import constants +from gramps.plugins.lib.maps.geography import GeoGraphyView +from gramps.gui.selectors import SelectorFactory +from gramps.gen.utils.db import (get_birth_or_fallback, get_death_or_fallback) +from gramps.gui.uimanager import ActionGroup + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +_UI_DEF = [ + ''' + +
+ + win.Back + _Back + + + win.Forward + _Forward + +
+
+ + win.HomePerson + _Home + +
+
+ ''', + ''' +
+ + win.PrintView + Print... + +
+ ''', + ''' +
+ + win.AddBook + _Add Bookmark + + + win.EditBook + %s... + +
+ ''' % _('Organize Bookmarks'), # Following are the Toolbar items + ''' + + + + go-previous + win.Back + ''' + '''Go to the previous object in the history + _Back + True + + + False + + + + + go-next + win.Forward + ''' + '''Go to the next object in the history + _Forward + True + + + False + + + + + go-home + win.HomePerson + ''' + '''Go to the home person + _Home + True + + + False + + + + + gramps-person + win.RefPerson + ''' + '''Select the person which is the reference for life ways + reference _Person + True + + + False + + + + ''', + ''' + + + + document-print + win.PrintView + ''' + '''Print or save the Map + Print... + True + + + False + + + + '''] + +# pylint: disable=no-member +# pylint: disable=unused-argument + +#------------------------------------------------------------------------- +# +# GeoView +# +#------------------------------------------------------------------------- +class GeoClose(GeoGraphyView): + """ + The view used to render person map. + """ + CONFIGSETTINGS = ( + ('geography.path', constants.GEOGRAPHY_PATH), + + ('geography.zoom', 10), + ('geography.zoom_when_center', 12), + ('geography.show_cross', True), + ('geography.lock', False), + ('geography.center-lat', 0.0), + ('geography.center-lon', 0.0), + ('geography.use-keypad', True), + + ('geography.map_service', constants.OPENSTREETMAP), + ('geography.max_places', 5000), + + # specific to geoclose : + + ('geography.color1', 'blue'), + ('geography.color2', 'green'), + ('geography.maximum_meeting_zone', 5), + + ) + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + GeoGraphyView.__init__(self, _("Have they been able to meet?"), + pdata, dbstate, uistate, + PersonBookmarks, + nav_group) + self.dbstate = dbstate + self.uistate = uistate + self.place_list = [] + self.all_place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + self.refperson = None + self.refperson_bookmark = None + self.nbplaces = 0 + self.nbmarkers = 0 + self.sort = [] + self.tracks = [] + self.additional_uis.append(self.additional_ui()) + self.ref_person = None + self.skip_list = [] + self.track = [] + self.place_list_active = [] + self.place_list_ref = [] + self.cal = config.get('preferences.calendar-format-report') + self.no_show_places_in_status_bar = False + self.add_item = None + self.newmenu = None + self.config_meeting_slider = None + self.dbstate.connect('database-changed', self.reset_change_db) + + def reset_change_db(self, dummy_dbase): + """ + Used to reset the family reference + """ + self.refperson = None + + + def get_title(self): + """ + Used to set the titlebar in the configuration window. + """ + return _('GeoClose') + + def get_stock(self): + """ + Returns the name of the stock icon to use for the display. + This assumes that this icon has already been registered + as a stock icon. + """ + return 'gramps-relation' + + def get_viewtype_stock(self): + """Type of view in category + """ + return 'geo-show-family' + + def additional_ui(self): + """ + Specifies the UIManager XML code that defines the menus and buttons + associated with the interface. + """ + return _UI_DEF + + def navigation_type(self): + """ + Indicates the navigation type. Navigation type can be the string + name of any of the primary objects. + """ + return 'Person' + + def goto_handle(self, handle=None): + """ + Rebuild the tree with the given person handle as the root. + """ + self.place_list_active = [] + self.place_list_ref = [] + self.all_place_list = [] + self.sort = [] + self.places_found = [] + self.nbmarkers = 0 + self.nbplaces = 0 + self.place_without_coordinates = [] + self.remove_all_gps() + self.remove_all_markers() + self.lifeway_layer.clear_ways() + self.message_layer.clear_messages() + self.message_layer.set_font_attributes(None, None, None) + active = self.get_active() + if active: + indiv1 = self.dbstate.db.get_person_from_handle(active) + color = self._config.get('geography.color2') + self._createmap(indiv1, color, self.place_list_active, False) + if self.refperson: + color = self._config.get('geography.color1') + self.message_layer.add_message( + _("Reference : %(name)s ( %(birth)s - %(death)s )") % { + 'name': _nd.display(self.refperson), + 'birth': self.birth(self.refperson), + 'death': self.death(self.refperson)}) + if indiv1: + self.message_layer.add_message( + _("The other : %(name)s ( %(birth)s - %(death)s )") % { + 'name': _nd.display(indiv1), + 'birth': self.birth(indiv1), + 'death': self.death(indiv1)}) + else: + self.message_layer.add_message(_("The other person is unknown")) + self._createmap(self.refperson, color, self.place_list_ref, True) + if self.refperson_bookmark is None: + self.refperson_bookmark = self.refperson.get_handle() + self.add_bookmark_from_popup(None, self.refperson_bookmark) + else: + self.message_layer.add_message( + _("You must choose one reference person.")) + self.message_layer.add_message(_("Go to the person view and select " + "the people you want to compare. " + "Return to this view and use the" + " history.")) + self.possible_meeting(self.place_list_ref, self.place_list_active) + self.uistate.modify_statusbar(self.dbstate) + + def birth(self, person): + """ + return "" or the birth date of the person + """ + birth = get_birth_or_fallback(self.dbstate.db, person) + if birth and birth.get_type() != EventType.BIRTH: + sdate = get_date(birth) + if sdate: + bdate = "%s" % escape(sdate) + else: + bdate = "" + elif birth: + bdate = escape(get_date(birth)) + else: + bdate = "" + return bdate + + def death(self, person): + """ + return "" or the death date of the person + """ + death = get_death_or_fallback(self.dbstate.db, person) + if death and death.get_type() != EventType.DEATH: + sdate = get_date(death) + if sdate: + ddate = "%s" % escape(sdate) + else: + ddate = "" + elif death: + ddate = escape(get_date(death)) + else: + ddate = "" + return ddate + + def define_actions(self): + """ + Define action for the reference person button. + """ + GeoGraphyView.define_actions(self) + self._add_action('RefPerson', self.select_person) + + def select_person(self, *obj): + """ + Open a selection box to choose the ref person. + """ + self.track = [] + self.skip_list = [] + self.refperson = None + self.refperson_bookmark = None + selectperson = SelectorFactory('Person') + sel = selectperson(self.dbstate, self.uistate, self.track, + _("Select the person which will be our reference."), + skip=self.skip_list) + self.refperson = sel.run() + self.goto_handle(None) + + def select_person2(self, *obj): + """ + Open a selection box to choose the secondary person. + """ + selectperson = SelectorFactory('Person') + sel = selectperson(self.dbstate, self.uistate, self.track, + _("Select the person which will be our active."), + skip=self.skip_list) + self.uistate.set_active(sel.run().get_handle(), 'Person') + self.goto_handle(None) + + def build_tree(self): + """ + This is called by the parent class when the view becomes visible. Since + all handling of visibility is now in rebuild_trees, see that for more + information. + """ + self.lifeway_layer.clear_ways() + if not self.dbstate.is_open(): + return + active = self.get_active() + if active: + person = self.dbstate.db.get_person_from_handle(active) + if person is None: + self.goto_handle(None) + else: + self.goto_handle(handle=person) + else: + self.goto_handle(None) + + def draw(self, menu, marks, color, reference): + """ + Create all moves for the people's event. + """ + points = [] + mark = None + for mark in marks: + startlat = float(mark[3]) + startlon = float(mark[4]) + not_stored = True + for idx in range(0, len(points)): + if points[idx][0] == startlat and points[idx][1] == startlon: + not_stored = False + if not_stored: + points.append((startlat, startlon)) + self.lifeway_layer.add_way(points, color) + if reference: + self.lifeway_layer.add_way_ref(points, 'orange', + float(self._config.get("geography.maximum_meeting_zone")) / 10) + return False + + def possible_meeting(self, place_list_ref, place_list_active): + """ + Try to see if two persons can be to the same place during their life. + If yes, show a marker with the dates foe each person. + """ + radius = float(self._config.get("geography.maximum_meeting_zone")/10.0) + for ref in place_list_ref: + for act in place_list_active: + if (hypot(float(act[3])-float(ref[3]), + float(act[4])-float(ref[4])) <= radius) == True: + # we are in the meeting zone + self.add_marker(None, None, act[3], act[4], act[7], True, 1) + self.all_place_list.append(act) + self.add_marker(None, None, ref[3], ref[4], ref[7], True, 1) + self.all_place_list.append(ref) + + def _createmap(self, person, color, place_list, reference): + """ + Create all markers for each people's event in the database which has + a lat/lon. + """ + dbstate = self.dbstate + self.cal = config.get('preferences.calendar-format-report') + self.place_list = place_list + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + latitude = "" + longitude = "" + if person is not None: + # For each event, if we have a place, set a marker. + for event_ref in person.get_event_ref_list(): + if not event_ref: + continue + event = dbstate.db.get_event_from_handle(event_ref.ref) + role = event_ref.get_role() + try: + date = event.get_date_object().to_calendar(self.cal) + except: + continue + eyear = str("%04d" % date.get_year()) + \ + str("%02d" % date.get_month()) + \ + str("%02d" % date.get_day()) + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle(place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon(latitude, + longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + descr1 = _("%(eventtype)s : %(name)s") % { + 'eventtype': evt, + 'name': _nd.display(person)} + # place.get_longitude and place.get_latitude return + # one string. We have coordinates when the two values + # contains non null string. + if longitude and latitude: + self._append_to_places_list(descr, evt, + _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord( + place.gramps_id, descr) + family_list = person.get_family_handle_list() + descr1 = " - " + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + descr1 = " - " + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + descr1 = "%s - " % _nd.display(father) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + descr1 = "%s%s" % (descr1, _nd.display(mother)) + for event_ref in family.get_event_ref_list(): + if event_ref: + event = dbstate.db.get_event_from_handle( + event_ref.ref) + role = event_ref.get_role() + if event.get_place_handle(): + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle( + place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon( + latitude, longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + eyear = str( + "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) + if longitude and latitude: + self._append_to_places_list(descr, + evt, _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord(place.gramps_id, descr) + + sort1 = sorted(self.place_list, key=operator.itemgetter(6)) + self.draw(None, sort1, color, reference) + # merge with the last results + merge_list = [] + for the_list in self.sort, sort1: + merge_list += the_list + self.sort = sorted(merge_list, key=operator.itemgetter(6)) + + def bubble_message(self, event, lat, lon, marks): + """ + Create the menu for the selected marker + """ + self.newmenu = Gtk.Menu() + menu = self.newmenu + menu.set_title("person") + events = [] + message = "" + oldplace = "" + prevmark = None + for mark in marks: + for plce in self.all_place_list: + if plce[3] == mark[3] and plce[4] == mark[4]: + if plce[10] in events: + continue + else: + events.append(plce[10]) + + if plce[0] != oldplace: + message = "%s :" % plce[0] + self.add_place_bubble_message(event, lat, lon, + marks, menu, + message, plce) + oldplace = plce[0] + message = "" + evt = self.dbstate.db.get_event_from_gramps_id(plce[10]) + # format the date as described in preferences. + date = displayer.display(evt.get_date_object()) + if date == "": + date = _("Unknown") + if plce[11] == EventRoleType.PRIMARY: + message = "(%s) %s : %s" % (date, plce[2], plce[1]) + elif plce[11] == EventRoleType.FAMILY: + (father_name, + mother_name) = self._get_father_and_mother_name(evt) + message = "(%s) %s : %s - %s" % (date, plce[7], + father_name, + mother_name) + else: + descr = evt.get_description() + if descr == "": + descr = _('No description') + message = "(%s) %s => %s" % (date, plce[11], descr) + prevmark = plce + self.add_item = Gtk.MenuItem(label=message) + add_item = self.add_item + add_item.show() + menu.append(add_item) + self.itemoption = Gtk.Menu() + itemoption = self.itemoption + itemoption.set_title(message) + itemoption.show() + add_item.set_submenu(itemoption) + modify = Gtk.MenuItem(label=_("Edit Event")) + modify.show() + modify.connect("activate", self.edit_event, + event, lat, lon, prevmark) + itemoption.append(modify) + center = Gtk.MenuItem(label=_("Center on this place")) + center.show() + center.connect("activate", self.center_here, + event, lat, lon, prevmark) + itemoption.append(center) + menu.show() + menu.popup(None, None, None, + None, event.button, event.time) + return 0 + + def add_specific_menu(self, menu, event, lat, lon): + """ + Add specific entry to the navigation menu. + """ + add_item = Gtk.MenuItem() + add_item.show() + menu.append(add_item) + add_item = Gtk.MenuItem( + label=_("Choose and bookmark the new reference person")) + add_item.connect("activate", self.select_person) + add_item.show() + menu.append(add_item) + + add_item = Gtk.MenuItem( + label=_("Choose the new active person")) + add_item.connect("activate", self.select_person2) + add_item.show() + menu.append(add_item) + return + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Person Filter",), + ()) + + def specific_options(self, configdialog): + """ + Add specific entry to the preference menu. + Must be done in the associated view. + """ + grid = Gtk.Grid() + grid.set_border_width(12) + grid.set_column_spacing(6) + grid.set_row_spacing(6) + configdialog.add_text(grid, + _('The meeting zone probability radius.\n' + 'The colored zone is approximative.\n' + 'The meeting zone is only shown for the reference person.\n' + 'The value 9 means about 42 miles or 67 kms.\n' + 'The value 1 means about 4.6 miles or 7.5 kms.\n' + 'The value is in tenth of degree.'), + 1, line_wrap=False) + self.config_meeting_slider = configdialog.add_slider(grid, + "", + 2, 'geography.maximum_meeting_zone', + (1, 9)) + return _('The selection parameters'), grid + + def config_connect(self): + """ + used to monitor changes in the ini file + """ + self._config.connect('geography.maximum_meeting_zone', + self.cb_update_meeting_radius) + + def cb_update_meeting_radius(self, client, cnxn_id, entry, data): + """ + Called when the radius change + """ + self.goto_handle(handle=None) + diff --git a/geo-view-select-active-person/gramps/plugins/view/geoclose.py.0 b/geo-view-select-active-person/gramps/plugins/view/geoclose.py.0 new file mode 100644 index 0000000..adc47d7 --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geoclose.py.0 @@ -0,0 +1,698 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011-2016 Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Geography for two persons +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +import operator +from gi.repository import Gtk +from math import hypot +from html import escape + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger("GeoGraphy.geoclose") + +#------------------------------------------------------------------------- +# +# Gramps Modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import EventRoleType, EventType +from gramps.gen.config import config +from gramps.gen.datehandler import displayer, get_date +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.gen.utils.place import conv_lat_lon +from gramps.gui.views.bookmarks import PersonBookmarks +from gramps.plugins.lib.maps import constants +from gramps.plugins.lib.maps.geography import GeoGraphyView +from gramps.gui.selectors import SelectorFactory +from gramps.gen.utils.db import (get_birth_or_fallback, get_death_or_fallback) +from gramps.gui.uimanager import ActionGroup + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +_UI_DEF = [ + ''' + +
+ + win.Back + _Back + + + win.Forward + _Forward + +
+
+ + win.HomePerson + _Home + +
+
+ ''', + ''' +
+ + win.PrintView + Print... + +
+ ''', + ''' +
+ + win.AddBook + _Add Bookmark + + + win.EditBook + %s... + +
+ ''' % _('Organize Bookmarks'), # Following are the Toolbar items + ''' + + + + go-previous + win.Back + ''' + '''Go to the previous object in the history + _Back + True + + + False + + + + + go-next + win.Forward + ''' + '''Go to the next object in the history + _Forward + True + + + False + + + + + go-home + win.HomePerson + ''' + '''Go to the home person + _Home + True + + + False + + + + + gramps-person + win.RefPerson + ''' + '''Select the person which is the reference for life ways + reference _Person + True + + + False + + + + ''', + ''' + + + + document-print + win.PrintView + ''' + '''Print or save the Map + Print... + True + + + False + + + + '''] + +# pylint: disable=no-member +# pylint: disable=unused-argument + +#------------------------------------------------------------------------- +# +# GeoView +# +#------------------------------------------------------------------------- +class GeoClose(GeoGraphyView): + """ + The view used to render person map. + """ + CONFIGSETTINGS = ( + ('geography.path', constants.GEOGRAPHY_PATH), + + ('geography.zoom', 10), + ('geography.zoom_when_center', 12), + ('geography.show_cross', True), + ('geography.lock', False), + ('geography.center-lat', 0.0), + ('geography.center-lon', 0.0), + ('geography.use-keypad', True), + + ('geography.map_service', constants.OPENSTREETMAP), + ('geography.max_places', 5000), + + # specific to geoclose : + + ('geography.color1', 'blue'), + ('geography.color2', 'green'), + ('geography.maximum_meeting_zone', 5), + + ) + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + GeoGraphyView.__init__(self, _("Have they been able to meet?"), + pdata, dbstate, uistate, + PersonBookmarks, + nav_group) + self.dbstate = dbstate + self.uistate = uistate + self.place_list = [] + self.all_place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + self.refperson = None + self.refperson_bookmark = None + self.nbplaces = 0 + self.nbmarkers = 0 + self.sort = [] + self.tracks = [] + self.additional_uis.append(self.additional_ui()) + self.ref_person = None + self.skip_list = [] + self.track = [] + self.place_list_active = [] + self.place_list_ref = [] + self.cal = config.get('preferences.calendar-format-report') + self.no_show_places_in_status_bar = False + self.add_item = None + self.newmenu = None + self.config_meeting_slider = None + self.dbstate.connect('database-changed', self.reset_change_db) + + def reset_change_db(self, dummy_dbase): + """ + Used to reset the family reference + """ + self.refperson = None + + + def get_title(self): + """ + Used to set the titlebar in the configuration window. + """ + return _('GeoClose') + + def get_stock(self): + """ + Returns the name of the stock icon to use for the display. + This assumes that this icon has already been registered + as a stock icon. + """ + return 'gramps-relation' + + def get_viewtype_stock(self): + """Type of view in category + """ + return 'geo-show-family' + + def additional_ui(self): + """ + Specifies the UIManager XML code that defines the menus and buttons + associated with the interface. + """ + return _UI_DEF + + def navigation_type(self): + """ + Indicates the navigation type. Navigation type can be the string + name of any of the primary objects. + """ + return 'Person' + + def goto_handle(self, handle=None): + """ + Rebuild the tree with the given person handle as the root. + """ + self.place_list_active = [] + self.place_list_ref = [] + self.all_place_list = [] + self.sort = [] + self.places_found = [] + self.nbmarkers = 0 + self.nbplaces = 0 + self.place_without_coordinates = [] + self.remove_all_gps() + self.remove_all_markers() + self.lifeway_layer.clear_ways() + self.message_layer.clear_messages() + self.message_layer.set_font_attributes(None, None, None) + active = self.get_active() + if active: + indiv1 = self.dbstate.db.get_person_from_handle(active) + color = self._config.get('geography.color2') + self._createmap(indiv1, color, self.place_list_active, False) + if self.refperson: + color = self._config.get('geography.color1') + self.message_layer.add_message( + _("Reference : %(name)s ( %(birth)s - %(death)s )") % { + 'name': _nd.display(self.refperson), + 'birth': self.birth(self.refperson), + 'death': self.death(self.refperson)}) + if indiv1: + self.message_layer.add_message( + _("The other : %(name)s ( %(birth)s - %(death)s )") % { + 'name': _nd.display(indiv1), + 'birth': self.birth(indiv1), + 'death': self.death(indiv1)}) + else: + self.message_layer.add_message(_("The other person is unknown")) + self._createmap(self.refperson, color, self.place_list_ref, True) + if self.refperson_bookmark is None: + self.refperson_bookmark = self.refperson.get_handle() + self.add_bookmark_from_popup(None, self.refperson_bookmark) + else: + self.message_layer.add_message( + _("You must choose one reference person.")) + self.message_layer.add_message(_("Go to the person view and select " + "the people you want to compare. " + "Return to this view and use the" + " history.")) + self.possible_meeting(self.place_list_ref, self.place_list_active) + self.uistate.modify_statusbar(self.dbstate) + + def birth(self, person): + """ + return "" or the birth date of the person + """ + birth = get_birth_or_fallback(self.dbstate.db, person) + if birth and birth.get_type() != EventType.BIRTH: + sdate = get_date(birth) + if sdate: + bdate = "%s" % escape(sdate) + else: + bdate = "" + elif birth: + bdate = escape(get_date(birth)) + else: + bdate = "" + return bdate + + def death(self, person): + """ + return "" or the death date of the person + """ + death = get_death_or_fallback(self.dbstate.db, person) + if death and death.get_type() != EventType.DEATH: + sdate = get_date(death) + if sdate: + ddate = "%s" % escape(sdate) + else: + ddate = "" + elif death: + ddate = escape(get_date(death)) + else: + ddate = "" + return ddate + + def define_actions(self): + """ + Define action for the reference person button. + """ + GeoGraphyView.define_actions(self) + self._add_action('RefPerson', self.select_person) + + def select_person(self, *obj): + """ + Open a selection box to choose the ref person. + """ + self.track = [] + self.skip_list = [] + self.refperson = None + self.refperson_bookmark = None + selectperson = SelectorFactory('Person') + sel = selectperson(self.dbstate, self.uistate, self.track, + _("Select the person which will be our reference."), + skip=self.skip_list) + self.refperson = sel.run() + self.goto_handle(None) + + def build_tree(self): + """ + This is called by the parent class when the view becomes visible. Since + all handling of visibility is now in rebuild_trees, see that for more + information. + """ + self.lifeway_layer.clear_ways() + if not self.dbstate.is_open(): + return + active = self.get_active() + if active: + person = self.dbstate.db.get_person_from_handle(active) + if person is None: + self.goto_handle(None) + else: + self.goto_handle(handle=person) + else: + self.goto_handle(None) + + def draw(self, menu, marks, color, reference): + """ + Create all moves for the people's event. + """ + points = [] + mark = None + for mark in marks: + startlat = float(mark[3]) + startlon = float(mark[4]) + not_stored = True + for idx in range(0, len(points)): + if points[idx][0] == startlat and points[idx][1] == startlon: + not_stored = False + if not_stored: + points.append((startlat, startlon)) + self.lifeway_layer.add_way(points, color) + if reference: + self.lifeway_layer.add_way_ref(points, 'orange', + float(self._config.get("geography.maximum_meeting_zone")) / 10) + return False + + def possible_meeting(self, place_list_ref, place_list_active): + """ + Try to see if two persons can be to the same place during their life. + If yes, show a marker with the dates foe each person. + """ + radius = float(self._config.get("geography.maximum_meeting_zone")/10.0) + for ref in place_list_ref: + for act in place_list_active: + if (hypot(float(act[3])-float(ref[3]), + float(act[4])-float(ref[4])) <= radius) == True: + # we are in the meeting zone + self.add_marker(None, None, act[3], act[4], act[7], True, 1) + self.all_place_list.append(act) + self.add_marker(None, None, ref[3], ref[4], ref[7], True, 1) + self.all_place_list.append(ref) + + def _createmap(self, person, color, place_list, reference): + """ + Create all markers for each people's event in the database which has + a lat/lon. + """ + dbstate = self.dbstate + self.cal = config.get('preferences.calendar-format-report') + self.place_list = place_list + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + latitude = "" + longitude = "" + if person is not None: + # For each event, if we have a place, set a marker. + for event_ref in person.get_event_ref_list(): + if not event_ref: + continue + event = dbstate.db.get_event_from_handle(event_ref.ref) + role = event_ref.get_role() + try: + date = event.get_date_object().to_calendar(self.cal) + except: + continue + eyear = str("%04d" % date.get_year()) + \ + str("%02d" % date.get_month()) + \ + str("%02d" % date.get_day()) + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle(place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon(latitude, + longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + descr1 = _("%(eventtype)s : %(name)s") % { + 'eventtype': evt, + 'name': _nd.display(person)} + # place.get_longitude and place.get_latitude return + # one string. We have coordinates when the two values + # contains non null string. + if longitude and latitude: + self._append_to_places_list(descr, evt, + _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord( + place.gramps_id, descr) + family_list = person.get_family_handle_list() + descr1 = " - " + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + descr1 = " - " + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + descr1 = "%s - " % _nd.display(father) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + descr1 = "%s%s" % (descr1, _nd.display(mother)) + for event_ref in family.get_event_ref_list(): + if event_ref: + event = dbstate.db.get_event_from_handle( + event_ref.ref) + role = event_ref.get_role() + if event.get_place_handle(): + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle( + place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon( + latitude, longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + eyear = str( + "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) + if longitude and latitude: + self._append_to_places_list(descr, + evt, _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord(place.gramps_id, descr) + + sort1 = sorted(self.place_list, key=operator.itemgetter(6)) + self.draw(None, sort1, color, reference) + # merge with the last results + merge_list = [] + for the_list in self.sort, sort1: + merge_list += the_list + self.sort = sorted(merge_list, key=operator.itemgetter(6)) + + def bubble_message(self, event, lat, lon, marks): + """ + Create the menu for the selected marker + """ + self.newmenu = Gtk.Menu() + menu = self.newmenu + menu.set_title("person") + events = [] + message = "" + oldplace = "" + prevmark = None + for mark in marks: + for plce in self.all_place_list: + if plce[3] == mark[3] and plce[4] == mark[4]: + if plce[10] in events: + continue + else: + events.append(plce[10]) + + if plce[0] != oldplace: + message = "%s :" % plce[0] + self.add_place_bubble_message(event, lat, lon, + marks, menu, + message, plce) + oldplace = plce[0] + message = "" + evt = self.dbstate.db.get_event_from_gramps_id(plce[10]) + # format the date as described in preferences. + date = displayer.display(evt.get_date_object()) + if date == "": + date = _("Unknown") + if plce[11] == EventRoleType.PRIMARY: + message = "(%s) %s : %s" % (date, plce[2], plce[1]) + elif plce[11] == EventRoleType.FAMILY: + (father_name, + mother_name) = self._get_father_and_mother_name(evt) + message = "(%s) %s : %s - %s" % (date, plce[7], + father_name, + mother_name) + else: + descr = evt.get_description() + if descr == "": + descr = _('No description') + message = "(%s) %s => %s" % (date, plce[11], descr) + prevmark = plce + self.add_item = Gtk.MenuItem(label=message) + add_item = self.add_item + add_item.show() + menu.append(add_item) + self.itemoption = Gtk.Menu() + itemoption = self.itemoption + itemoption.set_title(message) + itemoption.show() + add_item.set_submenu(itemoption) + modify = Gtk.MenuItem(label=_("Edit Event")) + modify.show() + modify.connect("activate", self.edit_event, + event, lat, lon, prevmark) + itemoption.append(modify) + center = Gtk.MenuItem(label=_("Center on this place")) + center.show() + center.connect("activate", self.center_here, + event, lat, lon, prevmark) + itemoption.append(center) + menu.show() + menu.popup(None, None, None, + None, event.button, event.time) + return 0 + + def add_specific_menu(self, menu, event, lat, lon): + """ + Add specific entry to the navigation menu. + """ + add_item = Gtk.MenuItem() + add_item.show() + menu.append(add_item) + add_item = Gtk.MenuItem( + label=_("Choose and bookmark the new reference person")) + add_item.connect("activate", self.select_person) + add_item.show() + menu.append(add_item) + return + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Person Filter",), + ()) + + def specific_options(self, configdialog): + """ + Add specific entry to the preference menu. + Must be done in the associated view. + """ + grid = Gtk.Grid() + grid.set_border_width(12) + grid.set_column_spacing(6) + grid.set_row_spacing(6) + configdialog.add_text(grid, + _('The meeting zone probability radius.\n' + 'The colored zone is approximative.\n' + 'The meeting zone is only shown for the reference person.\n' + 'The value 9 means about 42 miles or 67 kms.\n' + 'The value 1 means about 4.6 miles or 7.5 kms.\n' + 'The value is in tenth of degree.'), + 1, line_wrap=False) + self.config_meeting_slider = configdialog.add_slider(grid, + "", + 2, 'geography.maximum_meeting_zone', + (1, 9)) + return _('The selection parameters'), grid + + def config_connect(self): + """ + used to monitor changes in the ini file + """ + self._config.connect('geography.maximum_meeting_zone', + self.cb_update_meeting_radius) + + def cb_update_meeting_radius(self, client, cnxn_id, entry, data): + """ + Called when the radius change + """ + self.goto_handle(handle=None) + diff --git a/geo-view-select-active-person/gramps/plugins/view/geoclose.py.patch b/geo-view-select-active-person/gramps/plugins/view/geoclose.py.patch new file mode 100644 index 0000000..8ce12cc --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geoclose.py.patch @@ -0,0 +1,19 @@ +397a398,408 +> def select_person2(self, *obj): +> """ +> Open a selection box to choose the secondary person. +> """ +> selectperson = SelectorFactory('Person') +> sel = selectperson(self.dbstate, self.uistate, self.track, +> _("Select the person which will be our active."), +> skip=self.skip_list) +> self.uistate.set_active(sel.run().get_handle(), 'Person') +> self.goto_handle(None) +> +651a663,668 +> add_item.show() +> menu.append(add_item) +> +> add_item = Gtk.MenuItem( +> label=_("Choose the new active person")) +> add_item.connect("activate", self.select_person2) diff --git a/geo-view-select-active-person/gramps/plugins/view/geofamclose.py b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py new file mode 100644 index 0000000..0e5392d --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py @@ -0,0 +1,902 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011-2016 Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Geography for two families +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +import operator +from gi.repository import Gtk +from math import hypot + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger("GeoGraphy.geofamilyclose") + +#------------------------------------------------------------------------- +# +# Gramps Modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import EventRoleType, EventType +from gramps.gen.config import config +from gramps.gen.datehandler import displayer +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.gen.utils.place import conv_lat_lon +from gramps.gui.views.navigationview import NavigationView +from gramps.gui.views.bookmarks import FamilyBookmarks +from gramps.plugins.lib.maps import constants +from gramps.plugins.lib.maps.geography import GeoGraphyView +from gramps.gui.selectors import SelectorFactory + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +_UI_DEF = [ + ''' + +
+ + win.Back + _Back + + + win.Forward + _Forward + +
+
+ + win.HomePerson + _Home + +
+
+''', + ''' +
+ + win.PrintView + Print... + +
+''', + ''' +
+ + win.AddBook + _Add Bookmark + + + win.EditBook + %s... + +
+''' % _('Organize Bookmarks'), # Following are the Toolbar items + ''' + + + + go-previous + win.Back + ''' + '''Go to the previous object in the history + _Back + True + + + False + + + + + go-next + win.Forward + ''' + '''Go to the next object in the history + _Forward + True + + + False + + + + + go-home + win.HomePerson + ''' + '''Go to the home person + _Home + True + + + False + + + + + gramps-family + win.RefFamily + ''' + '''Select the family which is the reference for life ways + reference _Family + True + + + False + + + +''', + ''' + + + + document-print + win.PrintView + ''' + '''Print or save the Map + Print... + True + + + False + + + + '''] + +# pylint: disable=no-member +# pylint: disable=unused-variable +# pylint: disable=unused-argument + +#------------------------------------------------------------------------- +# +# GeoView +# +#------------------------------------------------------------------------- +class GeoFamClose(GeoGraphyView): + """ + The view used to render family's map. + """ + CONFIGSETTINGS = ( + ('geography.path', constants.GEOGRAPHY_PATH), + + ('geography.zoom', 10), + ('geography.zoom_when_center', 12), + ('geography.show_cross', True), + ('geography.lock', False), + ('geography.center-lat', 0.0), + ('geography.center-lon', 0.0), + ('geography.use-keypad', True), + + ('geography.map_service', constants.OPENSTREETMAP), + ('geography.max_places', 5000), + + # specific to geoclose : + + ('geography.color1', 'blue'), + ('geography.color2', 'green'), + ('geography.maximum_meeting_zone', 5), + + ) + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + GeoGraphyView.__init__(self, + _("Have these two families been able to meet?"), + pdata, dbstate, uistate, + FamilyBookmarks, nav_group) + self.dbstate = dbstate + self.uistate = uistate + self.place_list = [] + self.all_place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + self.reffamily = None + self.reffamily_bookmark = None + self.nbplaces = 0 + self.nbmarkers = 0 + self.sort = [] + self.tracks = [] + self.additional_uis.append(self.additional_ui()) + self.ref_family = None + self.skip_list = [] + self.track = [] + self.place_list_active = [] + self.place_list_ref = [] + self.cal = config.get('preferences.calendar-format-report') + self.no_show_places_in_status_bar = False + self.config_meeting_slider = None + self.dbstate.connect('database-changed', self.reset_change_db) + + def reset_change_db(self, dummy_dbase): + """ + Used to reset the family reference + """ + self.reffamily = None + + def get_title(self): + """ + Used to set the titlebar in the configuration window. + """ + return _('GeoFamClose') + + def get_stock(self): + """ + Returns the name of the stock icon to use for the display. + This assumes that this icon has already been registered + as a stock icon. + """ + return 'geo-show-family' + + def get_viewtype_stock(self): + """Type of view in category + """ + return 'geo-show-family' + + def additional_ui(self): + """ + Specifies the UIManager XML code that defines the menus and buttons + associated with the interface. + """ + return _UI_DEF + + def navigation_type(self): + """ + Indicates the navigation type. Navigation type can be the string + name of any of the primary objects. + """ + return 'Family' + + def family_label(self, family): + """ + Create the family label depending on existence of father and mother + """ + if family is None: + return "Unknown" + father = mother = None + hdl = family.get_father_handle() + if hdl: + father = self.dbstate.db.get_person_from_handle(hdl) + hdl = family.get_mother_handle() + if hdl: + mother = self.dbstate.db.get_person_from_handle(hdl) + if father and mother: + label = _("%(gramps_id)s : %(father)s and %(mother)s") % { + 'father' : _nd.display(father), + 'mother' : _nd.display(mother), + 'gramps_id' : family.gramps_id, + } + elif father: + label = "%(gramps_id)s : %(father)s" % { + 'father' : _nd.display(father), + 'gramps_id' : family.gramps_id, + } + elif mother: + label = "%(gramps_id)s : %(mother)s" % { + 'mother' : _nd.display(mother), + 'gramps_id' : family.gramps_id, + } + else: + # No translation for bare gramps_id + label = "%(gramps_id)s :" % { + 'gramps_id' : family.gramps_id, + } + return label + + def goto_handle(self, handle=None): + """ + Rebuild the tree with the given family handle as reference. + """ + self.place_list_active = [] + self.place_list_ref = [] + self.all_place_list = [] + self.sort = [] + self.places_found = [] + self.place_without_coordinates = [] + self.nbmarkers = 0 + self.nbplaces = 0 + self.remove_all_gps() + self.remove_all_markers() + self.lifeway_layer.clear_ways() + self.message_layer.clear_messages() + self.message_layer.set_font_attributes(None, None, None) + active = self.get_active() + family = None + if active: + family = self.dbstate.db.get_family_from_handle(active) + color = self._config.get('geography.color2') + self._createmap(family, color, self.place_list_active, False) + if self.reffamily: + color = self._config.get('geography.color1') + self._createmap(self.reffamily, color, self.place_list_ref, True) + self.message_layer.add_message(_("Family reference : %s") % + self.family_label(self.reffamily)) + if family: + self.message_layer.add_message(_("The other family : %s") % + self.family_label(family)) + else: + self.message_layer.add_message(_("The other family : %s") % + _("Unknown")) + if self.reffamily_bookmark is None: + self.reffamily_bookmark = self.reffamily.get_handle() + self.add_bookmark_from_popup(None, self.reffamily_bookmark) + else: + self.message_layer.add_message( + _("You must choose one reference family.")) + self.message_layer.add_message( + _("Go to the family view and select " + "the families you want to compare. " + "Return to this view and use the history.")) + if family is not None: + self._possible_family_meeting(self.reffamily, family) + self.uistate.modify_statusbar(self.dbstate) + + def define_actions(self): + """ + Define action for the reference family button. + """ + GeoGraphyView.define_actions(self) + self._add_action('RefFamily', self.select_family) + + def select_family(self, *obj): + """ + Open a selection box to choose the ref family. + """ + self.track = [] + self.skip_list = [] + self.ref_family = None + self.reffamily_bookmark = None + select_family = SelectorFactory('Family') + sel = select_family(self.dbstate, self.uistate) + self.reffamily = sel.run() + self.goto_handle(None) + + def select_family2(self, *obj): + """ + Open a selection box to choose the secondary family. + """ + select_family = SelectorFactory('Family') + sel = select_family(self.dbstate, self.uistate) + self.uistate.set_active(sel.run().get_handle(), 'Family') + self.goto_handle(None) + + def build_tree(self): + """ + This is called by the parent class when the view becomes visible. Since + all handling of visibility is now in rebuild_trees, see that for more + information. + """ + self.lifeway_layer.clear_ways() + if not self.dbstate.is_open(): + return + active = self.get_active() + if active: + family = self.dbstate.db.get_family_from_handle(active) + if family is None: + self.goto_handle(None) + else: + self.goto_handle(handle=family) + else: + self.goto_handle(None) + + def draw(self, menu, marks, color, reference): + """ + Create all moves for the people's event. + """ + points = [] + mark = None + for mark in marks: + startlat = float(mark[3]) + startlon = float(mark[4]) + not_stored = True + for idx in range(0, len(points)): + if points[idx][0] == startlat and points[idx][1] == startlon: + not_stored = False + if not_stored: + points.append((startlat, startlon)) + self.lifeway_layer.add_way(points, color) + if reference: + self.lifeway_layer.add_way_ref(points, 'orange', + float( + self._config.get("geography.maximum_meeting_zone")) / 10) + return False + + def _place_list_for_person(self, person): + """ + get place list for one person + """ + place_list = [] + for event in self.sort: + if event[1] == _nd.display(person): + place_list.append(event) + return place_list + + def possible_meeting(self, ref_person, person): + """ + Try to see if two persons can be to the same place during their life. + If yes, show a marker with the dates for each person. + """ + self.place_list_ref = self._place_list_for_person(ref_person) + self.place_list_active = self._place_list_for_person(person) + radius = float(self._config.get("geography.maximum_meeting_zone")/10.0) + for ref in self.place_list_ref: + for act in self.place_list_active: + if (hypot(float(act[3])-float(ref[3]), + float(act[4])-float(ref[4])) <= radius) == True: + # we are in the meeting zone + self.add_marker(None, None, act[3], act[4], act[7], True, 1) + self.all_place_list.append(act) + self.add_marker(None, None, ref[3], ref[4], ref[7], True, 1) + self.all_place_list.append(ref) + + def _expose_persone_to_family(self, ref_person, family): + """ + try to search one or more meeting zone for all persons of one family + with one reference person + """ + dbstate = self.dbstate + try: + person = dbstate.db.get_person_from_handle( + family.get_father_handle()) + except: + return + if person is None: # family without father ? + person = dbstate.db.get_person_from_handle( + family.get_mother_handle()) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + self.possible_meeting(father, ref_person) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self.possible_meeting(mother, ref_person) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self.possible_meeting(child, ref_person) + else: + self.possible_meeting(person, ref_person) + + + def _possible_family_meeting(self, reference, family): + """ + try to expose each person of the reference family to the second family + """ + dbstate = self.dbstate + person = None + try: + person = dbstate.db.get_person_from_handle( + reference.get_father_handle()) + except: + return + if person is None: # family without father ? + handle = reference.get_mother_handle() + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is None: + handle = self.uistate.get_active('Person') + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + self._expose_persone_to_family(father, family) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self._expose_persone_to_family(mother, family) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self._expose_persone_to_family(child, family) + else: + self._expose_persone_to_family(person, family) + + + def _createmap_for_one_person(self, person, color, place_list, reference): + """ + Create all markers for each people's event in the database which has + a lat/lon. + """ + self.place_list = [] + dbstate = self.dbstate + if person is not None: + # For each event, if we have a place, set a marker. + for event_ref in person.get_event_ref_list(): + if not event_ref: + continue + event = dbstate.db.get_event_from_handle(event_ref.ref) + role = event_ref.get_role() + try: + date = event.get_date_object().to_calendar(self.cal) + except: + continue + eyear = str("%04d" % date.get_year()) + \ + str("%02d" % date.get_month()) + \ + str("%02d" % date.get_day()) + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle(place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon(latitude, + longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + descr1 = _("%(eventtype)s : %(name)s") % { + 'eventtype': evt, + 'name': _nd.display(person)} + # place.get_longitude and place.get_latitude return + # one string. We have coordinates when the two values + # contains non null string. + if longitude and latitude: + self._append_to_places_list(descr, evt, + _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord( + place.gramps_id, descr) + family_list = person.get_family_handle_list() + descr1 = " - " + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + fhandle = family_list[0] # first is primary + father = mother = None + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + descr1 = "%s - " % _nd.display(father) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + descr1 = "%s%s" % (descr1, _nd.display(mother)) + for event_ref in family.get_event_ref_list(): + if event_ref: + event = dbstate.db.get_event_from_handle( + event_ref.ref) + role = event_ref.get_role() + if event.get_place_handle(): + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle( + place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon( + latitude, longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType( + event.get_type()) + eyear = str( + "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) + if longitude and latitude: + self._append_to_places_list(descr, + evt, _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord(place.gramps_id, descr) + + sort1 = sorted(self.place_list, key=operator.itemgetter(6)) + self.draw(None, sort1, color, reference) + # merge with the last results + merge_list = [] + for the_list in self.sort, sort1: + merge_list += the_list + self.sort = sorted(merge_list, key=operator.itemgetter(6)) + + def _createmap_for_one_family(self, family, color, place_list, reference): + """ + Create all markers for one family : all event's places with a lat/lon. + """ + dbstate = self.dbstate + person = None + try: + person = dbstate.db.get_person_from_handle( + family.get_father_handle()) + except: + return + family_id = family.gramps_id + if person is None: # family without father ? + handle = family.get_mother_handle() + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is None: + handle = self.uistate.get_active('Person') + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + comment = _("Father : %(id)s : %(name)s") % { + 'id': father.gramps_id, + 'name': _nd.display(father)} + self._createmap_for_one_person(father, color, + place_list, reference) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + comment = _("Mother : %(id)s : %(name)s") % { + 'id': mother.gramps_id, + 'name': _nd.display(mother)} + self._createmap_for_one_person(mother, color, + place_list, reference) + index = 0 + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + index += 1 + comment = _("Child : %(id)s - %(index)d " + ": %(name)s") % { + 'id' : child.gramps_id, + 'index' : index, + 'name' : _nd.display(child) + } + self._createmap_for_one_person(child, color, + place_list, + reference) + else: + comment = _("Person : %(id)s %(name)s has no family.") % { + 'id' : person.gramps_id, + 'name' : _nd.display(person) + } + self._createmap_for_one_person(person, color, + place_list, reference) + + def _createmap(self, family_x, color, place_list, reference): + """ + Create all markers for each family's person in the database which has + a lat/lon. + """ + dbstate = self.dbstate + self.cal = config.get('preferences.calendar-format-report') + self.place_list = place_list + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #self.minyear = 9999 + #self.maxyear = 0 + latitude = "" + longitude = "" + self.place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #family = self.dbstate.db.get_family_from_handle(family_x) + family = family_x + if family is None: + handle = self.uistate.get_active('Person') + person = None + if handle: + person = self.dbstate.db.get_family_from_handle(handle) + if not person: + return + family_list = person.get_family_handle_list() + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + self._createmap_for_one_family(family, color, + place_list, reference) + else: + self._createmap_for_one_family(family, color, place_list, reference) + #self._create_markers() + + def bubble_message(self, event, lat, lon, marks): + """ + Create the menu for the selected marker + """ + self.menu = Gtk.Menu() + menu = self.menu + menu.set_title("family") + events = [] + message = "" + oldplace = "" + prevmark = None + for mark in marks: + for plce in self.all_place_list: + if plce[3] == mark[3] and plce[4] == mark[4]: + if plce[10] in events: + continue + else: + events.append(plce[10]) + + if plce[0] != oldplace: + message = "%s :" % plce[0] + self.add_place_bubble_message(event, lat, lon, + marks, menu, + message, plce) + oldplace = plce[0] + message = "" + evt = self.dbstate.db.get_event_from_gramps_id(plce[10]) + # format the date as described in preferences. + date = displayer.display(evt.get_date_object()) + if date == "": + date = _("Unknown") + if plce[11] == EventRoleType.PRIMARY: + message = "(%s) %s : %s" % (date, plce[2], plce[1]) + elif plce[11] == EventRoleType.FAMILY: + (father_name, + mother_name) = self._get_father_and_mother_name(evt) + message = "(%s) %s : %s - %s" % (date, plce[7], + father_name, + mother_name) + else: + descr = evt.get_description() + if descr == "": + descr = _('No description') + message = "(%s) %s => %s" % (date, plce[11], descr) + prevmark = plce + add_item = Gtk.MenuItem(label=message) + add_item.show() + menu.append(add_item) + self.itemoption = Gtk.Menu() + itemoption = self.itemoption + itemoption.set_title(message) + itemoption.show() + add_item.set_submenu(itemoption) + modify = Gtk.MenuItem(label=_("Edit Event")) + modify.show() + modify.connect("activate", self.edit_event, + event, lat, lon, prevmark) + itemoption.append(modify) + center = Gtk.MenuItem(label=_("Center on this place")) + center.show() + center.connect("activate", self.center_here, + event, lat, lon, prevmark) + itemoption.append(center) + menu.show() + menu.popup(None, None, None, + None, event.button, event.time) + return 0 + + def add_specific_menu(self, menu, event, lat, lon): + """ + Add specific entry to the navigation menu. + """ + add_item = Gtk.MenuItem() + add_item.show() + menu.append(add_item) + add_item = Gtk.MenuItem( + label=_("Choose and bookmark the new reference family")) + add_item.connect("activate", self.select_family) + add_item.show() + menu.append(add_item) + + add_item = Gtk.MenuItem( + label=_("Choose the new active family")) + add_item.connect("activate", self.select_family2) + add_item.show() + menu.append(add_item) + + return + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Person Filter",), + ()) + + def specific_options(self, configdialog): + """ + Add specific entry to the preference menu. + Must be done in the associated view. + """ + grid = Gtk.Grid() + grid.set_border_width(12) + grid.set_column_spacing(6) + grid.set_row_spacing(6) + configdialog.add_text(grid, + _('The meeting zone probability radius.\n' + 'The colored zone is approximative.\n' + 'The meeting zone is only shown for the reference family.\n' + 'The value 9 means about 42 miles or 67 kms.\n' + 'The value 1 means about 4.6 miles or 7.5 kms.\n' + 'The value is in tenth of degree.'), + 1, line_wrap=False) + self.config_meeting_slider = configdialog.add_slider(grid, + "", + 2, 'geography.maximum_meeting_zone', + (1, 9)) + return _('The selection parameters'), grid + + def config_connect(self): + """ + used to monitor changes in the ini file + """ + self._config.connect('geography.maximum_meeting_zone', + self.cb_update_meeting_radius) + + def cb_update_meeting_radius(self, client, cnxn_id, entry, data): + """ + Called when the radius change + """ + self.goto_handle(handle=None) + diff --git a/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.0 b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.0 new file mode 100644 index 0000000..7dda8b1 --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.0 @@ -0,0 +1,886 @@ +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011-2016 Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Geography for two families +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +import operator +from gi.repository import Gtk +from math import hypot + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger("GeoGraphy.geofamilyclose") + +#------------------------------------------------------------------------- +# +# Gramps Modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import EventRoleType, EventType +from gramps.gen.config import config +from gramps.gen.datehandler import displayer +from gramps.gen.display.name import displayer as _nd +from gramps.gen.display.place import displayer as _pd +from gramps.gen.utils.place import conv_lat_lon +from gramps.gui.views.navigationview import NavigationView +from gramps.gui.views.bookmarks import FamilyBookmarks +from gramps.plugins.lib.maps import constants +from gramps.plugins.lib.maps.geography import GeoGraphyView +from gramps.gui.selectors import SelectorFactory + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +_UI_DEF = [ + ''' + +
+ + win.Back + _Back + + + win.Forward + _Forward + +
+
+ + win.HomePerson + _Home + +
+
+''', + ''' +
+ + win.PrintView + Print... + +
+''', + ''' +
+ + win.AddBook + _Add Bookmark + + + win.EditBook + %s... + +
+''' % _('Organize Bookmarks'), # Following are the Toolbar items + ''' + + + + go-previous + win.Back + ''' + '''Go to the previous object in the history + _Back + True + + + False + + + + + go-next + win.Forward + ''' + '''Go to the next object in the history + _Forward + True + + + False + + + + + go-home + win.HomePerson + ''' + '''Go to the home person + _Home + True + + + False + + + + + gramps-family + win.RefFamily + ''' + '''Select the family which is the reference for life ways + reference _Family + True + + + False + + + +''', + ''' + + + + document-print + win.PrintView + ''' + '''Print or save the Map + Print... + True + + + False + + + + '''] + +# pylint: disable=no-member +# pylint: disable=unused-variable +# pylint: disable=unused-argument + +#------------------------------------------------------------------------- +# +# GeoView +# +#------------------------------------------------------------------------- +class GeoFamClose(GeoGraphyView): + """ + The view used to render family's map. + """ + CONFIGSETTINGS = ( + ('geography.path', constants.GEOGRAPHY_PATH), + + ('geography.zoom', 10), + ('geography.zoom_when_center', 12), + ('geography.show_cross', True), + ('geography.lock', False), + ('geography.center-lat', 0.0), + ('geography.center-lon', 0.0), + ('geography.use-keypad', True), + + ('geography.map_service', constants.OPENSTREETMAP), + ('geography.max_places', 5000), + + # specific to geoclose : + + ('geography.color1', 'blue'), + ('geography.color2', 'green'), + ('geography.maximum_meeting_zone', 5), + + ) + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + GeoGraphyView.__init__(self, + _("Have these two families been able to meet?"), + pdata, dbstate, uistate, + FamilyBookmarks, nav_group) + self.dbstate = dbstate + self.uistate = uistate + self.place_list = [] + self.all_place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + self.minyear = 9999 + self.maxyear = 0 + self.reffamily = None + self.reffamily_bookmark = None + self.nbplaces = 0 + self.nbmarkers = 0 + self.sort = [] + self.tracks = [] + self.additional_uis.append(self.additional_ui()) + self.ref_family = None + self.skip_list = [] + self.track = [] + self.place_list_active = [] + self.place_list_ref = [] + self.cal = config.get('preferences.calendar-format-report') + self.no_show_places_in_status_bar = False + self.config_meeting_slider = None + self.dbstate.connect('database-changed', self.reset_change_db) + + def reset_change_db(self, dummy_dbase): + """ + Used to reset the family reference + """ + self.reffamily = None + + def get_title(self): + """ + Used to set the titlebar in the configuration window. + """ + return _('GeoFamClose') + + def get_stock(self): + """ + Returns the name of the stock icon to use for the display. + This assumes that this icon has already been registered + as a stock icon. + """ + return 'geo-show-family' + + def get_viewtype_stock(self): + """Type of view in category + """ + return 'geo-show-family' + + def additional_ui(self): + """ + Specifies the UIManager XML code that defines the menus and buttons + associated with the interface. + """ + return _UI_DEF + + def navigation_type(self): + """ + Indicates the navigation type. Navigation type can be the string + name of any of the primary objects. + """ + return 'Family' + + def family_label(self, family): + """ + Create the family label depending on existence of father and mother + """ + if family is None: + return "Unknown" + father = mother = None + hdl = family.get_father_handle() + if hdl: + father = self.dbstate.db.get_person_from_handle(hdl) + hdl = family.get_mother_handle() + if hdl: + mother = self.dbstate.db.get_person_from_handle(hdl) + if father and mother: + label = _("%(gramps_id)s : %(father)s and %(mother)s") % { + 'father' : _nd.display(father), + 'mother' : _nd.display(mother), + 'gramps_id' : family.gramps_id, + } + elif father: + label = "%(gramps_id)s : %(father)s" % { + 'father' : _nd.display(father), + 'gramps_id' : family.gramps_id, + } + elif mother: + label = "%(gramps_id)s : %(mother)s" % { + 'mother' : _nd.display(mother), + 'gramps_id' : family.gramps_id, + } + else: + # No translation for bare gramps_id + label = "%(gramps_id)s :" % { + 'gramps_id' : family.gramps_id, + } + return label + + def goto_handle(self, handle=None): + """ + Rebuild the tree with the given family handle as reference. + """ + self.place_list_active = [] + self.place_list_ref = [] + self.all_place_list = [] + self.sort = [] + self.places_found = [] + self.place_without_coordinates = [] + self.nbmarkers = 0 + self.nbplaces = 0 + self.remove_all_gps() + self.remove_all_markers() + self.lifeway_layer.clear_ways() + self.message_layer.clear_messages() + self.message_layer.set_font_attributes(None, None, None) + active = self.get_active() + family = None + if active: + family = self.dbstate.db.get_family_from_handle(active) + color = self._config.get('geography.color2') + self._createmap(family, color, self.place_list_active, False) + if self.reffamily: + color = self._config.get('geography.color1') + self._createmap(self.reffamily, color, self.place_list_ref, True) + self.message_layer.add_message(_("Family reference : %s") % + self.family_label(self.reffamily)) + if family: + self.message_layer.add_message(_("The other family : %s") % + self.family_label(family)) + else: + self.message_layer.add_message(_("The other family : %s") % + _("Unknown")) + if self.reffamily_bookmark is None: + self.reffamily_bookmark = self.reffamily.get_handle() + self.add_bookmark_from_popup(None, self.reffamily_bookmark) + else: + self.message_layer.add_message( + _("You must choose one reference family.")) + self.message_layer.add_message( + _("Go to the family view and select " + "the families you want to compare. " + "Return to this view and use the history.")) + if family is not None: + self._possible_family_meeting(self.reffamily, family) + self.uistate.modify_statusbar(self.dbstate) + + def define_actions(self): + """ + Define action for the reference family button. + """ + GeoGraphyView.define_actions(self) + self._add_action('RefFamily', self.select_family) + + def select_family(self, *obj): + """ + Open a selection box to choose the ref family. + """ + self.track = [] + self.skip_list = [] + self.ref_family = None + self.reffamily_bookmark = None + select_family = SelectorFactory('Family') + sel = select_family(self.dbstate, self.uistate) + self.reffamily = sel.run() + self.goto_handle(None) + + def build_tree(self): + """ + This is called by the parent class when the view becomes visible. Since + all handling of visibility is now in rebuild_trees, see that for more + information. + """ + self.lifeway_layer.clear_ways() + if not self.dbstate.is_open(): + return + active = self.get_active() + if active: + family = self.dbstate.db.get_family_from_handle(active) + if family is None: + self.goto_handle(None) + else: + self.goto_handle(handle=family) + else: + self.goto_handle(None) + + def draw(self, menu, marks, color, reference): + """ + Create all moves for the people's event. + """ + points = [] + mark = None + for mark in marks: + startlat = float(mark[3]) + startlon = float(mark[4]) + not_stored = True + for idx in range(0, len(points)): + if points[idx][0] == startlat and points[idx][1] == startlon: + not_stored = False + if not_stored: + points.append((startlat, startlon)) + self.lifeway_layer.add_way(points, color) + if reference: + self.lifeway_layer.add_way_ref(points, 'orange', + float( + self._config.get("geography.maximum_meeting_zone")) / 10) + return False + + def _place_list_for_person(self, person): + """ + get place list for one person + """ + place_list = [] + for event in self.sort: + if event[1] == _nd.display(person): + place_list.append(event) + return place_list + + def possible_meeting(self, ref_person, person): + """ + Try to see if two persons can be to the same place during their life. + If yes, show a marker with the dates for each person. + """ + self.place_list_ref = self._place_list_for_person(ref_person) + self.place_list_active = self._place_list_for_person(person) + radius = float(self._config.get("geography.maximum_meeting_zone")/10.0) + for ref in self.place_list_ref: + for act in self.place_list_active: + if (hypot(float(act[3])-float(ref[3]), + float(act[4])-float(ref[4])) <= radius) == True: + # we are in the meeting zone + self.add_marker(None, None, act[3], act[4], act[7], True, 1) + self.all_place_list.append(act) + self.add_marker(None, None, ref[3], ref[4], ref[7], True, 1) + self.all_place_list.append(ref) + + def _expose_persone_to_family(self, ref_person, family): + """ + try to search one or more meeting zone for all persons of one family + with one reference person + """ + dbstate = self.dbstate + try: + person = dbstate.db.get_person_from_handle( + family.get_father_handle()) + except: + return + if person is None: # family without father ? + person = dbstate.db.get_person_from_handle( + family.get_mother_handle()) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + self.possible_meeting(father, ref_person) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self.possible_meeting(mother, ref_person) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self.possible_meeting(child, ref_person) + else: + self.possible_meeting(person, ref_person) + + + def _possible_family_meeting(self, reference, family): + """ + try to expose each person of the reference family to the second family + """ + dbstate = self.dbstate + person = None + try: + person = dbstate.db.get_person_from_handle( + reference.get_father_handle()) + except: + return + if person is None: # family without father ? + handle = reference.get_mother_handle() + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is None: + handle = self.uistate.get_active('Person') + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + self._expose_persone_to_family(father, family) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + self._expose_persone_to_family(mother, family) + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + self._expose_persone_to_family(child, family) + else: + self._expose_persone_to_family(person, family) + + + def _createmap_for_one_person(self, person, color, place_list, reference): + """ + Create all markers for each people's event in the database which has + a lat/lon. + """ + self.place_list = [] + dbstate = self.dbstate + if person is not None: + # For each event, if we have a place, set a marker. + for event_ref in person.get_event_ref_list(): + if not event_ref: + continue + event = dbstate.db.get_event_from_handle(event_ref.ref) + role = event_ref.get_role() + try: + date = event.get_date_object().to_calendar(self.cal) + except: + continue + eyear = str("%04d" % date.get_year()) + \ + str("%02d" % date.get_month()) + \ + str("%02d" % date.get_day()) + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle(place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon(latitude, + longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType(event.get_type()) + descr1 = _("%(eventtype)s : %(name)s") % { + 'eventtype': evt, + 'name': _nd.display(person)} + # place.get_longitude and place.get_latitude return + # one string. We have coordinates when the two values + # contains non null string. + if longitude and latitude: + self._append_to_places_list(descr, evt, + _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord( + place.gramps_id, descr) + family_list = person.get_family_handle_list() + descr1 = " - " + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + fhandle = family_list[0] # first is primary + father = mother = None + fam = dbstate.db.get_family_from_handle(fhandle) + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + descr1 = "%s - " % _nd.display(father) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + descr1 = "%s%s" % (descr1, _nd.display(mother)) + for event_ref in family.get_event_ref_list(): + if event_ref: + event = dbstate.db.get_event_from_handle( + event_ref.ref) + role = event_ref.get_role() + if event.get_place_handle(): + place_handle = event.get_place_handle() + if place_handle: + place = dbstate.db.get_place_from_handle( + place_handle) + if place: + longitude = place.get_longitude() + latitude = place.get_latitude() + latitude, longitude = conv_lat_lon( + latitude, longitude, "D.D8") + descr = _pd.display(dbstate.db, place) + evt = EventType( + event.get_type()) + eyear = str( + "%04d" % event.get_date_object().to_calendar(self.cal).get_year()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_month()) + \ + str("%02d" % event.get_date_object().to_calendar(self.cal).get_day()) + if longitude and latitude: + self._append_to_places_list(descr, + evt, _nd.display(person), + latitude, longitude, + descr1, eyear, + event.get_type(), + person.gramps_id, + place.gramps_id, + event.gramps_id, + role + ) + else: + self._append_to_places_without_coord(place.gramps_id, descr) + + sort1 = sorted(self.place_list, key=operator.itemgetter(6)) + self.draw(None, sort1, color, reference) + # merge with the last results + merge_list = [] + for the_list in self.sort, sort1: + merge_list += the_list + self.sort = sorted(merge_list, key=operator.itemgetter(6)) + + def _createmap_for_one_family(self, family, color, place_list, reference): + """ + Create all markers for one family : all event's places with a lat/lon. + """ + dbstate = self.dbstate + person = None + try: + person = dbstate.db.get_person_from_handle( + family.get_father_handle()) + except: + return + family_id = family.gramps_id + if person is None: # family without father ? + handle = family.get_mother_handle() + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is None: + handle = self.uistate.get_active('Person') + if handle: + person = dbstate.db.get_person_from_handle(handle) + if person is not None: + family_list = person.get_family_handle_list() + if len(family_list) > 0: + fhandle = family_list[0] # first is primary + fam = dbstate.db.get_family_from_handle(fhandle) + father = mother = None + handle = fam.get_father_handle() + if handle: + father = dbstate.db.get_person_from_handle(handle) + if father: + comment = _("Father : %(id)s : %(name)s") % { + 'id': father.gramps_id, + 'name': _nd.display(father)} + self._createmap_for_one_person(father, color, + place_list, reference) + handle = fam.get_mother_handle() + if handle: + mother = dbstate.db.get_person_from_handle(handle) + if mother: + comment = _("Mother : %(id)s : %(name)s") % { + 'id': mother.gramps_id, + 'name': _nd.display(mother)} + self._createmap_for_one_person(mother, color, + place_list, reference) + index = 0 + child_ref_list = fam.get_child_ref_list() + if child_ref_list: + for child_ref in child_ref_list: + child = dbstate.db.get_person_from_handle(child_ref.ref) + if child: + index += 1 + comment = _("Child : %(id)s - %(index)d " + ": %(name)s") % { + 'id' : child.gramps_id, + 'index' : index, + 'name' : _nd.display(child) + } + self._createmap_for_one_person(child, color, + place_list, + reference) + else: + comment = _("Person : %(id)s %(name)s has no family.") % { + 'id' : person.gramps_id, + 'name' : _nd.display(person) + } + self._createmap_for_one_person(person, color, + place_list, reference) + + def _createmap(self, family_x, color, place_list, reference): + """ + Create all markers for each family's person in the database which has + a lat/lon. + """ + dbstate = self.dbstate + self.cal = config.get('preferences.calendar-format-report') + self.place_list = place_list + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #self.minyear = 9999 + #self.maxyear = 0 + latitude = "" + longitude = "" + self.place_list = [] + self.place_without_coordinates = [] + self.minlat = self.maxlat = self.minlon = self.maxlon = 0.0 + #family = self.dbstate.db.get_family_from_handle(family_x) + family = family_x + if family is None: + handle = self.uistate.get_active('Person') + person = None + if handle: + person = self.dbstate.db.get_family_from_handle(handle) + if not person: + return + family_list = person.get_family_handle_list() + for family_hdl in family_list: + family = self.dbstate.db.get_family_from_handle(family_hdl) + if family is not None: + self._createmap_for_one_family(family, color, + place_list, reference) + else: + self._createmap_for_one_family(family, color, place_list, reference) + #self._create_markers() + + def bubble_message(self, event, lat, lon, marks): + """ + Create the menu for the selected marker + """ + self.menu = Gtk.Menu() + menu = self.menu + menu.set_title("family") + events = [] + message = "" + oldplace = "" + prevmark = None + for mark in marks: + for plce in self.all_place_list: + if plce[3] == mark[3] and plce[4] == mark[4]: + if plce[10] in events: + continue + else: + events.append(plce[10]) + + if plce[0] != oldplace: + message = "%s :" % plce[0] + self.add_place_bubble_message(event, lat, lon, + marks, menu, + message, plce) + oldplace = plce[0] + message = "" + evt = self.dbstate.db.get_event_from_gramps_id(plce[10]) + # format the date as described in preferences. + date = displayer.display(evt.get_date_object()) + if date == "": + date = _("Unknown") + if plce[11] == EventRoleType.PRIMARY: + message = "(%s) %s : %s" % (date, plce[2], plce[1]) + elif plce[11] == EventRoleType.FAMILY: + (father_name, + mother_name) = self._get_father_and_mother_name(evt) + message = "(%s) %s : %s - %s" % (date, plce[7], + father_name, + mother_name) + else: + descr = evt.get_description() + if descr == "": + descr = _('No description') + message = "(%s) %s => %s" % (date, plce[11], descr) + prevmark = plce + add_item = Gtk.MenuItem(label=message) + add_item.show() + menu.append(add_item) + self.itemoption = Gtk.Menu() + itemoption = self.itemoption + itemoption.set_title(message) + itemoption.show() + add_item.set_submenu(itemoption) + modify = Gtk.MenuItem(label=_("Edit Event")) + modify.show() + modify.connect("activate", self.edit_event, + event, lat, lon, prevmark) + itemoption.append(modify) + center = Gtk.MenuItem(label=_("Center on this place")) + center.show() + center.connect("activate", self.center_here, + event, lat, lon, prevmark) + itemoption.append(center) + menu.show() + menu.popup(None, None, None, + None, event.button, event.time) + return 0 + + def add_specific_menu(self, menu, event, lat, lon): + """ + Add specific entry to the navigation menu. + """ + add_item = Gtk.MenuItem() + add_item.show() + menu.append(add_item) + add_item = Gtk.MenuItem( + label=_("Choose and bookmark the new reference family")) + add_item.connect("activate", self.select_family) + add_item.show() + menu.append(add_item) + return + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Person Filter",), + ()) + + def specific_options(self, configdialog): + """ + Add specific entry to the preference menu. + Must be done in the associated view. + """ + grid = Gtk.Grid() + grid.set_border_width(12) + grid.set_column_spacing(6) + grid.set_row_spacing(6) + configdialog.add_text(grid, + _('The meeting zone probability radius.\n' + 'The colored zone is approximative.\n' + 'The meeting zone is only shown for the reference family.\n' + 'The value 9 means about 42 miles or 67 kms.\n' + 'The value 1 means about 4.6 miles or 7.5 kms.\n' + 'The value is in tenth of degree.'), + 1, line_wrap=False) + self.config_meeting_slider = configdialog.add_slider(grid, + "", + 2, 'geography.maximum_meeting_zone', + (1, 9)) + return _('The selection parameters'), grid + + def config_connect(self): + """ + used to monitor changes in the ini file + """ + self._config.connect('geography.maximum_meeting_zone', + self.cb_update_meeting_radius) + + def cb_update_meeting_radius(self, client, cnxn_id, entry, data): + """ + Called when the radius change + """ + self.goto_handle(handle=None) + diff --git a/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.patch b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.patch new file mode 100644 index 0000000..c797e28 --- /dev/null +++ b/geo-view-select-active-person/gramps/plugins/view/geofamclose.py.patch @@ -0,0 +1,18 @@ +390a391,399 +> def select_family2(self, *obj): +> """ +> Open a selection box to choose the secondary family. +> """ +> select_family = SelectorFactory('Family') +> sel = select_family(self.dbstate, self.uistate) +> self.uistate.set_active(sel.run().get_handle(), 'Family') +> self.goto_handle(None) +> +841a851,857 +> +> add_item = Gtk.MenuItem( +> label=_("Choose the new active family")) +> add_item.connect("activate", self.select_family2) +> add_item.show() +> menu.append(add_item) +> diff --git a/hyperlink-person-associations/gramps/gui/editors/editpersonref.py b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py new file mode 100644 index 0000000..48cb37f --- /dev/null +++ b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py @@ -0,0 +1,268 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# 2009 Gary Burton +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +The EditPersonRef module provides the EditPersonRef class. +""" + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- + +import pickle + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.display.name import displayer as name_displayer +from .editsecondary import EditSecondary +from gramps.gen.lib import NoteType +from ..widgets import MonitoredEntry, PrivacyButton +from ..selectors import SelectorFactory +from .displaytabs import CitationEmbedList, NoteTab +from ..glade import Glade +from ..ddtargets import DdTargets +from gi.repository import Gdk +from gramps.gen.const import URL_MANUAL_SECT1 +from ..display import display_url + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 +WIKI_HELP_SEC = _('manual|Person_Reference_Editor') + +#------------------------------------------------------------------------- +# +# EditPersonRef class +# +#------------------------------------------------------------------------- +class EditPersonRef(EditSecondary): + """ + Displays a dialog that allows the user to edit a person reference. + """ + + def __init__(self, dbstate, uistate, track, personref, callback): + """ + Displays the dialog box. + + personref - The person reference that is to be edited + """ + EditSecondary.__init__(self, dbstate, uistate, track, + personref, callback) + + def _local_init(self): + + self.top = Glade() + self.set_window(self.top.toplevel, + self.top.get_object("title"), + _('Person Reference Editor')) + self.setup_configs('interface.person-ref', 600, 350) + + self.person_label = self.top.get_object('person') + self.person_label.set_use_markup(True) + + #allow for drop: + self.person_label.drag_dest_set(Gtk.DestDefaults.MOTION | + Gtk.DestDefaults.DROP, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + self.person_label.connect('drag_data_received', self.on_drag_persondata_received) + self.person_label.connect('activate_link', self.on_person_label_activate_link) + self._update_dnd_capability() + + def _update_dnd_capability(self): + self.label_event_box = self.top.get_object('person_event_box') + # Set the drag action from the label + if self.obj.ref: + self.label_event_box.drag_source_set( + Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], Gdk.DragAction.COPY) + self.label_event_box.drag_source_set_icon_name('gramps-person') + self.label_event_box.connect('drag_data_get', self.drag_data_get) + else: + self.label_event_box.drag_source_unset() + + def _setup_fields(self): + + if self.obj.ref: + p = self.dbstate.db.get_person_from_handle(self.obj.ref) + self.person_label.set_markup("" + name_displayer.display(p) + "") + # self.person_label.set_text(name_displayer.display(p)) + + self.street = MonitoredEntry( + self.top.get_object("relationship"), + self.obj.set_relation, + self.obj.get_relation, + self.db.readonly) + + self.priv = PrivacyButton( + self.top.get_object("private"), + self.obj, + self.db.readonly) + + def _connect_signals(self): + self.define_cancel_button(self.top.get_object('cancel')) + self.define_ok_button(self.top.get_object('ok'),self.save) + self.top.get_object('select').connect('clicked',self._select_person) + self.define_help_button(self.top.get_object('help'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('person-rebuild', self.close) + self._add_db_signal('person-delete', self.check_for_close) + + def check_for_close(self, handles): + """ + Callback method for delete signals. + If there is a delete signal of the primary object we are editing, the + editor (and all child windows spawned) should be closed + """ + if self.obj.ref in handles: + self.close() + + def _select_person(self, obj): + SelectPerson = SelectorFactory('Person') + + sel = SelectPerson(self.dbstate, self.uistate, self.track) + person = sel.run() + self.update_person(person) + + def update_person(self, person): + if person: + self.obj.ref = person.get_handle() + self.person_label.set_markup("" + name_displayer.display(person) + "") + # self.person_label.set_text(name_displayer.display(person)) + self._update_dnd_capability() + + def find_parent_with_attr(self, attr="dbstate"): + """ + """ + # Find a parent with attr: + obj = self + while obj: + if hasattr(obj, attr): + break + obj = obj.get_parent() + return obj + + def on_person_label_activate_link(self, widget, uri): + """ + Handle the standard gtk interface for activate_link. + """ + # this is stupid + if uri.startswith("gramps://"): + # if in a window: + win_obj = self.find_parent_with_attr(attr="dbstate") + if win_obj: + # Edit the object: + obj_class, prop, value = uri[9:].split("/") + from ..editors import EditObject + EditObject(win_obj.dbstate, + win_obj.uistate, + win_obj.track, + obj_class, prop, value) + return + + display_url(uri) + + def on_drag_persondata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + self.update_person(person) + + def drag_data_get(self, widget, context, sel_data, info, time): + # get the selected object, returning if not is defined + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.ref, 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and inserts them into the main + window. + """ + + notebook = Gtk.Notebook() + + self.srcref_list = CitationEmbedList(self.dbstate, self.uistate, + self.track, + self.obj.get_citation_list()) + self._add_tab(notebook, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.obj.get_note_list(), + notetype=NoteType.ASSOCIATION) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self._setup_notebook_tabs(notebook) + notebook.show_all() + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def build_menu_names(self, obj): + return (_('Person Reference'),_('Person Reference Editor')) + + def save(self,*obj): + """ + Called when the OK button is pressed. Gets data from the + form and updates the data structure. + """ + + if self.obj.ref: + if self.callback: + self.callback(self.obj) + self.callback = None + self.close() + else: + from ..dialog import ErrorDialog + + ErrorDialog( + _('No person selected'), + _('You must either select a person or Cancel the edit'), + parent=self.uistate.window) diff --git a/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.0 b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.0 new file mode 100644 index 0000000..ea23055 --- /dev/null +++ b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.0 @@ -0,0 +1,232 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# 2009 Gary Burton +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +The EditPersonRef module provides the EditPersonRef class. +""" + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- + +import pickle + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.display.name import displayer as name_displayer +from .editsecondary import EditSecondary +from gramps.gen.lib import NoteType +from ..widgets import MonitoredEntry, PrivacyButton +from ..selectors import SelectorFactory +from .displaytabs import CitationEmbedList, NoteTab +from ..glade import Glade +from ..ddtargets import DdTargets +from gi.repository import Gdk +from gramps.gen.const import URL_MANUAL_SECT1 + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 +WIKI_HELP_SEC = _('manual|Person_Reference_Editor') + +#------------------------------------------------------------------------- +# +# EditPersonRef class +# +#------------------------------------------------------------------------- +class EditPersonRef(EditSecondary): + """ + Displays a dialog that allows the user to edit a person reference. + """ + + def __init__(self, dbstate, uistate, track, personref, callback): + """ + Displays the dialog box. + + personref - The person reference that is to be edited + """ + EditSecondary.__init__(self, dbstate, uistate, track, + personref, callback) + + def _local_init(self): + + self.top = Glade() + self.set_window(self.top.toplevel, + self.top.get_object("title"), + _('Person Reference Editor')) + self.setup_configs('interface.person-ref', 600, 350) + + self.person_label = self.top.get_object('person') + + #allow for drop: + self.person_label.drag_dest_set(Gtk.DestDefaults.MOTION | + Gtk.DestDefaults.DROP, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + self.person_label.connect('drag_data_received', self.on_drag_persondata_received) + self._update_dnd_capability() + + def _update_dnd_capability(self): + self.label_event_box = self.top.get_object('person_event_box') + # Set the drag action from the label + if self.obj.ref: + self.label_event_box.drag_source_set( + Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], Gdk.DragAction.COPY) + self.label_event_box.drag_source_set_icon_name('gramps-person') + self.label_event_box.connect('drag_data_get', self.drag_data_get) + else: + self.label_event_box.drag_source_unset() + + def _setup_fields(self): + + if self.obj.ref: + p = self.dbstate.db.get_person_from_handle(self.obj.ref) + self.person_label.set_text(name_displayer.display(p)) + + self.street = MonitoredEntry( + self.top.get_object("relationship"), + self.obj.set_relation, + self.obj.get_relation, + self.db.readonly) + + self.priv = PrivacyButton( + self.top.get_object("private"), + self.obj, + self.db.readonly) + + def _connect_signals(self): + self.define_cancel_button(self.top.get_object('cancel')) + self.define_ok_button(self.top.get_object('ok'),self.save) + self.top.get_object('select').connect('clicked',self._select_person) + self.define_help_button(self.top.get_object('help'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('person-rebuild', self.close) + self._add_db_signal('person-delete', self.check_for_close) + + def check_for_close(self, handles): + """ + Callback method for delete signals. + If there is a delete signal of the primary object we are editing, the + editor (and all child windows spawned) should be closed + """ + if self.obj.ref in handles: + self.close() + + def _select_person(self, obj): + SelectPerson = SelectorFactory('Person') + + sel = SelectPerson(self.dbstate, self.uistate, self.track) + person = sel.run() + self.update_person(person) + + def update_person(self, person): + if person: + self.obj.ref = person.get_handle() + self.person_label.set_text(name_displayer.display(person)) + self._update_dnd_capability() + + def on_drag_persondata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + self.update_person(person) + + def drag_data_get(self, widget, context, sel_data, info, time): + # get the selected object, returning if not is defined + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.ref, 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and inserts them into the main + window. + """ + + notebook = Gtk.Notebook() + + self.srcref_list = CitationEmbedList(self.dbstate, self.uistate, + self.track, + self.obj.get_citation_list()) + self._add_tab(notebook, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.obj.get_note_list(), + notetype=NoteType.ASSOCIATION) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self._setup_notebook_tabs(notebook) + notebook.show_all() + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def build_menu_names(self, obj): + return (_('Person Reference'),_('Person Reference Editor')) + + def save(self,*obj): + """ + Called when the OK button is pressed. Gets data from the + form and updates the data structure. + """ + + if self.obj.ref: + if self.callback: + self.callback(self.obj) + self.callback = None + self.close() + else: + from ..dialog import ErrorDialog + + ErrorDialog( + _('No person selected'), + _('You must either select a person or Cancel the edit'), + parent=self.uistate.window) diff --git a/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.patch b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.patch new file mode 100644 index 0000000..7c656a1 --- /dev/null +++ b/hyperlink-person-associations/gramps/gui/editors/editpersonref.py.patch @@ -0,0 +1,48 @@ +58a59 +> from ..display import display_url +96a98 +> self.person_label.set_use_markup(True) +103a106 +> self.person_label.connect('activate_link', self.on_person_label_activate_link) +122c125,126 +< self.person_label.set_text(name_displayer.display(p)) +--- +> self.person_label.set_markup("" + name_displayer.display(p) + "") +> # self.person_label.set_text(name_displayer.display(p)) +169c173,174 +< self.person_label.set_text(name_displayer.display(person)) +--- +> self.person_label.set_markup("" + name_displayer.display(person) + "") +> # self.person_label.set_text(name_displayer.display(person)) +170a176,206 +> +> def find_parent_with_attr(self, attr="dbstate"): +> """ +> """ +> # Find a parent with attr: +> obj = self +> while obj: +> if hasattr(obj, attr): +> break +> obj = obj.get_parent() +> return obj +> +> def on_person_label_activate_link(self, widget, uri): +> """ +> Handle the standard gtk interface for activate_link. +> """ +> # this is stupid +> if uri.startswith("gramps://"): +> # if in a window: +> win_obj = self.find_parent_with_attr(attr="dbstate") +> if win_obj: +> # Edit the object: +> obj_class, prop, value = uri[9:].split("/") +> from ..editors import EditObject +> EditObject(win_obj.dbstate, +> win_obj.uistate, +> win_obj.track, +> obj_class, prop, value) +> return +> +> display_url(uri) diff --git a/media-reference-place-chooser/README.md b/media-reference-place-chooser/README.md new file mode 100644 index 0000000..1c4540e --- /dev/null +++ b/media-reference-place-chooser/README.md @@ -0,0 +1,3 @@ +# Media Reference Place Chooser + +This patch adds a Place chooser to the media reference editor. The Place chooser stores the GRAMPS handle to the Place object as an attribute. \ No newline at end of file diff --git a/media-reference-place-chooser/gramps/gui/editors/editmediaref.py b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py new file mode 100644 index 0000000..bd13dec --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py @@ -0,0 +1,580 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# 2008-2009 Stephane Charette +# 2009 Gary Burton +# 2011 Robert Cheramy +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import GdkPixbuf +from gi.repository import Gdk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from ..utils import open_file_with_default_application +from gramps.gen.const import THUMBSCALE +from gramps.gen.mime import get_description, get_type +from gramps.gen.utils.thumbnails import get_thumbnail_image, find_mime_type_pixbuf +from gramps.gen.utils.file import (media_path_full, find_file, create_checksum) +from gramps.gen.lib import NoteType, Attribute +from gramps.gen.db import DbTxn +from .objectentries import PlaceEntry +from ..glade import Glade +from .displaytabs import (CitationEmbedList, MediaAttrEmbedList, MediaBackRefList, + NoteTab) +from ..widgets import (MonitoredSpinButton, MonitoredEntry, PrivacyButton, + MonitoredDate, MonitoredTagList, SelectionWidget, Region) +from .editreference import RefTab, EditReference +from .addmedia import AddMedia +from gramps.gen.const import URL_MANUAL_SECT2 + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT2 +WIKI_HELP_SEC = _('manual|Media_Reference_Editor_dialog') + +#------------------------------------------------------------------------- +# +# EditMediaRef +# +#------------------------------------------------------------------------- +class EditMediaRef(EditReference): + + def __init__(self, state, uistate, track, media, media_ref, update): + EditReference.__init__(self, state, uistate, track, media, + media_ref, update) + if not self.source.get_handle(): + #show the addmedia dialog immediately, with track of parent. + AddMedia(state, self.uistate, self.track, self.source, + self._update_addmedia) + + def _local_init(self): + + self.top = Glade() + self.set_window(self.top.toplevel, + self.top.get_object('title'), + _('Media Reference Editor')) + self.setup_configs('interface.media-ref', 600, 450) + + self.define_warn_box(self.top.get_object("warn_box")) + self.top.get_object("label427").set_text(_("Y coordinate|Y")) + self.top.get_object("label428").set_text(_("Y coordinate|Y")) + + tblref = self.top.get_object('table50') + self.notebook_ref = self.top.get_object('notebook_ref') + self.track_ref_for_deletion("notebook_ref") + self.expander = self.top.get_object('expander1') + #recreate start page as GrampsTab + self.notebook_ref.remove_page(0) + self.reftab = RefTab(self.dbstate, self.uistate, self.track, + _('General'), tblref) + self.track_ref_for_deletion("reftab") + tblref = self.top.get_object('table2') + self.notebook_shared = self.top.get_object('notebook_shared') + #recreate start page as GrampsTab + self.notebook_shared.remove_page(0) + self.track_ref_for_deletion("notebook_shared") + self.primtab = RefTab(self.dbstate, self.uistate, self.track, + _('_General'), tblref) + self.track_ref_for_deletion("primtab") + self.rect_pixbuf = None + + def setup_filepath(self): + self.select = self.top.get_object('file_select') + self.track_ref_for_deletion("select") + self.file_path = self.top.get_object("path") + self.track_ref_for_deletion("file_path") + + self.file_path.set_text(self.source.get_path()) + self.select.connect('clicked', self.select_file) + + def determine_mime(self): + descr = get_description(self.source.get_mime_type()) + if descr: + self.mimetext.set_text(descr) + + path = self.file_path.get_text() + path_full = media_path_full(self.db, path) + if path != self.source.get_path() and path_full != self.source.get_path(): + #redetermine mime + mime = get_type(find_file(path_full)) + self.source.set_mime_type(mime) + descr = get_description(mime) + if descr: + self.mimetext.set_text(descr) + else: + self.mimetext.set_text(_('Unknown')) + #if mime type not set, is note + if not self.source.get_mime_type(): + self.mimetext.set_text(_('Note')) + + def draw_preview(self): + """ + Draw the two preview images. This method can be called on eg change of + the path. + """ + mtype = self.source.get_mime_type() + if mtype: + fullpath = media_path_full(self.db, self.source.get_path()) + pb = get_thumbnail_image(fullpath, mtype) + self.pixmap.set_from_pixbuf(pb) + self.selection.load_image(fullpath) + else: + pb = find_mime_type_pixbuf('text/plain') + self.pixmap.set_from_pixbuf(pb) + self.selection.load_image('') + + def _setup_fields(self): + + ebox_shared = self.top.get_object('eventbox') + ebox_shared.connect('button-press-event', self.button_press_event) + self.pixmap = self.top.get_object("pixmap") + self.mimetext = self.top.get_object("type") + self.track_ref_for_deletion("mimetext") + + coord = self.source_ref.get_rectangle() + #upgrade path: set invalid (from eg old db) to none + + if coord is not None and coord in ( + (None,)*4, + (0, 0, 100, 100), + (coord[0], coord[1])*2 + ): + coord = None + + if coord is not None: + self.rectangle = coord + else: + self.rectangle = (0, 0, 100, 100) + + self.selection = SelectionWidget() + self.selection.set_multiple_selection(False) + self.selection.connect("region-modified", self.region_modified) + self.selection.connect("region-created", self.region_modified) + self.expander.connect("activate", self.selection.expander) + frame = self.top.get_object("frame9") + frame.add(self.selection) + self.track_ref_for_deletion("selection") + self.selection.show() + + self.setup_filepath() + self.determine_mime() + + corners = ["corner1_x", "corner1_y", "corner2_x", "corner2_y"] + + if coord and isinstance(coord, tuple): + for index, corner in enumerate(corners): + self.top.get_object(corner).set_value(coord[index]) + else: + for corner, value in zip(corners, [0, 0, 100, 100]): + self.top.get_object(corner).set_value(value) + + if self.dbstate.db.readonly: + for corner in corners: + self.top.get_object(corner).set_sensitive(False) + + self.corner1_x_spinbutton = MonitoredSpinButton( + self.top.get_object("corner1_x"), + self.set_corner1_x, + self.get_corner1_x, + self.db.readonly) + self.track_ref_for_deletion("corner1_x_spinbutton") + + self.corner1_y_spinbutton = MonitoredSpinButton( + self.top.get_object("corner1_y"), + self.set_corner1_y, + self.get_corner1_y, + self.db.readonly) + self.track_ref_for_deletion("corner1_y_spinbutton") + + self.corner2_x_spinbutton = MonitoredSpinButton( + self.top.get_object("corner2_x"), + self.set_corner2_x, + self.get_corner2_x, + self.db.readonly) + self.track_ref_for_deletion("corner2_x_spinbutton") + + self.corner2_y_spinbutton = MonitoredSpinButton( + self.top.get_object("corner2_y"), + self.set_corner2_y, + self.get_corner2_y, + self.db.readonly) + self.track_ref_for_deletion("corner2_y_spinbutton") + + self.descr_window = MonitoredEntry( + self.top.get_object("description"), + self.source.set_description, + self.source.get_description, + self.db.readonly) + + self.ref_privacy = PrivacyButton( + self.top.get_object("private"), + self.source_ref, + self.db.readonly) + + self.gid = MonitoredEntry( + self.top.get_object("gid"), + self.source.set_gramps_id, + self.source.get_gramps_id, + self.db.readonly) + + self.privacy = PrivacyButton( + self.top.get_object("privacy"), + self.source, + self.db.readonly) + + self.path_obj = MonitoredEntry( + self.top.get_object("path"), + self.source.set_path, + self.source.get_path, + self.db.readonly) + + self.date_field = MonitoredDate( + self.top.get_object("date_entry"), + self.top.get_object("date_edit"), + self.source.get_date_object(), + self.uistate, self.track, + self.db.readonly) + + + self.place = PlaceEntry(self.dbstate, self.uistate, self.track, + self.top.get_object("place"), + self.top.get_object("place_event_box"), + self.set_place_handle, + self.get_place_handle, + self.top.get_object("add_del_place"), self.top.get_object("select_place")) + + self.tags = MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.source.set_tag_list, + self.source.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + def _post_init(self): + """ + Initialization that must happen after the window is shown. + """ + self.draw_preview() + self.update_region() + + def set_corner1_x(self, value): + """ + Callback for the signal handling of the spinbutton for the first + corner x coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the first corner x coordinate of the subsection in int + """ + + self.rectangle = (value,) + self.rectangle[1:] + self.update_region() + + def set_corner1_y(self, value): + """ + Callback for the signal handling of the spinbutton for the first + corner y coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the first corner y coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:1] + (value,) + self.rectangle[2:] + self.update_region() + + def set_corner2_x(self, value): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the second corner x coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:2] + (value,) + self.rectangle[3:] + self.update_region() + + def set_corner2_y(self, value): + """ + Callback for the signal handling of the spinbutton for the second + corner y coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the second corner y coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:3] + (value,) + self.update_region() + + def get_corner1_x(self): + """ + Callback for the signal handling of the spinbutton for the first corner + x coordinate of the subsection. + + @returns: the first corner x coordinate of the subsection or 0 if + there is no selection + """ + return self.rectangle[0] + + def get_corner1_y(self): + """ + Callback for the signal handling of the spinbutton for the first corner + y coordinate of the subsection. + + @returns: the first corner y coordinate of the subsection or 0 if + there is no selection + """ + return self.rectangle[1] + + def get_corner2_x(self): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + + @returns: the second corner x coordinate of the subsection or 100 if + there is no selection + """ + return self.rectangle[2] + + def get_corner2_y(self): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + + @returns: the second corner x coordinate of the subsection or 100 if + there is no selection + """ + return self.rectangle[3] + + def update_region(self): + """ + Updates the thumbnail of the specified subsection. + """ + if not self.selection.is_image_loaded(): + return + real = self.selection.proportional_to_real_rect(self.rectangle) + region = Region(real[0], real[1], real[2], real[3]) + self.selection.set_regions([region]) + self.selection.select(region) # update the selection box shown + self.selection.refresh() + + def region_modified(self, widget): + """ + Update new values when the selection is changed. + """ + real = self.selection.get_selection() + coords = self.selection.real_to_proportional_rect(real) + self.corner1_x_spinbutton.set_value(coords[0]) + self.corner1_y_spinbutton.set_value(coords[1]) + self.corner2_x_spinbutton.set_value(coords[2]) + self.corner2_y_spinbutton.set_value(coords[3]) + + def build_menu_names(self, person): + """ + Provide the information needed by the base class to define the + window management menu entries. + """ + if self.source: + submenu_label = _('Media: %s') % self.source.get_gramps_id() + else: + submenu_label = _('New Media') + return (_('Media Reference Editor'),submenu_label) + + def button_press_event(self, obj, event): + if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS + and event.button == 1): + photo_path = media_path_full(self.db, self.source.get_path()) + open_file_with_default_application(photo_path, self.uistate) + + def _update_addmedia(self, obj): + """ + Called when the add media dialog has been called. + This allows us to update the main form in response to + any changes: Redraw relevant fields: description, mimetype and path + """ + for obj in (self.descr_window, self.path_obj): + obj.update() + self.determine_mime() + self.update_checksum() + self.draw_preview() + + def update_checksum(self): + self.uistate.set_busy_cursor(True) + media_path = media_path_full(self.dbstate.db, self.source.get_path()) + self.source.set_checksum(create_checksum(os.path.normpath(media_path))) + self.uistate.set_busy_cursor(False) + + def select_file(self, val): + self.determine_mime() + path = self.file_path.get_text() + self.source.set_path(path) + AddMedia(self.dbstate, self.uistate, self.track, self.source, + self._update_addmedia) + + def _connect_signals(self): + self.define_cancel_button(self.top.get_object('button84')) + self.define_ok_button(self.top.get_object('button82'),self.save) + # TODO help button (rename glade button name) + self.define_help_button(self.top.get_object('button104'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('media-rebuild', self.close) + self._add_db_signal('media-delete', self.check_for_close) + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and inserts them into the main + window. + """ + notebook_ref = self.top.get_object('notebook_ref') + notebook_src = self.top.get_object('notebook_shared') + + self._add_tab(notebook_src, self.primtab) + self._add_tab(notebook_ref, self.reftab) + + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source_ref.get_citation_list()) + self._add_tab(notebook_ref, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.attr_list = MediaAttrEmbedList(self.dbstate,self.uistate,self.track, + self.source_ref.get_attribute_list()) + self._add_tab(notebook_ref, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.backref_list = MediaBackRefList(self.dbstate,self.uistate,self.track, + self.db.find_backlink_handles(self.source.handle), + self.enable_warnbox + ) + self._add_tab(notebook_src, self.backref_list) + self.track_ref_for_deletion("backref_list") + + self.note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.source_ref.get_note_list(), + notetype=NoteType.MEDIAREF) + self._add_tab(notebook_ref, self.note_ref_tab) + self.track_ref_for_deletion("note_ref_tab") + + self.src_srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source.get_citation_list()) + self._add_tab(notebook_src, self.src_srcref_list) + self.track_ref_for_deletion("src_srcref_list") + + self.src_attr_list = MediaAttrEmbedList(self.dbstate,self.uistate,self.track, + self.source.get_attribute_list()) + self._add_tab(notebook_src, self.src_attr_list) + self.track_ref_for_deletion("src_attr_list") + + self.src_note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.source.get_note_list(), + notetype=NoteType.MEDIA) + self._add_tab(notebook_src, self.src_note_ref_tab) + self.track_ref_for_deletion("src_note_ref_tab") + + self._setup_notebook_tabs(notebook_src) + self._setup_notebook_tabs(notebook_ref) + + def get_place_handle(self): + for attr in self.source.get_attribute_list(): + if attr.get_type() == "Place": + return attr.get_value() + return None + + def set_place_handle(self, value): + for attr in self.source.get_attribute_list(): + if attr.get_type() == "Place": + attr.set_value(value) + return + + attr = Attribute() + attr.set_type("Place") + attr.set_value(value) + self.source.get_attribute_list().append(attr) + + def save(self,*obj): + + #first save primary object + if self.source.handle: + with DbTxn(_("Edit Media Object (%s)") % + self.source.get_description(), self.db) as trans: + self.db.commit_media(self.source, trans) + else: + if self.check_for_duplicate_id('Media'): + return + with DbTxn(_("Add Media Object (%s)") % + self.source.get_description(), self.db) as trans: + self.db.add_media(self.source, trans) + + #save reference object in memory + coord = ( + self.top.get_object("corner1_x").get_value_as_int(), + self.top.get_object("corner1_y").get_value_as_int(), + self.top.get_object("corner2_x").get_value_as_int(), + self.top.get_object("corner2_y").get_value_as_int(), + ) + + #do not set unset or invalid coord + + if coord is not None and coord in ( + (None,)*4, + (0, 0, 100, 100), + (coord[0], coord[1])*2 + ): + coord = None + + self.source_ref.set_rectangle(coord) + + #call callback if given + if self.update: + self.update(self.source_ref,self.source) + self.update = None + self.close() diff --git a/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.0 b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.0 new file mode 100644 index 0000000..86d6682 --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.0 @@ -0,0 +1,554 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# 2008-2009 Stephane Charette +# 2009 Gary Burton +# 2011 Robert Cheramy +# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2013 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import GdkPixbuf +from gi.repository import Gdk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from ..utils import open_file_with_default_application +from gramps.gen.const import THUMBSCALE +from gramps.gen.mime import get_description, get_type +from gramps.gen.utils.thumbnails import get_thumbnail_image, find_mime_type_pixbuf +from gramps.gen.utils.file import (media_path_full, find_file, create_checksum) +from gramps.gen.lib import NoteType +from gramps.gen.db import DbTxn +from ..glade import Glade +from .displaytabs import (CitationEmbedList, MediaAttrEmbedList, MediaBackRefList, + NoteTab) +from ..widgets import (MonitoredSpinButton, MonitoredEntry, PrivacyButton, + MonitoredDate, MonitoredTagList, SelectionWidget, Region) +from .editreference import RefTab, EditReference +from .addmedia import AddMedia +from gramps.gen.const import URL_MANUAL_SECT2 + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT2 +WIKI_HELP_SEC = _('manual|Media_Reference_Editor_dialog') + +#------------------------------------------------------------------------- +# +# EditMediaRef +# +#------------------------------------------------------------------------- +class EditMediaRef(EditReference): + + def __init__(self, state, uistate, track, media, media_ref, update): + EditReference.__init__(self, state, uistate, track, media, + media_ref, update) + if not self.source.get_handle(): + #show the addmedia dialog immediately, with track of parent. + AddMedia(state, self.uistate, self.track, self.source, + self._update_addmedia) + + def _local_init(self): + + self.top = Glade() + self.set_window(self.top.toplevel, + self.top.get_object('title'), + _('Media Reference Editor')) + self.setup_configs('interface.media-ref', 600, 450) + + self.define_warn_box(self.top.get_object("warn_box")) + self.top.get_object("label427").set_text(_("Y coordinate|Y")) + self.top.get_object("label428").set_text(_("Y coordinate|Y")) + + tblref = self.top.get_object('table50') + self.notebook_ref = self.top.get_object('notebook_ref') + self.track_ref_for_deletion("notebook_ref") + self.expander = self.top.get_object('expander1') + #recreate start page as GrampsTab + self.notebook_ref.remove_page(0) + self.reftab = RefTab(self.dbstate, self.uistate, self.track, + _('General'), tblref) + self.track_ref_for_deletion("reftab") + tblref = self.top.get_object('table2') + self.notebook_shared = self.top.get_object('notebook_shared') + #recreate start page as GrampsTab + self.notebook_shared.remove_page(0) + self.track_ref_for_deletion("notebook_shared") + self.primtab = RefTab(self.dbstate, self.uistate, self.track, + _('_General'), tblref) + self.track_ref_for_deletion("primtab") + self.rect_pixbuf = None + + def setup_filepath(self): + self.select = self.top.get_object('file_select') + self.track_ref_for_deletion("select") + self.file_path = self.top.get_object("path") + self.track_ref_for_deletion("file_path") + + self.file_path.set_text(self.source.get_path()) + self.select.connect('clicked', self.select_file) + + def determine_mime(self): + descr = get_description(self.source.get_mime_type()) + if descr: + self.mimetext.set_text(descr) + + path = self.file_path.get_text() + path_full = media_path_full(self.db, path) + if path != self.source.get_path() and path_full != self.source.get_path(): + #redetermine mime + mime = get_type(find_file(path_full)) + self.source.set_mime_type(mime) + descr = get_description(mime) + if descr: + self.mimetext.set_text(descr) + else: + self.mimetext.set_text(_('Unknown')) + #if mime type not set, is note + if not self.source.get_mime_type(): + self.mimetext.set_text(_('Note')) + + def draw_preview(self): + """ + Draw the two preview images. This method can be called on eg change of + the path. + """ + mtype = self.source.get_mime_type() + if mtype: + fullpath = media_path_full(self.db, self.source.get_path()) + pb = get_thumbnail_image(fullpath, mtype) + self.pixmap.set_from_pixbuf(pb) + self.selection.load_image(fullpath) + else: + pb = find_mime_type_pixbuf('text/plain') + self.pixmap.set_from_pixbuf(pb) + self.selection.load_image('') + + def _setup_fields(self): + + ebox_shared = self.top.get_object('eventbox') + ebox_shared.connect('button-press-event', self.button_press_event) + self.pixmap = self.top.get_object("pixmap") + self.mimetext = self.top.get_object("type") + self.track_ref_for_deletion("mimetext") + + coord = self.source_ref.get_rectangle() + #upgrade path: set invalid (from eg old db) to none + + if coord is not None and coord in ( + (None,)*4, + (0, 0, 100, 100), + (coord[0], coord[1])*2 + ): + coord = None + + if coord is not None: + self.rectangle = coord + else: + self.rectangle = (0, 0, 100, 100) + + self.selection = SelectionWidget() + self.selection.set_multiple_selection(False) + self.selection.connect("region-modified", self.region_modified) + self.selection.connect("region-created", self.region_modified) + self.expander.connect("activate", self.selection.expander) + frame = self.top.get_object("frame9") + frame.add(self.selection) + self.track_ref_for_deletion("selection") + self.selection.show() + + self.setup_filepath() + self.determine_mime() + + corners = ["corner1_x", "corner1_y", "corner2_x", "corner2_y"] + + if coord and isinstance(coord, tuple): + for index, corner in enumerate(corners): + self.top.get_object(corner).set_value(coord[index]) + else: + for corner, value in zip(corners, [0, 0, 100, 100]): + self.top.get_object(corner).set_value(value) + + if self.dbstate.db.readonly: + for corner in corners: + self.top.get_object(corner).set_sensitive(False) + + self.corner1_x_spinbutton = MonitoredSpinButton( + self.top.get_object("corner1_x"), + self.set_corner1_x, + self.get_corner1_x, + self.db.readonly) + self.track_ref_for_deletion("corner1_x_spinbutton") + + self.corner1_y_spinbutton = MonitoredSpinButton( + self.top.get_object("corner1_y"), + self.set_corner1_y, + self.get_corner1_y, + self.db.readonly) + self.track_ref_for_deletion("corner1_y_spinbutton") + + self.corner2_x_spinbutton = MonitoredSpinButton( + self.top.get_object("corner2_x"), + self.set_corner2_x, + self.get_corner2_x, + self.db.readonly) + self.track_ref_for_deletion("corner2_x_spinbutton") + + self.corner2_y_spinbutton = MonitoredSpinButton( + self.top.get_object("corner2_y"), + self.set_corner2_y, + self.get_corner2_y, + self.db.readonly) + self.track_ref_for_deletion("corner2_y_spinbutton") + + self.descr_window = MonitoredEntry( + self.top.get_object("description"), + self.source.set_description, + self.source.get_description, + self.db.readonly) + + self.ref_privacy = PrivacyButton( + self.top.get_object("private"), + self.source_ref, + self.db.readonly) + + self.gid = MonitoredEntry( + self.top.get_object("gid"), + self.source.set_gramps_id, + self.source.get_gramps_id, + self.db.readonly) + + self.privacy = PrivacyButton( + self.top.get_object("privacy"), + self.source, + self.db.readonly) + + self.path_obj = MonitoredEntry( + self.top.get_object("path"), + self.source.set_path, + self.source.get_path, + self.db.readonly) + + self.date_field = MonitoredDate( + self.top.get_object("date_entry"), + self.top.get_object("date_edit"), + self.source.get_date_object(), + self.uistate, self.track, + self.db.readonly) + + self.tags = MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.source.set_tag_list, + self.source.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + def _post_init(self): + """ + Initialization that must happen after the window is shown. + """ + self.draw_preview() + self.update_region() + + def set_corner1_x(self, value): + """ + Callback for the signal handling of the spinbutton for the first + corner x coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the first corner x coordinate of the subsection in int + """ + + self.rectangle = (value,) + self.rectangle[1:] + self.update_region() + + def set_corner1_y(self, value): + """ + Callback for the signal handling of the spinbutton for the first + corner y coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the first corner y coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:1] + (value,) + self.rectangle[2:] + self.update_region() + + def set_corner2_x(self, value): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the second corner x coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:2] + (value,) + self.rectangle[3:] + self.update_region() + + def set_corner2_y(self, value): + """ + Callback for the signal handling of the spinbutton for the second + corner y coordinate of the subsection. + Updates the subsection thumbnail using the given value + + @param value: the second corner y coordinate of the subsection in int + """ + + self.rectangle = self.rectangle[:3] + (value,) + self.update_region() + + def get_corner1_x(self): + """ + Callback for the signal handling of the spinbutton for the first corner + x coordinate of the subsection. + + @returns: the first corner x coordinate of the subsection or 0 if + there is no selection + """ + return self.rectangle[0] + + def get_corner1_y(self): + """ + Callback for the signal handling of the spinbutton for the first corner + y coordinate of the subsection. + + @returns: the first corner y coordinate of the subsection or 0 if + there is no selection + """ + return self.rectangle[1] + + def get_corner2_x(self): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + + @returns: the second corner x coordinate of the subsection or 100 if + there is no selection + """ + return self.rectangle[2] + + def get_corner2_y(self): + """ + Callback for the signal handling of the spinbutton for the second + corner x coordinate of the subsection. + + @returns: the second corner x coordinate of the subsection or 100 if + there is no selection + """ + return self.rectangle[3] + + def update_region(self): + """ + Updates the thumbnail of the specified subsection. + """ + if not self.selection.is_image_loaded(): + return + real = self.selection.proportional_to_real_rect(self.rectangle) + region = Region(real[0], real[1], real[2], real[3]) + self.selection.set_regions([region]) + self.selection.select(region) # update the selection box shown + self.selection.refresh() + + def region_modified(self, widget): + """ + Update new values when the selection is changed. + """ + real = self.selection.get_selection() + coords = self.selection.real_to_proportional_rect(real) + self.corner1_x_spinbutton.set_value(coords[0]) + self.corner1_y_spinbutton.set_value(coords[1]) + self.corner2_x_spinbutton.set_value(coords[2]) + self.corner2_y_spinbutton.set_value(coords[3]) + + def build_menu_names(self, person): + """ + Provide the information needed by the base class to define the + window management menu entries. + """ + if self.source: + submenu_label = _('Media: %s') % self.source.get_gramps_id() + else: + submenu_label = _('New Media') + return (_('Media Reference Editor'),submenu_label) + + def button_press_event(self, obj, event): + if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS + and event.button == 1): + photo_path = media_path_full(self.db, self.source.get_path()) + open_file_with_default_application(photo_path, self.uistate) + + def _update_addmedia(self, obj): + """ + Called when the add media dialog has been called. + This allows us to update the main form in response to + any changes: Redraw relevant fields: description, mimetype and path + """ + for obj in (self.descr_window, self.path_obj): + obj.update() + self.determine_mime() + self.update_checksum() + self.draw_preview() + + def update_checksum(self): + self.uistate.set_busy_cursor(True) + media_path = media_path_full(self.dbstate.db, self.source.get_path()) + self.source.set_checksum(create_checksum(os.path.normpath(media_path))) + self.uistate.set_busy_cursor(False) + + def select_file(self, val): + self.determine_mime() + path = self.file_path.get_text() + self.source.set_path(path) + AddMedia(self.dbstate, self.uistate, self.track, self.source, + self._update_addmedia) + + def _connect_signals(self): + self.define_cancel_button(self.top.get_object('button84')) + self.define_ok_button(self.top.get_object('button82'),self.save) + # TODO help button (rename glade button name) + self.define_help_button(self.top.get_object('button104'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('media-rebuild', self.close) + self._add_db_signal('media-delete', self.check_for_close) + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and inserts them into the main + window. + """ + notebook_ref = self.top.get_object('notebook_ref') + notebook_src = self.top.get_object('notebook_shared') + + self._add_tab(notebook_src, self.primtab) + self._add_tab(notebook_ref, self.reftab) + + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source_ref.get_citation_list()) + self._add_tab(notebook_ref, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.attr_list = MediaAttrEmbedList(self.dbstate,self.uistate,self.track, + self.source_ref.get_attribute_list()) + self._add_tab(notebook_ref, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.backref_list = MediaBackRefList(self.dbstate,self.uistate,self.track, + self.db.find_backlink_handles(self.source.handle), + self.enable_warnbox + ) + self._add_tab(notebook_src, self.backref_list) + self.track_ref_for_deletion("backref_list") + + self.note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.source_ref.get_note_list(), + notetype=NoteType.MEDIAREF) + self._add_tab(notebook_ref, self.note_ref_tab) + self.track_ref_for_deletion("note_ref_tab") + + self.src_srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source.get_citation_list()) + self._add_tab(notebook_src, self.src_srcref_list) + self.track_ref_for_deletion("src_srcref_list") + + self.src_attr_list = MediaAttrEmbedList(self.dbstate,self.uistate,self.track, + self.source.get_attribute_list()) + self._add_tab(notebook_src, self.src_attr_list) + self.track_ref_for_deletion("src_attr_list") + + self.src_note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.source.get_note_list(), + notetype=NoteType.MEDIA) + self._add_tab(notebook_src, self.src_note_ref_tab) + self.track_ref_for_deletion("src_note_ref_tab") + + self._setup_notebook_tabs(notebook_src) + self._setup_notebook_tabs(notebook_ref) + + def save(self,*obj): + + #first save primary object + if self.source.handle: + with DbTxn(_("Edit Media Object (%s)") % + self.source.get_description(), self.db) as trans: + self.db.commit_media(self.source, trans) + else: + if self.check_for_duplicate_id('Media'): + return + with DbTxn(_("Add Media Object (%s)") % + self.source.get_description(), self.db) as trans: + self.db.add_media(self.source, trans) + + #save reference object in memory + coord = ( + self.top.get_object("corner1_x").get_value_as_int(), + self.top.get_object("corner1_y").get_value_as_int(), + self.top.get_object("corner2_x").get_value_as_int(), + self.top.get_object("corner2_y").get_value_as_int(), + ) + + #do not set unset or invalid coord + + if coord is not None and coord in ( + (None,)*4, + (0, 0, 100, 100), + (coord[0], coord[1])*2 + ): + coord = None + + self.source_ref.set_rectangle(coord) + + #call callback if given + if self.update: + self.update(self.source_ref,self.source) + self.update = None + self.close() diff --git a/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.patch b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.patch new file mode 100644 index 0000000..8396cdc --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/editors/editmediaref.py.patch @@ -0,0 +1,33 @@ +53c53 +< from gramps.gen.lib import NoteType +--- +> from gramps.gen.lib import NoteType, Attribute +54a55 +> from .objectentries import PlaceEntry +275a277,284 +> +> self.place = PlaceEntry(self.dbstate, self.uistate, self.track, +> self.top.get_object("place"), +> self.top.get_object("place_event_box"), +> self.set_place_handle, +> self.get_place_handle, +> self.top.get_object("add_del_place"), self.top.get_object("select_place")) +> +515a525,541 +> +> def get_place_handle(self): +> for attr in self.source.get_attribute_list(): +> if attr.get_type() == "Place": +> return attr.get_value() +> return None +> +> def set_place_handle(self, value): +> for attr in self.source.get_attribute_list(): +> if attr.get_type() == "Place": +> attr.set_value(value) +> return +> +> attr = Attribute() +> attr.set_type("Place") +> attr.set_value(value) +> self.source.get_attribute_list().append(attr) diff --git a/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade new file mode 100644 index 0000000..3fd7540 --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade @@ -0,0 +1,957 @@ + + + + + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + False + 600 + 450 + dialog + + + True + False + vertical + 8 + + + True + False + end + + + _Cancel + True + True + True + True + True + + + False + False + 0 + + + + + _OK + True + True + True + True + True + True + + + False + False + 1 + + + + + _Help + True + True + True + True + True + + + False + False + 2 + + + + + False + True + end + 0 + + + + + True + False + 6 + vertical + 6 + + + True + True + + + True + False + 12 + 6 + 12 + + + True + False + end + _Corner 2: X + True + corner2_x + + + 1 + 4 + + + + + True + False + end + Y coordinate|Y + True + corner2_y + + + 1 + 5 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment4 + 1 + True + + + 2 + 5 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment3 + 1 + True + + + 2 + 4 + + + + + True + False + Referenced region of the image media object. +Select a region with clicking and holding the mouse button on the top left corner of the region you want, dragging the mouse to the bottom right corner of the region, and then releasing the mouse button. + True + True + 0 + + + + + + True + False + Preview + + + + + + + + 0 + 0 + 6 + + + + + True + False + end + _Corner 1: X + True + corner1_x + + + 1 + 2 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + + none + adjustment2 + 1 + True + if-valid + + + 2 + 2 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment1 + 1 + True + + + 2 + 3 + + + + + True + False + end + Y coordinate|Y + True + corner1_y + + + 1 + 3 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + True + True + True + none + + + True + False + dialog-password + 1 + + + Privacy + + + + + + + Private + + + + + + False + False + end + 0 + + + + + 2 + 0 + + + + + True + False + True + + + 2 + 1 + + + + + + + + + + + + + True + False + General + + + + + + False + + + + + True + True + 10 + 0 + + + + + True + True + + + True + True + + + True + False + 12 + 6 + 12 + + + True + False + start + _Path: + True + center + path + + + 1 + 4 + + + + + True + False + start + _ID: + True + center + gid + + + 1 + 0 + + + + + True + False + start + _Title: + True + center + description + + + 1 + 1 + + + + + True + True + Descriptive title for this media object. + True + + + + 2 + 1 + + + + + False + 6 + 12 + + + True + False + 48 + dialog-warning + 6 + + + False + False + 0 + + + + + 400 + True + False + start + 3 + 3 + <b>Note:</b> Any changes in the shared media object information will be reflected in the media object itself. + True + True + True + + + False + False + 1 + + + + + 0 + 7 + 3 + + + + + True + False + 0 + + + True + False + True + Double click image to view in an external viewer + Double click image to view in an external viewer + + + 100 + 100 + True + False + + + + + + + True + False + Preview + + + + + + + + 0 + 0 + 5 + + + + + True + False + start + Type: + type + + + 1 + 6 + + + + + True + False + Type of media object as indicated by the computer, eg Image, Video, ... + start + + + 2 + 6 + + + + + True + False + start + _Date: + True + date_entry + + + 1 + 2 + + + + + True + True + True + + + + 2 + 2 + + + + + True + True + True + True + Invoke date editor + none + + + True + False + gramps-date + + + Date + + + + + + + + + + Date + + + + + + 3 + 2 + + + + + True + True + True + none + + + True + False + dialog-password + 1 + + + Privacy + + + + + + + Private + + + + + 3 + 0 + + + + + True + True + True + + + + 2 + 0 + + + + + True + True + True + True + Select a file + none + + + True + False + document-open + 1 + + + Folder + + + + + + + + + + Path + + + + + 3 + 4 + + + + + True + True + True + + + + 2 + 4 + + + + + True + False + start + _Tags: + True + tag_button + + + 1 + 5 + + + + + True + False + True + + + 2 + 5 + + + + + True + True + True + + + + + + Tags + + + + + 3 + 5 + + + + + True + False + start + 3 + 3 + _Place: + True + center + select_place + + + 1 + 3 + + + + + True + True + True + none + + + True + False + + + + + + 3 + 3 + + + + + True + False + + + False + False + + + True + False + start + True + end + + + + + True + True + 0 + + + + + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + True + False + + + + + + 3 + 3 + + + + + True + False + + + False + False + + + True + False + start + True + end + + + + + True + True + 0 + + + + + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + + + + Place + + + + + + False + False + 1 + + + + + 2 + 3 + + + + + + + + + + + + + + + + + + + + False + + + + + True + False + General + + + + + + False + + + + + + + True + False + Shared Information + + + + + + + + False + False + 1 + + + + + True + True + 1 + + + + + + button84 + button82 + button104 + + + diff --git a/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.0 b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.0 new file mode 100644 index 0000000..71febc2 --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.0 @@ -0,0 +1,799 @@ + + + + + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + + + False + 600 + 450 + dialog + + + True + False + vertical + 8 + + + True + False + end + + + _Cancel + True + True + True + True + True + + + False + False + 0 + + + + + _OK + True + True + True + True + True + True + + + False + False + 1 + + + + + _Help + True + True + True + True + True + + + False + False + 2 + + + + + False + True + end + 0 + + + + + True + False + 6 + vertical + 6 + + + True + True + + + True + False + 12 + 6 + 12 + + + True + False + end + _Corner 2: X + True + corner2_x + + + 1 + 4 + + + + + True + False + end + Y coordinate|Y + True + corner2_y + + + 1 + 5 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment4 + 1 + True + + + 2 + 5 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment3 + 1 + True + + + 2 + 4 + + + + + True + False + Referenced region of the image media object. +Select a region with clicking and holding the mouse button on the top left corner of the region you want, dragging the mouse to the bottom right corner of the region, and then releasing the mouse button. + True + True + 0 + + + + + + True + False + Preview + + + + + + + + 0 + 0 + 6 + + + + + True + False + end + _Corner 1: X + True + corner1_x + + + 1 + 2 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + + none + adjustment2 + 1 + True + if-valid + + + 2 + 2 + + + + + True + True + If media is an image, select the specific part of the image you want to reference. +You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner. + + adjustment1 + 1 + True + + + 2 + 3 + + + + + True + False + end + Y coordinate|Y + True + corner1_y + + + 1 + 3 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + True + True + True + none + + + True + False + dialog-password + 1 + + + Privacy + + + + + + + Private + + + + + + False + False + end + 0 + + + + + 2 + 0 + + + + + True + False + True + + + 2 + 1 + + + + + + + + + + + + + True + False + General + + + + + + False + + + + + True + True + 10 + 0 + + + + + True + True + + + True + True + + + True + False + 12 + 6 + 12 + + + True + False + start + _Path: + True + center + path + + + 1 + 3 + + + + + True + False + start + _ID: + True + center + gid + + + 1 + 0 + + + + + True + False + start + _Title: + True + center + description + + + 1 + 1 + + + + + True + True + Descriptive title for this media object. + True + + + + 2 + 1 + + + + + False + 6 + 12 + + + True + False + 48 + dialog-warning + 6 + + + False + False + 0 + + + + + 400 + True + False + start + 3 + 3 + <b>Note:</b> Any changes in the shared media object information will be reflected in the media object itself. + True + True + True + + + False + False + 1 + + + + + 0 + 6 + 3 + + + + + True + False + 0 + + + True + False + True + Double click image to view in an external viewer + Double click image to view in an external viewer + + + 100 + 100 + True + False + + + + + + + True + False + Preview + + + + + + + + 0 + 0 + 5 + + + + + True + False + start + Type: + type + + + 1 + 5 + + + + + True + False + Type of media object as indicated by the computer, eg Image, Video, ... + start + + + 2 + 5 + + + + + True + False + start + _Date: + True + date_entry + + + 1 + 2 + + + + + True + True + True + + + + 2 + 2 + + + + + True + True + True + True + Invoke date editor + none + + + True + False + gramps-date + + + Date + + + + + + + + + + Date + + + + + + 3 + 2 + + + + + True + True + True + none + + + True + False + dialog-password + 1 + + + Privacy + + + + + + + Private + + + + + 3 + 0 + + + + + True + True + True + + + + 2 + 0 + + + + + True + True + True + True + Select a file + none + + + True + False + document-open + 1 + + + Folder + + + + + + + + + + Path + + + + + 3 + 3 + + + + + True + True + True + + + + 2 + 3 + + + + + True + False + start + _Tags: + True + tag_button + + + 1 + 4 + + + + + True + False + True + + + 2 + 4 + + + + + True + True + True + + + + + + Tags + + + + + 3 + 4 + + + + + + + + + + + + + + + + + False + + + + + True + False + General + + + + + + False + + + + + + + True + False + Shared Information + + + + + + + + False + False + 1 + + + + + True + True + 1 + + + + + + button84 + button82 + button104 + + + diff --git a/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.patch b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.patch new file mode 100644 index 0000000..cbec5ca --- /dev/null +++ b/media-reference-place-chooser/gramps/gui/glade/editmediaref.glade.patch @@ -0,0 +1,197 @@ +386c386 +< 3 +--- +> 4 +473c473 +< 6 +--- +> 7 +526c526 +< 5 +--- +> 6 +538c538 +< 5 +--- +> 6 +676c676 +< 3 +--- +> 4 +688c688 +< 3 +--- +> 4 +702c702 +< 4 +--- +> 5 +713c713 +< 4 +--- +> 5 +731,732c731,887 +< 3 +< 4 +--- +> 3 +> 5 +> +> +> +> +> True +> False +> start +> 3 +> 3 +> _Place: +> True +> center +> select_place +> +> +> 1 +> 3 +> +> +> +> +> True +> True +> True +> none +> +> +> True +> False +> +> +> +> +> +> 3 +> 3 +> +> +> +> +> True +> False +> +> +> False +> False +> +> +> True +> False +> start +> True +> end +> +> +> +> +> True +> True +> 0 +> +> +> +> +> True +> True +> True +> none +> +> +> True +> False +> gtk-index +> +> +> Selector +> +> +> +> +> +> +> True +> False +> +> +> +> +> +> 3 +> 3 +> +> +> +> +> True +> False +> +> +> False +> False +> +> +> True +> False +> start +> True +> end +> +> +> +> +> True +> True +> 0 +> +> +> +> +> True +> True +> True +> none +> +> +> True +> False +> gtk-index +> +> +> Selector +> +> +> +> +> +> +> +> +> +> Place +> +> +> +> +> +> False +> False +> 1 +> +> +> +> +> 2 +> 3 +733a889,891 +> +> +> diff --git a/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py new file mode 100644 index 0000000..13a6bc5 --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py @@ -0,0 +1,1325 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2009 Gary Burton +# Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import pickle + +#------------------------------------------------------------------------- +# +# enable logging for error handling +# +#------------------------------------------------------------------------- +import logging +log = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from ..ddtargets import DdTargets +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Pango +from gi.repository import GObject +from gi.repository import GLib + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.config import config +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.lib import ChildRef, Family, Name, NoteType, Person, Surname +from gramps.gen.db import DbTxn +from gramps.gen.errors import WindowActiveError +from gramps.gen.datehandler import displayer +from ..glade import Glade + +from .editprimary import EditPrimary +from .editchildref import EditChildRef +from .editperson import EditPerson +from .displaytabs import (EmbeddedList, EventEmbedList, CitationEmbedList, + FamilyAttrEmbedList, NoteTab, GalleryTab, + FamilyLdsEmbedList, ChildModel, + TEXT_COL, MARKUP_COL, ICON_COL) +from ..widgets import (PrivacyButton, MonitoredEntry, MonitoredDataType, + MonitoredTagList) +from gramps.gen.plug import CATEGORY_QR_FAMILY +from ..dialog import (ErrorDialog, RunDatabaseRepair, WarningDialog, + MessageHideDialog) +from gramps.gen.utils.db import (get_birth_or_fallback, get_death_or_fallback, + get_marriage_or_fallback, preset_name, family_name) +from ..selectors import SelectorFactory +from gramps.gen.utils.id import create_id +from gramps.gen.const import URL_MANUAL_SECT1 +from ..dbguielement import DbGUIElement + +from gramps.gen.utils.file import media_path_full +from gramps.gui.widgets import Photo + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 +WIKI_HELP_SEC = _('manual|Family_Editor_dialog') + +SelectPerson = SelectorFactory('Person') + +_RETURN = Gdk.keyval_from_name("Return") +_KP_ENTER = Gdk.keyval_from_name("KP_Enter") +_LEFT_BUTTON = 1 +_RIGHT_BUTTON = 3 + + +class ChildEmbedList(DbGUIElement, EmbeddedList): + """ + The child embed list is specific to the Edit Family dialog, so it + is contained here instead of in displaytabs. + """ + + _HANDLE_COL = 14 + _DND_TYPE = DdTargets.CHILDREF + _DND_EXTRA = DdTargets.PERSON_LINK + + _MSG = { + 'add' : _('Create a new person and add the child to the family'), + 'del' : _('Remove the child from the family'), + 'edit' : _('Edit the child reference'), + 'share' : _('Add an existing person as a child of the family'), + 'up' : _('Move the child up in the children list'), + 'down' : _('Move the child down in the children list'), + } + + # (name, column in model, width, markup/text, font weight) + _column_names = [ + (_('#'), 0, 25, TEXT_COL, -1, None), + (_('ID'), 1, 60, TEXT_COL, -1, None), + (_('Name'), 10, 250, TEXT_COL, -1, None), + (_('Gender'), 3, 75, TEXT_COL, -1, None), + (_('Paternal'), 4, 100, TEXT_COL, -1, None), + (_('Maternal'), 5, 100, TEXT_COL, -1, None), + (_('Birth Date'), 11, 150, MARKUP_COL, -1, None), + (_('Death Date'), 12, 150, MARKUP_COL, -1, None), + (_('Birth Place'), 8, 150, TEXT_COL, -1, None), + (_('Death Place'), 9, 150, TEXT_COL, -1, None), + None, + None, + None, + (_('Private'), 13, 30, ICON_COL, -1, 'gramps-lock') + ] + + def __init__(self, dbstate, uistate, track, family): + """ + Create the object, storing the passed family value + """ + self.family = family + DbGUIElement.__init__(self, dbstate.db) + EmbeddedList.__init__(self, dbstate, uistate, track, _('Chil_dren'), + ChildModel, share_button=True, move_buttons=True) + + def _connect_db_signals(self): + """ + called on init of DbGUIElement, connect to db as required. + """ + #note: event-rebuild closes the editors, so no need to connect to it + self.callman.register_callbacks( + {'person-update': self.person_change, # change to person we track + 'person-delete': self.person_delete, # delete of person we track + }) + self.callman.connect_all(keys=['person']) + + def person_change(self, *obj): + """ + Callback method called when a tracked person changes (description + changes...) + """ + self.rebuild() + + def person_delete(self, hndls): + """ + Callback method called when a tracked person is deleted. + There are two possibilities: + * a tracked non-workgroup person is deleted, just rebuilding the view + will correct this. + * a workgroup person is deleted. The person must be removed from the + obj so that no inconsistent data is shown. + """ + for handle in hndls: + prefs = self.get_data() + ref_list = [pref.ref for pref in prefs] + indexlist = [] + last = -1 + while True: + try: + last = ref_list.index(handle, last + 1) + indexlist.append(last) + except ValueError: + break + #remove the deleted workgroup persons from the object + for index in reversed(indexlist): + del prefs[index] + #now rebuild the display tab + self.rebuild() + + def get_popup_menu_items(self): + return [ + (False, _('Edit child'), self.edit_child_button_clicked), + (True, _('_Add'), self.add_button_clicked), + (True, _('Add an existing child'), self.share_button_clicked), + (False, _('Edit relationship'), self.edit_button_clicked), + (True, _('_Remove'), self.del_button_clicked), + ] + + def get_middle_click(self): + return self.edit_child_button_clicked + + + def get_icon_name(self): + return 'gramps-family' + + def get_data(self): + """ + Normally, get_data returns a list. However, we return family + object here instead. + """ + prefs = self.family.get_child_ref_list() + self.callman.register_handles( + {'person': [eref.ref for eref in prefs]}) + return prefs + + def column_order(self): + return [(1, 13), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), + (0, 8), (0, 9)] + + def add_button_clicked(self, obj=None): + person = Person() + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 0: + name = self.north_american() + elif autoname == 2: + name = self.latin_american() + else: + name = self.no_name() + person.set_primary_name(name) + + EditPerson(self.dbstate, self.uistate, self.track, person, + self.new_child_added) + + def handle_extra_type(self, objtype, obj): + """ + Called when a person is dropped onto the list. objtype will be + 'person-link' and obj will contain a person handle. + """ + person = self.dbstate.db.get_person_from_handle(obj) + self.new_child_added(person) + + def new_child_added(self, person): + ref = ChildRef() + ref.ref = person.get_handle() + self.family.add_child_ref(ref) + self.rebuild() + GLib.idle_add(self.tree.scroll_to_cell, + len(self.family.get_child_ref_list()) - 1) + self.call_edit_childref(ref) + + def child_ref_edited(self, person): + self.rebuild() + + def share_button_clicked(self, obj=None): + # it only makes sense to skip those who are already in the family + skip_list = [self.family.get_father_handle(), + self.family.get_mother_handle()] + skip_list.extend(x.ref for x in self.family.get_child_ref_list()) + + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Child"), skip=skip_list) + person = sel.run() + + if person: + ref = ChildRef() + ref.ref = person.get_handle() + self.family.add_child_ref(ref) + self.rebuild() + GLib.idle_add(self.tree.scroll_to_cell, + len(self.family.get_child_ref_list()) - 1) + self.call_edit_childref(ref) + + def run(self, skip): + skip_list = [_f for _f in skip if _f] + SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Child"), skip=skip_list) + + def del_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + self.family.remove_child_ref(ref) + self.rebuild() + + def edit_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + self.call_edit_childref(ref) + + def call_edit_childref(self, ref): + p = self.dbstate.db.get_person_from_handle(ref.ref) + n = name_displayer.display(p) + try: + EditChildRef(n, self.dbstate, self.uistate, self.track, + ref, self.child_ref_edited) + except WindowActiveError: + pass + + def edit_child_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + p = self.dbstate.db.get_person_from_handle(ref.ref) + try: + EditPerson(self.dbstate, self.uistate, self.track, + p, self.child_ref_edited) + except WindowActiveError: + pass + + def north_american(self): + """ + Child inherits name from father + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + father_handle = self.family.get_father_handle() + if father_handle: + father = self.dbstate.db.get_person_from_handle(father_handle) + preset_name(father, name) + return name + + def no_name(self): + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + return name + + def latin_american(self): + """ + Child inherits name from father and mother + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + if self.family: + father_handle = self.family.get_father_handle() + father = (self.dbstate.db.get_person_from_handle(father_handle) if + father_handle else None) + mother_handle = self.family.get_mother_handle() + mother = (self.dbstate.db.get_person_from_handle(mother_handle) if + mother_handle else None) + if not father and not mother: + return name + if not father: + preset_name(mother, name) + return name + if not mother: + preset_name(father, name) + return name + #we take first surname, and keep that + mothername = Name() + preset_name(mother, mothername) + preset_name(father, name) + mothersurname = mothername.get_surname_list()[0] + mothersurname.set_primary(False) + name.set_surname_list([name.get_surname_list()[0], mothersurname]) + return name + else: + return name + +class FastMaleFilter: + + def __init__(self, db): + self.db = db + + def match(self, handle, db): + value = self.db.get_raw_person_data(handle) + return value[2] == Person.MALE + +class FastFemaleFilter: + + def __init__(self, db): + self.db = db + + def match(self, handle, db): + value = self.db.get_raw_person_data(handle) + return value[2] == Person.FEMALE + +#------------------------------------------------------------------------- +# +# EditFamily +# +#------------------------------------------------------------------------- +class EditFamily(EditPrimary): + + QR_CATEGORY = CATEGORY_QR_FAMILY + + def __init__(self, dbstate, uistate, track, family, callback=None): + + EditPrimary.__init__(self, dbstate, uistate, track, + family, dbstate.db.get_family_from_handle, + dbstate.db.get_family_from_gramps_id, + callback) + + # look for the scenerio of a child and no parents on a new + # family + + if (self.added and + not self.obj.get_father_handle() and + not self.obj.get_mother_handle() and + len(self.obj.get_child_ref_list()) == 1): + + self.add_parent = True + if not config.get('preferences.family-warn'): + for i in self.hidden: + i.set_sensitive(False) + + MessageHideDialog( + _("Adding parents to a person"), + _("It is possible to accidentally create multiple " + "families with the same parents. To help avoid " + "this problem, only the buttons to select parents " + "are available when you create a new family. The " + "remaining fields will become available after you " + "attempt to select a parent."), + 'preferences.family-warn', + parent=self.window) + else: + self.add_parent = False + + def _cleanup_on_exit(self): + """Unset all things that can block garbage collection. + Finalize rest + """ + #FIXME, we rebind show_all below, this prevents garbage collection of + # the dialog, fix the rebind + self.window.show_all = None + EditPrimary._cleanup_on_exit(self) + + def empty_object(self): + return Family() + + def _local_init(self): + self.added = self.obj.handle is None + if self.added: + self.obj.handle = create_id() + + self.build_interface() + self.load_data() + + def _connect_db_signals(self): + """ + implement from base class DbGUIElement + Register the callbacks we need. + Note: + * we do not connect to person-delete, as a delete of a person in + the family outside of this editor will cause a family-update + signal of this family + """ + self.callman.register_handles({'family': [self.obj.get_handle()]}) + self.callman.register_callbacks( + {'family-update': self.check_for_family_change, + 'family-delete': self.check_for_close, + 'family-rebuild': self._do_close, + 'event-update': self.topdata_updated, # change eg birth event fath + 'event-rebuild': self.topdata_updated, + 'event-delete': self.topdata_updated, # delete eg birth event fath + 'person-update': self.topdata_updated, # change eg name of father + 'person-delete' : self.person_delete, # mother/father deleted? + 'person-rebuild': self._do_close, + }) + self.callman.connect_all(keys=['family', 'event', 'person']) + + def person_delete(self, handles): + """ This checks if mother/father is deleted, specifically when newly + added before data is saved """ + for hndl in handles: + if self.obj.father_handle == hndl: + self.obj.father_handle = None + if self.obj.mother_handle == hndl: + self.obj.mother_handle = None + self.load_data() + + def check_for_family_change(self, handles): + """ + Callback for family-update signal + 1. This method checks to see if the family shown has been changed. This + is possible eg in the relationship view. If the family was changed, + the view is refreshed and a warning dialog shown to indicate all + changes have been lost. + If a source/note/event is deleted, this method is called too. This + is unfortunate as the displaytabs can track themself a delete and + correct the view for this. Therefore, these tabs are not rebuild. + Conclusion: this method updates so that remove/change of parent or + remove/change of children in relationship view reloads the family + from db. + 2. Changes in other families are of no consequence to the family shown + """ + if self.obj.get_handle() in handles: + #rebuild data + ## Todo: Gallery and note tab are not rebuild ?? + objreal = self.dbstate.db.get_family_from_handle( + self.obj.get_handle()) + #update selection of data that we obtain from database change: + maindatachanged = (self.obj.gramps_id != objreal.gramps_id or + self.obj.father_handle != objreal.father_handle or + self.obj.mother_handle != objreal.mother_handle or + self.obj.private != objreal.private or + self.obj.type != objreal.type or + self.obj.get_tag_list() != objreal.get_tag_list() or + self.obj.child_ref_list != objreal.child_ref_list) + if maindatachanged: + self.obj.gramps_id = objreal.gramps_id + self.obj.father_handle = objreal.father_handle + self.obj.mother_handle = objreal.mother_handle + self.obj.private = objreal.private + self.obj.type = objreal.type + self.obj.set_tag_list(objreal.get_tag_list()) + self.obj.child_ref_list = objreal.child_ref_list + self.reload_people() + + # No matter why the family changed (eg delete of a source), we notify + # the user + WarningDialog( + _("Family has changed"), + _("The %(object)s you are editing has changed outside this editor." + " This can be due to a change in one of the main views, for " + "example a source used here is deleted in the source view.\n" + "To make sure the information shown is still correct, the " + "data shown has been updated. Some edits you have made may have" + " been lost.") % {'object': _('family')}, + parent=self.window) + + def topdata_updated(self, *obj): + """ + Callback method called if data shown in top part of family editor + (a parent, birth/death event of parent) changes + Note: person events shown in the event list are not tracked, the + tabpage itself tracks it + """ + self.load_data() + + def show_buttons(self): + """ + Used to reshow hidden/showing buttons. + """ + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + + def reload_people(self): + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + self.child_tab.rebuild() + + def get_menu_title(self): + if self.obj and self.obj.get_handle(): + dialog_title = family_name(self.obj, self.db, _("New Family")) + dialog_title = _("Family") + ': ' + dialog_title + else: + dialog_title = _("New Family") + return dialog_title + + def build_menu_names(self, family): + return (_('Edit Family'), self.get_menu_title()) + + def build_interface(self): + self.top = Glade() + self.set_window(self.top.toplevel, None, self.get_menu_title()) + self.setup_configs('interface.family', 700, 500) + + # HACK: how to prevent hidden items from showing + # when you use show_all? + # Consider using show() rather than show_all()? + # FIXME: remove if we can use show() + self.window.show_all = self.window.show + + self.fbirth = self.top.get_object('fbirth') + self.fdeath = self.top.get_object('fdeath') + self.fbirth_label = self.top.get_object('label578') + self.fdeath_label = self.top.get_object('label579') + + self.mbirth = self.top.get_object('mbirth') + self.mdeath = self.top.get_object('mdeath') + self.mbirth_label = self.top.get_object('label567') + self.mdeath_label = self.top.get_object('label568') + + self.mname = self.top.get_object('mname') + self.fname = self.top.get_object('fname') + + self.mimg = self.top.get_object('mimg') + self.fimg = self.top.get_object('fimg') + + self.mbutton_index = self.top.get_object('mbutton_index') + self.mbutton_add = self.top.get_object('mbutton_add') + self.mbutton_del = self.top.get_object('mbutton_del') + self.mbutton_edit = self.top.get_object('mbutton_edit') + + self.mbutton_index.set_tooltip_text(_("Select a person as the mother")) + self.mbutton_add.set_tooltip_text(_("Add a new person as the mother")) + self.mbutton_del.set_tooltip_text(_("Remove the person as the mother")) + + self.mbutton_edit.connect('button-press-event', self.edit_mother) + self.mbutton_edit.connect('key-press-event', self.edit_mother) + self.mbutton_index.connect('clicked', self.sel_mother_clicked) + self.mbutton_del.connect('clicked', self.del_mother_clicked) + self.mbutton_add.connect('clicked', self.add_mother_clicked) + + self.fbutton_index = self.top.get_object('fbutton_index') + self.fbutton_add = self.top.get_object('fbutton_add') + self.fbutton_del = self.top.get_object('fbutton_del') + self.fbutton_edit = self.top.get_object('fbutton_edit') + + self.fbutton_index.set_tooltip_text(_("Select a person as the father")) + self.fbutton_add.set_tooltip_text(_("Add a new person as the father")) + self.fbutton_del.set_tooltip_text(_("Remove the person as the father")) + + self.fbutton_edit.connect('button-press-event', self.edit_father) + self.fbutton_edit.connect('key-press-event', self.edit_father) + self.fbutton_index.connect('clicked', self.sel_father_clicked) + self.fbutton_del.connect('clicked', self.del_father_clicked) + self.fbutton_add.connect('clicked', self.add_father_clicked) + + if self.added: + return # avoids crash on drag because not in db yet + #allow for a context menu + self.set_contexteventbox(self.top.get_object("eventboxtop")) + #allow for drag of the family object from eventboxtop + self.contexteventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.FAMILY_LINK.target()], + Gdk.DragAction.COPY) + self.contexteventbox.drag_source_set_icon_name('gramps-family') + self.contexteventbox.connect('drag_data_get', self.on_drag_data_get_family) + + def on_drag_data_get_family(self,widget, context, sel_data, info, time): + if info == DdTargets.FAMILY_LINK.app_id: + data = (DdTargets.FAMILY_LINK.drag_type, id(self), self.obj.get_handle(), 0) + sel_data.set(DdTargets.FAMILY_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _update_parent_dnd_handler(self, event_box, parent_handle, on_drag_data_get, on_drag_data_received): + """ + Set the drag action from the label of father when he exists + """ + if parent_handle: + # Allow drag + if not event_box.drag_source_get_target_list(): + event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + event_box.drag_source_set_icon_name('gramps-person') + event_box.connect('drag_data_get', on_drag_data_get) + #Disallow drop: + if event_box.drag_dest_get_target_list(): + event_box.drag_dest_unset() + else: + # Disallow drag + if event_box.drag_source_get_target_list(): + event_box.drag_source_unset() + #allow for drop: + if not event_box.drag_dest_get_target_list(): + event_box.drag_dest_set(Gtk.DestDefaults.MOTION | + Gtk.DestDefaults.DROP, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + event_box.connect('drag_data_received', on_drag_data_received) + + def on_drag_fatherdata_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_father_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def on_drag_motherdata_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_mother_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _connect_signals(self): + self.define_ok_button(self.top.get_object('ok'), self.save) + self.define_cancel_button(self.top.get_object('cancel')) + # TODO help button (rename glade button name) + self.define_help_button(self.top.get_object('button119'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _can_be_replaced(self): + pass + + def _setup_fields(self): + + self.private = PrivacyButton( + self.top.get_object('private'), + self.obj, + self.db.readonly) + + self.gid = MonitoredEntry( + self.top.get_object('gid'), + self.obj.set_gramps_id, + self.obj.get_gramps_id, + self.db.readonly) + + self.tags = MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.obj.set_tag_list, + self.obj.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + self.data_type = MonitoredDataType( + self.top.get_object('marriage_type'), + self.obj.set_relationship, + self.obj.get_relationship, + self.db.readonly, + self.db.get_family_relation_types(), + ) + + def load_data(self): + """ + Show top data of family editor: father and mother info + and set self.phandles with all person handles in the family + """ + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + + self.phandles = [mhandle, fhandle] + self.phandles.extend(x.ref for x in self.obj.get_child_ref_list()) + + self.phandles = [_f for _f in self.phandles if _f] + + def get_start_date(self): + """ + Get the start date for a family, usually a marriage date, or + something close to marriage. + """ + event = get_marriage_or_fallback(self.dbstate.db, self.obj) + return event.get_date_object() if event else None + + def _create_tabbed_pages(self): + + notebook = Gtk.Notebook() + + self.child_list = ChildEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj) + self.child_tab = self._add_tab(notebook, self.child_list) + self.track_ref_for_deletion("child_list") + self.track_ref_for_deletion("child_tab") + + self.event_list = EventEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj, + start_date=self.get_start_date()) + + self._add_tab(notebook, self.event_list) + self.track_ref_for_deletion("event_list") + + self.citation_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.citation_list) + self.track_ref_for_deletion("citation_list") + + self.attr_list = FamilyAttrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_attribute_list()) + self._add_tab(notebook, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.note_tab = NoteTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_note_list(), + self.get_menu_title(), + notetype=NoteType.FAMILY) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self.gallery_tab = GalleryTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_media_list()) + self._add_tab(notebook, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.lds_embed = FamilyLdsEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_lds_ord_list()) + self._add_tab(notebook, self.lds_embed) + self.track_ref_for_deletion("lds_embed") + + self._setup_notebook_tabs( notebook) + notebook.show_all() + + self.hidden = (notebook, self.top.get_object('info')) + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def update_father(self, handle): + self.load_parent(handle, self.fname, self.fimg, self.fbirth, self.fbirth_label, + self.fdeath, self.fdeath_label, + self.fbutton_index, self.fbutton_add, + self.fbutton_del, self.fbutton_edit) + self._update_parent_dnd_handler(self.top.get_object('ftable_event_box'), + self.obj.get_father_handle(), + self.on_drag_fatherdata_get, + self.on_drag_fatherdata_received) + + def update_mother(self, handle): + self.load_parent(handle, self.mname, self.mimg, self.mbirth, self.mbirth_label, + self.mdeath, self.mdeath_label, + self.mbutton_index, self.mbutton_add, + self.mbutton_del, self.mbutton_edit) + self._update_parent_dnd_handler(self.top.get_object('mtable_event_box'), + self.obj.get_mother_handle(), + self.on_drag_motherdata_get, + self.on_drag_motherdata_received) + + def add_mother_clicked(self, obj): + person = Person() + person.set_gender(Person.FEMALE) + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 2: + name = self.latin_american_child("mother") + else: + name = self.no_name() + person.set_primary_name(name) + EditPerson(self.dbstate, self.uistate, self.track, person, + self.new_mother_added) + + def add_father_clicked(self, obj): + person = Person() + person.set_gender(Person.MALE) + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 0: + name = self.north_american_child() + elif autoname == 2: + name = self.latin_american_child("father") + else: + name = self.no_name() + person.set_primary_name(name) + EditPerson(self.dbstate, self.uistate, self.track, + person, self.new_father_added) + + def new_mother_added(self, person): + for i in self.hidden: + i.set_sensitive(True) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def new_father_added(self, person): + for i in self.hidden: + i.set_sensitive(True) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def del_mother_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + self.obj.set_mother_handle(None) + self.update_mother(None) + + def sel_mother_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + data_filter = FastFemaleFilter(self.dbstate.db) + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Mother"), + filter=data_filter, + skip=[x.ref for x in self.obj.get_child_ref_list()]) + person = sel.run() + + if person: + self.check_for_existing_family(self.obj.get_father_handle(), + person.handle, + self.obj.handle) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def on_change_father(self, selector_window, obj): + if obj.__class__ == Person: + try: + person = obj + self.obj.set_father_handle(person.get_handle()) + self.update_father(person.get_handle()) + except: + log.warning("Failed to update father: \n" + "obj returned from selector was: %s\n" + % (repr(obj),)) + raise + + else: + log.warning( + "Object selector returned obj.__class__ = %s, it should " + "have been of type %s." % (obj.__class__.__name__, + Person.__name__)) + + selector_window.close() + + def del_father_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + self.obj.set_father_handle(None) + self.update_father(None) + + def sel_father_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + data_filter = FastMaleFilter(self.dbstate.db) + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Father"), + filter=data_filter, + skip=[x.ref for x in self.obj.get_child_ref_list()]) + person = sel.run() + if person: + self.check_for_existing_family(person.handle, + self.obj.get_mother_handle(), + self.obj.handle) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def check_for_existing_family(self, father_handle, mother_handle, + family_handle): + + if father_handle: + father = self.dbstate.db.get_person_from_handle(father_handle) + ffam = set(father.get_family_handle_list()) + if mother_handle: + mother = self.dbstate.db.get_person_from_handle(mother_handle) + mfam = set(mother.get_family_handle_list()) + common = list(mfam.intersection(ffam)) + if len(common) > 0: + if self.add_parent or self.obj.handle not in common: + WarningDialog( + _('Duplicate Family'), + _('A family with these parents already exists ' + 'in the database. If you save, you will create ' + 'a duplicate family. It is recommended that ' + 'you cancel the editing of this window, and ' + 'select the existing family'), + parent=self.window) + + def edit_father(self, obj, event): + handle = self.obj.get_father_handle() + return self.edit_person(obj, event, handle) + + def edit_mother(self, obj, event): + handle = self.obj.get_mother_handle() + return self.edit_person(obj, event, handle) + + def edit_person(self, obj, event, handle): + if button_activated(event, _LEFT_BUTTON): + try: + person = self.db.get_person_from_handle(handle) + EditPerson(self.dbstate, self.uistate, + self.track, person) + except WindowActiveError: + pass + + def load_parent(self, handle, name_obj, img_obj, birth_obj, birth_label, death_obj, + death_label, btn_index, btn_add, btn_del, btn_edit): + # is a parent used here: + is_used = handle is not None + + # now we display the area: + if is_used: + db = self.db + person = db.get_person_from_handle(handle) + name = "%s [%s]" % (name_displayer.display(person), + person.gramps_id) + birth = get_birth_or_fallback(db, person) + self.callman.register_handles({'person': [handle]}) + if birth: + #if event changes it view needs to update + self.callman.register_handles({'event': [birth.get_handle()]}) + # translators: needed for French, ignore otherwise + birth_label.set_label(_("%s:") % birth.get_type()) + + death = get_death_or_fallback(db, person) + if death: + #if event changes it view needs to update + self.callman.register_handles({'event': [death.get_handle()]}) + # translators: needed for French, ignore otherwise + death_label.set_label(_("%s:") % death.get_type()) + + btn_edit.set_tooltip_text(_('Edit %s') % name) + btn_index.hide() + btn_add.hide() + btn_del.show() + btn_edit.show() + + if img_obj: + load_person_image(self, person, img_obj) + + else: + name = "" + birth = None + death = None + + btn_index.show() + btn_add.show() + btn_del.hide() + btn_edit.hide() + + if name_obj: + name_obj.set_text(name) + if birth: + birth_str = displayer.display(birth.get_date_object()) + else: + birth_str = "" + birth_obj.set_text(birth_str) + if death: + death_str = displayer.display(death.get_date_object()) + else: + death_str = "" + death_obj.set_text(death_str) + + def fix_parent_handles(self, orig_handle, new_handle, trans): + if orig_handle != new_handle: + if orig_handle: + person = self.db.get_person_from_handle(orig_handle) + person.family_list.remove(self.obj.handle) + self.db.commit_person(person, trans) + if new_handle: + person = self.db.get_person_from_handle(new_handle) + person.family_list.append(self.obj.handle) + self.db.commit_person(person, trans) + + def on_drag_fatherdata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if self.obj.get_father_handle(): + return + for i in self.hidden: + i.set_sensitive(True) + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + + if person: + self.check_for_existing_family(person.handle, + self.obj.get_mother_handle(), + self.obj.handle) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def on_drag_motherdata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if self.obj.get_mother_handle(): + return + for i in self.hidden: + i.set_sensitive(True) + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + + if person: + self.check_for_existing_family(self.obj.get_father_handle(), + person.handle, + self.obj.handle) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def object_is_empty(self): + return (not self.obj.get_father_handle() and + not self.obj.get_mother_handle() and + len(self.obj.get_child_ref_list()) == 0 + ) + + def save(self, *obj): + ## FIXME: how to catch a specific error? + #try: + self.__do_save() + #except bsddb_db.DBRunRecoveryError as msg: + # RunDatabaseRepair(msg[1], parent=self.window) + + def __do_save(self): + self.ok_button.set_sensitive(False) + + if not self.added: + original = self.db.get_family_from_handle(self.obj.handle) + else: + original = None + + # do some basic checks + + child_list = [ ref.ref for ref in self.obj.get_child_ref_list() ] + + if self.obj.get_father_handle() in child_list: + + father = self.db.get_person_from_handle(self.obj.get_father_handle()) + name = "%s [%s]" % (name_displayer.display(father), + father.gramps_id) + ErrorDialog(_("A father cannot be his own child"), + _("%s is listed as both the father and child " + "of the family.") % name, + parent=self.window) + self.ok_button.set_sensitive(True) + return + elif self.obj.get_mother_handle() in child_list: + + mother = self.db.get_person_from_handle(self.obj.get_mother_handle()) + name = "%s [%s]" % (name_displayer.display(mother), + mother.gramps_id) + ErrorDialog(_("A mother cannot be her own child"), + _("%s is listed as both the mother and child " + "of the family.") % name, + parent=self.window) + self.ok_button.set_sensitive(True) + return + + if not original and self.object_is_empty(): + ErrorDialog( + _("Cannot save family"), + _("No data exists for this family. " + "Please enter data or cancel the edit."), + parent=self.window) + self.ok_button.set_sensitive(True) + return + + (uses_dupe_id, id) = self._uses_duplicate_id() + if uses_dupe_id: + msg1 = _("Cannot save family. ID already exists.") + msg2 = _("You have attempted to use the existing Gramps ID with " + "value %(id)s. This value is already used. Please " + "enter a different ID or leave " + "blank to get the next available ID value.") % { + 'id' : id} + ErrorDialog(msg1, msg2, parent=self.window) + self.ok_button.set_sensitive(True) + return + + # We disconnect the callbacks to all signals we connected earlier. + # This prevents the signals originating in any of the following + # commits from being caught by us again. + self._cleanup_callbacks() + + if not original and not self.object_is_empty(): + with DbTxn(_("Add Family"), self.db) as trans: + + # find the father, add the family handle to the father + handle = self.obj.get_father_handle() + if handle: + parent = self.db.get_person_from_handle(handle) + parent.add_family_handle(self.obj.handle) + self.db.commit_person(parent, trans) + + # find the mother, add the family handle to the mother + handle = self.obj.get_mother_handle() + if handle: + parent = self.db.get_person_from_handle(handle) + parent.add_family_handle(self.obj.handle) + self.db.commit_person(parent, trans) + + # for each child, add the family handle to the child + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + # fix - relationships need to be extracted from the list + child.add_parent_family_handle(self.obj.handle) + self.db.commit_person(child, trans) + + self.db.add_family(self.obj, trans) + elif self.data_has_changed(): + + with DbTxn(_("Edit Family"), self.db) as trans: + + self.fix_parent_handles(original.get_father_handle(), + self.obj.get_father_handle(), trans) + self.fix_parent_handles(original.get_mother_handle(), + self.obj.get_mother_handle(), trans) + + orig_set = set(original.get_child_ref_list()) + new_set = set(self.obj.get_child_ref_list()) + + # remove the family from children which have been removed + for ref in orig_set.difference(new_set): + person = self.db.get_person_from_handle(ref.ref) + person.remove_parent_family_handle(self.obj.handle) + self.db.commit_person(person, trans) + + # add the family to children which have been added + for ref in new_set.difference(orig_set): + person = self.db.get_person_from_handle(ref.ref) + person.add_parent_family_handle(self.obj.handle) + self.db.commit_person(person, trans) + + if self.object_is_empty(): + self.db.remove_family_relationships(self.obj.handle, trans) + else: + if not self.obj.get_gramps_id(): + self.obj.set_gramps_id( + self.db.find_next_family_gramps_id()) + self.db.commit_family(self.obj, trans) + + self._do_close() + if self.callback: + self.callback(self.obj) + self.callback = None + + def no_name(self): + """ + Default surname guess. + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + return name + + def north_american_child(self): + """ + If SURNAME_GUESSING is north american, then find a child + and return their name for the father. + """ + # for each child, find one with a last name + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + if child: + preset_name(child, name) + return name + return name + + def latin_american_child(self, parent): + """ + If SURNAME_GUESSING is latin american, then find a child + and return their name for the father or mother. + + parent = "mother" | "father" + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + # for each child, find one with a last name + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + if child: + pname = child.get_primary_name() + preset_name(child, name) # add the known family surnames, etc. + surnames = name.get_surname_list() + if len(surnames) < 2: + return name + else: + #return first for the father, and last for the mother + if parent == 'father': + name.set_surname_list([surnames[0]]) + return name + else: + name.set_surname_list([surnames[-1]]) + return name + return name + +def button_activated(event, mouse_button): + if (event.type == Gdk.EventType.BUTTON_PRESS and + event.button == mouse_button) or \ + (event.type == Gdk.EventType.KEY_PRESS and + event.keyval in (_RETURN, _KP_ENTER)): + return True + else: + return False +def destroy_cb(widget, data): + """ + Callback for gtk_container_foreach + """ + widget.destroy() + +def load_person_image(self, person, photo_container): + """ + Load the primary image if it exists. + """ + photo = Photo(True) + photo.show() + + photo_container.foreach(destroy_cb, None) + photo_container.add(photo) + + media_list = person.get_media_list() + if media_list: + media_ref = media_list[0] + object_handle = media_ref.get_reference_handle() + obj = self.dbstate.db.get_media_from_handle(object_handle) + full_path = media_path_full(self.dbstate.db, obj.get_path()) + mime_type = obj.get_mime_type() + if mime_type and mime_type.startswith("image"): + photo.set_image(full_path, mime_type, media_ref.get_rectangle()) + photo.set_uistate(self.uistate, object_handle) + else: + photo.set_image(None) + photo.set_uistate(None, None) + else: + photo.set_image(None) + photo.set_uistate(None, None) diff --git a/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.0 b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.0 new file mode 100644 index 0000000..dfc59f3 --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.0 @@ -0,0 +1,1283 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2009 Gary Burton +# Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import pickle + +#------------------------------------------------------------------------- +# +# enable logging for error handling +# +#------------------------------------------------------------------------- +import logging +log = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from ..ddtargets import DdTargets +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Pango +from gi.repository import GObject +from gi.repository import GLib + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.config import config +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.lib import ChildRef, Family, Name, NoteType, Person, Surname +from gramps.gen.db import DbTxn +from gramps.gen.errors import WindowActiveError +from gramps.gen.datehandler import displayer +from ..glade import Glade + +from .editprimary import EditPrimary +from .editchildref import EditChildRef +from .editperson import EditPerson +from .displaytabs import (EmbeddedList, EventEmbedList, CitationEmbedList, + FamilyAttrEmbedList, NoteTab, GalleryTab, + FamilyLdsEmbedList, ChildModel, + TEXT_COL, MARKUP_COL, ICON_COL) +from ..widgets import (PrivacyButton, MonitoredEntry, MonitoredDataType, + MonitoredTagList) +from gramps.gen.plug import CATEGORY_QR_FAMILY +from ..dialog import (ErrorDialog, RunDatabaseRepair, WarningDialog, + MessageHideDialog) +from gramps.gen.utils.db import (get_birth_or_fallback, get_death_or_fallback, + get_marriage_or_fallback, preset_name, family_name) +from ..selectors import SelectorFactory +from gramps.gen.utils.id import create_id +from gramps.gen.const import URL_MANUAL_SECT1 +from ..dbguielement import DbGUIElement + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 +WIKI_HELP_SEC = _('manual|Family_Editor_dialog') + +SelectPerson = SelectorFactory('Person') + +_RETURN = Gdk.keyval_from_name("Return") +_KP_ENTER = Gdk.keyval_from_name("KP_Enter") +_LEFT_BUTTON = 1 +_RIGHT_BUTTON = 3 + + +class ChildEmbedList(DbGUIElement, EmbeddedList): + """ + The child embed list is specific to the Edit Family dialog, so it + is contained here instead of in displaytabs. + """ + + _HANDLE_COL = 14 + _DND_TYPE = DdTargets.CHILDREF + _DND_EXTRA = DdTargets.PERSON_LINK + + _MSG = { + 'add' : _('Create a new person and add the child to the family'), + 'del' : _('Remove the child from the family'), + 'edit' : _('Edit the child reference'), + 'share' : _('Add an existing person as a child of the family'), + 'up' : _('Move the child up in the children list'), + 'down' : _('Move the child down in the children list'), + } + + # (name, column in model, width, markup/text, font weight) + _column_names = [ + (_('#'), 0, 25, TEXT_COL, -1, None), + (_('ID'), 1, 60, TEXT_COL, -1, None), + (_('Name'), 10, 250, TEXT_COL, -1, None), + (_('Gender'), 3, 75, TEXT_COL, -1, None), + (_('Paternal'), 4, 100, TEXT_COL, -1, None), + (_('Maternal'), 5, 100, TEXT_COL, -1, None), + (_('Birth Date'), 11, 150, MARKUP_COL, -1, None), + (_('Death Date'), 12, 150, MARKUP_COL, -1, None), + (_('Birth Place'), 8, 150, TEXT_COL, -1, None), + (_('Death Place'), 9, 150, TEXT_COL, -1, None), + None, + None, + None, + (_('Private'), 13, 30, ICON_COL, -1, 'gramps-lock') + ] + + def __init__(self, dbstate, uistate, track, family): + """ + Create the object, storing the passed family value + """ + self.family = family + DbGUIElement.__init__(self, dbstate.db) + EmbeddedList.__init__(self, dbstate, uistate, track, _('Chil_dren'), + ChildModel, share_button=True, move_buttons=True) + + def _connect_db_signals(self): + """ + called on init of DbGUIElement, connect to db as required. + """ + #note: event-rebuild closes the editors, so no need to connect to it + self.callman.register_callbacks( + {'person-update': self.person_change, # change to person we track + 'person-delete': self.person_delete, # delete of person we track + }) + self.callman.connect_all(keys=['person']) + + def person_change(self, *obj): + """ + Callback method called when a tracked person changes (description + changes...) + """ + self.rebuild() + + def person_delete(self, hndls): + """ + Callback method called when a tracked person is deleted. + There are two possibilities: + * a tracked non-workgroup person is deleted, just rebuilding the view + will correct this. + * a workgroup person is deleted. The person must be removed from the + obj so that no inconsistent data is shown. + """ + for handle in hndls: + prefs = self.get_data() + ref_list = [pref.ref for pref in prefs] + indexlist = [] + last = -1 + while True: + try: + last = ref_list.index(handle, last + 1) + indexlist.append(last) + except ValueError: + break + #remove the deleted workgroup persons from the object + for index in reversed(indexlist): + del prefs[index] + #now rebuild the display tab + self.rebuild() + + def get_popup_menu_items(self): + return [ + (False, _('Edit child'), self.edit_child_button_clicked), + (True, _('_Add'), self.add_button_clicked), + (True, _('Add an existing child'), self.share_button_clicked), + (False, _('Edit relationship'), self.edit_button_clicked), + (True, _('_Remove'), self.del_button_clicked), + ] + + def get_middle_click(self): + return self.edit_child_button_clicked + + + def get_icon_name(self): + return 'gramps-family' + + def get_data(self): + """ + Normally, get_data returns a list. However, we return family + object here instead. + """ + prefs = self.family.get_child_ref_list() + self.callman.register_handles( + {'person': [eref.ref for eref in prefs]}) + return prefs + + def column_order(self): + return [(1, 13), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), + (0, 8), (0, 9)] + + def add_button_clicked(self, obj=None): + person = Person() + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 0: + name = self.north_american() + elif autoname == 2: + name = self.latin_american() + else: + name = self.no_name() + person.set_primary_name(name) + + EditPerson(self.dbstate, self.uistate, self.track, person, + self.new_child_added) + + def handle_extra_type(self, objtype, obj): + """ + Called when a person is dropped onto the list. objtype will be + 'person-link' and obj will contain a person handle. + """ + person = self.dbstate.db.get_person_from_handle(obj) + self.new_child_added(person) + + def new_child_added(self, person): + ref = ChildRef() + ref.ref = person.get_handle() + self.family.add_child_ref(ref) + self.rebuild() + GLib.idle_add(self.tree.scroll_to_cell, + len(self.family.get_child_ref_list()) - 1) + self.call_edit_childref(ref) + + def child_ref_edited(self, person): + self.rebuild() + + def share_button_clicked(self, obj=None): + # it only makes sense to skip those who are already in the family + skip_list = [self.family.get_father_handle(), + self.family.get_mother_handle()] + skip_list.extend(x.ref for x in self.family.get_child_ref_list()) + + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Child"), skip=skip_list) + person = sel.run() + + if person: + ref = ChildRef() + ref.ref = person.get_handle() + self.family.add_child_ref(ref) + self.rebuild() + GLib.idle_add(self.tree.scroll_to_cell, + len(self.family.get_child_ref_list()) - 1) + self.call_edit_childref(ref) + + def run(self, skip): + skip_list = [_f for _f in skip if _f] + SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Child"), skip=skip_list) + + def del_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + self.family.remove_child_ref(ref) + self.rebuild() + + def edit_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + self.call_edit_childref(ref) + + def call_edit_childref(self, ref): + p = self.dbstate.db.get_person_from_handle(ref.ref) + n = name_displayer.display(p) + try: + EditChildRef(n, self.dbstate, self.uistate, self.track, + ref, self.child_ref_edited) + except WindowActiveError: + pass + + def edit_child_button_clicked(self, obj=None): + ref = self.get_selected() + if ref: + p = self.dbstate.db.get_person_from_handle(ref.ref) + try: + EditPerson(self.dbstate, self.uistate, self.track, + p, self.child_ref_edited) + except WindowActiveError: + pass + + def north_american(self): + """ + Child inherits name from father + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + father_handle = self.family.get_father_handle() + if father_handle: + father = self.dbstate.db.get_person_from_handle(father_handle) + preset_name(father, name) + return name + + def no_name(self): + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + return name + + def latin_american(self): + """ + Child inherits name from father and mother + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + if self.family: + father_handle = self.family.get_father_handle() + father = (self.dbstate.db.get_person_from_handle(father_handle) if + father_handle else None) + mother_handle = self.family.get_mother_handle() + mother = (self.dbstate.db.get_person_from_handle(mother_handle) if + mother_handle else None) + if not father and not mother: + return name + if not father: + preset_name(mother, name) + return name + if not mother: + preset_name(father, name) + return name + #we take first surname, and keep that + mothername = Name() + preset_name(mother, mothername) + preset_name(father, name) + mothersurname = mothername.get_surname_list()[0] + mothersurname.set_primary(False) + name.set_surname_list([name.get_surname_list()[0], mothersurname]) + return name + else: + return name + +class FastMaleFilter: + + def __init__(self, db): + self.db = db + + def match(self, handle, db): + value = self.db.get_raw_person_data(handle) + return value[2] == Person.MALE + +class FastFemaleFilter: + + def __init__(self, db): + self.db = db + + def match(self, handle, db): + value = self.db.get_raw_person_data(handle) + return value[2] == Person.FEMALE + +#------------------------------------------------------------------------- +# +# EditFamily +# +#------------------------------------------------------------------------- +class EditFamily(EditPrimary): + + QR_CATEGORY = CATEGORY_QR_FAMILY + + def __init__(self, dbstate, uistate, track, family, callback=None): + + EditPrimary.__init__(self, dbstate, uistate, track, + family, dbstate.db.get_family_from_handle, + dbstate.db.get_family_from_gramps_id, + callback) + + # look for the scenerio of a child and no parents on a new + # family + + if (self.added and + not self.obj.get_father_handle() and + not self.obj.get_mother_handle() and + len(self.obj.get_child_ref_list()) == 1): + + self.add_parent = True + if not config.get('preferences.family-warn'): + for i in self.hidden: + i.set_sensitive(False) + + MessageHideDialog( + _("Adding parents to a person"), + _("It is possible to accidentally create multiple " + "families with the same parents. To help avoid " + "this problem, only the buttons to select parents " + "are available when you create a new family. The " + "remaining fields will become available after you " + "attempt to select a parent."), + 'preferences.family-warn', + parent=self.window) + else: + self.add_parent = False + + def _cleanup_on_exit(self): + """Unset all things that can block garbage collection. + Finalize rest + """ + #FIXME, we rebind show_all below, this prevents garbage collection of + # the dialog, fix the rebind + self.window.show_all = None + EditPrimary._cleanup_on_exit(self) + + def empty_object(self): + return Family() + + def _local_init(self): + self.added = self.obj.handle is None + if self.added: + self.obj.handle = create_id() + + self.build_interface() + self.load_data() + + def _connect_db_signals(self): + """ + implement from base class DbGUIElement + Register the callbacks we need. + Note: + * we do not connect to person-delete, as a delete of a person in + the family outside of this editor will cause a family-update + signal of this family + """ + self.callman.register_handles({'family': [self.obj.get_handle()]}) + self.callman.register_callbacks( + {'family-update': self.check_for_family_change, + 'family-delete': self.check_for_close, + 'family-rebuild': self._do_close, + 'event-update': self.topdata_updated, # change eg birth event fath + 'event-rebuild': self.topdata_updated, + 'event-delete': self.topdata_updated, # delete eg birth event fath + 'person-update': self.topdata_updated, # change eg name of father + 'person-delete' : self.person_delete, # mother/father deleted? + 'person-rebuild': self._do_close, + }) + self.callman.connect_all(keys=['family', 'event', 'person']) + + def person_delete(self, handles): + """ This checks if mother/father is deleted, specifically when newly + added before data is saved """ + for hndl in handles: + if self.obj.father_handle == hndl: + self.obj.father_handle = None + if self.obj.mother_handle == hndl: + self.obj.mother_handle = None + self.load_data() + + def check_for_family_change(self, handles): + """ + Callback for family-update signal + 1. This method checks to see if the family shown has been changed. This + is possible eg in the relationship view. If the family was changed, + the view is refreshed and a warning dialog shown to indicate all + changes have been lost. + If a source/note/event is deleted, this method is called too. This + is unfortunate as the displaytabs can track themself a delete and + correct the view for this. Therefore, these tabs are not rebuild. + Conclusion: this method updates so that remove/change of parent or + remove/change of children in relationship view reloads the family + from db. + 2. Changes in other families are of no consequence to the family shown + """ + if self.obj.get_handle() in handles: + #rebuild data + ## Todo: Gallery and note tab are not rebuild ?? + objreal = self.dbstate.db.get_family_from_handle( + self.obj.get_handle()) + #update selection of data that we obtain from database change: + maindatachanged = (self.obj.gramps_id != objreal.gramps_id or + self.obj.father_handle != objreal.father_handle or + self.obj.mother_handle != objreal.mother_handle or + self.obj.private != objreal.private or + self.obj.type != objreal.type or + self.obj.get_tag_list() != objreal.get_tag_list() or + self.obj.child_ref_list != objreal.child_ref_list) + if maindatachanged: + self.obj.gramps_id = objreal.gramps_id + self.obj.father_handle = objreal.father_handle + self.obj.mother_handle = objreal.mother_handle + self.obj.private = objreal.private + self.obj.type = objreal.type + self.obj.set_tag_list(objreal.get_tag_list()) + self.obj.child_ref_list = objreal.child_ref_list + self.reload_people() + + # No matter why the family changed (eg delete of a source), we notify + # the user + WarningDialog( + _("Family has changed"), + _("The %(object)s you are editing has changed outside this editor." + " This can be due to a change in one of the main views, for " + "example a source used here is deleted in the source view.\n" + "To make sure the information shown is still correct, the " + "data shown has been updated. Some edits you have made may have" + " been lost.") % {'object': _('family')}, + parent=self.window) + + def topdata_updated(self, *obj): + """ + Callback method called if data shown in top part of family editor + (a parent, birth/death event of parent) changes + Note: person events shown in the event list are not tracked, the + tabpage itself tracks it + """ + self.load_data() + + def show_buttons(self): + """ + Used to reshow hidden/showing buttons. + """ + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + + def reload_people(self): + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + self.child_tab.rebuild() + + def get_menu_title(self): + if self.obj and self.obj.get_handle(): + dialog_title = family_name(self.obj, self.db, _("New Family")) + dialog_title = _("Family") + ': ' + dialog_title + else: + dialog_title = _("New Family") + return dialog_title + + def build_menu_names(self, family): + return (_('Edit Family'), self.get_menu_title()) + + def build_interface(self): + self.top = Glade() + self.set_window(self.top.toplevel, None, self.get_menu_title()) + self.setup_configs('interface.family', 700, 500) + + # HACK: how to prevent hidden items from showing + # when you use show_all? + # Consider using show() rather than show_all()? + # FIXME: remove if we can use show() + self.window.show_all = self.window.show + + self.fbirth = self.top.get_object('fbirth') + self.fdeath = self.top.get_object('fdeath') + self.fbirth_label = self.top.get_object('label578') + self.fdeath_label = self.top.get_object('label579') + + self.mbirth = self.top.get_object('mbirth') + self.mdeath = self.top.get_object('mdeath') + self.mbirth_label = self.top.get_object('label567') + self.mdeath_label = self.top.get_object('label568') + + self.mname = self.top.get_object('mname') + self.fname = self.top.get_object('fname') + + self.mbutton_index = self.top.get_object('mbutton_index') + self.mbutton_add = self.top.get_object('mbutton_add') + self.mbutton_del = self.top.get_object('mbutton_del') + self.mbutton_edit = self.top.get_object('mbutton_edit') + + self.mbutton_index.set_tooltip_text(_("Select a person as the mother")) + self.mbutton_add.set_tooltip_text(_("Add a new person as the mother")) + self.mbutton_del.set_tooltip_text(_("Remove the person as the mother")) + + self.mbutton_edit.connect('button-press-event', self.edit_mother) + self.mbutton_edit.connect('key-press-event', self.edit_mother) + self.mbutton_index.connect('clicked', self.sel_mother_clicked) + self.mbutton_del.connect('clicked', self.del_mother_clicked) + self.mbutton_add.connect('clicked', self.add_mother_clicked) + + self.fbutton_index = self.top.get_object('fbutton_index') + self.fbutton_add = self.top.get_object('fbutton_add') + self.fbutton_del = self.top.get_object('fbutton_del') + self.fbutton_edit = self.top.get_object('fbutton_edit') + + self.fbutton_index.set_tooltip_text(_("Select a person as the father")) + self.fbutton_add.set_tooltip_text(_("Add a new person as the father")) + self.fbutton_del.set_tooltip_text(_("Remove the person as the father")) + + self.fbutton_edit.connect('button-press-event', self.edit_father) + self.fbutton_edit.connect('key-press-event', self.edit_father) + self.fbutton_index.connect('clicked', self.sel_father_clicked) + self.fbutton_del.connect('clicked', self.del_father_clicked) + self.fbutton_add.connect('clicked', self.add_father_clicked) + + if self.added: + return # avoids crash on drag because not in db yet + #allow for a context menu + self.set_contexteventbox(self.top.get_object("eventboxtop")) + #allow for drag of the family object from eventboxtop + self.contexteventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.FAMILY_LINK.target()], + Gdk.DragAction.COPY) + self.contexteventbox.drag_source_set_icon_name('gramps-family') + self.contexteventbox.connect('drag_data_get', self.on_drag_data_get_family) + + def on_drag_data_get_family(self,widget, context, sel_data, info, time): + if info == DdTargets.FAMILY_LINK.app_id: + data = (DdTargets.FAMILY_LINK.drag_type, id(self), self.obj.get_handle(), 0) + sel_data.set(DdTargets.FAMILY_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _update_parent_dnd_handler(self, event_box, parent_handle, on_drag_data_get, on_drag_data_received): + """ + Set the drag action from the label of father when he exists + """ + if parent_handle: + # Allow drag + if not event_box.drag_source_get_target_list(): + event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + event_box.drag_source_set_icon_name('gramps-person') + event_box.connect('drag_data_get', on_drag_data_get) + #Disallow drop: + if event_box.drag_dest_get_target_list(): + event_box.drag_dest_unset() + else: + # Disallow drag + if event_box.drag_source_get_target_list(): + event_box.drag_source_unset() + #allow for drop: + if not event_box.drag_dest_get_target_list(): + event_box.drag_dest_set(Gtk.DestDefaults.MOTION | + Gtk.DestDefaults.DROP, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + event_box.connect('drag_data_received', on_drag_data_received) + + def on_drag_fatherdata_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_father_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def on_drag_motherdata_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_mother_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _connect_signals(self): + self.define_ok_button(self.top.get_object('ok'), self.save) + self.define_cancel_button(self.top.get_object('cancel')) + # TODO help button (rename glade button name) + self.define_help_button(self.top.get_object('button119'), + WIKI_HELP_PAGE, WIKI_HELP_SEC) + + def _can_be_replaced(self): + pass + + def _setup_fields(self): + + self.private = PrivacyButton( + self.top.get_object('private'), + self.obj, + self.db.readonly) + + self.gid = MonitoredEntry( + self.top.get_object('gid'), + self.obj.set_gramps_id, + self.obj.get_gramps_id, + self.db.readonly) + + self.tags = MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.obj.set_tag_list, + self.obj.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + self.data_type = MonitoredDataType( + self.top.get_object('marriage_type'), + self.obj.set_relationship, + self.obj.get_relationship, + self.db.readonly, + self.db.get_family_relation_types(), + ) + + def load_data(self): + """ + Show top data of family editor: father and mother info + and set self.phandles with all person handles in the family + """ + fhandle = self.obj.get_father_handle() + self.update_father(fhandle) + + mhandle = self.obj.get_mother_handle() + self.update_mother(mhandle) + + self.phandles = [mhandle, fhandle] + self.phandles.extend(x.ref for x in self.obj.get_child_ref_list()) + + self.phandles = [_f for _f in self.phandles if _f] + + def get_start_date(self): + """ + Get the start date for a family, usually a marriage date, or + something close to marriage. + """ + event = get_marriage_or_fallback(self.dbstate.db, self.obj) + return event.get_date_object() if event else None + + def _create_tabbed_pages(self): + + notebook = Gtk.Notebook() + + self.child_list = ChildEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj) + self.child_tab = self._add_tab(notebook, self.child_list) + self.track_ref_for_deletion("child_list") + self.track_ref_for_deletion("child_tab") + + self.event_list = EventEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj, + start_date=self.get_start_date()) + + self._add_tab(notebook, self.event_list) + self.track_ref_for_deletion("event_list") + + self.citation_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.citation_list) + self.track_ref_for_deletion("citation_list") + + self.attr_list = FamilyAttrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_attribute_list()) + self._add_tab(notebook, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.note_tab = NoteTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_note_list(), + self.get_menu_title(), + notetype=NoteType.FAMILY) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self.gallery_tab = GalleryTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_media_list()) + self._add_tab(notebook, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.lds_embed = FamilyLdsEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_lds_ord_list()) + self._add_tab(notebook, self.lds_embed) + self.track_ref_for_deletion("lds_embed") + + self._setup_notebook_tabs( notebook) + notebook.show_all() + + self.hidden = (notebook, self.top.get_object('info')) + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def update_father(self, handle): + self.load_parent(handle, self.fname, self.fbirth, self.fbirth_label, + self.fdeath, self.fdeath_label, + self.fbutton_index, self.fbutton_add, + self.fbutton_del, self.fbutton_edit) + self._update_parent_dnd_handler(self.top.get_object('ftable_event_box'), + self.obj.get_father_handle(), + self.on_drag_fatherdata_get, + self.on_drag_fatherdata_received) + + def update_mother(self, handle): + self.load_parent(handle, self.mname, self.mbirth, self.mbirth_label, + self.mdeath, self.mdeath_label, + self.mbutton_index, self.mbutton_add, + self.mbutton_del, self.mbutton_edit) + self._update_parent_dnd_handler(self.top.get_object('mtable_event_box'), + self.obj.get_mother_handle(), + self.on_drag_motherdata_get, + self.on_drag_motherdata_received) + + def add_mother_clicked(self, obj): + person = Person() + person.set_gender(Person.FEMALE) + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 2: + name = self.latin_american_child("mother") + else: + name = self.no_name() + person.set_primary_name(name) + EditPerson(self.dbstate, self.uistate, self.track, person, + self.new_mother_added) + + def add_father_clicked(self, obj): + person = Person() + person.set_gender(Person.MALE) + autoname = config.get('behavior.surname-guessing') + #_("Father's surname"), + #_("None"), + #_("Combination of mother's and father's surname"), + #_("Icelandic style"), + if autoname == 0: + name = self.north_american_child() + elif autoname == 2: + name = self.latin_american_child("father") + else: + name = self.no_name() + person.set_primary_name(name) + EditPerson(self.dbstate, self.uistate, self.track, + person, self.new_father_added) + + def new_mother_added(self, person): + for i in self.hidden: + i.set_sensitive(True) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def new_father_added(self, person): + for i in self.hidden: + i.set_sensitive(True) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def del_mother_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + self.obj.set_mother_handle(None) + self.update_mother(None) + + def sel_mother_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + data_filter = FastFemaleFilter(self.dbstate.db) + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Mother"), + filter=data_filter, + skip=[x.ref for x in self.obj.get_child_ref_list()]) + person = sel.run() + + if person: + self.check_for_existing_family(self.obj.get_father_handle(), + person.handle, + self.obj.handle) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def on_change_father(self, selector_window, obj): + if obj.__class__ == Person: + try: + person = obj + self.obj.set_father_handle(person.get_handle()) + self.update_father(person.get_handle()) + except: + log.warning("Failed to update father: \n" + "obj returned from selector was: %s\n" + % (repr(obj),)) + raise + + else: + log.warning( + "Object selector returned obj.__class__ = %s, it should " + "have been of type %s." % (obj.__class__.__name__, + Person.__name__)) + + selector_window.close() + + def del_father_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + self.obj.set_father_handle(None) + self.update_father(None) + + def sel_father_clicked(self, obj): + for i in self.hidden: + i.set_sensitive(True) + + data_filter = FastMaleFilter(self.dbstate.db) + sel = SelectPerson(self.dbstate, self.uistate, self.track, + _("Select Father"), + filter=data_filter, + skip=[x.ref for x in self.obj.get_child_ref_list()]) + person = sel.run() + if person: + self.check_for_existing_family(person.handle, + self.obj.get_mother_handle(), + self.obj.handle) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def check_for_existing_family(self, father_handle, mother_handle, + family_handle): + + if father_handle: + father = self.dbstate.db.get_person_from_handle(father_handle) + ffam = set(father.get_family_handle_list()) + if mother_handle: + mother = self.dbstate.db.get_person_from_handle(mother_handle) + mfam = set(mother.get_family_handle_list()) + common = list(mfam.intersection(ffam)) + if len(common) > 0: + if self.add_parent or self.obj.handle not in common: + WarningDialog( + _('Duplicate Family'), + _('A family with these parents already exists ' + 'in the database. If you save, you will create ' + 'a duplicate family. It is recommended that ' + 'you cancel the editing of this window, and ' + 'select the existing family'), + parent=self.window) + + def edit_father(self, obj, event): + handle = self.obj.get_father_handle() + return self.edit_person(obj, event, handle) + + def edit_mother(self, obj, event): + handle = self.obj.get_mother_handle() + return self.edit_person(obj, event, handle) + + def edit_person(self, obj, event, handle): + if button_activated(event, _LEFT_BUTTON): + try: + person = self.db.get_person_from_handle(handle) + EditPerson(self.dbstate, self.uistate, + self.track, person) + except WindowActiveError: + pass + + def load_parent(self, handle, name_obj, birth_obj, birth_label, death_obj, + death_label, btn_index, btn_add, btn_del, btn_edit): + # is a parent used here: + is_used = handle is not None + + # now we display the area: + if is_used: + db = self.db + person = db.get_person_from_handle(handle) + name = "%s [%s]" % (name_displayer.display(person), + person.gramps_id) + birth = get_birth_or_fallback(db, person) + self.callman.register_handles({'person': [handle]}) + if birth: + #if event changes it view needs to update + self.callman.register_handles({'event': [birth.get_handle()]}) + # translators: needed for French, ignore otherwise + birth_label.set_label(_("%s:") % birth.get_type()) + + death = get_death_or_fallback(db, person) + if death: + #if event changes it view needs to update + self.callman.register_handles({'event': [death.get_handle()]}) + # translators: needed for French, ignore otherwise + death_label.set_label(_("%s:") % death.get_type()) + + btn_edit.set_tooltip_text(_('Edit %s') % name) + btn_index.hide() + btn_add.hide() + btn_del.show() + btn_edit.show() + else: + name = "" + birth = None + death = None + + btn_index.show() + btn_add.show() + btn_del.hide() + btn_edit.hide() + + if name_obj: + name_obj.set_text(name) + if birth: + birth_str = displayer.display(birth.get_date_object()) + else: + birth_str = "" + birth_obj.set_text(birth_str) + if death: + death_str = displayer.display(death.get_date_object()) + else: + death_str = "" + death_obj.set_text(death_str) + + def fix_parent_handles(self, orig_handle, new_handle, trans): + if orig_handle != new_handle: + if orig_handle: + person = self.db.get_person_from_handle(orig_handle) + person.family_list.remove(self.obj.handle) + self.db.commit_person(person, trans) + if new_handle: + person = self.db.get_person_from_handle(new_handle) + person.family_list.append(self.obj.handle) + self.db.commit_person(person, trans) + + def on_drag_fatherdata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if self.obj.get_father_handle(): + return + for i in self.hidden: + i.set_sensitive(True) + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + + if person: + self.check_for_existing_family(person.handle, + self.obj.get_mother_handle(), + self.obj.handle) + self.obj.set_father_handle(person.handle) + self.update_father(person.handle) + + def on_drag_motherdata_received(self, widget, context, x, y, sel_data, + info, time): + """ + Handle the standard gtk interface for drag_data_received. + """ + if self.obj.get_mother_handle(): + return + for i in self.hidden: + i.set_sensitive(True) + if sel_data and sel_data.get_data(): + (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) + person = self.db.get_person_from_handle(handle) + + if person: + self.check_for_existing_family(self.obj.get_father_handle(), + person.handle, + self.obj.handle) + self.obj.set_mother_handle(person.handle) + self.update_mother(person.handle) + + def object_is_empty(self): + return (not self.obj.get_father_handle() and + not self.obj.get_mother_handle() and + len(self.obj.get_child_ref_list()) == 0 + ) + + def save(self, *obj): + ## FIXME: how to catch a specific error? + #try: + self.__do_save() + #except bsddb_db.DBRunRecoveryError as msg: + # RunDatabaseRepair(msg[1], parent=self.window) + + def __do_save(self): + self.ok_button.set_sensitive(False) + + if not self.added: + original = self.db.get_family_from_handle(self.obj.handle) + else: + original = None + + # do some basic checks + + child_list = [ ref.ref for ref in self.obj.get_child_ref_list() ] + + if self.obj.get_father_handle() in child_list: + + father = self.db.get_person_from_handle(self.obj.get_father_handle()) + name = "%s [%s]" % (name_displayer.display(father), + father.gramps_id) + ErrorDialog(_("A father cannot be his own child"), + _("%s is listed as both the father and child " + "of the family.") % name, + parent=self.window) + self.ok_button.set_sensitive(True) + return + elif self.obj.get_mother_handle() in child_list: + + mother = self.db.get_person_from_handle(self.obj.get_mother_handle()) + name = "%s [%s]" % (name_displayer.display(mother), + mother.gramps_id) + ErrorDialog(_("A mother cannot be her own child"), + _("%s is listed as both the mother and child " + "of the family.") % name, + parent=self.window) + self.ok_button.set_sensitive(True) + return + + if not original and self.object_is_empty(): + ErrorDialog( + _("Cannot save family"), + _("No data exists for this family. " + "Please enter data or cancel the edit."), + parent=self.window) + self.ok_button.set_sensitive(True) + return + + (uses_dupe_id, id) = self._uses_duplicate_id() + if uses_dupe_id: + msg1 = _("Cannot save family. ID already exists.") + msg2 = _("You have attempted to use the existing Gramps ID with " + "value %(id)s. This value is already used. Please " + "enter a different ID or leave " + "blank to get the next available ID value.") % { + 'id' : id} + ErrorDialog(msg1, msg2, parent=self.window) + self.ok_button.set_sensitive(True) + return + + # We disconnect the callbacks to all signals we connected earlier. + # This prevents the signals originating in any of the following + # commits from being caught by us again. + self._cleanup_callbacks() + + if not original and not self.object_is_empty(): + with DbTxn(_("Add Family"), self.db) as trans: + + # find the father, add the family handle to the father + handle = self.obj.get_father_handle() + if handle: + parent = self.db.get_person_from_handle(handle) + parent.add_family_handle(self.obj.handle) + self.db.commit_person(parent, trans) + + # find the mother, add the family handle to the mother + handle = self.obj.get_mother_handle() + if handle: + parent = self.db.get_person_from_handle(handle) + parent.add_family_handle(self.obj.handle) + self.db.commit_person(parent, trans) + + # for each child, add the family handle to the child + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + # fix - relationships need to be extracted from the list + child.add_parent_family_handle(self.obj.handle) + self.db.commit_person(child, trans) + + self.db.add_family(self.obj, trans) + elif self.data_has_changed(): + + with DbTxn(_("Edit Family"), self.db) as trans: + + self.fix_parent_handles(original.get_father_handle(), + self.obj.get_father_handle(), trans) + self.fix_parent_handles(original.get_mother_handle(), + self.obj.get_mother_handle(), trans) + + orig_set = set(original.get_child_ref_list()) + new_set = set(self.obj.get_child_ref_list()) + + # remove the family from children which have been removed + for ref in orig_set.difference(new_set): + person = self.db.get_person_from_handle(ref.ref) + person.remove_parent_family_handle(self.obj.handle) + self.db.commit_person(person, trans) + + # add the family to children which have been added + for ref in new_set.difference(orig_set): + person = self.db.get_person_from_handle(ref.ref) + person.add_parent_family_handle(self.obj.handle) + self.db.commit_person(person, trans) + + if self.object_is_empty(): + self.db.remove_family_relationships(self.obj.handle, trans) + else: + if not self.obj.get_gramps_id(): + self.obj.set_gramps_id( + self.db.find_next_family_gramps_id()) + self.db.commit_family(self.obj, trans) + + self._do_close() + if self.callback: + self.callback(self.obj) + self.callback = None + + def no_name(self): + """ + Default surname guess. + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + return name + + def north_american_child(self): + """ + If SURNAME_GUESSING is north american, then find a child + and return their name for the father. + """ + # for each child, find one with a last name + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + if child: + preset_name(child, name) + return name + return name + + def latin_american_child(self, parent): + """ + If SURNAME_GUESSING is latin american, then find a child + and return their name for the father or mother. + + parent = "mother" | "father" + """ + name = Name() + #the editor requires a surname + name.add_surname(Surname()) + name.set_primary_surname(0) + # for each child, find one with a last name + for ref in self.obj.get_child_ref_list(): + child = self.db.get_person_from_handle(ref.ref) + if child: + pname = child.get_primary_name() + preset_name(child, name) # add the known family surnames, etc. + surnames = name.get_surname_list() + if len(surnames) < 2: + return name + else: + #return first for the father, and last for the mother + if parent == 'father': + name.set_surname_list([surnames[0]]) + return name + else: + name.set_surname_list([surnames[-1]]) + return name + return name + +def button_activated(event, mouse_button): + if (event.type == Gdk.EventType.BUTTON_PRESS and + event.button == mouse_button) or \ + (event.type == Gdk.EventType.KEY_PRESS and + event.keyval in (_RETURN, _KP_ENTER)): + return True + else: + return False diff --git a/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.patch b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.patch new file mode 100644 index 0000000..066fd93 --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/editors/editfamily.py.patch @@ -0,0 +1,58 @@ +84a85,87 +> from gramps.gen.utils.file import media_path_full +> from gramps.gui.widgets import Photo +> +591a595,597 +> +> self.mimg = self.top.get_object('mimg') +> self.fimg = self.top.get_object('fimg') +806c812 +< self.load_parent(handle, self.fname, self.fbirth, self.fbirth_label, +--- +> self.load_parent(handle, self.fname, self.fimg, self.fbirth, self.fbirth_label, +816c822 +< self.load_parent(handle, self.mname, self.mbirth, self.mbirth_label, +--- +> self.load_parent(handle, self.mname, self.mimg, self.mbirth, self.mbirth_label, +978c984 +< def load_parent(self, handle, name_obj, birth_obj, birth_label, death_obj, +--- +> def load_parent(self, handle, name_obj, img_obj, birth_obj, birth_label, death_obj, +1008a1015,1018 +> +> if img_obj: +> load_person_image(self, person, img_obj) +> +1283a1294,1325 +> def destroy_cb(widget, data): +> """ +> Callback for gtk_container_foreach +> """ +> widget.destroy() +> +> def load_person_image(self, person, photo_container): +> """ +> Load the primary image if it exists. +> """ +> photo = Photo(True) +> photo.show() +> +> photo_container.foreach(destroy_cb, None) +> photo_container.add(photo) +> +> media_list = person.get_media_list() +> if media_list: +> media_ref = media_list[0] +> object_handle = media_ref.get_reference_handle() +> obj = self.dbstate.db.get_media_from_handle(object_handle) +> full_path = media_path_full(self.dbstate.db, obj.get_path()) +> mime_type = obj.get_mime_type() +> if mime_type and mime_type.startswith("image"): +> photo.set_image(full_path, mime_type, media_ref.get_rectangle()) +> photo.set_uistate(self.uistate, object_handle) +> else: +> photo.set_image(None) +> photo.set_uistate(None, None) +> else: +> photo.set_image(None) +> photo.set_uistate(None, None) diff --git a/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade new file mode 100644 index 0000000..595482a --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade @@ -0,0 +1,860 @@ + + + + + + + False + dialog + + + True + False + vertical + + + True + False + end + + + _Cancel + False + True + True + True + True + True + Abandon changes and close window + Abandon changes and close window + True + + + False + False + 0 + + + + + _OK + False + True + True + True + True + True + True + Accept changes and close window + Accept changes and close window + True + + + False + False + 1 + + + + + _Help + False + True + True + True + True + True + + + False + False + end + 2 + + + + + False + True + end + 0 + + + + + True + False + False + + + True + False + 6 + vertical + 4 + + + True + False + + + True + False + False + + + 132 + True + False + 6 + 6 + 12 + + + True + False + start + 6 + Name: + + + 1 + 1 + + + + + True + False + start + 6 + Birth: + + + 1 + 2 + + + + + True + False + start + 6 + Death: + + + 1 + 3 + + + + + True + False + + + True + False + start + Father/partner1 + + + + + + False + False + 0 + + + + + False + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + Father + + + + + False + False + 2 + 1 + + + + + False + True + True + True + none + + + True + False + list-add + + + Add + + + + + + + Add + + + + + False + False + 2 + + + + + False + True + True + True + none + + + True + False + list-remove + + + Remove + + + + + + + Remove + + + + + False + False + 3 + + + + + False + True + True + True + none + + + True + False + gtk-edit + + + Edition + + + + + + + Edit + + + + + + False + False + 4 + + + + + 1 + 0 + 2 + + + + + True + False + start + True + + + 2 + 2 + + + + + True + False + start + True + + + 2 + 3 + + + + + True + False + start + True + end + + + 2 + 1 + + + + + True + False + + + 0 + 0 + 4 + + + + + + + + + + + + + + + + + + + False + True + 0 + + + + + + + + True + False + False + + + 132 + True + False + 6 + 6 + 12 + + + True + False + start + 6 + Name: + + + 1 + 1 + + + + + True + False + start + 6 + Birth: + + + 1 + 2 + + + + + True + False + start + 6 + Death: + + + 1 + 3 + + + + + True + False + start + True + + + 2 + 3 + + + + + True + False + start + True + + + 2 + 2 + + + + + True + False + + + True + False + start + Mother/partner2 + + + + + + False + False + 0 + + + + + False + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + Mother + + + + + False + False + 1 + + + + + False + True + True + True + none + + + True + False + list-add + + + Add + + + + + + + Add + + + + + False + False + 2 + + + + + False + True + True + True + True + Indicates if the record is private + Indicates if the record is private + none + + + True + False + dialog-password + + + Privacy + + + + + + + + False + False + end + 3 + + + + + False + True + True + True + none + + + True + False + list-remove + + + Remove + + + + + + + Remove + + + + + False + False + 4 + + + + + False + True + True + True + none + + + True + False + gtk-edit + + + Edition + + + + + + + Edit + + + + + + False + False + 5 + + + + + 1 + 0 + 2 + + + + + True + False + start + True + end + + + 2 + 1 + + + + + True + False + + + 0 + 0 + 4 + + + + + + + + + + + + + + + + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + 6 + 6 + 12 + + + True + False + start + Relationship Information + + + + + + 0 + 0 + 4 + + + + + True + False + start + 6 + _ID: + True + gid + + + 0 + 1 + + + + + True + True + A unique ID for the family + True + + 6 + + + 1 + 1 + + + + + True + False + start + _Type: + True + center + marriage_type + + + 2 + 1 + + + + + True + False + The relationship type, eg 'Married' or 'Unmarried'. Use Events for more details. + True + True + + + True + True + + + + + 3 + 1 + + + + + True + False + start + 6 + _Tags: + True + tag_button + + + 0 + 2 + + + + + True + False + + + True + False + True + + + True + True + 0 + + + + + False + True + True + True + Edit the tag list + + + + + + + + + Tags + + + + + False + False + 1 + + + + + 1 + 2 + 3 + + + + + False + True + 1 + + + + + + + True + True + 1 + + + + + + cancel + ok + button119 + + + diff --git a/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.0 b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.0 new file mode 100644 index 0000000..a634982 --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.0 @@ -0,0 +1,814 @@ + + + + + + + False + dialog + + + True + False + vertical + + + True + False + end + + + _Cancel + False + True + True + True + True + True + Abandon changes and close window + Abandon changes and close window + True + + + False + False + 0 + + + + + _OK + False + True + True + True + True + True + True + Accept changes and close window + Accept changes and close window + True + + + False + False + 1 + + + + + _Help + False + True + True + True + True + True + + + False + False + end + 2 + + + + + False + True + end + 0 + + + + + True + False + False + + + True + False + 6 + vertical + 4 + + + True + False + + + True + False + False + + + 132 + True + False + 6 + 6 + 12 + + + True + False + start + 6 + Name: + + + 0 + 1 + + + + + True + False + start + 6 + Birth: + + + 0 + 2 + + + + + True + False + start + 6 + Death: + + + 0 + 3 + + + + + True + False + + + True + False + start + Father/partner1 + + + + + + False + False + 0 + + + + + False + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + Father + + + + + False + False + 2 + 1 + + + + + False + True + True + True + none + + + True + False + list-add + + + Add + + + + + + + Add + + + + + False + False + 2 + + + + + False + True + True + True + none + + + True + False + list-remove + + + Remove + + + + + + + Remove + + + + + False + False + 3 + + + + + False + True + True + True + none + + + True + False + gtk-edit + + + Edition + + + + + + + Edit + + + + + + False + False + 4 + + + + + 0 + 0 + 2 + + + + + True + False + start + True + + + 1 + 2 + + + + + True + False + start + True + + + 1 + 3 + + + + + True + False + start + True + end + + + 1 + 1 + + + + + + + False + True + 0 + + + + + + + + True + False + False + + + 132 + True + False + 6 + 6 + 12 + + + True + False + start + 6 + Name: + + + 0 + 1 + + + + + True + False + start + 6 + Birth: + + + 0 + 2 + + + + + True + False + start + 6 + Death: + + + 0 + 3 + + + + + True + False + start + True + + + 1 + 3 + + + + + True + False + start + True + + + 1 + 2 + + + + + True + False + + + True + False + start + Mother/partner2 + + + + + + False + False + 0 + + + + + False + True + True + True + none + + + True + False + gtk-index + + + Selector + + + + + + + Mother + + + + + False + False + 1 + + + + + False + True + True + True + none + + + True + False + list-add + + + Add + + + + + + + Add + + + + + False + False + 2 + + + + + False + True + True + True + True + Indicates if the record is private + Indicates if the record is private + none + + + True + False + dialog-password + + + Privacy + + + + + + + + False + False + end + 3 + + + + + False + True + True + True + none + + + True + False + list-remove + + + Remove + + + + + + + Remove + + + + + False + False + 4 + + + + + False + True + True + True + none + + + True + False + gtk-edit + + + Edition + + + + + + + Edit + + + + + + False + False + 5 + + + + + 0 + 0 + 2 + + + + + True + False + start + True + end + + + 1 + 1 + + + + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + 6 + 6 + 12 + + + True + False + start + Relationship Information + + + + + + 0 + 0 + 4 + + + + + True + False + start + 6 + _ID: + True + gid + + + 0 + 1 + + + + + True + True + A unique ID for the family + True + + 6 + + + 1 + 1 + + + + + True + False + start + _Type: + True + center + marriage_type + + + 2 + 1 + + + + + True + False + The relationship type, eg 'Married' or 'Unmarried'. Use Events for more details. + True + True + + + True + True + + + + + 3 + 1 + + + + + True + False + start + 6 + _Tags: + True + tag_button + + + 0 + 2 + + + + + True + False + + + True + False + True + + + True + True + 0 + + + + + False + True + True + True + Edit the tag list + + + + + + + + + Tags + + + + + False + False + 1 + + + + + 1 + 2 + 3 + + + + + False + True + 1 + + + + + + + True + True + 1 + + + + + + cancel + ok + button119 + + + diff --git a/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.patch b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.patch new file mode 100644 index 0000000..4fb2244 --- /dev/null +++ b/person-thumbnails-in-family-editor/gramps/gui/glade/editfamily.glade.patch @@ -0,0 +1,104 @@ +121c121 +< 0 +--- +> 1 +134c134 +< 0 +--- +> 1 +147c147 +< 0 +--- +> 1 +299c299 +< 0 +--- +> 1 +312c312 +< 1 +--- +> 2 +324c324 +< 1 +--- +> 2 +337c337 +< 1 +--- +> 2 +340a341,363 +> +> +> True +> False +> +> +> 0 +> 0 +> 4 +> +> +> +> +> +> +> +> +> +> +> +> +> +> +375c398 +< 0 +--- +> 1 +388c411 +< 0 +--- +> 1 +401c424 +< 0 +--- +> 1 +413c436 +< 1 +--- +> 2 +425c448 +< 1 +--- +> 2 +607c630 +< 0 +--- +> 1 +621c644 +< 1 +--- +> 2 +623a647,669 +> +> +> +> True +> False +> +> +> 0 +> 0 +> 4 +> +> +> +> +> +> +> +> +> +> +> +> +> diff --git a/person-view-age-calculator/gramps/gui/editors/editperson.py b/person-view-age-calculator/gramps/gui/editors/editperson.py new file mode 100644 index 0000000..03be8f7 --- /dev/null +++ b/person-view-age-calculator/gramps/gui/editors/editperson.py @@ -0,0 +1,1126 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2009-2011 Gary Burton +# Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +EditPerson Dialog. Provide the interface to allow the Gramps program +to edit information about a particular Person. +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +from copy import copy +import pickle + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Pango + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.utils.file import media_path_full +from gramps.gen.utils.thumbnails import get_thumbnail_image +from ..utils import is_right_click, open_file_with_default_application +from gramps.gen.utils.db import get_birth_or_fallback +from gramps.gen.lib import NoteType, Person, Surname +from gramps.gen.db import DbTxn +from .. import widgets +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.errors import WindowActiveError +from ..glade import Glade +from ..ddtargets import DdTargets +from ..widgets.menuitem import add_menuitem + +from .editprimary import EditPrimary +from .editmediaref import EditMediaRef +from .editname import EditName +from gramps.gen.config import config +from ..dialog import ErrorDialog, ICON +from gramps.gen.errors import ValidationError + +from .displaytabs import (PersonEventEmbedList, NameEmbedList, CitationEmbedList, + AttrEmbedList, AddrEmbedList, NoteTab, GalleryTab, + WebEmbedList, PersonRefEmbedList, LdsEmbedList, + PersonBackRefList, SurnameTab) +from gramps.gen.plug import CATEGORY_QR_PERSON +from gramps.gen.const import URL_MANUAL_SECT1 +from gramps.gen.utils.id import create_id + +from gramps.gen.lib.date import Date, Today +from gramps.gen.datehandler import displayer as date_displayer +from datetime import date + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 + +_select_gender = ((True, False, False), + (False, True, False), + (False, False, True)) + +class SingSurn: + """ + Managing the single surname components + """ + def __init__(self, gladeobj): + self.top = gladeobj + + def hide_all(self): + #self.top.get_object('prefixlabel').hide() + self.top.get_object('prefix').hide() + self.top.get_object('surnamelabel').hide() + self.top.get_object('surname').hide() + self.top.get_object('originlabel').hide() + self.top.get_object('originlabel').hide() + self.top.get_object('cmborigin').hide() + self.top.get_object('multsurnamebtn').hide() + + def show_all(self): + #self.top.get_object('prefixlabel').show() + self.top.get_object('prefix').show() + self.top.get_object('surnamelabel').show() + self.top.get_object('surname').show() + self.top.get_object('originlabel').show() + self.top.get_object('originlabel').show() + self.top.get_object('cmborigin').show() + self.top.get_object('multsurnamebtn').show() + +class EditPerson(EditPrimary): + """ + The EditPerson dialog is derived from the EditPrimary class. + + It allows for the editing of the primary object type of Person. + + """ + + QR_CATEGORY = CATEGORY_QR_PERSON + + def __init__(self, dbstate, uistate, track, person, callback=None): + """ + Create an EditPerson window. + + Associate a person with the window. + + """ + EditPrimary.__init__(self, dbstate, uistate, track, person, + dbstate.db.get_person_from_handle, + dbstate.db.get_person_from_gramps_id, callback) + + def empty_object(self): + """ + Return an empty Person object for comparison for changes. + + This is used by the base class (EditPrimary). + + """ + person = Person() + #the editor requires a surname + person.primary_name.add_surname(Surname()) + person.primary_name.set_primary_surname(0) + return person + + def get_menu_title(self): + if self.obj and self.obj.get_handle(): + name = name_displayer.display(self.obj) + title = _('Person: %(name)s') % {'name': name} + else: + name = name_displayer.display(self.obj) + if name: + title = _('New Person: %(name)s') % {'name': name} + else: + title = _('New Person') + return title + + def get_preview_name(self): + prevname = name_displayer.display(self.obj) + return prevname + + def _local_init(self): + """ + Performs basic initialization, including setting up widgets and the + glade interface. + + Local initialization function. + This is called by the base class of EditPrimary, and overridden here. + + """ + self.pname = self.obj.get_primary_name() + self.should_guess_gender = (not self.obj.get_gramps_id() and + self.obj.get_gender () == + Person.UNKNOWN) + + self.added = self.obj.handle is None + if self.added: + self.obj.handle = create_id() + + self.top = Glade() + + self.set_window(self.top.toplevel, None, + self.get_menu_title()) + self.setup_configs('interface.person', 750, 550) + + self.obj_photo = self.top.get_object("personPix") + self.frame_photo = self.top.get_object("frame5") + self.eventbox = self.top.get_object("eventbox1") + self.singsurnfr = SingSurn(self.top) + self.multsurnfr = self.top.get_object("hboxmultsurnames") + self.singlesurn_active = True + + self.set_contexteventbox(self.top.get_object("eventboxtop")) + + def _post_init(self): + """ + Handle any initialization that needs to be done after the interface is + brought up. + + Post initalization function. + This is called by _EditPrimary's init routine, and overridden in the + derived class (this class). + + """ + self.load_person_image() + self.given.grab_focus() + self._changed_name(None) + self.top.get_object("hboxmultsurnames").pack_start(self.surntab, True, True, 0) + + if len(self.obj.get_primary_name().get_surname_list()) > 1: + self.multsurnfr.set_size_request(-1, + int(config.get('interface.surname-box-height'))) + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + self.singlesurn_active = False + else: + self.multsurnfr.hide() + self.singsurnfr.show_all() + self.singlesurn_active = True + #if self.pname.get_surname() and not self.pname.get_first_name(): + # self.given.grab_focus() + #else: + # self.surname_field.grab_focus() + + def _connect_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self.define_cancel_button(self.top.get_object("button15")) + self.define_ok_button(self.top.get_object("ok"), self.save) + self.define_help_button(self.top.get_object("button134"), + WIKI_HELP_PAGE, + _('manual|Editing_information_about_people')) + + self.given.connect("focus-out-event", self._given_focus_out_event) + self.top.get_object("editnamebtn").connect("clicked", + self._edit_name_clicked) + self.top.get_object("multsurnamebtn").connect("clicked", + self._mult_surn_clicked) + + self.eventbox.connect('button-press-event', + self._image_button_press) + # allow to initiate a drag-and-drop with this person if it has a handle + if self.added: + return # Avoid HandleError if dragging an objet not in db yet + self.contexteventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + self.contexteventbox.drag_source_set_icon_name('gramps-person') + self.contexteventbox.connect('drag_data_get', self._top_drag_data_get) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('person-rebuild', self._do_close) + self._add_db_signal('person-delete', self.check_for_close) + self._add_db_signal('family-rebuild', self.family_change) + self._add_db_signal('family-delete', self.family_change) + self._add_db_signal('family-update', self.family_change) + self._add_db_signal('family-add', self.family_change) + self._add_db_signal('event-rebuild', self.event_updated) + + def family_change(self, handle_list=[]): + """ + Callback for family change signals. + + This should rebuild the + backreferences to family in person when: + 1)a family the person is parent of changes. Person could have + been removed + 2)a family the person is child in changes. Child could have been + removed + 3)a family is changed. The person could be added as child or + parent + + """ + #As this would be an extensive check, we choose the easy path and + # rebuild family backreferences on all family changes + self._update_families() + + def _update_families(self): + phandle = self.obj.get_handle() + if self.dbstate.db.has_person_handle(phandle): + #new person has no handle yet and cannot be in a family. + person = self.dbstate.db.get_person_from_handle(phandle) + self.obj.set_family_handle_list(person.get_family_handle_list()) + self.obj.set_parent_family_handle_list( + person.get_parent_family_handle_list()) + #a family groupname in event_list might need to be changed + # we just rebuild the view always + self.event_list.rebuild_callback() + + def event_updated(self, obj): + #place in event might have changed, or person event shown in the list + # we just rebuild the view always + self.event_list.rebuild_callback() + + def _validate_call(self, widget, text): + """ a callname must be a part of the given name, see if this is the + case """ + validcall = self.given.obj.get_text().split() + dummy = copy(validcall) + for item in dummy: + validcall += item.split('-') + if text in validcall: + return + return ValidationError(_("Call name must be the given name that " + "is normally used.")) + + def _setup_fields(self): + """ + Connect the GrampsWidget objects to field in the interface. + + This allows the widgets to keep the data in the attached Person object + up to date at all times, eliminating a lot of need in 'save' routine. + + """ + + self.private = widgets.PrivacyButton( + self.top.get_object('private'), + self.obj, + self.db.readonly) + + self.gender = widgets.MonitoredMenu( + self.top.get_object('gender'), + self.obj.set_gender, + self.obj.get_gender, + ( + (_('female'), Person.FEMALE), + (_('male'), Person.MALE), + (_('unknown'), Person.UNKNOWN) + ), + self.db.readonly) + + self.ntype_field = widgets.MonitoredDataType( + self.top.get_object("ntype"), + self.pname.set_type, + self.pname.get_type, + self.db.readonly, + self.db.get_name_types()) + + #part of Given Name section + self.given = widgets.MonitoredEntry( + self.top.get_object("given_name"), + self.pname.set_first_name, + self.pname.get_first_name, + self.db.readonly) + + self.call = widgets.MonitoredEntry( + self.top.get_object("call"), + self.pname.set_call_name, + self.pname.get_call_name, + self.db.readonly) + self.call.connect("validate", self._validate_call) + #force validation now with initial entry + self.call.obj.validate(force=True) + + self.title = widgets.MonitoredEntry( + self.top.get_object("title"), + self.pname.set_title, + self.pname.get_title, + self.db.readonly) + + self.suffix = widgets.MonitoredEntryIndicator( + self.top.get_object("suffix"), + self.pname.set_suffix, + self.pname.get_suffix, + _('suffix'), + self.db.readonly) + + self.nick = widgets.MonitoredEntry( + self.top.get_object("nickname"), + self.pname.set_nick_name, + self.pname.get_nick_name, + self.db.readonly) + + #part of Single Surname section + self.surname_field = widgets.MonitoredEntry( + self.top.get_object("surname"), + self.pname.get_primary_surname().set_surname, + self.pname.get_primary_surname().get_surname, + self.db.readonly, + autolist=self.db.get_surname_list() if not self.db.readonly else []) + + self.prefix = widgets.MonitoredEntryIndicator( + self.top.get_object("prefix"), + self.pname.get_primary_surname().set_prefix, + self.pname.get_primary_surname().get_prefix, + _('prefix'), + self.db.readonly) + + self.ortype_field = widgets.MonitoredDataType( + self.top.get_object("cmborigin"), + self.pname.get_primary_surname().set_origintype, + self.pname.get_primary_surname().get_origintype, + self.db.readonly, + self.db.get_origin_types()) + + #other fields + + self.tags = widgets.MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.obj.set_tag_list, + self.obj.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + self.gid = widgets.MonitoredEntry( + self.top.get_object("gid"), + self.obj.set_gramps_id, + self.obj.get_gramps_id, + self.db.readonly) + + #make sure title updates automatically + for obj in [self.top.get_object("given_name"), + self.top.get_object("nickname"), + self.top.get_object("call"), + self.top.get_object("suffix"), + self.top.get_object("prefix"), + self.top.get_object("surname"), + ]: + obj.connect('changed', self._changed_name) + + self.preview_name = self.top.get_object("full_name") + self.preview_name.override_font(Pango.FontDescription('sans bold 12')) + self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, + self.obj.get_primary_name(), + on_change=self._changed_name) + +# BEGIN: Added by Michael J Becker 2020-09-23 02:25:48 -0500 >>> _setup_fields(self) + self.age_label = self.top.get_object("age_label") + self.age_label.set_label("Age: %s" % self.get_age()) + + def get_age(self): + """ + Get the age of the person formatted as a string, if possible. + """ + age_precision = config.get('preferences.age-display-precision') + thedate = Today() + if thedate and self.get_start_date(): + return (thedate - self.get_start_date()).format(precision=age_precision) + else: + return "" + +# END: Added by Michael J Becker 2020-09-23 02:25:48 -0500 <<< get_start_date(self) + + + def get_start_date(self): + """ + Get the start date for a person, usually a birth date, or + something close to birth. + """ + event = get_birth_or_fallback(self.dbstate.db, self.obj) + return event.get_date_object() if event else None + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and insert them into the main window. + """ + notebook = Gtk.Notebook() + notebook.set_scrollable(True) + + self.event_list = PersonEventEmbedList( + self.dbstate, + self.uistate, + self.track, + self.obj, + start_date=self.get_start_date()) + + self._add_tab(notebook, self.event_list) + self.track_ref_for_deletion("event_list") + + self.name_list = NameEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_alternate_names(), + self.obj, + self.name_callback) + self._add_tab(notebook, self.name_list) + self.track_ref_for_deletion("name_list") + + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.attr_list = AttrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_attribute_list()) + self._add_tab(notebook, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.addr_list = AddrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_address_list()) + self._add_tab(notebook, self.addr_list) + self.track_ref_for_deletion("addr_list") + + self.note_tab = NoteTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_note_list(), + self.get_menu_title(), + notetype=NoteType.PERSON) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self.gallery_tab = GalleryTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_media_list(), + self.load_person_image) + self._add_tab(notebook, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.web_list = WebEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_url_list()) + self._add_tab(notebook, self.web_list) + self.track_ref_for_deletion("web_list") + + self.person_ref_list = PersonRefEmbedList(self.dbstate, self.uistate, + self.track, + self.obj.get_person_ref_list()) + self._add_tab(notebook, self.person_ref_list) + self.track_ref_for_deletion("person_ref_list") + + self.lds_list = LdsEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_lds_ord_list()) + self._add_tab(notebook, self.lds_list) + self.track_ref_for_deletion("lds_list") + + self.backref_tab = PersonBackRefList(self.dbstate, + self.uistate, + self.track, + self.db.find_backlink_handles(self.obj.handle)) + self._add_tab(notebook, self.backref_tab) + self.track_ref_for_deletion("backref_tab") + + self._setup_notebook_tabs(notebook) + notebook.show_all() + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def _changed_name(self, *obj): + """ + callback to changes typed by user to the person name. + Update the window title, and default name in name tab + """ + self.update_title(self.get_menu_title()) + self.preview_name.set_text(self.get_preview_name()) + self.name_list.update_defname() + + def name_callback(self): + """ + Callback if changes happen in the name tab that impact the preferred + name. + """ + self.pname = self.obj.get_primary_name() + + self.ntype_field.reinit(self.pname.set_type, self.pname.get_type) + self.given.reinit(self.pname.set_first_name, self.pname.get_first_name) + self.call.reinit(self.pname.set_call_name, self.pname.get_call_name) + self.title.reinit(self.pname.set_title, self.pname.get_title) + self.suffix.reinit(self.pname.set_suffix, self.pname.get_suffix) + self.nick.reinit(self.pname.set_nick_name, self.pname.get_nick_name) + #part of Single Surname section + self.surname_field.reinit( + self.pname.get_primary_surname().set_surname, + self.pname.get_primary_surname().get_surname) + + self.prefix.reinit( + self.pname.get_primary_surname().set_prefix, + self.pname.get_primary_surname().get_prefix) + + self.ortype_field.reinit( + self.pname.get_primary_surname().set_origintype, + self.pname.get_primary_surname().get_origintype) + + self.__renewmultsurnames() + + if len(self.pname.get_surname_list()) == 1: + self.singlesurn_active = True + else: + self.singlesurn_active = False + if self.singlesurn_active: + self.multsurnfr.hide() + self.singsurnfr.show_all() + else: + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + + def build_menu_names(self, person): + """ + Provide the information needed by the base class to define the + window management menu entries. + """ + return (_('Edit Person'), self.get_menu_title()) + + def _image_button_press(self, obj, event): + """ + Button press event that is caught when a button has been pressed while + on the image on the main form. + + This does not apply to the images in galleries, just the image on the + main form. + + """ + if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS + and event.button == 1): + + media_list = self.obj.get_media_list() + if media_list: + media_ref = media_list[0] + object_handle = media_ref.get_reference_handle() + media_obj = self.db.get_media_from_handle(object_handle) + + try: + EditMediaRef(self.dbstate, self.uistate, self.track, + media_obj, media_ref, self.load_photo) + except WindowActiveError: + pass + + elif is_right_click(event): + media_list = self.obj.get_media_list() + if media_list: + photo = media_list[0] + self._show_popup(photo, event) + #do not propagate further: + return True + + def _show_popup(self, photo, event): + """ + Look for right-clicks on a picture and create a popup menu of the + available actions. + """ + self.imgmenu = Gtk.Menu() + menu = self.imgmenu + menu.set_title(_("Media Object")) + obj = self.db.get_media_from_handle(photo.get_reference_handle()) + if obj: + add_menuitem(menu, _("View"), photo, + self._popup_view_photo) + add_menuitem(menu, _("Edit Object Properties"), photo, + self._popup_change_description) + menu.popup(None, None, None, None, event.button, event.time) + + def _popup_view_photo(self, obj): + """ + Open this picture in the default picture viewer. + """ + media_list = self.obj.get_media_list() + if media_list: + photo = media_list[0] + object_handle = photo.get_reference_handle() + ref_obj = self.db.get_media_from_handle(object_handle) + photo_path = media_path_full(self.db, ref_obj.get_path()) + open_file_with_default_application(photo_path, self.uistate) + + def _popup_change_description(self, obj): + """ + Bring up the EditMediaRef dialog for the image on the main form. + """ + media_list = self.obj.get_media_list() + if media_list: + media_ref = media_list[0] + object_handle = media_ref.get_reference_handle() + media_obj = self.db.get_media_from_handle(object_handle) + EditMediaRef(self.dbstate, self.uistate, self.track, + media_obj, media_ref, self.load_photo) + + def _top_contextmenu(self, prefix): + """ + Override from base class, the menuitems and actiongroups for the top + of context menu. + """ + if self.added: + # Don't add items if not a real person yet + return '', [] + + _actions = [('ActivePerson', self._make_active), + ('HomePerson', self._make_home_person)] + + ui_top_cm = ( + ''' + + {prefix}.ActivePerson + Make Active Person''' + ''' + + + {prefix}.HomePerson + Make Home Person''' + ''' + + '''.format(prefix=prefix)) + + return ui_top_cm, _actions + + def _top_drag_data_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _post_build_popup_ui(self): + """ + Override base class, make inactive home action if not needed. + """ + if self.added: + return + home_action = self.uistate.uimanager.get_action(self.action_group, + 'HomePerson') + if (self.dbstate.db.get_default_person() and + self.obj.get_handle() == + self.dbstate.db.get_default_person().get_handle()): + home_action.set_enabled(False) + else: + home_action.set_enabled(True) + + def _make_active(self, obj, value): + self.uistate.set_active(self.obj.get_handle(), 'Person') + + def _make_home_person(self, obj, value): + handle = self.obj.get_handle() + if handle: + self.dbstate.db.set_default_person_handle(handle) + + def _given_focus_out_event (self, entry, event): + """ + Callback that occurs when the user leaves the given name field, + allowing us to attempt to guess the gender of the person if + so requested. + """ + if not self.should_guess_gender: + return False + try: + gender_type = self.db.genderStats.guess_gender( + str(entry.get_text())) + self.gender.force(gender_type) + except: + return False + return False + + def _check_for_unknown_gender(self): + if self.obj.get_gender() == Person.UNKNOWN: + d = GenderDialog(parent=self.window) + gender = d.run() + d.destroy() + if gender >= 0: + self.obj.set_gender(gender) + + def _update_family_ids(self): + # Update each of the families child lists to reflect any + # change in ordering due to the new birth date + family = self.obj.get_main_parents_family_handle() + if (family): + f = self.db.get_family_from_handle(family) + new_order = self.reorder_child_ref_list(self.obj, + f.get_child_ref_list()) + f.set_child_ref_list(new_order) + for family in self.obj.get_parent_family_handle_list(): + f = self.db.get_family_from_handle(family) + new_order = self.reorder_child_ref_list(self.obj, + f.get_child_ref_list()) + f.set_child_ref_list(new_order) + + error = False + if self.original: + (female, male, unknown) = _select_gender[self.obj.get_gender()] + if male and self.original.get_gender() != Person.MALE: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_mother_handle(): + if temp_family.get_father_handle() is not None: + error = True + else: + temp_family.set_mother_handle(None) + temp_family.set_father_handle(self.obj) + elif female and self.original != Person.FEMALE: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_father_handle(): + if temp_family.get_mother_handle() is not None: + error = True + else: + temp_family.set_father_handle(None) + temp_family.set_mother_handle(self.obj) + elif unknown and self.original.get_gender() != Person.UNKNOWN: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_father_handle(): + if temp_family.get_mother_handle() is not None: + error = True + else: + temp_family.set_father_handle(None) + temp_family.set_mother_handle(self.obj) + if self.obj == temp_family.get_mother_handle(): + if temp_family.get_father_handle() is not None: + error = True + else: + temp_family.set_mother_handle(None) + temp_family.set_father_handle(self.obj) + + if error: + msg2 = _("Problem changing the gender") + msg = _("Changing the gender caused problems " + "with marriage information.\nPlease check " + "the person's marriages.") + ErrorDialog(msg2, msg, parent=self.window) + + def save(self, *obj): + """ + Save the data. + """ + self.ok_button.set_sensitive(False) + if self.object_is_empty(): + ErrorDialog(_("Cannot save person"), + _("No data exists for this person. Please " + "enter data or cancel the edit."), + parent=self.window) + self.ok_button.set_sensitive(True) + return + # fix surname problems + for name in [self.obj.get_primary_name()] + self.obj.get_alternate_names(): + if len(name.get_surname_list()) > 1: + newlist = [surn for surn in name.get_surname_list() if not surn.is_empty()] + if len(newlist) != len(name.get_surname_list()): + name.set_surname_list(newlist) + if len(name.get_surname_list()) == 0: + name.set_surname_list([Surname()]) + try: + primind = [surn.get_primary() for surn in name.get_surname_list()].index(True) + except ValueError: + primind = 0 # no match + name.set_primary_surname(primind) + + # fix ide problems + (uses_dupe_id, id) = self._uses_duplicate_id() + if uses_dupe_id: + prim_object = self.get_from_gramps_id(id) + name = self.name_displayer.display(prim_object) + msg1 = _("Cannot save person. ID already exists.") + msg2 = _("You have attempted to use the existing Gramps ID with " + "value %(id)s. This value is already used by '" + "%(prim_object)s'. Please enter a different ID or leave " + "blank to get the next available ID value.") % { + 'id' : id, 'prim_object' : name } + ErrorDialog(msg1, msg2, parent=self.window) + self.ok_button.set_sensitive(True) + return + + self._check_for_unknown_gender() + + self.db.set_birth_death_index(self.obj) + + if not self.obj.handle: + with DbTxn(_("Add Person (%s)") % \ + self.name_displayer.display(self.obj), + self.db) as trans: + self.db.add_person(self.obj, trans) + else: + if self.data_has_changed(): + with DbTxn(_("Edit Person (%s)") % \ + self.name_displayer.display(self.obj), + self.db) as trans: + if not self.obj.get_gramps_id(): + self.obj.set_gramps_id(self.db.find_next_person_gramps_id()) + self.db.commit_person(self.obj, trans) + + self._do_close() + if self.callback: + self.callback(self.obj) + self.callback = None + + def _edit_name_clicked(self, obj): + """ + Bring up the EditName dialog for this name. + + Called when the edit name button is clicked for the primary name + on the main form (not in the names tab). + + """ + try: + EditName(self.dbstate, self.uistate, self.track, + self.pname, self._update_name) + except WindowActiveError: + pass + + def _mult_surn_clicked(self, obj): + """ + Show the list entry of multiple surnames + """ + self.singsurnfr.hide_all() + self.singlesurn_active = False + #update multsurnfr for possible changes + self.__renewmultsurnames() + self.multsurnfr.show_all() + + def _update_name(self, name): + """ + Called when the primary name has been changed by the EditName + dialog. + + This allows us to update the main form in response to any changes. + + """ + for obj in (self.ntype_field, self.given, self.call, self.title, + self.suffix, self.nick, self.surname_field, self.prefix, + self.ortype_field): + obj.update() + + self.__renewmultsurnames() + + if len(self.obj.get_primary_name().get_surname_list()) == 1: + self.singlesurn_active = True + else: + self.singlesurn_active = False + if self.singlesurn_active: + self.multsurnfr.hide() + self.singsurnfr.show_all() + + else: + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + + def __renewmultsurnames(self): + """Update mult surnames section with what is presently the + correct surname. + It is easier to recreate the entire mult surnames GUI than + changing what has changed visually. + """ + #remove present surname tab, and put new one + msurhbox = self.top.get_object("hboxmultsurnames") + msurhbox.remove(self.surntab) + self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, + self.obj.get_primary_name(), + on_change=self._changed_name) + self.multsurnfr.set_size_request(-1, + int(config.get('interface.surname-box-height'))) + msurhbox.pack_start(self.surntab, True, True, 0) + + def load_person_image(self): + """ + Load the primary image into the main form if it exists. + + Used as callback on Gallery Tab too. + + """ + media_list = self.obj.get_media_list() + if media_list: + ref = media_list[0] + handle = ref.get_reference_handle() + obj = self.dbstate.db.get_media_from_handle(handle) + if obj is None : + #notify user of error + from ..dialog import RunDatabaseRepair + RunDatabaseRepair( + _('Non existing media found in the Gallery'), + parent=self.window) + else : + self.load_photo(ref, obj) + else: + self.obj_photo.hide() + self.frame_photo.hide() + + def load_photo(self, ref, obj): + """ + Load the person's main photo using the Thumbnailer. + """ + pixbuf = get_thumbnail_image( + media_path_full(self.dbstate.db, + obj.get_path()), + obj.get_mime_type(), + ref.get_rectangle()) + + self.obj_photo.set_from_pixbuf(pixbuf) + self.obj_photo.show() + self.frame_photo.show_all() + + def birth_dates_in_order(self, child_ref_list): + """ + Check any *valid* birthdates in the list to insure that they are in + numerically increasing order. + """ + inorder = True + prev_date = 0 + handle_list = [ref.ref for ref in child_ref_list] + for i in range(len(handle_list)): + child_handle = handle_list[i] + child = self.db.get_person_from_handle(child_handle) + if child.get_birth_ref(): + event_handle = child.get_birth_ref().ref + event = self.db.get_event_from_handle(event_handle) + child_date = event.get_date_object().get_sort_value() + else: + continue + if (prev_date <= child_date): # <= allows for twins + prev_date = child_date + else: + inorder = False + return inorder + + def reorder_child_ref_list(self, person, child_ref_list): + """ + Reorder the child list to put the specified person in his/her + correct birth order. + + Only check *valid* birthdates. Move the person as short a distance as + possible. + + """ + + if self.birth_dates_in_order(child_ref_list): + return(child_ref_list) + + # Build the person's date string once + event_ref = person.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + person_bday = event.get_date_object().get_sort_value() + else: + person_bday = 0 + + # First, see if the person needs to be moved forward in the list + handle_list = [ref.ref for ref in child_ref_list] + + index = handle_list.index(person.get_handle()) + target = index + for i in range(index-1, -1, -1): + other = self.db.get_person_from_handle(handle_list[i]) + event_ref = other.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + other_bday = event.get_date_object().get_sort_value() + if other_bday == 0: + continue + if person_bday < other_bday: + target = i + else: + continue + + # Now try moving to a later position in the list + if (target == index): + for i in range(index, len(handle_list)): + other = self.db.get_person_from_handle(handle_list[i]) + event_ref = other.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + other_bday = event.get_date_object().get_sort_value() + if other_bday == "99999999": + continue + if person_bday > other_bday: + target = i + else: + continue + + # Actually need to move? Do it now. + if (target != index): + ref = child_ref_list.pop(index) + child_ref_list.insert(target, ref) + return child_ref_list + + def _cleanup_on_exit(self): + """Unset all things that can block garbage collection. + Finalize rest + """ +## self.private.destroy() +## self.gender.destroy() +## self.ntype_field.destroy() +## self.given.destroy() +## self.call.destroy() +## self.title.destroy() +## self.suffix.destroy() +## self.nick.destroy() +## self.surname_field.destroy() +## self.prefix.destroy() +## self.ortype_field.destroy() +## self.tags.destroy() +## self.gid.destroy() + EditPrimary._cleanup_on_exit(self) + #config.save() + + +class GenderDialog(Gtk.MessageDialog): + def __init__(self, parent=None): + Gtk.MessageDialog.__init__(self, + parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + ) + self.set_icon(ICON) + self.set_title('') + + self.set_markup('%s' % + _('Unknown gender specified')) + self.format_secondary_text( + _("The gender of the person is currently unknown. " + "Usually, this is a mistake. Please specify the gender.")) + + self.add_button(_('_Male'), Person.MALE) + self.add_button(_('_Female'), Person.FEMALE) + self.add_button(_('_Unknown'), Person.UNKNOWN) diff --git a/person-view-age-calculator/gramps/gui/editors/editperson.py.0 b/person-view-age-calculator/gramps/gui/editors/editperson.py.0 new file mode 100644 index 0000000..15f34ed --- /dev/null +++ b/person-view-age-calculator/gramps/gui/editors/editperson.py.0 @@ -0,0 +1,1104 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2009-2011 Gary Burton +# Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +EditPerson Dialog. Provide the interface to allow the Gramps program +to edit information about a particular Person. +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +from copy import copy +import pickle + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Pango + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +from gramps.gen.utils.file import media_path_full +from gramps.gen.utils.thumbnails import get_thumbnail_image +from ..utils import is_right_click, open_file_with_default_application +from gramps.gen.utils.db import get_birth_or_fallback +from gramps.gen.lib import NoteType, Person, Surname +from gramps.gen.db import DbTxn +from .. import widgets +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.errors import WindowActiveError +from ..glade import Glade +from ..ddtargets import DdTargets +from ..widgets.menuitem import add_menuitem + +from .editprimary import EditPrimary +from .editmediaref import EditMediaRef +from .editname import EditName +from gramps.gen.config import config +from ..dialog import ErrorDialog, ICON +from gramps.gen.errors import ValidationError + +from .displaytabs import (PersonEventEmbedList, NameEmbedList, CitationEmbedList, + AttrEmbedList, AddrEmbedList, NoteTab, GalleryTab, + WebEmbedList, PersonRefEmbedList, LdsEmbedList, + PersonBackRefList, SurnameTab) +from gramps.gen.plug import CATEGORY_QR_PERSON +from gramps.gen.const import URL_MANUAL_SECT1 +from gramps.gen.utils.id import create_id + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +WIKI_HELP_PAGE = URL_MANUAL_SECT1 + +_select_gender = ((True, False, False), + (False, True, False), + (False, False, True)) + +class SingSurn: + """ + Managing the single surname components + """ + def __init__(self, gladeobj): + self.top = gladeobj + + def hide_all(self): + #self.top.get_object('prefixlabel').hide() + self.top.get_object('prefix').hide() + self.top.get_object('surnamelabel').hide() + self.top.get_object('surname').hide() + self.top.get_object('originlabel').hide() + self.top.get_object('originlabel').hide() + self.top.get_object('cmborigin').hide() + self.top.get_object('multsurnamebtn').hide() + + def show_all(self): + #self.top.get_object('prefixlabel').show() + self.top.get_object('prefix').show() + self.top.get_object('surnamelabel').show() + self.top.get_object('surname').show() + self.top.get_object('originlabel').show() + self.top.get_object('originlabel').show() + self.top.get_object('cmborigin').show() + self.top.get_object('multsurnamebtn').show() + +class EditPerson(EditPrimary): + """ + The EditPerson dialog is derived from the EditPrimary class. + + It allows for the editing of the primary object type of Person. + + """ + + QR_CATEGORY = CATEGORY_QR_PERSON + + def __init__(self, dbstate, uistate, track, person, callback=None): + """ + Create an EditPerson window. + + Associate a person with the window. + + """ + EditPrimary.__init__(self, dbstate, uistate, track, person, + dbstate.db.get_person_from_handle, + dbstate.db.get_person_from_gramps_id, callback) + + def empty_object(self): + """ + Return an empty Person object for comparison for changes. + + This is used by the base class (EditPrimary). + + """ + person = Person() + #the editor requires a surname + person.primary_name.add_surname(Surname()) + person.primary_name.set_primary_surname(0) + return person + + def get_menu_title(self): + if self.obj and self.obj.get_handle(): + name = name_displayer.display(self.obj) + title = _('Person: %(name)s') % {'name': name} + else: + name = name_displayer.display(self.obj) + if name: + title = _('New Person: %(name)s') % {'name': name} + else: + title = _('New Person') + return title + + def get_preview_name(self): + prevname = name_displayer.display(self.obj) + return prevname + + def _local_init(self): + """ + Performs basic initialization, including setting up widgets and the + glade interface. + + Local initialization function. + This is called by the base class of EditPrimary, and overridden here. + + """ + self.pname = self.obj.get_primary_name() + self.should_guess_gender = (not self.obj.get_gramps_id() and + self.obj.get_gender () == + Person.UNKNOWN) + + self.added = self.obj.handle is None + if self.added: + self.obj.handle = create_id() + + self.top = Glade() + + self.set_window(self.top.toplevel, None, + self.get_menu_title()) + self.setup_configs('interface.person', 750, 550) + + self.obj_photo = self.top.get_object("personPix") + self.frame_photo = self.top.get_object("frame5") + self.eventbox = self.top.get_object("eventbox1") + self.singsurnfr = SingSurn(self.top) + self.multsurnfr = self.top.get_object("hboxmultsurnames") + self.singlesurn_active = True + + self.set_contexteventbox(self.top.get_object("eventboxtop")) + + def _post_init(self): + """ + Handle any initialization that needs to be done after the interface is + brought up. + + Post initalization function. + This is called by _EditPrimary's init routine, and overridden in the + derived class (this class). + + """ + self.load_person_image() + self.given.grab_focus() + self._changed_name(None) + self.top.get_object("hboxmultsurnames").pack_start(self.surntab, True, True, 0) + + if len(self.obj.get_primary_name().get_surname_list()) > 1: + self.multsurnfr.set_size_request(-1, + int(config.get('interface.surname-box-height'))) + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + self.singlesurn_active = False + else: + self.multsurnfr.hide() + self.singsurnfr.show_all() + self.singlesurn_active = True + #if self.pname.get_surname() and not self.pname.get_first_name(): + # self.given.grab_focus() + #else: + # self.surname_field.grab_focus() + + def _connect_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self.define_cancel_button(self.top.get_object("button15")) + self.define_ok_button(self.top.get_object("ok"), self.save) + self.define_help_button(self.top.get_object("button134"), + WIKI_HELP_PAGE, + _('manual|Editing_information_about_people')) + + self.given.connect("focus-out-event", self._given_focus_out_event) + self.top.get_object("editnamebtn").connect("clicked", + self._edit_name_clicked) + self.top.get_object("multsurnamebtn").connect("clicked", + self._mult_surn_clicked) + + self.eventbox.connect('button-press-event', + self._image_button_press) + # allow to initiate a drag-and-drop with this person if it has a handle + if self.added: + return # Avoid HandleError if dragging an objet not in db yet + self.contexteventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, + [DdTargets.PERSON_LINK.target()], + Gdk.DragAction.COPY) + self.contexteventbox.drag_source_set_icon_name('gramps-person') + self.contexteventbox.connect('drag_data_get', self._top_drag_data_get) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + self._add_db_signal('person-rebuild', self._do_close) + self._add_db_signal('person-delete', self.check_for_close) + self._add_db_signal('family-rebuild', self.family_change) + self._add_db_signal('family-delete', self.family_change) + self._add_db_signal('family-update', self.family_change) + self._add_db_signal('family-add', self.family_change) + self._add_db_signal('event-rebuild', self.event_updated) + + def family_change(self, handle_list=[]): + """ + Callback for family change signals. + + This should rebuild the + backreferences to family in person when: + 1)a family the person is parent of changes. Person could have + been removed + 2)a family the person is child in changes. Child could have been + removed + 3)a family is changed. The person could be added as child or + parent + + """ + #As this would be an extensive check, we choose the easy path and + # rebuild family backreferences on all family changes + self._update_families() + + def _update_families(self): + phandle = self.obj.get_handle() + if self.dbstate.db.has_person_handle(phandle): + #new person has no handle yet and cannot be in a family. + person = self.dbstate.db.get_person_from_handle(phandle) + self.obj.set_family_handle_list(person.get_family_handle_list()) + self.obj.set_parent_family_handle_list( + person.get_parent_family_handle_list()) + #a family groupname in event_list might need to be changed + # we just rebuild the view always + self.event_list.rebuild_callback() + + def event_updated(self, obj): + #place in event might have changed, or person event shown in the list + # we just rebuild the view always + self.event_list.rebuild_callback() + + def _validate_call(self, widget, text): + """ a callname must be a part of the given name, see if this is the + case """ + validcall = self.given.obj.get_text().split() + dummy = copy(validcall) + for item in dummy: + validcall += item.split('-') + if text in validcall: + return + return ValidationError(_("Call name must be the given name that " + "is normally used.")) + + def _setup_fields(self): + """ + Connect the GrampsWidget objects to field in the interface. + + This allows the widgets to keep the data in the attached Person object + up to date at all times, eliminating a lot of need in 'save' routine. + + """ + + self.private = widgets.PrivacyButton( + self.top.get_object('private'), + self.obj, + self.db.readonly) + + self.gender = widgets.MonitoredMenu( + self.top.get_object('gender'), + self.obj.set_gender, + self.obj.get_gender, + ( + (_('female'), Person.FEMALE), + (_('male'), Person.MALE), + (_('unknown'), Person.UNKNOWN) + ), + self.db.readonly) + + self.ntype_field = widgets.MonitoredDataType( + self.top.get_object("ntype"), + self.pname.set_type, + self.pname.get_type, + self.db.readonly, + self.db.get_name_types()) + + #part of Given Name section + self.given = widgets.MonitoredEntry( + self.top.get_object("given_name"), + self.pname.set_first_name, + self.pname.get_first_name, + self.db.readonly) + + self.call = widgets.MonitoredEntry( + self.top.get_object("call"), + self.pname.set_call_name, + self.pname.get_call_name, + self.db.readonly) + self.call.connect("validate", self._validate_call) + #force validation now with initial entry + self.call.obj.validate(force=True) + + self.title = widgets.MonitoredEntry( + self.top.get_object("title"), + self.pname.set_title, + self.pname.get_title, + self.db.readonly) + + self.suffix = widgets.MonitoredEntryIndicator( + self.top.get_object("suffix"), + self.pname.set_suffix, + self.pname.get_suffix, + _('suffix'), + self.db.readonly) + + self.nick = widgets.MonitoredEntry( + self.top.get_object("nickname"), + self.pname.set_nick_name, + self.pname.get_nick_name, + self.db.readonly) + + #part of Single Surname section + self.surname_field = widgets.MonitoredEntry( + self.top.get_object("surname"), + self.pname.get_primary_surname().set_surname, + self.pname.get_primary_surname().get_surname, + self.db.readonly, + autolist=self.db.get_surname_list() if not self.db.readonly else []) + + self.prefix = widgets.MonitoredEntryIndicator( + self.top.get_object("prefix"), + self.pname.get_primary_surname().set_prefix, + self.pname.get_primary_surname().get_prefix, + _('prefix'), + self.db.readonly) + + self.ortype_field = widgets.MonitoredDataType( + self.top.get_object("cmborigin"), + self.pname.get_primary_surname().set_origintype, + self.pname.get_primary_surname().get_origintype, + self.db.readonly, + self.db.get_origin_types()) + + #other fields + + self.tags = widgets.MonitoredTagList( + self.top.get_object("tag_label"), + self.top.get_object("tag_button"), + self.obj.set_tag_list, + self.obj.get_tag_list, + self.db, + self.uistate, self.track, + self.db.readonly) + + self.gid = widgets.MonitoredEntry( + self.top.get_object("gid"), + self.obj.set_gramps_id, + self.obj.get_gramps_id, + self.db.readonly) + + #make sure title updates automatically + for obj in [self.top.get_object("given_name"), + self.top.get_object("nickname"), + self.top.get_object("call"), + self.top.get_object("suffix"), + self.top.get_object("prefix"), + self.top.get_object("surname"), + ]: + obj.connect('changed', self._changed_name) + + self.preview_name = self.top.get_object("full_name") + self.preview_name.override_font(Pango.FontDescription('sans bold 12')) + self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, + self.obj.get_primary_name(), + on_change=self._changed_name) + + def get_start_date(self): + """ + Get the start date for a person, usually a birth date, or + something close to birth. + """ + event = get_birth_or_fallback(self.dbstate.db, self.obj) + return event.get_date_object() if event else None + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and insert them into the main window. + """ + notebook = Gtk.Notebook() + notebook.set_scrollable(True) + + self.event_list = PersonEventEmbedList( + self.dbstate, + self.uistate, + self.track, + self.obj, + start_date=self.get_start_date()) + + self._add_tab(notebook, self.event_list) + self.track_ref_for_deletion("event_list") + + self.name_list = NameEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_alternate_names(), + self.obj, + self.name_callback) + self._add_tab(notebook, self.name_list) + self.track_ref_for_deletion("name_list") + + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self.attr_list = AttrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_attribute_list()) + self._add_tab(notebook, self.attr_list) + self.track_ref_for_deletion("attr_list") + + self.addr_list = AddrEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_address_list()) + self._add_tab(notebook, self.addr_list) + self.track_ref_for_deletion("addr_list") + + self.note_tab = NoteTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_note_list(), + self.get_menu_title(), + notetype=NoteType.PERSON) + self._add_tab(notebook, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self.gallery_tab = GalleryTab(self.dbstate, + self.uistate, + self.track, + self.obj.get_media_list(), + self.load_person_image) + self._add_tab(notebook, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.web_list = WebEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_url_list()) + self._add_tab(notebook, self.web_list) + self.track_ref_for_deletion("web_list") + + self.person_ref_list = PersonRefEmbedList(self.dbstate, self.uistate, + self.track, + self.obj.get_person_ref_list()) + self._add_tab(notebook, self.person_ref_list) + self.track_ref_for_deletion("person_ref_list") + + self.lds_list = LdsEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_lds_ord_list()) + self._add_tab(notebook, self.lds_list) + self.track_ref_for_deletion("lds_list") + + self.backref_tab = PersonBackRefList(self.dbstate, + self.uistate, + self.track, + self.db.find_backlink_handles(self.obj.handle)) + self._add_tab(notebook, self.backref_tab) + self.track_ref_for_deletion("backref_tab") + + self._setup_notebook_tabs(notebook) + notebook.show_all() + self.top.get_object('vbox').pack_start(notebook, True, True, 0) + + def _changed_name(self, *obj): + """ + callback to changes typed by user to the person name. + Update the window title, and default name in name tab + """ + self.update_title(self.get_menu_title()) + self.preview_name.set_text(self.get_preview_name()) + self.name_list.update_defname() + + def name_callback(self): + """ + Callback if changes happen in the name tab that impact the preferred + name. + """ + self.pname = self.obj.get_primary_name() + + self.ntype_field.reinit(self.pname.set_type, self.pname.get_type) + self.given.reinit(self.pname.set_first_name, self.pname.get_first_name) + self.call.reinit(self.pname.set_call_name, self.pname.get_call_name) + self.title.reinit(self.pname.set_title, self.pname.get_title) + self.suffix.reinit(self.pname.set_suffix, self.pname.get_suffix) + self.nick.reinit(self.pname.set_nick_name, self.pname.get_nick_name) + #part of Single Surname section + self.surname_field.reinit( + self.pname.get_primary_surname().set_surname, + self.pname.get_primary_surname().get_surname) + + self.prefix.reinit( + self.pname.get_primary_surname().set_prefix, + self.pname.get_primary_surname().get_prefix) + + self.ortype_field.reinit( + self.pname.get_primary_surname().set_origintype, + self.pname.get_primary_surname().get_origintype) + + self.__renewmultsurnames() + + if len(self.pname.get_surname_list()) == 1: + self.singlesurn_active = True + else: + self.singlesurn_active = False + if self.singlesurn_active: + self.multsurnfr.hide() + self.singsurnfr.show_all() + else: + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + + def build_menu_names(self, person): + """ + Provide the information needed by the base class to define the + window management menu entries. + """ + return (_('Edit Person'), self.get_menu_title()) + + def _image_button_press(self, obj, event): + """ + Button press event that is caught when a button has been pressed while + on the image on the main form. + + This does not apply to the images in galleries, just the image on the + main form. + + """ + if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS + and event.button == 1): + + media_list = self.obj.get_media_list() + if media_list: + media_ref = media_list[0] + object_handle = media_ref.get_reference_handle() + media_obj = self.db.get_media_from_handle(object_handle) + + try: + EditMediaRef(self.dbstate, self.uistate, self.track, + media_obj, media_ref, self.load_photo) + except WindowActiveError: + pass + + elif is_right_click(event): + media_list = self.obj.get_media_list() + if media_list: + photo = media_list[0] + self._show_popup(photo, event) + #do not propagate further: + return True + + def _show_popup(self, photo, event): + """ + Look for right-clicks on a picture and create a popup menu of the + available actions. + """ + self.imgmenu = Gtk.Menu() + menu = self.imgmenu + menu.set_title(_("Media Object")) + obj = self.db.get_media_from_handle(photo.get_reference_handle()) + if obj: + add_menuitem(menu, _("View"), photo, + self._popup_view_photo) + add_menuitem(menu, _("Edit Object Properties"), photo, + self._popup_change_description) + menu.popup(None, None, None, None, event.button, event.time) + + def _popup_view_photo(self, obj): + """ + Open this picture in the default picture viewer. + """ + media_list = self.obj.get_media_list() + if media_list: + photo = media_list[0] + object_handle = photo.get_reference_handle() + ref_obj = self.db.get_media_from_handle(object_handle) + photo_path = media_path_full(self.db, ref_obj.get_path()) + open_file_with_default_application(photo_path, self.uistate) + + def _popup_change_description(self, obj): + """ + Bring up the EditMediaRef dialog for the image on the main form. + """ + media_list = self.obj.get_media_list() + if media_list: + media_ref = media_list[0] + object_handle = media_ref.get_reference_handle() + media_obj = self.db.get_media_from_handle(object_handle) + EditMediaRef(self.dbstate, self.uistate, self.track, + media_obj, media_ref, self.load_photo) + + def _top_contextmenu(self, prefix): + """ + Override from base class, the menuitems and actiongroups for the top + of context menu. + """ + if self.added: + # Don't add items if not a real person yet + return '', [] + + _actions = [('ActivePerson', self._make_active), + ('HomePerson', self._make_home_person)] + + ui_top_cm = ( + ''' + + {prefix}.ActivePerson + Make Active Person''' + ''' + + + {prefix}.HomePerson + Make Home Person''' + ''' + + '''.format(prefix=prefix)) + + return ui_top_cm, _actions + + def _top_drag_data_get(self, widget, context, sel_data, info, time): + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, id(self), self.obj.get_handle(), 0) + sel_data.set(DdTargets.PERSON_LINK.atom_drag_type, 8, pickle.dumps(data)) + + def _post_build_popup_ui(self): + """ + Override base class, make inactive home action if not needed. + """ + if self.added: + return + home_action = self.uistate.uimanager.get_action(self.action_group, + 'HomePerson') + if (self.dbstate.db.get_default_person() and + self.obj.get_handle() == + self.dbstate.db.get_default_person().get_handle()): + home_action.set_enabled(False) + else: + home_action.set_enabled(True) + + def _make_active(self, obj, value): + self.uistate.set_active(self.obj.get_handle(), 'Person') + + def _make_home_person(self, obj, value): + handle = self.obj.get_handle() + if handle: + self.dbstate.db.set_default_person_handle(handle) + + def _given_focus_out_event (self, entry, event): + """ + Callback that occurs when the user leaves the given name field, + allowing us to attempt to guess the gender of the person if + so requested. + """ + if not self.should_guess_gender: + return False + try: + gender_type = self.db.genderStats.guess_gender( + str(entry.get_text())) + self.gender.force(gender_type) + except: + return False + return False + + def _check_for_unknown_gender(self): + if self.obj.get_gender() == Person.UNKNOWN: + d = GenderDialog(parent=self.window) + gender = d.run() + d.destroy() + if gender >= 0: + self.obj.set_gender(gender) + + def _update_family_ids(self): + # Update each of the families child lists to reflect any + # change in ordering due to the new birth date + family = self.obj.get_main_parents_family_handle() + if (family): + f = self.db.get_family_from_handle(family) + new_order = self.reorder_child_ref_list(self.obj, + f.get_child_ref_list()) + f.set_child_ref_list(new_order) + for family in self.obj.get_parent_family_handle_list(): + f = self.db.get_family_from_handle(family) + new_order = self.reorder_child_ref_list(self.obj, + f.get_child_ref_list()) + f.set_child_ref_list(new_order) + + error = False + if self.original: + (female, male, unknown) = _select_gender[self.obj.get_gender()] + if male and self.original.get_gender() != Person.MALE: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_mother_handle(): + if temp_family.get_father_handle() is not None: + error = True + else: + temp_family.set_mother_handle(None) + temp_family.set_father_handle(self.obj) + elif female and self.original != Person.FEMALE: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_father_handle(): + if temp_family.get_mother_handle() is not None: + error = True + else: + temp_family.set_father_handle(None) + temp_family.set_mother_handle(self.obj) + elif unknown and self.original.get_gender() != Person.UNKNOWN: + for tmp_handle in self.obj.get_family_handle_list(): + temp_family = self.db.get_family_from_handle(tmp_handle) + if self.obj == temp_family.get_father_handle(): + if temp_family.get_mother_handle() is not None: + error = True + else: + temp_family.set_father_handle(None) + temp_family.set_mother_handle(self.obj) + if self.obj == temp_family.get_mother_handle(): + if temp_family.get_father_handle() is not None: + error = True + else: + temp_family.set_mother_handle(None) + temp_family.set_father_handle(self.obj) + + if error: + msg2 = _("Problem changing the gender") + msg = _("Changing the gender caused problems " + "with marriage information.\nPlease check " + "the person's marriages.") + ErrorDialog(msg2, msg, parent=self.window) + + def save(self, *obj): + """ + Save the data. + """ + self.ok_button.set_sensitive(False) + if self.object_is_empty(): + ErrorDialog(_("Cannot save person"), + _("No data exists for this person. Please " + "enter data or cancel the edit."), + parent=self.window) + self.ok_button.set_sensitive(True) + return + # fix surname problems + for name in [self.obj.get_primary_name()] + self.obj.get_alternate_names(): + if len(name.get_surname_list()) > 1: + newlist = [surn for surn in name.get_surname_list() if not surn.is_empty()] + if len(newlist) != len(name.get_surname_list()): + name.set_surname_list(newlist) + if len(name.get_surname_list()) == 0: + name.set_surname_list([Surname()]) + try: + primind = [surn.get_primary() for surn in name.get_surname_list()].index(True) + except ValueError: + primind = 0 # no match + name.set_primary_surname(primind) + + # fix ide problems + (uses_dupe_id, id) = self._uses_duplicate_id() + if uses_dupe_id: + prim_object = self.get_from_gramps_id(id) + name = self.name_displayer.display(prim_object) + msg1 = _("Cannot save person. ID already exists.") + msg2 = _("You have attempted to use the existing Gramps ID with " + "value %(id)s. This value is already used by '" + "%(prim_object)s'. Please enter a different ID or leave " + "blank to get the next available ID value.") % { + 'id' : id, 'prim_object' : name } + ErrorDialog(msg1, msg2, parent=self.window) + self.ok_button.set_sensitive(True) + return + + self._check_for_unknown_gender() + + self.db.set_birth_death_index(self.obj) + + if not self.obj.handle: + with DbTxn(_("Add Person (%s)") % \ + self.name_displayer.display(self.obj), + self.db) as trans: + self.db.add_person(self.obj, trans) + else: + if self.data_has_changed(): + with DbTxn(_("Edit Person (%s)") % \ + self.name_displayer.display(self.obj), + self.db) as trans: + if not self.obj.get_gramps_id(): + self.obj.set_gramps_id(self.db.find_next_person_gramps_id()) + self.db.commit_person(self.obj, trans) + + self._do_close() + if self.callback: + self.callback(self.obj) + self.callback = None + + def _edit_name_clicked(self, obj): + """ + Bring up the EditName dialog for this name. + + Called when the edit name button is clicked for the primary name + on the main form (not in the names tab). + + """ + try: + EditName(self.dbstate, self.uistate, self.track, + self.pname, self._update_name) + except WindowActiveError: + pass + + def _mult_surn_clicked(self, obj): + """ + Show the list entry of multiple surnames + """ + self.singsurnfr.hide_all() + self.singlesurn_active = False + #update multsurnfr for possible changes + self.__renewmultsurnames() + self.multsurnfr.show_all() + + def _update_name(self, name): + """ + Called when the primary name has been changed by the EditName + dialog. + + This allows us to update the main form in response to any changes. + + """ + for obj in (self.ntype_field, self.given, self.call, self.title, + self.suffix, self.nick, self.surname_field, self.prefix, + self.ortype_field): + obj.update() + + self.__renewmultsurnames() + + if len(self.obj.get_primary_name().get_surname_list()) == 1: + self.singlesurn_active = True + else: + self.singlesurn_active = False + if self.singlesurn_active: + self.multsurnfr.hide() + self.singsurnfr.show_all() + + else: + self.singsurnfr.hide_all() + self.multsurnfr.show_all() + + def __renewmultsurnames(self): + """Update mult surnames section with what is presently the + correct surname. + It is easier to recreate the entire mult surnames GUI than + changing what has changed visually. + """ + #remove present surname tab, and put new one + msurhbox = self.top.get_object("hboxmultsurnames") + msurhbox.remove(self.surntab) + self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, + self.obj.get_primary_name(), + on_change=self._changed_name) + self.multsurnfr.set_size_request(-1, + int(config.get('interface.surname-box-height'))) + msurhbox.pack_start(self.surntab, True, True, 0) + + def load_person_image(self): + """ + Load the primary image into the main form if it exists. + + Used as callback on Gallery Tab too. + + """ + media_list = self.obj.get_media_list() + if media_list: + ref = media_list[0] + handle = ref.get_reference_handle() + obj = self.dbstate.db.get_media_from_handle(handle) + if obj is None : + #notify user of error + from ..dialog import RunDatabaseRepair + RunDatabaseRepair( + _('Non existing media found in the Gallery'), + parent=self.window) + else : + self.load_photo(ref, obj) + else: + self.obj_photo.hide() + self.frame_photo.hide() + + def load_photo(self, ref, obj): + """ + Load the person's main photo using the Thumbnailer. + """ + pixbuf = get_thumbnail_image( + media_path_full(self.dbstate.db, + obj.get_path()), + obj.get_mime_type(), + ref.get_rectangle()) + + self.obj_photo.set_from_pixbuf(pixbuf) + self.obj_photo.show() + self.frame_photo.show_all() + + def birth_dates_in_order(self, child_ref_list): + """ + Check any *valid* birthdates in the list to insure that they are in + numerically increasing order. + """ + inorder = True + prev_date = 0 + handle_list = [ref.ref for ref in child_ref_list] + for i in range(len(handle_list)): + child_handle = handle_list[i] + child = self.db.get_person_from_handle(child_handle) + if child.get_birth_ref(): + event_handle = child.get_birth_ref().ref + event = self.db.get_event_from_handle(event_handle) + child_date = event.get_date_object().get_sort_value() + else: + continue + if (prev_date <= child_date): # <= allows for twins + prev_date = child_date + else: + inorder = False + return inorder + + def reorder_child_ref_list(self, person, child_ref_list): + """ + Reorder the child list to put the specified person in his/her + correct birth order. + + Only check *valid* birthdates. Move the person as short a distance as + possible. + + """ + + if self.birth_dates_in_order(child_ref_list): + return(child_ref_list) + + # Build the person's date string once + event_ref = person.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + person_bday = event.get_date_object().get_sort_value() + else: + person_bday = 0 + + # First, see if the person needs to be moved forward in the list + handle_list = [ref.ref for ref in child_ref_list] + + index = handle_list.index(person.get_handle()) + target = index + for i in range(index-1, -1, -1): + other = self.db.get_person_from_handle(handle_list[i]) + event_ref = other.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + other_bday = event.get_date_object().get_sort_value() + if other_bday == 0: + continue + if person_bday < other_bday: + target = i + else: + continue + + # Now try moving to a later position in the list + if (target == index): + for i in range(index, len(handle_list)): + other = self.db.get_person_from_handle(handle_list[i]) + event_ref = other.get_birth_ref() + if event_ref: + event = self.db.get_event_from_handle(event_ref.ref) + other_bday = event.get_date_object().get_sort_value() + if other_bday == "99999999": + continue + if person_bday > other_bday: + target = i + else: + continue + + # Actually need to move? Do it now. + if (target != index): + ref = child_ref_list.pop(index) + child_ref_list.insert(target, ref) + return child_ref_list + + def _cleanup_on_exit(self): + """Unset all things that can block garbage collection. + Finalize rest + """ +## self.private.destroy() +## self.gender.destroy() +## self.ntype_field.destroy() +## self.given.destroy() +## self.call.destroy() +## self.title.destroy() +## self.suffix.destroy() +## self.nick.destroy() +## self.surname_field.destroy() +## self.prefix.destroy() +## self.ortype_field.destroy() +## self.tags.destroy() +## self.gid.destroy() + EditPrimary._cleanup_on_exit(self) + #config.save() + + +class GenderDialog(Gtk.MessageDialog): + def __init__(self, parent=None): + Gtk.MessageDialog.__init__(self, + parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + ) + self.set_icon(ICON) + self.set_title('') + + self.set_markup('%s' % + _('Unknown gender specified')) + self.format_secondary_text( + _("The gender of the person is currently unknown. " + "Usually, this is a mistake. Please specify the gender.")) + + self.add_button(_('_Male'), Person.MALE) + self.add_button(_('_Female'), Person.FEMALE) + self.add_button(_('_Unknown'), Person.UNKNOWN) diff --git a/person-view-age-calculator/gramps/gui/editors/editperson.py.patch b/person-view-age-calculator/gramps/gui/editors/editperson.py.patch new file mode 100644 index 0000000..3ebb91a --- /dev/null +++ b/person-view-age-calculator/gramps/gui/editors/editperson.py.patch @@ -0,0 +1,24 @@ +80a81,84 +> from gramps.gen.lib.date import Date, Today +> from gramps.gen.datehandler import displayer as date_displayer +> from datetime import date +> +441a446,463 +> +> # BEGIN: Added by Michael J Becker 2020-09-23 02:25:48 -0500 >>> _setup_fields(self) +> self.age_label = self.top.get_object("age_label") +> self.age_label.set_label("Age: %s" % self.get_age()) +> +> def get_age(self): +> """ +> Get the age of the person formatted as a string, if possible. +> """ +> age_precision = config.get('preferences.age-display-precision') +> thedate = Today() +> if thedate and self.get_start_date(): +> return (thedate - self.get_start_date()).format(precision=age_precision) +> else: +> return "" +> +> # END: Added by Michael J Becker 2020-09-23 02:25:48 -0500 <<< get_start_date(self) +> diff --git a/person-view-age-calculator/gramps/gui/glade/editperson.glade b/person-view-age-calculator/gramps/gui/glade/editperson.glade new file mode 100644 index 0000000..17a9839 --- /dev/null +++ b/person-view-age-calculator/gramps/gui/glade/editperson.glade @@ -0,0 +1,823 @@ + + + + + + + True + False + gtk-edit + + + Edit + + + + + True + False + list-add + + + Add + + + + + False + True + dialog + + + + True + False + True + vertical + + + True + False + start + end + + + _Cancel + False + True + True + True + False + Abandon changes and close window + True + bottom + + + + False + False + 0 + + + + + _OK + False + True + True + True + False + Accept changes and close window + True + + + + False + False + 1 + + + + + _Help + False + True + True + True + False + True + + + + False + False + 2 + + + + + False + False + end + 0 + + + + + True + False + start + False + + + True + False + start + 3 + vertical + + + True + False + start + 4 + 3 + + + True + False + start + 3 + 3 + 3 + 3 + _Given: + True + center + given_name + + + 2 + 3 + + + + + True + True + True + True + The person's given names + + + + + 3 + 3 + 4 + + + + + True + False + start + C_all: + True + call + + + 6 + 2 + + + + + 75 + True + True + Part of the Given name that is the normally used name. If background is red, call name is not part of Given name and will not be printed underlined in some reports. + + + + 7 + 2 + 2 + + + + + True + False + start + 3 + 3 + 3 + 3 + T_itle: + True + title + + + 2 + 2 + + + + + 100 + True + True + A title used to refer to the person, such as 'Dr.' or 'Rev.' + True + + + + 3 + 2 + + + + + 75 + True + True + An optional suffix to the name, such as "Jr." or "III" + + + + Suffix + + + + + 7 + 3 + + + + + True + False + start + 3 + 3 + _Nick: + True + nickname + + + 4 + 2 + + + + + 100 + True + True + A descriptive name given in place of or in addition to the official given name. + True + + + + 5 + 2 + + + + + 114 + 114 + True + False + 2.2351741291171123e-10 + + + True + False + + + True + False + start + + + + + + + True + False + Image + + + + + + + + Image + + + + + 0 + 0 + 2 + 4 + + + + + 100 + True + False + An identification of what type of Name this is, eg. Birth Name, Married Name. + True + + + True + True + + + + + 7 + 1 + 2 + + + + + True + False + Click on a table cell to edit. + + + + + + + + + 2 + 5 + 6 + + + + + False + True + True + True + Use Multiple Surnames +Indicate that the surname consists of different parts. Every surname has its own prefix and a possible connector to the next surname. Eg., the surname Ramón y Cajal can be stored as Ramón, which is inherited from the father, the connector y, and Cajal, which is inherited from the mother. + image2 + True + + + Add + + + + + + 8 + 4 + + + + + True + False + + + True + False + start + 3 + 3 + General + + + + + + False + False + 0 + + + + + False + True + True + True + Set person as private data + end + none + + + True + False + 16 + dialog-password + + + Privacy + + + + + + + Private + + + + + + False + False + end + 1 + + + + + 0 + 6 + 9 + + + + + True + False + 6 + + + True + False + start + 6 + 6 + 3 + 3 + _Surname: + True + surname + + + False + True + 3 + 0 + + + + + 70 + True + True + An optional prefix for the family that is not used in sorting, such as "de" or "van". + + + + Prefix + + + + + False + True + 1 + + + + + 150 + True + True + Part of a person's name indicating the family to which the person belongs + True + + + + + + + True + True + 2 + + + + + 0 + 4 + 6 + + + + + False + True + True + True + Go to Name Editor to add more information about this name + image1 + + + + Edit + + + + + + 8 + 3 + + + + + True + False + 6 + + + True + False + start + O_rigin: + True + cmborigin + + + False + True + 0 + + + + + 75 + True + False + The origin of this family name for this family, eg 'Inherited' or 'Patronymic'. + True + + + True + True + + + + + True + True + 1 + + + + + 6 + 4 + 2 + + + + + True + False + 6 + + + True + False + start + 6 + 6 + G_ender: + True + gender + + + False + True + 0 + + + + + True + False + + + + 0 + + + + + False + True + 1 + + + + + True + False + start + _ID: + True + right + gid + + + False + True + 2 + + + + + 75 + True + True + A unique ID for the person. + + 6 + + + False + True + 3 + + + + + True + False + start + 6 + 6 + _Tags: + True + tag_button + + + True + True + 4 + + + + + True + False + start + 6 + 6 + Age: + + + True + True + 4 + + + + + True + False + + + True + True + 5 + + + + + False + True + True + True + end + + + + + + Tags + + + + + False + False + end + 6 + + + + + 0 + 7 + 9 + + + + + True + False + start + 3 + 3 + Preferred Name + + + + + + 2 + 0 + + + + + True + False + start + 3 + 3 + 3 + 3 + _Type: + True + center + ntype + + + 6 + 1 + + + + + True + False + start + 3 + 3 + 3 + 3 + True + True + end + + + 2 + 1 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + False + 2 + 0 + + + + + + + False + False + 1 + + + + + + button15 + ok + button134 + + + diff --git a/person-view-age-calculator/gramps/gui/glade/editperson.glade.0 b/person-view-age-calculator/gramps/gui/glade/editperson.glade.0 new file mode 100644 index 0000000..77f4578 --- /dev/null +++ b/person-view-age-calculator/gramps/gui/glade/editperson.glade.0 @@ -0,0 +1,808 @@ + + + + + + + True + False + gtk-edit + + + Edit + + + + + True + False + list-add + + + Add + + + + + False + True + dialog + + + + True + False + True + vertical + + + True + False + start + end + + + _Cancel + False + True + True + True + False + Abandon changes and close window + True + bottom + + + + False + False + 0 + + + + + _OK + False + True + True + True + False + Accept changes and close window + True + + + + False + False + 1 + + + + + _Help + False + True + True + True + False + True + + + + False + False + 2 + + + + + False + False + end + 0 + + + + + True + False + start + False + + + True + False + start + 3 + vertical + + + True + False + start + 4 + 3 + + + True + False + start + 3 + 3 + 3 + 3 + _Given: + True + center + given_name + + + 2 + 3 + + + + + True + True + True + True + The person's given names + + + + + 3 + 3 + 4 + + + + + True + False + start + C_all: + True + call + + + 6 + 2 + + + + + 75 + True + True + Part of the Given name that is the normally used name. If background is red, call name is not part of Given name and will not be printed underlined in some reports. + + + + 7 + 2 + 2 + + + + + True + False + start + 3 + 3 + 3 + 3 + T_itle: + True + title + + + 2 + 2 + + + + + 100 + True + True + A title used to refer to the person, such as 'Dr.' or 'Rev.' + True + + + + 3 + 2 + + + + + 75 + True + True + An optional suffix to the name, such as "Jr." or "III" + + + + Suffix + + + + + 7 + 3 + + + + + True + False + start + 3 + 3 + _Nick: + True + nickname + + + 4 + 2 + + + + + 100 + True + True + A descriptive name given in place of or in addition to the official given name. + True + + + + 5 + 2 + + + + + 114 + 114 + True + False + 2.2351741291171123e-10 + + + True + False + + + True + False + start + + + + + + + True + False + Image + + + + + + + + Image + + + + + 0 + 0 + 2 + 4 + + + + + 100 + True + False + An identification of what type of Name this is, eg. Birth Name, Married Name. + True + + + True + True + + + + + 7 + 1 + 2 + + + + + True + False + Click on a table cell to edit. + + + + + + + + + 2 + 5 + 6 + + + + + False + True + True + True + Use Multiple Surnames +Indicate that the surname consists of different parts. Every surname has its own prefix and a possible connector to the next surname. Eg., the surname Ramón y Cajal can be stored as Ramón, which is inherited from the father, the connector y, and Cajal, which is inherited from the mother. + image2 + True + + + Add + + + + + + 8 + 4 + + + + + True + False + + + True + False + start + 3 + 3 + General + + + + + + False + False + 0 + + + + + False + True + True + True + Set person as private data + end + none + + + True + False + 16 + dialog-password + + + Privacy + + + + + + + Private + + + + + + False + False + end + 1 + + + + + 0 + 6 + 9 + + + + + True + False + 6 + + + True + False + start + 6 + 6 + 3 + 3 + _Surname: + True + surname + + + False + True + 3 + 0 + + + + + 70 + True + True + An optional prefix for the family that is not used in sorting, such as "de" or "van". + + + + Prefix + + + + + False + True + 1 + + + + + 150 + True + True + Part of a person's name indicating the family to which the person belongs + True + + + + + + + True + True + 2 + + + + + 0 + 4 + 6 + + + + + False + True + True + True + Go to Name Editor to add more information about this name + image1 + + + + Edit + + + + + + 8 + 3 + + + + + True + False + 6 + + + True + False + start + O_rigin: + True + cmborigin + + + False + True + 0 + + + + + 75 + True + False + The origin of this family name for this family, eg 'Inherited' or 'Patronymic'. + True + + + True + True + + + + + True + True + 1 + + + + + 6 + 4 + 2 + + + + + True + False + 6 + + + True + False + start + 6 + 6 + G_ender: + True + gender + + + False + True + 0 + + + + + True + False + + + + 0 + + + + + False + True + 1 + + + + + True + False + start + _ID: + True + right + gid + + + False + True + 2 + + + + + 75 + True + True + A unique ID for the person. + + 6 + + + False + True + 3 + + + + + True + False + start + 6 + 6 + _Tags: + True + tag_button + + + False + True + 4 + + + + + True + False + + + True + True + 5 + + + + + False + True + True + True + end + + + + + + Tags + + + + + False + False + end + 6 + + + + + 0 + 7 + 9 + + + + + True + False + start + 3 + 3 + Preferred Name + + + + + + 2 + 0 + + + + + True + False + start + 3 + 3 + 3 + 3 + _Type: + True + center + ntype + + + 6 + 1 + + + + + True + False + start + 3 + 3 + 3 + 3 + True + True + end + + + 2 + 1 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + False + 2 + 0 + + + + + + + False + False + 1 + + + + + + button15 + ok + button134 + + + diff --git a/person-view-age-calculator/gramps/gui/glade/editperson.glade.patch b/person-view-age-calculator/gramps/gui/glade/editperson.glade.patch new file mode 100644 index 0000000..d6a7fd0 --- /dev/null +++ b/person-view-age-calculator/gramps/gui/glade/editperson.glade.patch @@ -0,0 +1,19 @@ +655c655,670 +< False +--- +> True +> True +> 4 +> +> +> +> +> True +> False +> start +> 6 +> 6 +> Age: +> +> +> True