From 6e6eace16bf88f07d2169e22bdf6c9ad716d49c7 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 29 Dec 2019 16:46:25 +0000 Subject: [PATCH 01/38] Add an "Actions" button the the FormGramplet window --- Form/formgramplet.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Form/formgramplet.py b/Form/formgramplet.py index 2a2a4fc0e..bd97d12b8 100644 --- a/Form/formgramplet.py +++ b/Form/formgramplet.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2009-2015 Nick Hall +# Copyright (C) 2019 Steve Youngs # # 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 @@ -130,6 +131,10 @@ def __create_gui(self): edit.connect("clicked", self.__edit_form, view.get_selection()) button_box.add(edit) + actions = Gtk.Button(label=_('_Actions'), use_underline=True) + actions.connect("clicked", self.__form_actions, view.get_selection()) + button_box.add(actions) + vbox.pack_start(view, expand=True, fill=True, padding=0) vbox.pack_end(button_box, expand=False, fill=True, padding=4) @@ -170,6 +175,18 @@ def __edit_form(self, widget, selection): except WindowActiveError: pass + def __form_actions(self, widget, selection): + """ + Display actions for the selected form. + """ + model, iter_ = selection.get_selected() + if iter_: + citation = model.get_value(iter_, 0) + try: + pass + except WindowActiveError: + pass + def main(self): """ Called to update the display. From 65e0b2e6990197db85e362f29c42356ebdc60c8a Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 29 Dec 2019 16:59:16 +0000 Subject: [PATCH 02/38] Add FormActions dialog. This dialog present a list of possible actions to the user. The user selects one or more actions to be run when OK is clicked. --- Form/actionbase.py | 34 ++++++++ Form/form.py | 6 ++ Form/formactions.py | 201 +++++++++++++++++++++++++++++++++++++++++++ Form/formgramplet.py | 4 +- 4 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 Form/actionbase.py create mode 100644 Form/formactions.py diff --git a/Form/actionbase.py b/Form/actionbase.py new file mode 100644 index 000000000..e44224d04 --- /dev/null +++ b/Form/actionbase.py @@ -0,0 +1,34 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2019 Steve Youngs +# +# 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. +# + +""" +ActionBase definitions. +""" + +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, + Person) + +class ActionBase(): + """ + A class to read form definitions from an XML file. + """ + def __init__(self): + pass diff --git a/Form/form.py b/Form/form.py index 179d870c7..9469f9df2 100644 --- a/Form/form.py +++ b/Form/form.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2009-2015 Nick Hall +# Copyright (C) 2019 Steve Youngs # # 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 @@ -82,6 +83,11 @@ CONFIG.register('interface.form-horiz-position', -1) CONFIG.register('interface.form-vert-position', -1) +CONFIG.register('interface.form-actions-width', 600) +CONFIG.register('interface.form-actions-height', 400) +CONFIG.register('interface.form-actions-horiz-position', -1) +CONFIG.register('interface.form-actions-vert-position', -1) + CONFIG.init() #------------------------------------------------------------------------ diff --git a/Form/formactions.py b/Form/formactions.py new file mode 100644 index 000000000..d1c32dfad --- /dev/null +++ b/Form/formactions.py @@ -0,0 +1,201 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2019 Steve Youngs +# +# 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. +# + +""" +Form action chooser +""" +import importlib.util +import inspect +import os +import gobject + +#------------------------------------------------------------------------ +# +# GTK modules +# +#------------------------------------------------------------------------ +from gi.repository import Gtk, GObject + +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ +from gramps.gui.managedwindow import ManagedWindow +from gramps.gen.config import config +from gramps.gen.datehandler import get_date +from gramps.gen.db import DbTxn + +#------------------------------------------------------------------------ +# +# Gramplet modules +# +#------------------------------------------------------------------------ +from editform import find_form_event +from form import (get_form_id, get_form_type) +from actionbase import ActionBase + +#------------------------------------------------------------------------ +# +# Internationalisation +# +#------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +try: + _trans = glocale.get_addon_translator(__file__) +except ValueError: + _trans = glocale.translation +_ = _trans.gettext + +#------------------------------------------------------------------------ +# +# FormActions class +# +#------------------------------------------------------------------------ +class FormActions(object): + """ + Form Action selector. + """ + + def __init__(self, dbstate, uistate, track, citation): + self.dbstate = dbstate + self.uistate = uistate + self.track = track + self.db = dbstate.db + self.citation = citation + source_handle = self.citation.get_reference_handle() + self.source = self.db.get_source_from_handle(source_handle) + self.form_id = get_form_id(self.source) + + self.actions_module = None + # for security reasons provide the full path to the actions_module .py file + full_path = os.path.join(os.path.dirname(__file__), '%s.py' % self.form_id) + if os.path.exists(full_path): + spec = importlib.util.spec_from_file_location('form.action.', full_path) + self.actions_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(self.actions_module) + + self.event = find_form_event(self.db, self.citation) + + self.top = self._create_dialog(self.get_dialog_title()) + + self._config = config.get_manager('form') + width = self._config.get('interface.form-actions-width') + height = self._config.get('interface.form-actions-height') + self.top.resize(width, height) + horiz_position = self._config.get('interface.form-actions-horiz-position') + vert_position = self._config.get('interface.form-actions-vert-position') + if horiz_position != -1: + self.top.move(horiz_position, vert_position) + + def _create_dialog(self, title): + """ + Create and display the GUI components of the action selector. + """ + top = Gtk.Dialog(title) + top.set_modal(True) + top.set_transient_for(self.uistate.window) + top.vbox.set_spacing(5) + + box = Gtk.Box() + top.vbox.pack_start(box, True, True, 5) + + self.model = Gtk.TreeStore(str, str, GObject.TYPE_PYOBJECT) + self.tree = Gtk.TreeView(model=self.model) + renderer = Gtk.CellRendererText() + column1 = Gtk.TreeViewColumn(_("Action"), renderer, text=0) + column1.set_sort_column_id(1) + column2 = Gtk.TreeViewColumn(_("Detail"), renderer, text=1) + self.tree.append_column(column1) + self.tree.append_column(column2) + + self.tree.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) + + slist = Gtk.ScrolledWindow() + slist.add(self.tree) + slist.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + box.pack_start(slist, True, True, 5) + + top.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL) + top.add_button(_('_OK'), Gtk.ResponseType.OK) + top.set_default_response(Gtk.ResponseType.OK) + + top.show_all() + + return top + + def _populate_model(self): + form_id = get_form_id(self.source) + if self.actions_module: + # get all classes defined in actions_module which are a subclass of ActionBase (but exclude ActionBase itself) + action_classes = inspect.getmembers(self.actions_module, lambda obj: inspect.isclass(obj) and obj is not ActionBase and issubclass(obj, ActionBase)) + + for action_class in action_classes: + action = (action_class[1])() + action.populate_model(self.db, self.citation, self.event, self.model) + + def run(self): + """ + Run the dialog and return the result. + """ + self._populate_model() + self.tree.expand_all() + while True: + response = self.top.run() + if response == Gtk.ResponseType.HELP: + display_help(webpage='Form_Addons') + else: + break + + (width, height) = self.top.get_size() + self._config.set('interface.form-actions-width', width) + self._config.set('interface.form-actions-height', height) + (root_x, root_y) = self.top.get_position() + self._config.set('interface.form-actions-horiz-position', root_x) + self._config.set('interface.form-actions-vert-position', root_y) + self._config.save() + + # run the selected actions + with DbTxn("FormActions", self.db) as trans: + (model, pathlist) = self.tree.get_selection().get_selected_rows() + for path in pathlist : + tree_iter = model.get_iter(path) + + command = model.get_value(tree_iter, 2) + if command: + (command)(self.db, trans) + + self.top.destroy() + + return None + + def help_clicked(self, obj): + """ + Display the relevant portion of Gramps manual + """ + display_help(webpage='Form_Addons') + + def get_dialog_title(self): + """ + Get the title of the dialog. + """ + dialog_title = _('Form: %s: %s') % (self.source.get_title(), self.citation.get_page()) + + return dialog_title diff --git a/Form/formgramplet.py b/Form/formgramplet.py index bd97d12b8..cce3a56b6 100644 --- a/Form/formgramplet.py +++ b/Form/formgramplet.py @@ -45,6 +45,7 @@ from gramps.gen.errors import WindowActiveError from gramps.gen.lib import Citation from editform import EditForm +from formactions import FormActions from selectform import SelectForm from form import get_form_citation @@ -183,7 +184,8 @@ def __form_actions(self, widget, selection): if iter_: citation = model.get_value(iter_, 0) try: - pass + actions = FormActions(self.gui.dbstate, self.gui.uistate, [], citation) + actions.run() except WindowActiveError: pass From 8602b8f13ee59dbc537384d1b9c2897b7d6499df Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 29 Dec 2019 17:02:49 +0000 Subject: [PATCH 03/38] Add example actions for the UK1841 form --- Form/UK1841.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ Form/actionbase.py | 20 ++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 Form/UK1841.py diff --git a/Form/UK1841.py b/Form/UK1841.py new file mode 100644 index 000000000..d8f6413e4 --- /dev/null +++ b/Form/UK1841.py @@ -0,0 +1,77 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2019 Steve Youngs +# +# 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. +# + +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, + Person) + +from actionbase import ActionBase + +#------------------------------------------------------------------------ +# +# Internationalisation +# +#------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +try: + _trans = glocale.get_addon_translator(__file__) +except ValueError: + _trans = glocale.translation + +_ = _trans.gettext + +class PrimaryNameCitation(ActionBase): + def __init__(self): + ActionBase.__init__(self) + pass + + def populate_model(self, db, citation, form_event, model): + parent = model.append(None, (_("Add Primary Name citation"), None, None)) + for item in db.find_backlink_handles(form_event.get_handle(), + include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + model.append(parent, (name_displayer.display(person), name_displayer.display(person), + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(db, trans, citation_handle, person_handle))) + + def command(db, trans, citation_handle, person_handle): + person = db.get_person_from_handle(person_handle) + person.get_primary_name().add_citation(citation_handle) + db.commit_person(person, trans) + +class OccupationEvent(ActionBase): + def __init__(self): + ActionBase.__init__(self) + pass + + def populate_model(self, db, citation, form_event, model): + parent = model.append(None, (_("Add Occupation event"), None, None)) + for item in db.find_backlink_handles(form_event.get_handle(), + include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event.get_handle(): + for attr in event_ref.get_attribute_list(): + if (attr.get_type() == "Occupation"): # Form specific _attribute name + occupation = attr.get_value() + if (occupation) : + model.append(parent, (name_displayer.display(person), occupation, + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.AddEventToPerson(db, trans, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) diff --git a/Form/actionbase.py b/Form/actionbase.py index e44224d04..0f4b2acea 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -32,3 +32,23 @@ class ActionBase(): """ def __init__(self): pass + + def AddEventToPerson(db, trans, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): + """ + Add a new event to the specified person. + """ + event = Event() + event.set_type(event_type) + event.set_date_object(event_date_object) + event.add_citation(citation_handle) + event.set_description(event_description) + + # add to the database + db.add_event(event, trans) + # Add new event reference to the Person record + event_ref = EventRef() + event_ref.ref = event.get_handle() + event_ref.set_role(event_role_type) + person = db.get_person_from_handle(person_handle) + person.add_event_ref(event_ref) + db.commit_person(person, trans) From c1919d4ee636c8b9df4b5e37d6e5bfb61764c601 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 29 Dec 2019 21:58:02 +0000 Subject: [PATCH 04/38] Initial attempt at adding Birth events from 1841 census --- Form/UK1841.py | 43 +++++++++++++++++++++++++++++++++++++++++-- Form/actionbase.py | 10 ++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index d8f6413e4..b36f82e38 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -18,11 +18,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +from gramps.gen.datehandler import displayer as date_displayer from gramps.gen.display.name import displayer as name_displayer -from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, +from gramps.gen.lib import (Date, Event, EventType, EventRef, EventRoleType, Person) -from actionbase import ActionBase +from actionbase import ActionBase, represents_int #------------------------------------------------------------------------ # @@ -56,6 +57,44 @@ def command(db, trans, citation_handle, person_handle): person.get_primary_name().add_citation(citation_handle) db.commit_person(person, trans) +class BirthEvent(ActionBase): + def __init__(self): + ActionBase.__init__(self) + pass + + def populate_model(self, db, citation, form_event, model): + # if there is no date on the form, no actions can be performed + if form_event.get_date_object(): + parent = model.append(None, (_("Add Birth event"), None, None)) + for item in db.find_backlink_handles(form_event.get_handle(), + include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event.get_handle(): + for attr in event_ref.get_attribute_list(): + if (attr.get_type() == "Age"): # Form specific _attribute name + age_string = attr.get_value() + if age_string and represents_int(age_string): + age = int(age_string) + if age: + birth_date = form_event.get_date_object() - age + birth_date.make_vague() + # Age was rounded down to the nearest five years for those aged 15 or over + # In practice this rule was not always followed by enumerators + if age < 15: + # no adjustment required + birth_date.set_modifier(Date.MOD_ABOUT) + elif not birth_date.is_compound(): + # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. + # create a compound range spanning the possible birth years + birth_range = (birth_date - 5).get_dmy() + (False,) + birth_date.get_dmy() + (False,) + birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) + birth_date.set_quality(Date.QUAL_CALCULATED) + + model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.AddEventToPerson(db, trans, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + class OccupationEvent(ActionBase): def __init__(self): ActionBase.__init__(self) diff --git a/Form/actionbase.py b/Form/actionbase.py index 0f4b2acea..eb794e485 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -52,3 +52,13 @@ def AddEventToPerson(db, trans, person_handle, event_type, event_date_object, ev person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) db.commit_person(person, trans) + +def represents_int(s): + """ + return True iff s is convertable to an int, False otherwise + """ + try: + int(s) + return True + except ValueError: + return False From 10a91166326c05a6e2c734c21a0c6b871d6bae30 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 11:11:07 +0000 Subject: [PATCH 05/38] Adjust function name for consistency --- Form/UK1841.py | 4 ++-- Form/actionbase.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index b36f82e38..0b56ef5e5 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -93,7 +93,7 @@ def populate_model(self, db, citation, form_event, model): birth_date.set_quality(Date.QUAL_CALCULATED) model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.AddEventToPerson(db, trans, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(db, trans, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) class OccupationEvent(ActionBase): def __init__(self): @@ -113,4 +113,4 @@ def populate_model(self, db, citation, form_event, model): occupation = attr.get_value() if (occupation) : model.append(parent, (name_displayer.display(person), occupation, - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.AddEventToPerson(db, trans, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(db, trans, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) diff --git a/Form/actionbase.py b/Form/actionbase.py index eb794e485..fb29bb5f0 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -33,7 +33,7 @@ class ActionBase(): def __init__(self): pass - def AddEventToPerson(db, trans, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): + def add_event_to_person(db, trans, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): """ Add a new event to the specified person. """ From f5a8ee6c3931422dcbf0cb823ee0885499b15f3a Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 11:25:02 +0000 Subject: [PATCH 06/38] For primary name citation, show the name as given on the form. This helps the user confirm the action is correct. --- Form/UK1841.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 0b56ef5e5..15241ec9d 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -49,8 +49,12 @@ def populate_model(self, db, citation, form_event, model): include_classes=['Person']): handle = item[1] person = db.get_person_from_handle(handle) - model.append(parent, (name_displayer.display(person), name_displayer.display(person), - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(db, trans, citation_handle, person_handle))) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event.get_handle(): + for attr in event_ref.get_attribute_list(): + if (attr.get_type() == "Name"): # Form specific _attribute name + model.append(parent, (name_displayer.display(person), attr.get_value(), + lambda db, trans, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(db, trans, citation_handle, person_handle))) def command(db, trans, citation_handle, person_handle): person = db.get_person_from_handle(person_handle) From bc6cc0c191093926f1851d380b239726af7b9fea Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 20:47:31 +0000 Subject: [PATCH 07/38] Allow each action to control its own db transaction. In future this will allow objects created by the action to be, optionally, edited by the user before committing to the db. --- Form/UK1841.py | 23 ++++++++++++++++++----- Form/actionbase.py | 28 +++++++++++++++++++++++++--- Form/formactions.py | 15 +++++++-------- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 15241ec9d..dcfb6ef9c 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -18,11 +18,22 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ from gramps.gen.datehandler import displayer as date_displayer +from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer from gramps.gen.lib import (Date, Event, EventType, EventRef, EventRoleType, Person) +#------------------------------------------------------------------------ +# +# Gramplet modules +# +#------------------------------------------------------------------------ from actionbase import ActionBase, represents_int #------------------------------------------------------------------------ @@ -54,12 +65,14 @@ def populate_model(self, db, citation, form_event, model): for attr in event_ref.get_attribute_list(): if (attr.get_type() == "Name"): # Form specific _attribute name model.append(parent, (name_displayer.display(person), attr.get_value(), - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(db, trans, citation_handle, person_handle))) + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) - def command(db, trans, citation_handle, person_handle): + def command(dbstate, uistate, track, citation_handle, person_handle): + db = dbstate.db person = db.get_person_from_handle(person_handle) person.get_primary_name().add_citation(citation_handle) - db.commit_person(person, trans) + with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: + db.commit_person(person, trans) class BirthEvent(ActionBase): def __init__(self): @@ -97,7 +110,7 @@ def populate_model(self, db, citation, form_event, model): birth_date.set_quality(Date.QUAL_CALCULATED) model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(db, trans, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) class OccupationEvent(ActionBase): def __init__(self): @@ -117,4 +130,4 @@ def populate_model(self, db, citation, form_event, model): occupation = attr.get_value() if (occupation) : model.append(parent, (name_displayer.display(person), occupation, - lambda db, trans, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(db, trans, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) diff --git a/Form/actionbase.py b/Form/actionbase.py index fb29bb5f0..ad48d2af4 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -22,10 +22,29 @@ ActionBase definitions. """ +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ +from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, Person) +#------------------------------------------------------------------------ +# +# Internationalisation +# +#------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +try: + _trans = glocale.get_addon_translator(__file__) +except ValueError: + _trans = glocale.translation + +_ = _trans.gettext + class ActionBase(): """ A class to read form definitions from an XML file. @@ -33,7 +52,8 @@ class ActionBase(): def __init__(self): pass - def add_event_to_person(db, trans, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): + def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): + db = dbstate.db """ Add a new event to the specified person. """ @@ -44,14 +64,16 @@ def add_event_to_person(db, trans, person_handle, event_type, event_date_object, event.set_description(event_description) # add to the database - db.add_event(event, trans) + with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: + db.add_event(event, trans) # Add new event reference to the Person record event_ref = EventRef() event_ref.ref = event.get_handle() event_ref.set_role(event_role_type) person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) - db.commit_person(person, trans) + with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: + db.commit_person(person, trans) def represents_int(s): """ diff --git a/Form/formactions.py b/Form/formactions.py index d1c32dfad..313fd60b5 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -173,14 +173,13 @@ def run(self): self._config.save() # run the selected actions - with DbTxn("FormActions", self.db) as trans: - (model, pathlist) = self.tree.get_selection().get_selected_rows() - for path in pathlist : - tree_iter = model.get_iter(path) - - command = model.get_value(tree_iter, 2) - if command: - (command)(self.db, trans) + (model, pathlist) = self.tree.get_selection().get_selected_rows() + for path in pathlist : + tree_iter = model.get_iter(path) + + command = model.get_value(tree_iter, 2) + if command: + (command)(self.dbstate, self.uistate, self.track) self.top.destroy() From f0df2101836384518463d75ef0859431af86b5bc Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 20:48:03 +0000 Subject: [PATCH 08/38] Pass dbstate, rather than db, into populate_model --- Form/UK1841.py | 9 ++++++--- Form/formactions.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index dcfb6ef9c..8e32b4c96 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -54,7 +54,8 @@ def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, db, citation, form_event, model): + def populate_model(self, dbstate, citation, form_event, model): + db = dbstate.db parent = model.append(None, (_("Add Primary Name citation"), None, None)) for item in db.find_backlink_handles(form_event.get_handle(), include_classes=['Person']): @@ -79,7 +80,8 @@ def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, db, citation, form_event, model): + def populate_model(self, dbstate, citation, form_event, model): + db = dbstate.db # if there is no date on the form, no actions can be performed if form_event.get_date_object(): parent = model.append(None, (_("Add Birth event"), None, None)) @@ -117,7 +119,8 @@ def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, db, citation, form_event, model): + def populate_model(self, dbstate, citation, form_event, model): + db = dbstate.db parent = model.append(None, (_("Add Occupation event"), None, None)) for item in db.find_backlink_handles(form_event.get_handle(), include_classes=['Person']): diff --git a/Form/formactions.py b/Form/formactions.py index 313fd60b5..c72ff2a99 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -149,7 +149,7 @@ def _populate_model(self): for action_class in action_classes: action = (action_class[1])() - action.populate_model(self.db, self.citation, self.event, self.model) + action.populate_model(self.dbstate, self.citation, self.event, self.model) def run(self): """ From bb671296608c925c702b1598823cfc0280d72c8c Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 21:03:10 +0000 Subject: [PATCH 09/38] Refactor code, extracting get_form_person_attr --- Form/UK1841.py | 79 +++++++++++++++++----------------------------- Form/actionbase.py | 16 ++++++++++ 2 files changed, 45 insertions(+), 50 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 8e32b4c96..c22ac2b98 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -57,16 +57,9 @@ def __init__(self): def populate_model(self, dbstate, citation, form_event, model): db = dbstate.db parent = model.append(None, (_("Add Primary Name citation"), None, None)) - for item in db.find_backlink_handles(form_event.get_handle(), - include_classes=['Person']): - handle = item[1] - person = db.get_person_from_handle(handle) - for event_ref in person.get_event_ref_list(): - if event_ref.ref == form_event.get_handle(): - for attr in event_ref.get_attribute_list(): - if (attr.get_type() == "Name"): # Form specific _attribute name - model.append(parent, (name_displayer.display(person), attr.get_value(), - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) + for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): + model.append(parent, (name_displayer.display(person), attr.get_value(), + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) def command(dbstate, uistate, track, citation_handle, person_handle): db = dbstate.db @@ -85,34 +78,27 @@ def populate_model(self, dbstate, citation, form_event, model): # if there is no date on the form, no actions can be performed if form_event.get_date_object(): parent = model.append(None, (_("Add Birth event"), None, None)) - for item in db.find_backlink_handles(form_event.get_handle(), - include_classes=['Person']): - handle = item[1] - person = db.get_person_from_handle(handle) - for event_ref in person.get_event_ref_list(): - if event_ref.ref == form_event.get_handle(): - for attr in event_ref.get_attribute_list(): - if (attr.get_type() == "Age"): # Form specific _attribute name - age_string = attr.get_value() - if age_string and represents_int(age_string): - age = int(age_string) - if age: - birth_date = form_event.get_date_object() - age - birth_date.make_vague() - # Age was rounded down to the nearest five years for those aged 15 or over - # In practice this rule was not always followed by enumerators - if age < 15: - # no adjustment required - birth_date.set_modifier(Date.MOD_ABOUT) - elif not birth_date.is_compound(): - # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. - # create a compound range spanning the possible birth years - birth_range = (birth_date - 5).get_dmy() + (False,) + birth_date.get_dmy() + (False,) - birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) - birth_date.set_quality(Date.QUAL_CALCULATED) + for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Age'): + age_string = attr.get_value() + if age_string and represents_int(age_string): + age = int(age_string) + if age: + birth_date = form_event.get_date_object() - age + birth_date.make_vague() + # Age was rounded down to the nearest five years for those aged 15 or over + # In practice this rule was not always followed by enumerators + if age < 15: + # no adjustment required + birth_date.set_modifier(Date.MOD_ABOUT) + elif not birth_date.is_compound(): + # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. + # create a compound range spanning the possible birth years + birth_range = (birth_date - 5).get_dmy() + (False,) + birth_date.get_dmy() + (False,) + birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) + birth_date.set_quality(Date.QUAL_CALCULATED) - model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) class OccupationEvent(ActionBase): def __init__(self): @@ -121,16 +107,9 @@ def __init__(self): def populate_model(self, dbstate, citation, form_event, model): db = dbstate.db - parent = model.append(None, (_("Add Occupation event"), None, None)) - for item in db.find_backlink_handles(form_event.get_handle(), - include_classes=['Person']): - handle = item[1] - person = db.get_person_from_handle(handle) - for event_ref in person.get_event_ref_list(): - if event_ref.ref == form_event.get_handle(): - for attr in event_ref.get_attribute_list(): - if (attr.get_type() == "Occupation"): # Form specific _attribute name - occupation = attr.get_value() - if (occupation) : - model.append(parent, (name_displayer.display(person), occupation, - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + parent = model.append(None, (_('Add Occupation event'), None, None)) + for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): + occupation = attr.get_value() + if (occupation) : + model.append(parent, (name_displayer.display(person), occupation, + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) diff --git a/Form/actionbase.py b/Form/actionbase.py index ad48d2af4..a9244200a 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -75,6 +75,22 @@ def add_event_to_person(dbstate, uistate, track, person_handle, event_type, even with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: db.commit_person(person, trans) + def get_form_person_attr(db, form_event_handle, attr_type): + """ + Find all persons referencing the form_event and which have an attribute of type attr_type + returns a list of matching (person, attribute) tuples + """ + result = [] + for item in db.find_backlink_handles(form_event_handle, include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event_handle: + for attr in event_ref.get_attribute_list(): + if (attr.get_type() == attr_type): + result.append((person, attr)) + return result + def represents_int(s): """ return True iff s is convertable to an int, False otherwise From 634c0a87094643455be186fc9589efbb6189c361 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 21:21:53 +0000 Subject: [PATCH 10/38] Rename populate_model to get_actions. get_actions returns the possible actions. This avoids leaking FormAction implementation detail (the use of a TreeStore) into the action classes. --- Form/UK1841.py | 24 ++++++++++++++---------- Form/formactions.py | 6 +++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index c22ac2b98..5b2d1f008 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -54,12 +54,14 @@ def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, dbstate, citation, form_event, model): + def get_actions(self, dbstate, citation, form_event): db = dbstate.db - parent = model.append(None, (_("Add Primary Name citation"), None, None)) + actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): - model.append(parent, (name_displayer.display(person), attr.get_value(), + pass + actions.append((name_displayer.display(person), attr.get_value(), lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) + return (_("Add Primary Name citation"), actions) def command(dbstate, uistate, track, citation_handle, person_handle): db = dbstate.db @@ -73,11 +75,11 @@ def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, dbstate, citation, form_event, model): + def get_actions(self, dbstate, citation, form_event): db = dbstate.db + actions = [] # if there is no date on the form, no actions can be performed if form_event.get_date_object(): - parent = model.append(None, (_("Add Birth event"), None, None)) for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Age'): age_string = attr.get_value() if age_string and represents_int(age_string): @@ -97,19 +99,21 @@ def populate_model(self, dbstate, citation, form_event, model): birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) birth_date.set_quality(Date.QUAL_CALCULATED) - model.append(parent, (name_displayer.display(person), date_displayer.display(birth_date), + actions.append((name_displayer.display(person), date_displayer.display(birth_date), lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + return (_("Add Birth event"), actions) class OccupationEvent(ActionBase): def __init__(self): ActionBase.__init__(self) pass - def populate_model(self, dbstate, citation, form_event, model): + def get_actions(self, dbstate, citation, form_event): db = dbstate.db - parent = model.append(None, (_('Add Occupation event'), None, None)) + actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() if (occupation) : - model.append(parent, (name_displayer.display(person), occupation, - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + actions.append((name_displayer.display(person), occupation, + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + return (_("Add Occupation event"), actions) diff --git a/Form/formactions.py b/Form/formactions.py index c72ff2a99..f80ddc4ce 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -149,7 +149,11 @@ def _populate_model(self): for action_class in action_classes: action = (action_class[1])() - action.populate_model(self.dbstate, self.citation, self.event, self.model) + (title, action_details) = action.get_actions(self.dbstate, self.citation, self.event) + if action_details: + parent = self.model.append(None, (title, None, None)) + for action_detail in action_details: + self.model.append(parent, action_detail) def run(self): """ From 66a1041e5333d97d01e99a2042080a083117064d Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Mon, 30 Dec 2019 21:59:44 +0000 Subject: [PATCH 11/38] A new action class: ResidenceEvent Adds a single residence and links to all people on the form. Residence place and date are copied from the form event place and date. --- Form/UK1841.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Form/UK1841.py b/Form/UK1841.py index 5b2d1f008..bc0586f27 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -26,8 +26,10 @@ from gramps.gen.datehandler import displayer as date_displayer from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.display.place import displayer as place_displayer from gramps.gen.lib import (Date, Event, EventType, EventRef, EventRoleType, Person) +from gramps.gen.utils.db import get_participant_from_event #------------------------------------------------------------------------ # @@ -117,3 +119,48 @@ def get_actions(self, dbstate, citation, form_event): actions.append((name_displayer.display(person), occupation, lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) + +class ResidenceEvent(ActionBase): + def __init__(self): + ActionBase.__init__(self) + pass + + def get_actions(self, dbstate, citation, form_event): + db = dbstate.db + # build a list of all the people referenced in the form. For 1841, all people have a PRIMARY event role + people = [] + for item in db.find_backlink_handles(form_event.get_handle(), include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event.get_handle(): + people.append((person.get_handle(), EventRoleType.PRIMARY)) + actions = [] + if people: + place = None + if form_event.get_place_handle(): + place = place_displayer.display(db, db.get_place_from_handle(form_event.get_place_handle())) + actions.append((get_participant_from_event(db, form_event.get_handle()), place, + lambda dbstate, uistate, track, citation_handle = citation.handle, people_handles = people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) + return (_("Add Residence event"), actions) + + def command(dbstate, uistate, track, citation_handle, event_date_object, event_place_handle, people_handles): + db = dbstate.db + # create the RESIDENCE event + event = Event() + event.set_type(EventType.RESIDENCE) + event.set_date_object(event_date_object) + event.set_place_handle(event_place_handle) + event.add_citation(citation_handle) + with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: + db.add_event(event, trans) + + # and reference the event from all people + event_ref = EventRef() + event_ref.ref = event.get_handle() + for (person_handle, role) in people_handles: + event_ref.set_role(role) + person = db.get_person_from_handle(person_handle) + person.add_event_ref(event_ref) + with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: + db.commit_person(person, trans) From f11e430764f32f6f19b43836cd5a8b44cb6548d8 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 14:28:50 +0000 Subject: [PATCH 12/38] Use checkboxes in the FormActions UI to indicate which actions to run --- Form/formactions.py | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index f80ddc4ce..f6c78b013 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -73,6 +73,10 @@ class FormActions(object): """ Form Action selector. """ + RUN_ACTION_COL = 0 + ACTION_COL = 1 + DETAIL_COL = 2 + ACTION_COMMAND_COL = 3 def __init__(self, dbstate, uistate, track, citation): self.dbstate = dbstate @@ -117,12 +121,19 @@ def _create_dialog(self, title): box = Gtk.Box() top.vbox.pack_start(box, True, True, 5) - self.model = Gtk.TreeStore(str, str, GObject.TYPE_PYOBJECT) + self.model = Gtk.TreeStore(bool, str, str, GObject.TYPE_PYOBJECT) self.tree = Gtk.TreeView(model=self.model) - renderer = Gtk.CellRendererText() - column1 = Gtk.TreeViewColumn(_("Action"), renderer, text=0) - column1.set_sort_column_id(1) - column2 = Gtk.TreeViewColumn(_("Detail"), renderer, text=1) + renderer_text = Gtk.CellRendererText() + column1 = Gtk.TreeViewColumn(_("Action")) + renderer_action_toggle = Gtk.CellRendererToggle() + renderer_action_toggle.connect('toggled', self.on_action_toggled) + column1.pack_start(renderer_action_toggle, False) + column1.add_attribute(renderer_action_toggle, 'active', self.RUN_ACTION_COL) + column1.pack_start(renderer_text, True) + column1.add_attribute(renderer_text, 'text', self.ACTION_COL) + column1.set_cell_data_func(renderer_action_toggle, FormActions.action_data_func) + + column2 = Gtk.TreeViewColumn(_("Detail"), renderer_text, text=self.DETAIL_COL) self.tree.append_column(column1) self.tree.append_column(column2) @@ -141,6 +152,12 @@ def _create_dialog(self, title): return top + def on_action_toggled(self, widget, path): + self.model[path][self.RUN_ACTION_COL] = not self.model[path][self.RUN_ACTION_COL] + + def action_data_func(col, cell, model, iter, user_data): + cell.set_property("visible", model.get_value(iter, FormActions.ACTION_COMMAND_COL)) + def _populate_model(self): form_id = get_form_id(self.source) if self.actions_module: @@ -151,9 +168,9 @@ def _populate_model(self): action = (action_class[1])() (title, action_details) = action.get_actions(self.dbstate, self.citation, self.event) if action_details: - parent = self.model.append(None, (title, None, None)) + parent = self.model.append(None, (False, title, None, None)) for action_detail in action_details: - self.model.append(parent, action_detail) + self.model.append(parent, (False, ) + action_detail) def run(self): """ @@ -177,13 +194,11 @@ def run(self): self._config.save() # run the selected actions - (model, pathlist) = self.tree.get_selection().get_selected_rows() - for path in pathlist : - tree_iter = model.get_iter(path) - - command = model.get_value(tree_iter, 2) - if command: - (command)(self.dbstate, self.uistate, self.track) + for action_type_row in self.model: + for action_row in action_type_row.iterchildren(): + if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): + command = action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL) + (command)(self.dbstate, self.uistate, self.track) self.top.destroy() From 704f756eff99ff170b5e198f8d5ea19cfaf87e1c Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 16:33:37 +0000 Subject: [PATCH 13/38] For user convenience, allow clicking on action categories to tick / untick all actions within that category. --- Form/formactions.py | 54 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index f6c78b013..ec86d28d6 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -74,9 +74,10 @@ class FormActions(object): Form Action selector. """ RUN_ACTION_COL = 0 - ACTION_COL = 1 - DETAIL_COL = 2 - ACTION_COMMAND_COL = 3 + RUN_INCONSISTENT_COL = 1 + ACTION_COL = 2 + DETAIL_COL = 3 + ACTION_COMMAND_COL = 4 def __init__(self, dbstate, uistate, track, citation): self.dbstate = dbstate @@ -121,7 +122,7 @@ def _create_dialog(self, title): box = Gtk.Box() top.vbox.pack_start(box, True, True, 5) - self.model = Gtk.TreeStore(bool, str, str, GObject.TYPE_PYOBJECT) + self.model = Gtk.TreeStore(bool, bool, str, str, GObject.TYPE_PYOBJECT) self.tree = Gtk.TreeView(model=self.model) renderer_text = Gtk.CellRendererText() column1 = Gtk.TreeViewColumn(_("Action")) @@ -129,9 +130,9 @@ def _create_dialog(self, title): renderer_action_toggle.connect('toggled', self.on_action_toggled) column1.pack_start(renderer_action_toggle, False) column1.add_attribute(renderer_action_toggle, 'active', self.RUN_ACTION_COL) + column1.add_attribute(renderer_action_toggle, 'inconsistent', self.RUN_INCONSISTENT_COL) column1.pack_start(renderer_text, True) column1.add_attribute(renderer_text, 'text', self.ACTION_COL) - column1.set_cell_data_func(renderer_action_toggle, FormActions.action_data_func) column2 = Gtk.TreeViewColumn(_("Detail"), renderer_text, text=self.DETAIL_COL) self.tree.append_column(column1) @@ -153,10 +154,39 @@ def _create_dialog(self, title): return top def on_action_toggled(self, widget, path): - self.model[path][self.RUN_ACTION_COL] = not self.model[path][self.RUN_ACTION_COL] - - def action_data_func(col, cell, model, iter, user_data): - cell.set_property("visible", model.get_value(iter, FormActions.ACTION_COMMAND_COL)) + row_iter = self.model.get_iter(path) + parent = self.model.iter_parent(row_iter) + if not parent: + # user clicked an action category row. toggle all children + new_state = not self.model[row_iter][self.RUN_ACTION_COL] + child = self.model.iter_children(row_iter) + while child: + self.model[child][self.RUN_ACTION_COL] = new_state + child = self.model.iter_next(child) + # all children are now consistent + self.model[row_iter][self.RUN_INCONSISTENT_COL] = False + # toggle RUN_ACTION_COL for the row that was clicked + self.model[row_iter][self.RUN_ACTION_COL] = not self.model[row_iter][self.RUN_ACTION_COL] + if parent: + # update the status of the parent + (consistent, value) = FormActions.all_children_consistent(self.model, parent, FormActions.RUN_ACTION_COL) + self.model[parent][self.RUN_INCONSISTENT_COL] = not consistent + self.model[parent][self.RUN_ACTION_COL] = consistent and value + + def all_children_consistent(model, parent, col): + consistent = True + value = False + child = model.iter_children(parent) + if child: # handle case of no children + # start with value of first child + value = model.get_value(child, col) + # advance to second child (if there is one) + child = model.iter_next(child) + # loop over all remaining children until we find an inconsistent value or reach the end + while consistent and child: + consistent = model.get_value(child, col) == value + child = model.iter_next(child) + return (consistent, value) def _populate_model(self): form_id = get_form_id(self.source) @@ -168,9 +198,11 @@ def _populate_model(self): action = (action_class[1])() (title, action_details) = action.get_actions(self.dbstate, self.citation, self.event) if action_details: - parent = self.model.append(None, (False, title, None, None)) + # add the action category + parent = self.model.append(None, (False, False, title, None, None)) for action_detail in action_details: - self.model.append(parent, (False, ) + action_detail) + # add available actions within this category + self.model.append(parent, (False, False) + action_detail) def run(self): """ From 6b8899bf1c0849899321d46a103e044305ba62b7 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 20:03:01 +0000 Subject: [PATCH 14/38] Only run the actions if the user actually clicks OK! --- Form/formactions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index ec86d28d6..e151a7a5c 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -225,12 +225,13 @@ def run(self): self._config.set('interface.form-actions-vert-position', root_y) self._config.save() - # run the selected actions - for action_type_row in self.model: - for action_row in action_type_row.iterchildren(): - if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): - command = action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL) - (command)(self.dbstate, self.uistate, self.track) + if response == Gtk.ResponseType.OK: + # run the selected actions + for action_type_row in self.model: + for action_row in action_type_row.iterchildren(): + if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): + command = action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL) + (command)(self.dbstate, self.uistate, self.track) self.top.destroy() From 74ffe88f4cc76cc22e39df817bb93f07e54fd87c Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 20:40:50 +0000 Subject: [PATCH 15/38] Remove unnecessary "pass" statements --- Form/UK1841.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index bc0586f27..69f9ca860 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -54,13 +54,11 @@ class PrimaryNameCitation(ActionBase): def __init__(self): ActionBase.__init__(self) - pass def get_actions(self, dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): - pass actions.append((name_displayer.display(person), attr.get_value(), lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @@ -75,7 +73,6 @@ def command(dbstate, uistate, track, citation_handle, person_handle): class BirthEvent(ActionBase): def __init__(self): ActionBase.__init__(self) - pass def get_actions(self, dbstate, citation, form_event): db = dbstate.db @@ -108,7 +105,6 @@ def get_actions(self, dbstate, citation, form_event): class OccupationEvent(ActionBase): def __init__(self): ActionBase.__init__(self) - pass def get_actions(self, dbstate, citation, form_event): db = dbstate.db @@ -123,7 +119,6 @@ def get_actions(self, dbstate, citation, form_event): class ResidenceEvent(ActionBase): def __init__(self): ActionBase.__init__(self) - pass def get_actions(self, dbstate, citation, form_event): db = dbstate.db From 1430d2776c43efa320ed87630700421ef3755bc7 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 20:43:29 +0000 Subject: [PATCH 16/38] New action: AlternateName which adds an alternate name to a Person on the form --- Form/UK1841.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 69f9ca860..c7c9cc9fe 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -28,7 +28,7 @@ from gramps.gen.display.name import displayer as name_displayer from gramps.gen.display.place import displayer as place_displayer from gramps.gen.lib import (Date, Event, EventType, EventRef, EventRoleType, - Person) + Name, Person) from gramps.gen.utils.db import get_participant_from_event #------------------------------------------------------------------------ @@ -70,6 +70,28 @@ def command(dbstate, uistate, track, citation_handle, person_handle): with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: db.commit_person(person, trans) +class AlternateName(ActionBase): + def __init__(self): + ActionBase.__init__(self) + + def get_actions(self, dbstate, citation, form_event): + db = dbstate.db + actions = [] + for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): + alternate = Name() + alternate.set_first_name(attr.get_value()) + alternate.add_citation(citation.handle) + actions.append((name_displayer.display(person), attr.get_value(), + lambda dbstate, uistate, track, person_handle = person.handle, alternate_ = alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) + return (_("Add alternate name"), actions) + + def command(dbstate, uistate, track, person_handle, alternate): + db = dbstate.db + person = db.get_person_from_handle(person_handle) + person.add_alternate_name(alternate) + with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: + db.commit_person(person, trans) + class BirthEvent(ActionBase): def __init__(self): ActionBase.__init__(self) From b90fb27b166cba0479dcb9c794a1fda5208f3393 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:01:03 +0000 Subject: [PATCH 17/38] Harden the way that form action modules are loaded and ensure that import statements within an action module work as expected. --- Form/formactions.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index e151a7a5c..0cb17c676 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -21,10 +21,18 @@ """ Form action chooser """ +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +import logging import importlib.util import inspect import os -import gobject +import sys + +LOG = logging.getLogger('.form') #------------------------------------------------------------------------ # @@ -93,9 +101,19 @@ def __init__(self, dbstate, uistate, track, citation): # for security reasons provide the full path to the actions_module .py file full_path = os.path.join(os.path.dirname(__file__), '%s.py' % self.form_id) if os.path.exists(full_path): - spec = importlib.util.spec_from_file_location('form.action.', full_path) - self.actions_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(self.actions_module) + # temporarily modify sys.path so that any import statements in the module get processed correctly + sys.path.insert(0, os.path.dirname(__file__)) + try: + spec = importlib.util.spec_from_file_location('form.actions.%s' % self.form_id, full_path) + self.actions_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(self.actions_module) + except (ValueError, ImportError, SyntaxError) as err: + self.actions_module = None + LOG.warning("Form plugin error (from '%s'): %s" + % (self.form_id, err)) + finally: + # must make sure we restore sys.path + sys.path.pop(0) self.event = find_form_event(self.db, self.citation) From bb0f7d997e22642cf5910fc443303379d58c679d Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:13:11 +0000 Subject: [PATCH 18/38] The way FormActions dynamically identified action classes gave no control over the order in which actions were added to the UI. Rather than make an already complex solution more complex, take a different, simpler, approach. The form action module now provides a standalone get_actions function which returns a list of all available actions the module can provide for the given citation and form event. As an additional benefit this gives a natural point to optimise the get_actions functions in the future, should this become necessary. For example, by pre-loading all Person records and attributes prior to calling the class.get_action functions. --- Form/UK1841.py | 48 ++++++++++++++++++++++----------------------- Form/formactions.py | 15 ++++++-------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index c7c9cc9fe..530765f8f 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -51,11 +51,21 @@ _ = _trans.gettext -class PrimaryNameCitation(ActionBase): - def __init__(self): - ActionBase.__init__(self) - - def get_actions(self, dbstate, citation, form_event): +def get_actions(dbstate, citation, form_event): + """ + return a list of all actions that this module can provide for the given citation and form + each list entry is a string, describing the action category, and a list of actions that can be performed. + """ + actions = [] + actions.append(PrimaryNameCitation.get_actions(dbstate, citation, form_event)) + actions.append(AlternateName.get_actions(dbstate, citation, form_event)) + actions.append(BirthEvent.get_actions(dbstate, citation, form_event)) + actions.append(OccupationEvent.get_actions(dbstate, citation, form_event)) + actions.append(ResidenceEvent.get_actions(dbstate, citation, form_event)) + return actions + +class PrimaryNameCitation: + def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): @@ -70,11 +80,8 @@ def command(dbstate, uistate, track, citation_handle, person_handle): with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: db.commit_person(person, trans) -class AlternateName(ActionBase): - def __init__(self): - ActionBase.__init__(self) - - def get_actions(self, dbstate, citation, form_event): +class AlternateName: + def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): @@ -92,11 +99,8 @@ def command(dbstate, uistate, track, person_handle, alternate): with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: db.commit_person(person, trans) -class BirthEvent(ActionBase): - def __init__(self): - ActionBase.__init__(self) - - def get_actions(self, dbstate, citation, form_event): +class BirthEvent: + def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] # if there is no date on the form, no actions can be performed @@ -124,11 +128,8 @@ def get_actions(self, dbstate, citation, form_event): lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) -class OccupationEvent(ActionBase): - def __init__(self): - ActionBase.__init__(self) - - def get_actions(self, dbstate, citation, form_event): +class OccupationEvent: + def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): @@ -138,11 +139,8 @@ def get_actions(self, dbstate, citation, form_event): lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) -class ResidenceEvent(ActionBase): - def __init__(self): - ActionBase.__init__(self) - - def get_actions(self, dbstate, citation, form_event): +class ResidenceEvent: + def get_actions(dbstate, citation, form_event): db = dbstate.db # build a list of all the people referenced in the form. For 1841, all people have a PRIMARY event role people = [] diff --git a/Form/formactions.py b/Form/formactions.py index 0cb17c676..f4269c2e8 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -58,7 +58,6 @@ #------------------------------------------------------------------------ from editform import find_form_event from form import (get_form_id, get_form_type) -from actionbase import ActionBase #------------------------------------------------------------------------ # @@ -209,16 +208,14 @@ def all_children_consistent(model, parent, col): def _populate_model(self): form_id = get_form_id(self.source) if self.actions_module: - # get all classes defined in actions_module which are a subclass of ActionBase (but exclude ActionBase itself) - action_classes = inspect.getmembers(self.actions_module, lambda obj: inspect.isclass(obj) and obj is not ActionBase and issubclass(obj, ActionBase)) - - for action_class in action_classes: - action = (action_class[1])() - (title, action_details) = action.get_actions(self.dbstate, self.citation, self.event) - if action_details: + # get the all actions that the actions module can provide for the form + # because the module is dynamically loaded, use getattr to retrieve the actual function to call + all_actions = getattr(self.actions_module, 'get_actions')(self.dbstate, self.citation, self.event) + for (title, actions) in all_actions: + if actions: # add the action category parent = self.model.append(None, (False, False, title, None, None)) - for action_detail in action_details: + for action_detail in actions: # add available actions within this category self.model.append(parent, (False, False) + action_detail) From 1f9e1df271c9c6d10c9c201611bba748bd866466 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:15:14 +0000 Subject: [PATCH 19/38] Correctly decorate static methods with @staticmethod --- Form/UK1841.py | 8 ++++++++ Form/actionbase.py | 2 ++ Form/formactions.py | 1 + 3 files changed, 11 insertions(+) diff --git a/Form/UK1841.py b/Form/UK1841.py index 530765f8f..f933c415d 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -65,6 +65,7 @@ def get_actions(dbstate, citation, form_event): return actions class PrimaryNameCitation: + @staticmethod def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] @@ -73,6 +74,7 @@ def get_actions(dbstate, citation, form_event): lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) + @staticmethod def command(dbstate, uistate, track, citation_handle, person_handle): db = dbstate.db person = db.get_person_from_handle(person_handle) @@ -81,6 +83,7 @@ def command(dbstate, uistate, track, citation_handle, person_handle): db.commit_person(person, trans) class AlternateName: + @staticmethod def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] @@ -92,6 +95,7 @@ def get_actions(dbstate, citation, form_event): lambda dbstate, uistate, track, person_handle = person.handle, alternate_ = alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) return (_("Add alternate name"), actions) + @staticmethod def command(dbstate, uistate, track, person_handle, alternate): db = dbstate.db person = db.get_person_from_handle(person_handle) @@ -100,6 +104,7 @@ def command(dbstate, uistate, track, person_handle, alternate): db.commit_person(person, trans) class BirthEvent: + @staticmethod def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] @@ -129,6 +134,7 @@ def get_actions(dbstate, citation, form_event): return (_("Add Birth event"), actions) class OccupationEvent: + @staticmethod def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] @@ -140,6 +146,7 @@ def get_actions(dbstate, citation, form_event): return (_("Add Occupation event"), actions) class ResidenceEvent: + @staticmethod def get_actions(dbstate, citation, form_event): db = dbstate.db # build a list of all the people referenced in the form. For 1841, all people have a PRIMARY event role @@ -159,6 +166,7 @@ def get_actions(dbstate, citation, form_event): lambda dbstate, uistate, track, citation_handle = citation.handle, people_handles = people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) + @staticmethod def command(dbstate, uistate, track, citation_handle, event_date_object, event_place_handle, people_handles): db = dbstate.db # create the RESIDENCE event diff --git a/Form/actionbase.py b/Form/actionbase.py index a9244200a..60471bd2a 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -52,6 +52,7 @@ class ActionBase(): def __init__(self): pass + @staticmethod def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db """ @@ -75,6 +76,7 @@ def add_event_to_person(dbstate, uistate, track, person_handle, event_type, even with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: db.commit_person(person, trans) + @staticmethod def get_form_person_attr(db, form_event_handle, attr_type): """ Find all persons referencing the form_event and which have an attribute of type attr_type diff --git a/Form/formactions.py b/Form/formactions.py index f4269c2e8..543f3d181 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -190,6 +190,7 @@ def on_action_toggled(self, widget, path): self.model[parent][self.RUN_INCONSISTENT_COL] = not consistent self.model[parent][self.RUN_ACTION_COL] = consistent and value + @staticmethod def all_children_consistent(model, parent, col): consistent = True value = False From 55fafb8604e931a3d820ac54ab0308bf0a309a7b Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:16:20 +0000 Subject: [PATCH 20/38] Remove ActionBase.__init__ since it does nothing --- Form/actionbase.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Form/actionbase.py b/Form/actionbase.py index 60471bd2a..b284fa337 100644 --- a/Form/actionbase.py +++ b/Form/actionbase.py @@ -45,13 +45,7 @@ _ = _trans.gettext -class ActionBase(): - """ - A class to read form definitions from an XML file. - """ - def __init__(self): - pass - +class ActionBase: @staticmethod def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db From 17113c5e44e02afd342560c5edfd9c75e74c24f5 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:29:41 +0000 Subject: [PATCH 21/38] Rename ActionBase to actionutils more accurately reflect its purpose. Remove the class ActionBase --- Form/UK1841.py | 16 ++++---- Form/actionbase.py | 98 --------------------------------------------- Form/actionutils.py | 91 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 106 deletions(-) delete mode 100644 Form/actionbase.py create mode 100644 Form/actionutils.py diff --git a/Form/UK1841.py b/Form/UK1841.py index f933c415d..c1391fe49 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -36,7 +36,7 @@ # Gramplet modules # #------------------------------------------------------------------------ -from actionbase import ActionBase, represents_int +import actionutils #------------------------------------------------------------------------ # @@ -69,7 +69,7 @@ class PrimaryNameCitation: def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] - for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): + for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): actions.append((name_displayer.display(person), attr.get_value(), lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @@ -87,7 +87,7 @@ class AlternateName: def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] - for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Name'): + for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): alternate = Name() alternate.set_first_name(attr.get_value()) alternate.add_citation(citation.handle) @@ -110,9 +110,9 @@ def get_actions(dbstate, citation, form_event): actions = [] # if there is no date on the form, no actions can be performed if form_event.get_date_object(): - for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Age'): + for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Age'): age_string = attr.get_value() - if age_string and represents_int(age_string): + if age_string and actionutils.represents_int(age_string): age = int(age_string) if age: birth_date = form_event.get_date_object() - age @@ -130,7 +130,7 @@ def get_actions(dbstate, citation, form_event): birth_date.set_quality(Date.QUAL_CALCULATED) actions.append((name_displayer.display(person), date_displayer.display(birth_date), - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) class OccupationEvent: @@ -138,11 +138,11 @@ class OccupationEvent: def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] - for (person, attr) in ActionBase.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): + for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() if (occupation) : actions.append((name_displayer.display(person), occupation, - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: ActionBase.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) class ResidenceEvent: diff --git a/Form/actionbase.py b/Form/actionbase.py deleted file mode 100644 index b284fa337..000000000 --- a/Form/actionbase.py +++ /dev/null @@ -1,98 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2019 Steve Youngs -# -# 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. -# - -""" -ActionBase definitions. -""" - -#------------------------------------------------------------------------ -# -# Gramps modules -# -#------------------------------------------------------------------------ -from gramps.gen.db import DbTxn -from gramps.gen.display.name import displayer as name_displayer -from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, - Person) - -#------------------------------------------------------------------------ -# -# Internationalisation -# -#------------------------------------------------------------------------ -from gramps.gen.const import GRAMPS_LOCALE as glocale -try: - _trans = glocale.get_addon_translator(__file__) -except ValueError: - _trans = glocale.translation - -_ = _trans.gettext - -class ActionBase: - @staticmethod - def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): - db = dbstate.db - """ - Add a new event to the specified person. - """ - event = Event() - event.set_type(event_type) - event.set_date_object(event_date_object) - event.add_citation(citation_handle) - event.set_description(event_description) - - # add to the database - with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: - db.add_event(event, trans) - # Add new event reference to the Person record - event_ref = EventRef() - event_ref.ref = event.get_handle() - event_ref.set_role(event_role_type) - person = db.get_person_from_handle(person_handle) - person.add_event_ref(event_ref) - with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: - db.commit_person(person, trans) - - @staticmethod - def get_form_person_attr(db, form_event_handle, attr_type): - """ - Find all persons referencing the form_event and which have an attribute of type attr_type - returns a list of matching (person, attribute) tuples - """ - result = [] - for item in db.find_backlink_handles(form_event_handle, include_classes=['Person']): - handle = item[1] - person = db.get_person_from_handle(handle) - for event_ref in person.get_event_ref_list(): - if event_ref.ref == form_event_handle: - for attr in event_ref.get_attribute_list(): - if (attr.get_type() == attr_type): - result.append((person, attr)) - return result - -def represents_int(s): - """ - return True iff s is convertable to an int, False otherwise - """ - try: - int(s) - return True - except ValueError: - return False diff --git a/Form/actionutils.py b/Form/actionutils.py new file mode 100644 index 000000000..f574c0140 --- /dev/null +++ b/Form/actionutils.py @@ -0,0 +1,91 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2019 Steve Youngs +# +# 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. +# + +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ +from gramps.gen.db import DbTxn +from gramps.gen.display.name import displayer as name_displayer +from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, + Person) + +#------------------------------------------------------------------------ +# +# Internationalisation +# +#------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +try: + _trans = glocale.get_addon_translator(__file__) +except ValueError: + _trans = glocale.translation + +_ = _trans.gettext + +def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): + db = dbstate.db + """ + Add a new event to the specified person. + """ + event = Event() + event.set_type(event_type) + event.set_date_object(event_date_object) + event.add_citation(citation_handle) + event.set_description(event_description) + + # add to the database + with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: + db.add_event(event, trans) + # Add new event reference to the Person record + event_ref = EventRef() + event_ref.ref = event.get_handle() + event_ref.set_role(event_role_type) + person = db.get_person_from_handle(person_handle) + person.add_event_ref(event_ref) + with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: + db.commit_person(person, trans) + +def get_form_person_attr(db, form_event_handle, attr_type): + """ + Find all persons referencing the form_event and which have an attribute of type attr_type + returns a list of matching (person, attribute) tuples + """ + result = [] + for item in db.find_backlink_handles(form_event_handle, include_classes=['Person']): + handle = item[1] + person = db.get_person_from_handle(handle) + for event_ref in person.get_event_ref_list(): + if event_ref.ref == form_event_handle: + for attr in event_ref.get_attribute_list(): + if (attr.get_type() == attr_type): + result.append((person, attr)) + return result + +def represents_int(s): + """ + return True iff s is convertable to an int, False otherwise + """ + try: + int(s) + return True + except ValueError: + return False From b25a1e9100117bb3ce55b114356bf8240fedd61f Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Tue, 31 Dec 2019 22:45:17 +0000 Subject: [PATCH 22/38] Give progress information when running actions --- Form/formactions.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index 543f3d181..8493dcf9c 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -243,11 +243,22 @@ def run(self): if response == Gtk.ResponseType.OK: # run the selected actions + self.uistate.set_busy_cursor(True) + self.uistate.progress.show() + self.uistate.pulse_progressbar(0) + # get the list of actions to be run + # this helps give meaningful progress information (because we know how many actions in total will be run) + actions = [] for action_type_row in self.model: for action_row in action_type_row.iterchildren(): if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): - command = action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL) - (command)(self.dbstate, self.uistate, self.track) + actions.append(action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL)) + # run the actions + for index, action in enumerate(actions): + (action)(self.dbstate, self.uistate, self.track) + self.uistate.pulse_progressbar((index + 1) / len(actions) * 100) + self.uistate.progress.hide() + self.uistate.set_busy_cursor(False) self.top.destroy() From cffc4326a776f829698d4a4b66bce5e694595d3e Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Wed, 1 Jan 2020 10:35:03 +0000 Subject: [PATCH 23/38] Add Help button to FormActions dialog --- Form/formactions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Form/formactions.py b/Form/formactions.py index 8493dcf9c..14cdf8f25 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2019 Steve Youngs +# Copyright (C) 2019-2020 Steve Youngs # # 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 @@ -50,6 +50,7 @@ from gramps.gen.config import config from gramps.gen.datehandler import get_date from gramps.gen.db import DbTxn +from gramps.gui.display import display_help #------------------------------------------------------------------------ # @@ -162,6 +163,7 @@ def _create_dialog(self, title): slist.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) box.pack_start(slist, True, True, 5) + top.add_button(_('_Help'), Gtk.ResponseType.HELP) top.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL) top.add_button(_('_OK'), Gtk.ResponseType.OK) top.set_default_response(Gtk.ResponseType.OK) From 106c51a2ca16ddfd483bc89b424778b7743b88cc Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Wed, 1 Jan 2020 10:35:39 +0000 Subject: [PATCH 24/38] Remove unused local variable. --- Form/formactions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Form/formactions.py b/Form/formactions.py index 14cdf8f25..7d9162a7f 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -209,7 +209,6 @@ def all_children_consistent(model, parent, col): return (consistent, value) def _populate_model(self): - form_id = get_form_id(self.source) if self.actions_module: # get the all actions that the actions module can provide for the form # because the module is dynamically loaded, use getattr to retrieve the actual function to call From dd2c273a3b9d2ce58e741366754195f9070a2131 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 21:03:06 +0000 Subject: [PATCH 25/38] Use str.format in preference to %-formatting for more flexible translation. --- Form/UK1841.py | 11 ++++++----- Form/actionutils.py | 4 ++-- Form/formactions.py | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index c1391fe49..339e5572d 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -79,7 +79,7 @@ def command(dbstate, uistate, track, citation_handle, person_handle): db = dbstate.db person = db.get_person_from_handle(person_handle) person.get_primary_name().add_citation(citation_handle) - with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: + with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) class AlternateName: @@ -91,7 +91,8 @@ def get_actions(dbstate, citation, form_event): alternate = Name() alternate.set_first_name(attr.get_value()) alternate.add_citation(citation.handle) - actions.append((name_displayer.display(person), attr.get_value(), + detail = _('Given Name: {name}').format(name=attr.get_value()) + actions.append((name_displayer.display(person), detail, lambda dbstate, uistate, track, person_handle = person.handle, alternate_ = alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) return (_("Add alternate name"), actions) @@ -100,7 +101,7 @@ def command(dbstate, uistate, track, person_handle, alternate): db = dbstate.db person = db.get_person_from_handle(person_handle) person.add_alternate_name(alternate) - with DbTxn(_("Add Person (%s)") % name_displayer.display(person), db) as trans: + with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) class BirthEvent: @@ -175,7 +176,7 @@ def command(dbstate, uistate, track, citation_handle, event_date_object, event_p event.set_date_object(event_date_object) event.set_place_handle(event_place_handle) event.add_citation(citation_handle) - with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: + with DbTxn(_("Add Event ({id})").format(id=event.get_gramps_id()), db) as trans: db.add_event(event, trans) # and reference the event from all people @@ -185,5 +186,5 @@ def command(dbstate, uistate, track, citation_handle, event_date_object, event_p event_ref.set_role(role) person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) - with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: + with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) diff --git a/Form/actionutils.py b/Form/actionutils.py index f574c0140..f99c972ba 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -53,7 +53,7 @@ def add_event_to_person(dbstate, uistate, track, person_handle, event_type, even event.set_description(event_description) # add to the database - with DbTxn(_("Add Event (%s)") % event.get_gramps_id(), db) as trans: + with DbTxn(_("Add Event ({0})").format(event.get_gramps_id()), db) as trans: db.add_event(event, trans) # Add new event reference to the Person record event_ref = EventRef() @@ -61,7 +61,7 @@ def add_event_to_person(dbstate, uistate, track, person_handle, event_type, even event_ref.set_role(event_role_type) person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) - with DbTxn(_("Add Event (%s)") % name_displayer.display(person), db) as trans: + with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) def get_form_person_attr(db, form_event_handle, attr_type): diff --git a/Form/formactions.py b/Form/formactions.py index 7d9162a7f..49b5bf67b 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -109,8 +109,7 @@ def __init__(self, dbstate, uistate, track, citation): spec.loader.exec_module(self.actions_module) except (ValueError, ImportError, SyntaxError) as err: self.actions_module = None - LOG.warning("Form plugin error (from '%s'): %s" - % (self.form_id, err)) + LOG.warning(_("Form plugin error (from '{path}'): {error}").format(path=full_path, error=err)) finally: # must make sure we restore sys.path sys.path.pop(0) @@ -275,6 +274,7 @@ def get_dialog_title(self): """ Get the title of the dialog. """ - dialog_title = _('Form: %s: %s') % (self.source.get_title(), self.citation.get_page()) + dialog_title = _('Form: {source_title}: {event_reference}').format( + source_title=self.source.get_title(), event_reference=self.citation.get_page()) return dialog_title From 2e6dc8361cde317081661f30f79f744015b69033 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 21:07:37 +0000 Subject: [PATCH 26/38] Use str.format rather than %-formatting for consistency --- Form/formactions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index 49b5bf67b..fa5a074f9 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -99,12 +99,12 @@ def __init__(self, dbstate, uistate, track, citation): self.actions_module = None # for security reasons provide the full path to the actions_module .py file - full_path = os.path.join(os.path.dirname(__file__), '%s.py' % self.form_id) + full_path = os.path.join(os.path.dirname(__file__), '{form_id}.py'.format(form_id=self.form_id)) if os.path.exists(full_path): # temporarily modify sys.path so that any import statements in the module get processed correctly sys.path.insert(0, os.path.dirname(__file__)) try: - spec = importlib.util.spec_from_file_location('form.actions.%s' % self.form_id, full_path) + spec = importlib.util.spec_from_file_location('form.actions.{form_id}'.format(form_id=self.form_id), full_path) self.actions_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(self.actions_module) except (ValueError, ImportError, SyntaxError) as err: From 570bd59009a84b9751078c1571fde63cf3dd242c Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 21:13:06 +0000 Subject: [PATCH 27/38] Add more context to the detail column. The additional context is to help the user understand what data the action will be adding to the family tree. --- Form/UK1841.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 339e5572d..8a58ff761 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -129,8 +129,8 @@ def get_actions(dbstate, citation, form_event): birth_range = (birth_date - 5).get_dmy() + (False,) + birth_date.get_dmy() + (False,) birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) birth_date.set_quality(Date.QUAL_CALCULATED) - - actions.append((name_displayer.display(person), date_displayer.display(birth_date), + detail = _('Age: {age}\nDate: {date}').format(age=age_string, date=date_displayer.display(birth_date)) + actions.append((name_displayer.display(person), detail, lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) @@ -142,7 +142,7 @@ def get_actions(dbstate, citation, form_event): for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() if (occupation) : - actions.append((name_displayer.display(person), occupation, + actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) @@ -160,10 +160,11 @@ def get_actions(dbstate, citation, form_event): people.append((person.get_handle(), EventRoleType.PRIMARY)) actions = [] if people: - place = None + detail = None if form_event.get_place_handle(): place = place_displayer.display(db, db.get_place_from_handle(form_event.get_place_handle())) - actions.append((get_participant_from_event(db, form_event.get_handle()), place, + detail = _('Place: {place}').format(place=place) + actions.append((get_participant_from_event(db, form_event.get_handle()), detail, lambda dbstate, uistate, track, citation_handle = citation.handle, people_handles = people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) From 5ad2aa841f737d9da1911d0e4f5e13752808dd6a Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 21:21:11 +0000 Subject: [PATCH 28/38] Aply consistent source formatting (autopep8) --- Form/UK1841.py | 45 +++++++++++++++---------- Form/actionutils.py | 11 +++--- Form/formactions.py | 81 ++++++++++++++++++++++++++++----------------- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 8a58ff761..f4a146903 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -18,11 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Gramps modules # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from gramps.gen.datehandler import displayer as date_displayer from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer @@ -31,18 +31,18 @@ Name, Person) from gramps.gen.utils.db import get_participant_from_event -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Gramplet modules # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ import actionutils -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Internationalisation # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale try: _trans = glocale.get_addon_translator(__file__) @@ -51,19 +51,22 @@ _ = _trans.gettext + def get_actions(dbstate, citation, form_event): """ return a list of all actions that this module can provide for the given citation and form each list entry is a string, describing the action category, and a list of actions that can be performed. """ actions = [] - actions.append(PrimaryNameCitation.get_actions(dbstate, citation, form_event)) + actions.append(PrimaryNameCitation.get_actions( + dbstate, citation, form_event)) actions.append(AlternateName.get_actions(dbstate, citation, form_event)) actions.append(BirthEvent.get_actions(dbstate, citation, form_event)) actions.append(OccupationEvent.get_actions(dbstate, citation, form_event)) actions.append(ResidenceEvent.get_actions(dbstate, citation, form_event)) return actions + class PrimaryNameCitation: @staticmethod def get_actions(dbstate, citation, form_event): @@ -71,7 +74,7 @@ def get_actions(dbstate, citation, form_event): actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): actions.append((name_displayer.display(person), attr.get_value(), - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) + lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @staticmethod @@ -82,6 +85,7 @@ def command(dbstate, uistate, track, citation_handle, person_handle): with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) + class AlternateName: @staticmethod def get_actions(dbstate, citation, form_event): @@ -93,7 +97,7 @@ def get_actions(dbstate, citation, form_event): alternate.add_citation(citation.handle) detail = _('Given Name: {name}').format(name=attr.get_value()) actions.append((name_displayer.display(person), detail, - lambda dbstate, uistate, track, person_handle = person.handle, alternate_ = alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) + lambda dbstate, uistate, track, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) return (_("Add alternate name"), actions) @staticmethod @@ -104,6 +108,7 @@ def command(dbstate, uistate, track, person_handle, alternate): with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) + class BirthEvent: @staticmethod def get_actions(dbstate, citation, form_event): @@ -126,14 +131,18 @@ def get_actions(dbstate, citation, form_event): elif not birth_date.is_compound(): # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. # create a compound range spanning the possible birth years - birth_range = (birth_date - 5).get_dmy() + (False,) + birth_date.get_dmy() + (False,) - birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar(), birth_range, newyear=birth_date.get_new_year()) + birth_range = (birth_date - 5).get_dmy() + \ + (False,) + birth_date.get_dmy() + (False,) + birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar( + ), birth_range, newyear=birth_date.get_new_year()) birth_date.set_quality(Date.QUAL_CALCULATED) - detail = _('Age: {age}\nDate: {date}').format(age=age_string, date=date_displayer.display(birth_date)) + detail = _('Age: {age}\nDate: {date}').format( + age=age_string, date=date_displayer.display(birth_date)) actions.append((name_displayer.display(person), detail, - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, birth_date_ = birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) + class OccupationEvent: @staticmethod def get_actions(dbstate, citation, form_event): @@ -141,11 +150,12 @@ def get_actions(dbstate, citation, form_event): actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() - if (occupation) : + if (occupation): actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), - lambda dbstate, uistate, track, citation_handle = citation.handle, person_handle = person.handle, occupation_ = occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) + class ResidenceEvent: @staticmethod def get_actions(dbstate, citation, form_event): @@ -162,10 +172,11 @@ def get_actions(dbstate, citation, form_event): if people: detail = None if form_event.get_place_handle(): - place = place_displayer.display(db, db.get_place_from_handle(form_event.get_place_handle())) + place = place_displayer.display( + db, db.get_place_from_handle(form_event.get_place_handle())) detail = _('Place: {place}').format(place=place) actions.append((get_participant_from_event(db, form_event.get_handle()), detail, - lambda dbstate, uistate, track, citation_handle = citation.handle, people_handles = people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) + lambda dbstate, uistate, track, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) @staticmethod diff --git a/Form/actionutils.py b/Form/actionutils.py index f99c972ba..bf610036a 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -18,21 +18,21 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Gramps modules # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, Person) -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Internationalisation # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale try: _trans = glocale.get_addon_translator(__file__) @@ -41,6 +41,7 @@ _ = _trans.gettext + def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db """ @@ -64,6 +65,7 @@ def add_event_to_person(dbstate, uistate, track, person_handle, event_type, even with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) + def get_form_person_attr(db, form_event_handle, attr_type): """ Find all persons referencing the form_event and which have an attribute of type attr_type @@ -80,6 +82,7 @@ def get_form_person_attr(db, form_event_handle, attr_type): result.append((person, attr)) return result + def represents_int(s): """ return True iff s is convertable to an int, False otherwise diff --git a/Form/formactions.py b/Form/formactions.py index fa5a074f9..c12a2c9c2 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -21,62 +21,69 @@ """ Form action chooser """ -#------------------------------------------------------------------------- +# ------------------------------------------------------------------------- # # Standard Python modules # -#------------------------------------------------------------------------- +# ------------------------------------------------------------------------- import logging import importlib.util import inspect import os import sys -LOG = logging.getLogger('.form') - -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # GTK modules # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from gi.repository import Gtk, GObject -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Gramps modules # -#------------------------------------------------------------------------ -from gramps.gui.managedwindow import ManagedWindow +# ------------------------------------------------------------------------ from gramps.gen.config import config +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.datehandler import get_date from gramps.gen.db import DbTxn from gramps.gui.display import display_help +from gramps.gui.managedwindow import ManagedWindow -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Gramplet modules # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ from editform import find_form_event from form import (get_form_id, get_form_type) -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# +# Logging +# +# ------------------------------------------------------------------------ +LOG = logging.getLogger('.form') + +# ------------------------------------------------------------------------ # # Internationalisation # -#------------------------------------------------------------------------ -from gramps.gen.const import GRAMPS_LOCALE as glocale +# ------------------------------------------------------------------------ try: _trans = glocale.get_addon_translator(__file__) except ValueError: _trans = glocale.translation _ = _trans.gettext -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # FormActions class # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + class FormActions(object): """ Form Action selector. @@ -99,17 +106,20 @@ def __init__(self, dbstate, uistate, track, citation): self.actions_module = None # for security reasons provide the full path to the actions_module .py file - full_path = os.path.join(os.path.dirname(__file__), '{form_id}.py'.format(form_id=self.form_id)) + full_path = os.path.join(os.path.dirname( + __file__), '{form_id}.py'.format(form_id=self.form_id)) if os.path.exists(full_path): # temporarily modify sys.path so that any import statements in the module get processed correctly sys.path.insert(0, os.path.dirname(__file__)) try: - spec = importlib.util.spec_from_file_location('form.actions.{form_id}'.format(form_id=self.form_id), full_path) + spec = importlib.util.spec_from_file_location( + 'form.actions.{form_id}'.format(form_id=self.form_id), full_path) self.actions_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(self.actions_module) except (ValueError, ImportError, SyntaxError) as err: self.actions_module = None - LOG.warning(_("Form plugin error (from '{path}'): {error}").format(path=full_path, error=err)) + LOG.warning(_("Form plugin error (from '{path}'): {error}").format( + path=full_path, error=err)) finally: # must make sure we restore sys.path sys.path.pop(0) @@ -122,8 +132,10 @@ def __init__(self, dbstate, uistate, track, citation): width = self._config.get('interface.form-actions-width') height = self._config.get('interface.form-actions-height') self.top.resize(width, height) - horiz_position = self._config.get('interface.form-actions-horiz-position') - vert_position = self._config.get('interface.form-actions-vert-position') + horiz_position = self._config.get( + 'interface.form-actions-horiz-position') + vert_position = self._config.get( + 'interface.form-actions-vert-position') if horiz_position != -1: self.top.move(horiz_position, vert_position) @@ -146,12 +158,15 @@ def _create_dialog(self, title): renderer_action_toggle = Gtk.CellRendererToggle() renderer_action_toggle.connect('toggled', self.on_action_toggled) column1.pack_start(renderer_action_toggle, False) - column1.add_attribute(renderer_action_toggle, 'active', self.RUN_ACTION_COL) - column1.add_attribute(renderer_action_toggle, 'inconsistent', self.RUN_INCONSISTENT_COL) + column1.add_attribute(renderer_action_toggle, + 'active', self.RUN_ACTION_COL) + column1.add_attribute(renderer_action_toggle, + 'inconsistent', self.RUN_INCONSISTENT_COL) column1.pack_start(renderer_text, True) column1.add_attribute(renderer_text, 'text', self.ACTION_COL) - column2 = Gtk.TreeViewColumn(_("Detail"), renderer_text, text=self.DETAIL_COL) + column2 = Gtk.TreeViewColumn( + _("Detail"), renderer_text, text=self.DETAIL_COL) self.tree.append_column(column1) self.tree.append_column(column2) @@ -187,7 +202,8 @@ def on_action_toggled(self, widget, path): self.model[row_iter][self.RUN_ACTION_COL] = not self.model[row_iter][self.RUN_ACTION_COL] if parent: # update the status of the parent - (consistent, value) = FormActions.all_children_consistent(self.model, parent, FormActions.RUN_ACTION_COL) + (consistent, value) = FormActions.all_children_consistent( + self.model, parent, FormActions.RUN_ACTION_COL) self.model[parent][self.RUN_INCONSISTENT_COL] = not consistent self.model[parent][self.RUN_ACTION_COL] = consistent and value @@ -211,14 +227,17 @@ def _populate_model(self): if self.actions_module: # get the all actions that the actions module can provide for the form # because the module is dynamically loaded, use getattr to retrieve the actual function to call - all_actions = getattr(self.actions_module, 'get_actions')(self.dbstate, self.citation, self.event) + all_actions = getattr(self.actions_module, 'get_actions')( + self.dbstate, self.citation, self.event) for (title, actions) in all_actions: if actions: # add the action category - parent = self.model.append(None, (False, False, title, None, None)) + parent = self.model.append( + None, (False, False, title, None, None)) for action_detail in actions: # add available actions within this category - self.model.append(parent, (False, False) + action_detail) + self.model.append( + parent, (False, False) + action_detail) def run(self): """ @@ -252,11 +271,13 @@ def run(self): for action_type_row in self.model: for action_row in action_type_row.iterchildren(): if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): - actions.append(action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL)) + actions.append(action_row.model.get_value( + action_row.iter, self.ACTION_COMMAND_COL)) # run the actions for index, action in enumerate(actions): (action)(self.dbstate, self.uistate, self.track) - self.uistate.pulse_progressbar((index + 1) / len(actions) * 100) + self.uistate.pulse_progressbar( + (index + 1) / len(actions) * 100) self.uistate.progress.hide() self.uistate.set_busy_cursor(False) From d30290452b1655db88eada29e494cdde44a8b2f6 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 22:24:54 +0000 Subject: [PATCH 29/38] Add an __init__ function to the actionutils module This is required to correctly initialise module global variables --- Form/actionutils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Form/actionutils.py b/Form/actionutils.py index bf610036a..4ca668065 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -41,6 +41,9 @@ _ = _trans.gettext +def __init__(): + pass + def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db From a12891ca8bdfe9008e0fbc38d07625ca00663a59 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 22:32:33 +0000 Subject: [PATCH 30/38] Allow each action to indicate if the detail of the action can be edited by the user when run. There are three editing options available to an action CANNOT_EDIT_DETAIL - no editing is possible CAN_EDIT_DETAIL - the user can choose to edit the detail MUST_EDIT_DETAIL - the user must edit the detail MUST_EDIT_DETAIL is intended to cater for cases where the action cannot be certain of some data. For example computing a date of birth from an age on a census form when the age is "1m" --- Form/UK1841.py | 56 ++++++++++++++++++++++++--------------------- Form/actionutils.py | 6 +++++ Form/formactions.py | 9 +++++--- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index f4a146903..0d5148430 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -73,7 +73,7 @@ def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): - actions.append((name_displayer.display(person), attr.get_value(), + actions.append((name_displayer.display(person), attr.get_value(), actionutils.CANNOT_EDIT_DETAIL, lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @@ -96,7 +96,7 @@ def get_actions(dbstate, citation, form_event): alternate.set_first_name(attr.get_value()) alternate.add_citation(citation.handle) detail = _('Given Name: {name}').format(name=attr.get_value()) - actions.append((name_displayer.display(person), detail, + actions.append((name_displayer.display(person), detail, actionutils.MUST_EDIT_DETAIL, lambda dbstate, uistate, track, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) return (_("Add alternate name"), actions) @@ -118,28 +118,32 @@ def get_actions(dbstate, citation, form_event): if form_event.get_date_object(): for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Age'): age_string = attr.get_value() - if age_string and actionutils.represents_int(age_string): - age = int(age_string) - if age: - birth_date = form_event.get_date_object() - age - birth_date.make_vague() - # Age was rounded down to the nearest five years for those aged 15 or over - # In practice this rule was not always followed by enumerators - if age < 15: - # no adjustment required - birth_date.set_modifier(Date.MOD_ABOUT) - elif not birth_date.is_compound(): - # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. - # create a compound range spanning the possible birth years - birth_range = (birth_date - 5).get_dmy() + \ - (False,) + birth_date.get_dmy() + (False,) - birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar( - ), birth_range, newyear=birth_date.get_new_year()) - birth_date.set_quality(Date.QUAL_CALCULATED) - detail = _('Age: {age}\nDate: {date}').format( - age=age_string, date=date_displayer.display(birth_date)) - actions.append((name_displayer.display(person), detail, - lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + if age_string: + birth_date = None + if actionutils.represents_int(age_string): + age = int(age_string) + if age: + birth_date = form_event.get_date_object() - age + birth_date.make_vague() + # Age was rounded down to the nearest five years for those aged 15 or over + # In practice this rule was not always followed by enumerators + if age < 15: + # no adjustment required + birth_date.set_modifier(Date.MOD_ABOUT) + elif not birth_date.is_compound(): + # in theory, birth_date will never be compound since 1841 census date was 1841-06-06. Let's handle it anyway. + # create a compound range spanning the possible birth years + birth_range = (birth_date - 5).get_dmy() + \ + (False,) + birth_date.get_dmy() + (False,) + birth_date.set(Date.QUAL_NONE, Date.MOD_RANGE, birth_date.get_calendar( + ), birth_range, newyear=birth_date.get_new_year()) + birth_date.set_quality(Date.QUAL_CALCULATED) + detail = _('Age: {age}\nDate: {date}').format( + age=age_string, date=date_displayer.display(birth_date)) + else: + detail = _('Age: {age}').format(age=age_string) + actions.append((name_displayer.display(person), detail, actionutils.CAN_EDIT_DETAIL if birth_date else actionutils.MUST_EDIT_DETAIL, + lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) @@ -151,7 +155,7 @@ def get_actions(dbstate, citation, form_event): for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() if (occupation): - actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), + actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), actionutils.CAN_EDIT_DETAIL, lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) @@ -175,7 +179,7 @@ def get_actions(dbstate, citation, form_event): place = place_displayer.display( db, db.get_place_from_handle(form_event.get_place_handle())) detail = _('Place: {place}').format(place=place) - actions.append((get_participant_from_event(db, form_event.get_handle()), detail, + actions.append((get_participant_from_event(db, form_event.get_handle()), detail, actionutils.MUST_EDIT_DETAIL, lambda dbstate, uistate, track, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) diff --git a/Form/actionutils.py b/Form/actionutils.py index 4ca668065..823206de0 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -41,6 +41,12 @@ _ = _trans.gettext +# Constants to define the options for editing the details of an action +CANNOT_EDIT_DETAIL = 0 +CAN_EDIT_DETAIL = 1 +MUST_EDIT_DETAIL = 2 + + def __init__(): pass diff --git a/Form/formactions.py b/Form/formactions.py index c12a2c9c2..41068f8fc 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -56,6 +56,7 @@ # Gramplet modules # # ------------------------------------------------------------------------ +import actionutils from editform import find_form_event from form import (get_form_id, get_form_type) @@ -92,7 +93,8 @@ class FormActions(object): RUN_INCONSISTENT_COL = 1 ACTION_COL = 2 DETAIL_COL = 3 - ACTION_COMMAND_COL = 4 + CAN_EDIT_DETAIL_COL = 4 # actionutils.CANNOT_EDIT_DETAIL, actionutils.CAN_EDIT_DETAIL or actionutils.MUST_EDIT_DETAIL + ACTION_COMMAND_COL = 5 def __init__(self, dbstate, uistate, track, citation): self.dbstate = dbstate @@ -151,7 +153,8 @@ def _create_dialog(self, title): box = Gtk.Box() top.vbox.pack_start(box, True, True, 5) - self.model = Gtk.TreeStore(bool, bool, str, str, GObject.TYPE_PYOBJECT) + self.model = Gtk.TreeStore( + bool, bool, str, str, int, GObject.TYPE_PYOBJECT) self.tree = Gtk.TreeView(model=self.model) renderer_text = Gtk.CellRendererText() column1 = Gtk.TreeViewColumn(_("Action")) @@ -233,7 +236,7 @@ def _populate_model(self): if actions: # add the action category parent = self.model.append( - None, (False, False, title, None, None)) + None, (False, False, title, None, actionutils.CANNOT_EDIT_DETAIL, None, False)) for action_detail in actions: # add available actions within this category self.model.append( From d1bb8a388027fd64775433675d155882b1532bab Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Thu, 2 Jan 2020 22:37:55 +0000 Subject: [PATCH 31/38] Add UI elements to allow user to select which actions they wish to edit --- Form/formactions.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index 41068f8fc..fb1f38851 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -95,6 +95,7 @@ class FormActions(object): DETAIL_COL = 3 CAN_EDIT_DETAIL_COL = 4 # actionutils.CANNOT_EDIT_DETAIL, actionutils.CAN_EDIT_DETAIL or actionutils.MUST_EDIT_DETAIL ACTION_COMMAND_COL = 5 + EDIT_DETAIL_COL = 6 def __init__(self, dbstate, uistate, track, citation): self.dbstate = dbstate @@ -154,7 +155,7 @@ def _create_dialog(self, title): top.vbox.pack_start(box, True, True, 5) self.model = Gtk.TreeStore( - bool, bool, str, str, int, GObject.TYPE_PYOBJECT) + bool, bool, str, str, int, GObject.TYPE_PYOBJECT, bool) self.tree = Gtk.TreeView(model=self.model) renderer_text = Gtk.CellRendererText() column1 = Gtk.TreeViewColumn(_("Action")) @@ -168,10 +169,20 @@ def _create_dialog(self, title): column1.pack_start(renderer_text, True) column1.add_attribute(renderer_text, 'text', self.ACTION_COL) + render_edit_detail_toggle = Gtk.CellRendererToggle() + render_edit_detail_toggle.connect( + "toggled", self.on_edit_detail_toggled) column2 = Gtk.TreeViewColumn( + _("Edit"), render_edit_detail_toggle, active=self.EDIT_DETAIL_COL) + column2.set_cell_data_func( + render_edit_detail_toggle, FormActions.detail_data_func) + + column3 = Gtk.TreeViewColumn( _("Detail"), renderer_text, text=self.DETAIL_COL) + self.tree.append_column(column1) self.tree.append_column(column2) + self.tree.append_column(column3) self.tree.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) @@ -210,6 +221,18 @@ def on_action_toggled(self, widget, path): self.model[parent][self.RUN_INCONSISTENT_COL] = not consistent self.model[parent][self.RUN_ACTION_COL] = consistent and value + @staticmethod + def detail_data_func(col, cell, model, iter, user_data): + can_edit_detail = model.get_value( + iter, FormActions.CAN_EDIT_DETAIL_COL) + cell.set_property("visible", can_edit_detail != + actionutils.CANNOT_EDIT_DETAIL) + cell.set_property("activatable", (can_edit_detail != actionutils.MUST_EDIT_DETAIL) and ( + model.get_value(iter, FormActions.RUN_ACTION_COL))) + + def on_edit_detail_toggled(self, widget, path): + self.model[path][self.EDIT_DETAIL_COL] = not self.model[path][self.EDIT_DETAIL_COL] + @staticmethod def all_children_consistent(model, parent, col): consistent = True @@ -240,7 +263,7 @@ def _populate_model(self): for action_detail in actions: # add available actions within this category self.model.append( - parent, (False, False) + action_detail) + parent, (False, False) + action_detail + (action_detail[2] == actionutils.MUST_EDIT_DETAIL,)) def run(self): """ From f0d5c701a88c7344efda0d2504ef45038bf11202 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Fri, 3 Jan 2020 20:41:13 +0000 Subject: [PATCH 32/38] If the user turns EDIT_DETAIL_COL on, automatically turn on RUN_ACTION_COL --- Form/formactions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index fb1f38851..226568eaf 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -227,11 +227,14 @@ def detail_data_func(col, cell, model, iter, user_data): iter, FormActions.CAN_EDIT_DETAIL_COL) cell.set_property("visible", can_edit_detail != actionutils.CANNOT_EDIT_DETAIL) - cell.set_property("activatable", (can_edit_detail != actionutils.MUST_EDIT_DETAIL) and ( - model.get_value(iter, FormActions.RUN_ACTION_COL))) + cell.set_property("activatable", can_edit_detail != actionutils.MUST_EDIT_DETAIL) def on_edit_detail_toggled(self, widget, path): - self.model[path][self.EDIT_DETAIL_COL] = not self.model[path][self.EDIT_DETAIL_COL] + edit_detail = not self.model[path][self.EDIT_DETAIL_COL] + self.model[path][self.EDIT_DETAIL_COL] = edit_detail + if edit_detail: + # as a convenience, if the user turns EDIT_DETAIL_COL on, automatically turn on RUN_ACTION_COL + self.model[path][self.RUN_ACTION_COL] = True @staticmethod def all_children_consistent(model, parent, col): From d9e45a77af9ba1dc11e74b2e205a0276132dfc58 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sat, 4 Jan 2020 22:44:29 +0000 Subject: [PATCH 33/38] FormActions is now based on ManagedWindow This allows us to reuse features of ManagedWindow and more easily have a modeless window --- Form/formactions.py | 160 ++++++++++++++++++++----------------------- Form/formgramplet.py | 5 +- 2 files changed, 77 insertions(+), 88 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index 226568eaf..7a9604d4b 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -48,8 +48,8 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.datehandler import get_date from gramps.gen.db import DbTxn -from gramps.gui.display import display_help from gramps.gui.managedwindow import ManagedWindow +import gramps.gui.display # ------------------------------------------------------------------------ # @@ -85,7 +85,7 @@ # ------------------------------------------------------------------------ -class FormActions(object): +class FormActions(ManagedWindow): """ Form Action selector. """ @@ -107,6 +107,8 @@ def __init__(self, dbstate, uistate, track, citation): self.source = self.db.get_source_from_handle(source_handle) self.form_id = get_form_id(self.source) + ManagedWindow.__init__(self, uistate, track, citation) + self.actions_module = None # for security reasons provide the full path to the actions_module .py file full_path = os.path.join(os.path.dirname( @@ -128,31 +130,26 @@ def __init__(self, dbstate, uistate, track, citation): sys.path.pop(0) self.event = find_form_event(self.db, self.citation) + top = self.__create_gui() + self.set_window(top, None, self.get_title()) + self._local_init() - self.top = self._create_dialog(self.get_dialog_title()) + self._populate_model() + self.tree.expand_all() - self._config = config.get_manager('form') - width = self._config.get('interface.form-actions-width') - height = self._config.get('interface.form-actions-height') - self.top.resize(width, height) - horiz_position = self._config.get( - 'interface.form-actions-horiz-position') - vert_position = self._config.get( - 'interface.form-actions-vert-position') - if horiz_position != -1: - self.top.move(horiz_position, vert_position) + self.show() - def _create_dialog(self, title): + def _local_init(self): + self.setup_configs('interface.form-actions', 750, 550) + + def __create_gui(self): """ Create and display the GUI components of the action selector. """ - top = Gtk.Dialog(title) - top.set_modal(True) - top.set_transient_for(self.uistate.window) - top.vbox.set_spacing(5) - - box = Gtk.Box() - top.vbox.pack_start(box, True, True, 5) + root = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) + root.set_transient_for(self.uistate.window) + # Initial position for first run + root.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.model = Gtk.TreeStore( bool, bool, str, str, int, GObject.TYPE_PYOBJECT, bool) @@ -189,16 +186,35 @@ def _create_dialog(self, title): slist = Gtk.ScrolledWindow() slist.add(self.tree) slist.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - box.pack_start(slist, True, True, 5) - top.add_button(_('_Help'), Gtk.ResponseType.HELP) - top.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL) - top.add_button(_('_OK'), Gtk.ResponseType.OK) - top.set_default_response(Gtk.ResponseType.OK) + button_box = Gtk.ButtonBox() + button_box.set_layout(Gtk.ButtonBoxStyle.END) + + help_btn = Gtk.Button(label=_('_Help'), use_underline=True) + help_btn.connect('clicked', self.display_help) + button_box.add(help_btn) + button_box.set_child_secondary(help_btn, True) + + close_btn = Gtk.Button(label=_('_Close'), use_underline=True) + close_btn.connect('clicked', self.close) + button_box.add(close_btn) + + run_btn = Gtk.Button(label=_('_Run'), use_underline=True) + run_btn.connect('clicked', self.run_actions) + button_box.add(run_btn) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + vbox.set_margin_left(2) + vbox.set_margin_right(2) + vbox.set_margin_top(2) + vbox.set_margin_bottom(2) - top.show_all() + vbox.pack_start(slist, expand=True, fill=True, padding=0) + vbox.pack_end(button_box, expand=False, fill=True, padding=0) - return top + root.add(vbox) + + return root def on_action_toggled(self, widget, path): row_iter = self.model.get_iter(path) @@ -268,63 +284,37 @@ def _populate_model(self): self.model.append( parent, (False, False) + action_detail + (action_detail[2] == actionutils.MUST_EDIT_DETAIL,)) - def run(self): - """ - Run the dialog and return the result. - """ - self._populate_model() - self.tree.expand_all() - while True: - response = self.top.run() - if response == Gtk.ResponseType.HELP: - display_help(webpage='Form_Addons') - else: - break - - (width, height) = self.top.get_size() - self._config.set('interface.form-actions-width', width) - self._config.set('interface.form-actions-height', height) - (root_x, root_y) = self.top.get_position() - self._config.set('interface.form-actions-horiz-position', root_x) - self._config.set('interface.form-actions-vert-position', root_y) - self._config.save() - - if response == Gtk.ResponseType.OK: - # run the selected actions - self.uistate.set_busy_cursor(True) - self.uistate.progress.show() - self.uistate.pulse_progressbar(0) - # get the list of actions to be run - # this helps give meaningful progress information (because we know how many actions in total will be run) - actions = [] - for action_type_row in self.model: - for action_row in action_type_row.iterchildren(): - if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): - actions.append(action_row.model.get_value( - action_row.iter, self.ACTION_COMMAND_COL)) - # run the actions - for index, action in enumerate(actions): - (action)(self.dbstate, self.uistate, self.track) - self.uistate.pulse_progressbar( - (index + 1) / len(actions) * 100) - self.uistate.progress.hide() - self.uistate.set_busy_cursor(False) - - self.top.destroy() - - return None - - def help_clicked(self, obj): + def run_actions(self, widget): + # run the selected actions + self.uistate.set_busy_cursor(True) + self.uistate.progress.show() + self.uistate.pulse_progressbar(0) + # get the list of actions to be run + # this helps give meaningful progress information (because we know how many actions in total will be run) + actions = [] + for action_type_row in self.model: + for action_row in action_type_row.iterchildren(): + if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): + actions.append(action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL)) + # run the actions + for index, action in enumerate(actions): + (action)(self.dbstate, self.uistate, self.track) + self.uistate.pulse_progressbar( + (index + 1) / len(actions) * 100) + self.uistate.progress.hide() + self.uistate.set_busy_cursor(False) + self.close() + + def display_help(self, obj): """ Display the relevant portion of Gramps manual """ - display_help(webpage='Form_Addons') - - def get_dialog_title(self): - """ - Get the title of the dialog. - """ - dialog_title = _('Form: {source_title}: {event_reference}').format( - source_title=self.source.get_title(), event_reference=self.citation.get_page()) - - return dialog_title + gramps.gui.display.display_help(webpage='Form_Addons') + + def get_title(self): + if self.source and self.citation: + title = _('Form: {source_title}: {event_reference}').format( + source_title=self.source.get_title(), event_reference=self.citation.get_page()) + else: + title = None + return title diff --git a/Form/formgramplet.py b/Form/formgramplet.py index cce3a56b6..b415e4b33 100644 --- a/Form/formgramplet.py +++ b/Form/formgramplet.py @@ -2,7 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2009-2015 Nick Hall -# Copyright (C) 2019 Steve Youngs +# Copyright (C) 2019-2020 Steve Youngs # # 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 @@ -184,8 +184,7 @@ def __form_actions(self, widget, selection): if iter_: citation = model.get_value(iter_, 0) try: - actions = FormActions(self.gui.dbstate, self.gui.uistate, [], citation) - actions.run() + FormActions(self.gui.dbstate, self.gui.uistate, [], citation) except WindowActiveError: pass From ffe1ea35a5d0dca592e3829bcb94a328a55be815 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sat, 4 Jan 2020 22:56:03 +0000 Subject: [PATCH 34/38] Pass edit_detail flag into the action commands. The flag is not currently used. --- Form/UK1841.py | 18 +++++++++--------- Form/actionutils.py | 4 ++-- Form/formactions.py | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 0d5148430..c8964fd27 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2019 Steve Youngs +# Copyright (C) 2019-2020 Steve Youngs # # 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 @@ -74,11 +74,11 @@ def get_actions(dbstate, citation, form_event): actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): actions.append((name_displayer.display(person), attr.get_value(), actionutils.CANNOT_EDIT_DETAIL, - lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, citation_handle, person_handle))) + lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, edit_detail, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @staticmethod - def command(dbstate, uistate, track, citation_handle, person_handle): + def command(dbstate, uistate, track, edit_detail, citation_handle, person_handle): db = dbstate.db person = db.get_person_from_handle(person_handle) person.get_primary_name().add_citation(citation_handle) @@ -97,11 +97,11 @@ def get_actions(dbstate, citation, form_event): alternate.add_citation(citation.handle) detail = _('Given Name: {name}').format(name=attr.get_value()) actions.append((name_displayer.display(person), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, person_handle, alternate_))) + lambda dbstate, uistate, track, edit_detail, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, edit_detail, person_handle, alternate_))) return (_("Add alternate name"), actions) @staticmethod - def command(dbstate, uistate, track, person_handle, alternate): + def command(dbstate, uistate, track, edit_detail, person_handle, alternate): db = dbstate.db person = db.get_person_from_handle(person_handle) person.add_alternate_name(alternate) @@ -143,7 +143,7 @@ def get_actions(dbstate, citation, form_event): else: detail = _('Age: {age}').format(age=age_string) actions.append((name_displayer.display(person), detail, actionutils.CAN_EDIT_DETAIL if birth_date else actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) @@ -156,7 +156,7 @@ def get_actions(dbstate, citation, form_event): occupation = attr.get_value() if (occupation): actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), actionutils.CAN_EDIT_DETAIL, - lambda dbstate, uistate, track, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) @@ -180,11 +180,11 @@ def get_actions(dbstate, citation, form_event): db, db.get_place_from_handle(form_event.get_place_handle())) detail = _('Place: {place}').format(place=place) actions.append((get_participant_from_event(db, form_event.get_handle()), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) + lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, edit_detail, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) @staticmethod - def command(dbstate, uistate, track, citation_handle, event_date_object, event_place_handle, people_handles): + def command(dbstate, uistate, track, edit_detail, citation_handle, event_date_object, event_place_handle, people_handles): db = dbstate.db # create the RESIDENCE event event = Event() diff --git a/Form/actionutils.py b/Form/actionutils.py index 823206de0..c491ff03e 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2019 Steve Youngs +# Copyright (C) 2019-2020 Steve Youngs # # 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 @@ -51,7 +51,7 @@ def __init__(): pass -def add_event_to_person(dbstate, uistate, track, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): +def add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db """ Add a new event to the specified person. diff --git a/Form/formactions.py b/Form/formactions.py index 7a9604d4b..267fc6b11 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -295,10 +295,11 @@ def run_actions(self, widget): for action_type_row in self.model: for action_row in action_type_row.iterchildren(): if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): - actions.append(action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL)) + actions.append((action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL), + action_row.model.get_value(action_row.iter, self.EDIT_DETAIL_COL))) # run the actions - for index, action in enumerate(actions): - (action)(self.dbstate, self.uistate, self.track) + for index, (action, edit_detail) in enumerate(actions): + (action)(self.dbstate, self.uistate, self.track, edit_detail) self.uistate.pulse_progressbar( (index + 1) / len(actions) * 100) self.uistate.progress.hide() From 5761b31c37b34c9ba2ed9add2880c1c87e629358 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sat, 4 Jan 2020 23:45:10 +0000 Subject: [PATCH 35/38] Pass a callback into each action command. The callback is called at the end of the command. Instead of looping over the actions to be run with a for loop, use a callback function to recursively call _do_next_action, with the remaining actions to be run. When there are no more actions left to run, close the FormAction window. In future, this change will allow each action command to show any number of editor windows, sequentially. The user will be able to cancel at any point. --- Form/UK1841.py | 23 ++++++++++++++--------- Form/actionutils.py | 5 +++-- Form/formactions.py | 32 +++++++++++++++++++++++--------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index c8964fd27..639758231 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -74,17 +74,18 @@ def get_actions(dbstate, citation, form_event): actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): actions.append((name_displayer.display(person), attr.get_value(), actionutils.CANNOT_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, edit_detail, citation_handle, person_handle))) + lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, edit_detail, callback, citation_handle, person_handle))) return (_("Add Primary Name citation"), actions) @staticmethod - def command(dbstate, uistate, track, edit_detail, citation_handle, person_handle): + def command(dbstate, uistate, track, edit_detail, callback, citation_handle, person_handle): db = dbstate.db person = db.get_person_from_handle(person_handle) person.get_primary_name().add_citation(citation_handle) with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) - + if callback: + callback() class AlternateName: @staticmethod @@ -97,16 +98,18 @@ def get_actions(dbstate, citation, form_event): alternate.add_citation(citation.handle) detail = _('Given Name: {name}').format(name=attr.get_value()) actions.append((name_displayer.display(person), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, edit_detail, person_handle, alternate_))) + lambda dbstate, uistate, track, edit_detail, callback, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, edit_detail, callback, person_handle, alternate_))) return (_("Add alternate name"), actions) @staticmethod - def command(dbstate, uistate, track, edit_detail, person_handle, alternate): + def command(dbstate, uistate, track, edit_detail, callback, person_handle, alternate): db = dbstate.db person = db.get_person_from_handle(person_handle) person.add_alternate_name(alternate) with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) + if callback: + callback() class BirthEvent: @@ -143,7 +146,7 @@ def get_actions(dbstate, citation, form_event): else: detail = _('Age: {age}').format(age=age_string) actions.append((name_displayer.display(person), detail, actionutils.CAN_EDIT_DETAIL if birth_date else actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) return (_("Add Birth event"), actions) @@ -156,7 +159,7 @@ def get_actions(dbstate, citation, form_event): occupation = attr.get_value() if (occupation): actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), actionutils.CAN_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) return (_("Add Occupation event"), actions) @@ -180,11 +183,11 @@ def get_actions(dbstate, citation, form_event): db, db.get_place_from_handle(form_event.get_place_handle())) detail = _('Place: {place}').format(place=place) actions.append((get_participant_from_event(db, form_event.get_handle()), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, edit_detail, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) + lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, edit_detail, callback, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) return (_("Add Residence event"), actions) @staticmethod - def command(dbstate, uistate, track, edit_detail, citation_handle, event_date_object, event_place_handle, people_handles): + def command(dbstate, uistate, track, edit_detail, callback, citation_handle, event_date_object, event_place_handle, people_handles): db = dbstate.db # create the RESIDENCE event event = Event() @@ -204,3 +207,5 @@ def command(dbstate, uistate, track, edit_detail, citation_handle, event_date_ob person.add_event_ref(event_ref) with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) + if callback: + callback() diff --git a/Form/actionutils.py b/Form/actionutils.py index c491ff03e..7b60e51bb 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -51,7 +51,7 @@ def __init__(): pass -def add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): +def add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): db = dbstate.db """ Add a new event to the specified person. @@ -73,7 +73,8 @@ def add_event_to_person(dbstate, uistate, track, edit_detail, person_handle, eve person.add_event_ref(event_ref) with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) - + if callback: + callback() def get_form_person_attr(db, form_event_handle, attr_type): """ diff --git a/Form/formactions.py b/Form/formactions.py index 267fc6b11..3f57ce021 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -286,7 +286,6 @@ def _populate_model(self): def run_actions(self, widget): # run the selected actions - self.uistate.set_busy_cursor(True) self.uistate.progress.show() self.uistate.pulse_progressbar(0) # get the list of actions to be run @@ -297,14 +296,29 @@ def run_actions(self, widget): if action_row.model.get_value(action_row.iter, self.RUN_ACTION_COL): actions.append((action_row.model.get_value(action_row.iter, self.ACTION_COMMAND_COL), action_row.model.get_value(action_row.iter, self.EDIT_DETAIL_COL))) - # run the actions - for index, (action, edit_detail) in enumerate(actions): - (action)(self.dbstate, self.uistate, self.track, edit_detail) - self.uistate.pulse_progressbar( - (index + 1) / len(actions) * 100) - self.uistate.progress.hide() - self.uistate.set_busy_cursor(False) - self.close() + self.count_actions = len(actions) + # run the actions, sequentially + self.do_next_action(actions, self.dbstate, self.uistate, self.track) + + def do_next_action(self, actions, dbstate, uistate, track): + # update the progressbar based on the number of actions completed so far + actions_completed = self.count_actions - len(actions) + self.uistate.pulse_progressbar(actions_completed / self.count_actions * 100) + if actions: + # actions remaining + # take the top action + (action, edit_detail) = actions[0] + # and run it passing, a callback to ourselves, but with actions=actions[1:] + # effectively indirect recursion via the callback + action(dbstate, uistate, track, edit_detail, lambda self=self, actions=actions[1:], dbstate=dbstate, uistate=uistate, track=track : self.do_next_action(actions, dbstate, uistate, track)) + else: + # no more actions. Stop showing progress + uistate.progress.hide() + # and close our window now that the actions have all run + self.close() + + def close(self, *obj): + ManagedWindow.close(self) def display_help(self, obj): """ From 33bb5529d6b364a36a35f6221fcf8818acf5eba0 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 5 Jan 2020 11:05:57 +0000 Subject: [PATCH 36/38] Refactor add_event_to_person into two functions: one to add the new event, the other to add the event_ref to a person --- Form/actionutils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Form/actionutils.py b/Form/actionutils.py index 7b60e51bb..68e9a7c13 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -52,23 +52,28 @@ def __init__(): def add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): - db = dbstate.db """ Add a new event to the specified person. """ + db = dbstate.db event = Event() event.set_type(event_type) event.set_date_object(event_date_object) event.add_citation(citation_handle) event.set_description(event_description) - # add to the database + # add the event to the database with DbTxn(_("Add Event ({0})").format(event.get_gramps_id()), db) as trans: db.add_event(event, trans) + add_event_ref_to_person(dbstate, uistate, track, edit_detail, + callback, person_handle, event.get_handle(), event_role_type) + +def add_event_ref_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, event_handle, event_role_type): # Add new event reference to the Person record event_ref = EventRef() - event_ref.ref = event.get_handle() + event_ref.ref = event_handle event_ref.set_role(event_role_type) + db = dbstate.db person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: From 56ba5f826c9ba4508afb22da4320597a7a5fedae Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 5 Jan 2020 11:45:59 +0000 Subject: [PATCH 37/38] Add action command editing to all existing actions. All action commands are now implemented as chained callbacks using an expanded set of helper functions in actionutils. --- Form/UK1841.py | 131 ++++++++++++++++++++++++-------------------- Form/actionutils.py | 130 ++++++++++++++++++++++++++++++++++++------- Form/formactions.py | 6 +- 3 files changed, 189 insertions(+), 78 deletions(-) diff --git a/Form/UK1841.py b/Form/UK1841.py index 639758231..537e148ec 100644 --- a/Form/UK1841.py +++ b/Form/UK1841.py @@ -73,19 +73,22 @@ def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): - actions.append((name_displayer.display(person), attr.get_value(), actionutils.CANNOT_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, edit_detail, callback, citation_handle, person_handle))) + actions.append((name_displayer.display(person), attr.get_value(), actionutils.CAN_EDIT_DETAIL, + # lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle: PrimaryNameCitation.command(dbstate, uistate, track, edit_detail, callback, citation_handle, person_handle))) + # action command callback + lambda dbstate, uistate, track, edit_detail, callback, person=person: + actionutils.edit_name(actionutils.update_name(name=person.get_primary_name(), citation_handle=citation.handle), + dbstate, uistate, track, edit_detail, + # edit_name callback + lambda name, person=person, dbstate=dbstate, uistate=uistate, track=track, edit_detail=edit_detail: + actionutils.commit_person(actionutils.update_person(person=person, primary_name=name), + dbstate, uistate, track, False, # nothing to edit, so force edit_detail=False + # commit_person callback + # call the top level callback + lambda person, callback=callback: callback())))) + return (_("Add Primary Name citation"), actions) - @staticmethod - def command(dbstate, uistate, track, edit_detail, callback, citation_handle, person_handle): - db = dbstate.db - person = db.get_person_from_handle(person_handle) - person.get_primary_name().add_citation(citation_handle) - with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: - db.commit_person(person, trans) - if callback: - callback() class AlternateName: @staticmethod @@ -93,24 +96,22 @@ def get_actions(dbstate, citation, form_event): db = dbstate.db actions = [] for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Name'): - alternate = Name() - alternate.set_first_name(attr.get_value()) - alternate.add_citation(citation.handle) detail = _('Given Name: {name}').format(name=attr.get_value()) - actions.append((name_displayer.display(person), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, callback, person_handle=person.handle, alternate_=alternate: AlternateName.command(dbstate, uistate, track, edit_detail, callback, person_handle, alternate_))) + actions.append((name_displayer.display(person), detail, + # the user should split the 'Name' attribute into the consituent parts of a Name object, so force MUST_EDIT_DETAIL + actionutils.MUST_EDIT_DETAIL, + # action command callback + lambda dbstate, uistate, track, edit_detail, callback, person=person, name=attr.get_value(): + actionutils.edit_name(actionutils.make_name(first_name=name, citation_handle=citation.handle), dbstate, uistate, track, edit_detail, + # edit_name callback + lambda name, dbstate=dbstate, uistate=uistate, track=track, edit_detail=edit_detail: + actionutils.add_alternate_name_to_person(name, person.handle, + dbstate, uistate, track, False, # nothing to edit, so force edit_detail=False + # add_alternate_name_to_person callback + # call the top level callback + lambda person, callback=callback: callback())))) return (_("Add alternate name"), actions) - @staticmethod - def command(dbstate, uistate, track, edit_detail, callback, person_handle, alternate): - db = dbstate.db - person = db.get_person_from_handle(person_handle) - person.add_alternate_name(alternate) - with DbTxn(_("Add Person ({name})").format(name=name_displayer.display(person)), db) as trans: - db.commit_person(person, trans) - if callback: - callback() - class BirthEvent: @staticmethod @@ -145,8 +146,21 @@ def get_actions(dbstate, citation, form_event): age=age_string, date=date_displayer.display(birth_date)) else: detail = _('Age: {age}').format(age=age_string) - actions.append((name_displayer.display(person), detail, actionutils.CAN_EDIT_DETAIL if birth_date else actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle, birth_date_=birth_date: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, EventType.BIRTH, birth_date_, None, citation_handle, EventRoleType.PRIMARY))) + + actions.append((name_displayer.display(person), detail, + actionutils.CAN_EDIT_DETAIL if birth_date else actionutils.MUST_EDIT_DETAIL, + # action command callback + lambda dbstate, uistate, track, edit_detail, callback, person=person, birth_date=birth_date: + # add a birth event + actionutils.add_event(actionutils.make_event(type=EventType.BIRTH, date_object=birth_date, citation_handle=citation.handle), + dbstate, uistate, track, edit_detail, + # add_event callback + lambda event, dbstate=dbstate, uistate=uistate, track=track, edit_detail=edit_detail, callback=callback, person_handle=person.handle: + # and then add a reference to the event to person + actionutils.add_event_ref_to_person(actionutils.make_event_ref(event_handle=event.get_handle(), role=EventRoleType.PRIMARY), person_handle, dbstate, uistate, track, edit_detail, + # add_event_ref_to_person callback + # call the top level callback + lambda person, callback=callback: callback())))) return (_("Add Birth event"), actions) @@ -158,8 +172,22 @@ def get_actions(dbstate, citation, form_event): for (person, attr) in actionutils.get_form_person_attr(db, form_event.get_handle(), 'Occupation'): occupation = attr.get_value() if (occupation): - actions.append((name_displayer.display(person), _('Description: {occupation}').format(occupation=occupation), actionutils.CAN_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, person_handle=person.handle, occupation_=occupation: actionutils.add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, EventType.OCCUPATION, form_event.get_date_object(), occupation_, citation_handle, EventRoleType.PRIMARY))) + detail = _('Description: {occupation}').format( + occupation=occupation) + actions.append((name_displayer.display(person), detail, + actionutils.CAN_EDIT_DETAIL, + # action command callback + lambda dbstate, uistate, track, edit_detail, callback, person=person, occupation=occupation: + # add a occupation event + actionutils.add_event(actionutils.make_event(type=EventType.OCCUPATION, description=occupation, date_object=form_event.get_date_object(), citation_handle=citation.handle), + dbstate, uistate, track, edit_detail, + # add_event callback + lambda event, dbstate=dbstate, uistate=uistate, track=track, edit_detail=edit_detail, callback=callback, person_handle=person.handle: + # and then add a reference to the event to person + actionutils.add_event_ref_to_person(actionutils.make_event_ref(event_handle=event.get_handle(), role=EventRoleType.PRIMARY), person_handle, dbstate, uistate, track, edit_detail, + # add_event_ref_to_person callback + # call the top level callback + lambda person, callback=callback: callback())))) return (_("Add Occupation event"), actions) @@ -168,44 +196,31 @@ class ResidenceEvent: def get_actions(dbstate, citation, form_event): db = dbstate.db # build a list of all the people referenced in the form. For 1841, all people have a PRIMARY event role - people = [] + event_ref_details = [] for item in db.find_backlink_handles(form_event.get_handle(), include_classes=['Person']): handle = item[1] person = db.get_person_from_handle(handle) for event_ref in person.get_event_ref_list(): if event_ref.ref == form_event.get_handle(): - people.append((person.get_handle(), EventRoleType.PRIMARY)) + event_ref_details.append( + (person.get_handle(), EventRoleType.PRIMARY)) actions = [] - if people: + if event_ref_details: detail = None if form_event.get_place_handle(): place = place_displayer.display( db, db.get_place_from_handle(form_event.get_place_handle())) detail = _('Place: {place}').format(place=place) + actions.append((get_participant_from_event(db, form_event.get_handle()), detail, actionutils.MUST_EDIT_DETAIL, - lambda dbstate, uistate, track, edit_detail, callback, citation_handle=citation.handle, people_handles=people: ResidenceEvent.command(dbstate, uistate, track, edit_detail, callback, citation_handle, form_event.get_date_object(), form_event.get_place_handle(), people_handles))) - return (_("Add Residence event"), actions) + # action command callback + lambda dbstate, uistate, track, edit_detail, callback: + # add a residence event + actionutils.add_event(actionutils.make_event(type=EventType.RESIDENCE, place_handle=form_event.get_place_handle(), date_object=form_event.get_date_object(), citation_handle=citation.handle), + dbstate, uistate, track, edit_detail, + # add_event callback + lambda event, dbstate=dbstate, uistate=uistate, track=track, edit_detail=edit_detail, callback=callback: + # call the top level callback with a dummy people argument that is the list of people to who we added an event_ref to + callback(people=[actionutils.do_add_event_ref_to_person(actionutils.make_event_ref(event_handle=event.get_handle(), role=event_ref_detail[1]), event_ref_detail[0], dbstate) for event_ref_detail in event_ref_details])))) - @staticmethod - def command(dbstate, uistate, track, edit_detail, callback, citation_handle, event_date_object, event_place_handle, people_handles): - db = dbstate.db - # create the RESIDENCE event - event = Event() - event.set_type(EventType.RESIDENCE) - event.set_date_object(event_date_object) - event.set_place_handle(event_place_handle) - event.add_citation(citation_handle) - with DbTxn(_("Add Event ({id})").format(id=event.get_gramps_id()), db) as trans: - db.add_event(event, trans) - - # and reference the event from all people - event_ref = EventRef() - event_ref.ref = event.get_handle() - for (person_handle, role) in people_handles: - event_ref.set_role(role) - person = db.get_person_from_handle(person_handle) - person.add_event_ref(event_ref) - with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: - db.commit_person(person, trans) - if callback: - callback() + return (_("Add Residence event"), actions) diff --git a/Form/actionutils.py b/Form/actionutils.py index 68e9a7c13..6005a7e16 100644 --- a/Form/actionutils.py +++ b/Form/actionutils.py @@ -26,7 +26,8 @@ from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer as name_displayer from gramps.gen.lib import (Event, EventType, EventRef, EventRoleType, - Person) + Name, Person) +from gramps.gui.editors import EditEvent, EditName, EditPerson # ------------------------------------------------------------------------ # @@ -51,39 +52,130 @@ def __init__(): pass -def add_event_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, event_type, event_date_object, event_description, citation_handle, event_role_type): +def make_event(type=EventType(), description=None, date_object=None, citation_handle=None, place_handle=None): """ - Add a new event to the specified person. + make an event initialised with the supplied values. + return the event created """ - db = dbstate.db event = Event() - event.set_type(event_type) - event.set_date_object(event_date_object) + event.set_type(type) + event.set_description(description) + event.set_date_object(date_object) event.add_citation(citation_handle) - event.set_description(event_description) + event.set_place_handle(place_handle) + return event - # add the event to the database - with DbTxn(_("Add Event ({0})").format(event.get_gramps_id()), db) as trans: - db.add_event(event, trans) - add_event_ref_to_person(dbstate, uistate, track, edit_detail, - callback, person_handle, event.get_handle(), event_role_type) -def add_event_ref_to_person(dbstate, uistate, track, edit_detail, callback, person_handle, event_handle, event_role_type): - # Add new event reference to the Person record +def make_event_ref(event_handle=None, role=EventRoleType()): + """ + make an event_ref initialised with the supplied values. + return the event_ref created + """ event_ref = EventRef() - event_ref.ref = event_handle - event_ref.set_role(event_role_type) + event_ref.set_reference_handle(event_handle) + event_ref.set_role(role) + return event_ref + + +def make_name(first_name=None, citation_handle=None): + name = Name() + if first_name: + name.set_first_name(first_name) + if citation_handle: + name.add_citation(citation_handle) + return name + + +def update_name(name, first_name=None, citation_handle=None): + if first_name: + name.set_first_name(first_name) + if citation_handle: + name.add_citation(citation_handle) + return name + + +def update_person(person, primary_name=None): + person.set_primary_name(primary_name) + return person + + +def commit_person(person, dbstate, uistate, track, edit_detail, callback): + """ + commit person to the database, optionally showing the editor window first. + callback(person) is called after successful commit. + Note: If the editor window is cancelled, the callback is not called. + """ + if edit_detail: + EditPerson(dbstate, uistate, track, person, callback) + else: + db = dbstate.db + with DbTxn(_("Update Person ({name})").format(name=name_displayer.display(person)), db) as trans: + db.commit_person(person, trans) + if callback: + callback(person) + + +def add_event(event, dbstate, uistate, track, edit_detail, callback): + """ + Add a new event to the database, calling callback(event) on successful completion. + If edit_detail is true, and the user cancels the editor window, the callback is not called. + """ + db = dbstate.db + if edit_detail: + EditEvent(dbstate, uistate, track, event, callback) + else: + # add the event to the database + with DbTxn(_("Add Event ({0})").format(event.get_gramps_id()), db) as trans: + db.add_event(event, trans) + if callback: + callback(event) + + +def do_add_event_ref_to_person(event_ref, person_handle, dbstate): + """ + Add event_ref to person_handle + return: person_handle + """ + # Add new event reference to the person person_handle db = dbstate.db person = db.get_person_from_handle(person_handle) person.add_event_ref(event_ref) with DbTxn(_("Add Event ({name})").format(name=name_displayer.display(person)), db) as trans: db.commit_person(person, trans) - if callback: - callback() + return person_handle + + +def add_alternate_name_to_person(name, person_handle, dbstate, uistate, track, edit_detail, callback): + # Add new altername name to the person person_handle + db = dbstate.db + person = db.get_person_from_handle(person_handle) + person.add_alternate_name(name) + commit_person(person, dbstate, uistate, track, edit_detail, callback) + + +def add_event_ref_to_person(event_ref, person_handle, dbstate, uistate, track, edit_detail, callback): + """ + Add event_ref to person_handle, calling callback(person) on successful completion. + If edit_detail is true, and the user cancels the editor window, the callback is not called. + return: the person to whom the evert_ref was added. + """ + # Add new event reference to the person person_handle + db = dbstate.db + person = db.get_person_from_handle(person_handle) + person.add_event_ref(event_ref) + commit_person(person, dbstate, uistate, track, edit_detail, callback) + + +def edit_name(name, dbstate, uistate, track, edit_detail, callback): + if edit_detail: + EditName(dbstate, uistate, track, name, callback) + else: + callback(name) + def get_form_person_attr(db, form_event_handle, attr_type): """ - Find all persons referencing the form_event and which have an attribute of type attr_type + Find all persons referencing the form_event and which have an attribute of type attr_type. returns a list of matching (person, attribute) tuples """ result = [] diff --git a/Form/formactions.py b/Form/formactions.py index 3f57ce021..fbfca64fc 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -310,7 +310,11 @@ def do_next_action(self, actions, dbstate, uistate, track): (action, edit_detail) = actions[0] # and run it passing, a callback to ourselves, but with actions=actions[1:] # effectively indirect recursion via the callback - action(dbstate, uistate, track, edit_detail, lambda self=self, actions=actions[1:], dbstate=dbstate, uistate=uistate, track=track : self.do_next_action(actions, dbstate, uistate, track)) + action(dbstate, uistate, track, edit_detail, + # kwargs is added for convenince of the action command authors. + # it is not used, but should not be removed. + lambda self=self, actions=actions[1:], dbstate=dbstate, uistate=uistate, track=track, **kwargs: + self.do_next_action(actions=actions, dbstate=dbstate, uistate=uistate, track=track)) else: # no more actions. Stop showing progress uistate.progress.hide() From 6bad6c1bd5c72d17bd6e50cbb93edc77e0a7ebe1 Mon Sep 17 00:00:00 2001 From: Steve Youngs Date: Sun, 5 Jan 2020 22:08:39 +0000 Subject: [PATCH 38/38] Make closing the FormActions window optional, and default to not closing. If not closing display a message after actions are successfully run. --- Form/formactions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Form/formactions.py b/Form/formactions.py index fbfca64fc..3acdd10a3 100644 --- a/Form/formactions.py +++ b/Form/formactions.py @@ -49,6 +49,7 @@ from gramps.gen.datehandler import get_date from gramps.gen.db import DbTxn from gramps.gui.managedwindow import ManagedWindow +import gramps.gui.dialog import gramps.gui.display # ------------------------------------------------------------------------ @@ -107,6 +108,8 @@ def __init__(self, dbstate, uistate, track, citation): self.source = self.db.get_source_from_handle(source_handle) self.form_id = get_form_id(self.source) + self.close_after_run = False # if True, close this window after running action command(s) + ManagedWindow.__init__(self, uistate, track, citation) self.actions_module = None @@ -318,8 +321,11 @@ def do_next_action(self, actions, dbstate, uistate, track): else: # no more actions. Stop showing progress uistate.progress.hide() - # and close our window now that the actions have all run - self.close() + # and, optionally, close our window now that the actions have all run + if self.close_after_run: + self.close() + else: + gramps.gui.dialog.OkDialog(_("All actions run successfully."), parent=self.window) def close(self, *obj): ManagedWindow.close(self)