From 208feceb0398d6c6b4e397563b9aa2eda2573590 Mon Sep 17 00:00:00 2001 From: Alois Poettker Date: Wed, 18 Apr 2018 17:08:01 +0200 Subject: [PATCH] Clone event w/o references (2nd pass, part B) Feature request #9604 This functionality in the Event list doubles an event with all the tags, citations, media, attributes and notes, but without the references. Its like an event addition, but with default values. --- .../editors/displaytabs/eventbackreflist.py | 17 +++++- gramps/gui/editors/editevent.py | 38 +++++++++---- gramps/gui/editors/editeventref.py | 2 +- gramps/plugins/view/eventview.py | 50 +++++++++++++++--- images/hicolor/16x16/actions/gramps-clone.png | Bin 0 -> 727 bytes images/hicolor/22x22/actions/gramps-clone.png | Bin 0 -> 1134 bytes images/hicolor/24x24/actions/gramps-clone.png | Bin 0 -> 1239 bytes images/hicolor/48x48/actions/gramps-clone.png | Bin 0 -> 3315 bytes images/hicolor/gramps-clone.png | Bin 0 -> 1239 bytes .../hicolor/scalable/actions/gramps-clone.svg | 24 +++++++++ 10 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 images/hicolor/16x16/actions/gramps-clone.png create mode 100644 images/hicolor/22x22/actions/gramps-clone.png create mode 100644 images/hicolor/24x24/actions/gramps-clone.png create mode 100644 images/hicolor/48x48/actions/gramps-clone.png create mode 100644 images/hicolor/gramps-clone.png create mode 100644 images/hicolor/scalable/actions/gramps-clone.svg diff --git a/gramps/gui/editors/displaytabs/eventbackreflist.py b/gramps/gui/editors/displaytabs/eventbackreflist.py index d88959f89..fafe995ae 100644 --- a/gramps/gui/editors/displaytabs/eventbackreflist.py +++ b/gramps/gui/editors/displaytabs/eventbackreflist.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2018 Alois Poettker # # 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 @@ -27,10 +28,24 @@ from .backrefmodel import BackRefModel from .backreflist import BackRefList class EventBackRefList(BackRefList): + """""" + def __init__(self, dbstate, uistate, track, obj, option=None, callback=None): + """ + Connector class between events and back reference mechanism + """ + self.option = option - def __init__(self, dbstate, uistate, track, obj, callback=None): BackRefList.__init__(self, dbstate, uistate, track, obj, BackRefModel, callback) def get_icon_name(self): return 'gramps-event' + + def get_data(self): + """ + Method overrides 'get_data' from BackRefList.py + """ + if self.option and self.option['action']: + return [] + else: + return self.obj diff --git a/gramps/gui/editors/editevent.py b/gramps/gui/editors/editevent.py index f2726f292..0d7ab6c1e 100644 --- a/gramps/gui/editors/editevent.py +++ b/gramps/gui/editors/editevent.py @@ -4,6 +4,7 @@ # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2009 Gary Burton # Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2018 Alois Poettker # # 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 @@ -71,6 +72,9 @@ WIKI_HELP_SEC = _('manual|New_Event_dialog') class EditEvent(EditPrimary): def __init__(self, dbstate, uistate, track, event, callback=None): + """""" + self.callback = callback + self.action = uistate.action.split('-')[1] EditPrimary.__init__(self, dbstate, uistate, track, event, dbstate.db.get_event_from_handle, @@ -86,18 +90,27 @@ class EditEvent(EditPrimary): return Event() def get_menu_title(self): + """ compile menu title out of different actions """ handle = self.obj.get_handle() + + event_action, event_name = '', '' if handle: + if self.action == 'clone': + event_action = _('Clone') + if self.action == 'edit': + event_action = _('Edit') + who = get_participant_from_event(self.db, handle) desc = self.obj.get_description() - event_name = self.obj.get_type() - if desc: - event_name = '%s - %s' % (event_name, desc) + event_name = self.obj.get_type().string if who: - event_name = '%s - %s' % (event_name, who) - dialog_title = _('Event: %s') % event_name + event_name = ': %s - %s' % (event_name, who) + elif desc: + event_name = ': %s - %s' % (event_name, desc) else: - dialog_title = _('New Event') + event_action = _('New') + + dialog_title = _('%s Event%s') % (event_action, event_name) return dialog_title def get_custom_events(self): @@ -207,10 +220,14 @@ class EditEvent(EditPrimary): self._add_tab(notebook, self.attr_list) handle_list = self.dbstate.db.find_backlink_handles(self.obj.handle) + # Additional variables in 'EventBackRefList' injected via 'option' + backref_option = {} + backref_option['action'] = self.action == 'clone' self.backref_list = EventBackRefList(self.dbstate, self.uistate, self.track, - handle_list) + handle_list, + option=backref_option) self._add_tab(notebook, self.backref_list) self._setup_notebook_tabs(notebook) @@ -269,7 +286,11 @@ class EditEvent(EditPrimary): self.db) as trans: self.db.add_event(self.obj, trans) else: - if self.data_has_changed(): + if self.action == 'clone': + with DbTxn(_("Clone Event"), self.db) as trans: + self.obj.handle = None + self.db.add_event(self.obj, trans) + elif self.data_has_changed(): with DbTxn(_("Edit Event (%s)") % self.obj.get_gramps_id(), self.db) as trans: if not self.obj.get_gramps_id(): @@ -287,7 +308,6 @@ class EditEvent(EditPrimary): entered date when importing from a XML file, so we can get an incorrect fail. """ - if self.db.readonly: return False elif self.obj.handle: diff --git a/gramps/gui/editors/editeventref.py b/gramps/gui/editors/editeventref.py index 676b1d78c..053b70bf6 100644 --- a/gramps/gui/editors/editeventref.py +++ b/gramps/gui/editors/editeventref.py @@ -241,7 +241,7 @@ class EditEventRef(EditReference): self.uistate, self.track, self.db.find_backlink_handles(self.source.handle), - self.enable_warnbox) + callback=self.enable_warnbox) self._add_tab(notebook, self.backref_tab) self.track_ref_for_deletion("backref_tab") diff --git a/gramps/plugins/view/eventview.py b/gramps/plugins/view/eventview.py index e133406fb..c369799f1 100644 --- a/gramps/plugins/view/eventview.py +++ b/gramps/plugins/view/eventview.py @@ -3,7 +3,7 @@ # Copyright (C) 2001-2007 Donald N. Allingham # Copyright (C) 2008 Gary Burton # Copyright (C) 2011 Tim G L Lyons -# Copyright (C) 2017 Alois Poettker +# Copyright (C) 2018 Alois Poettker # # 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 @@ -29,6 +29,7 @@ Provide the event view. # Standard python modules # #------------------------------------------------------------------------- +import copy import logging _LOG = logging.getLogger(".plugins.eventview") @@ -40,16 +41,16 @@ _LOG = logging.getLogger(".plugins.eventview") from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gui.dialog import ErrorDialog, MultiSelectDialog, QuestionDialog from gramps.gen.errors import WindowActiveError from gramps.gen.lib import Event +from gramps.gen.plug import CATEGORY_QR_EVENT from gramps.gen.utils.string import data_recover_msg +from gramps.gui.dialog import ErrorDialog, MultiSelectDialog, QuestionDialog from gramps.gui.ddtargets import DdTargets from gramps.gui.editors import EditEvent, DeleteEventQuery from gramps.gui.filters.sidebar import EventSidebarFilter from gramps.gui.merge import MergeEvent -from gramps.gen.plug import CATEGORY_QR_EVENT from gramps.gui.views.bookmarks import EventBookmarks from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON from gramps.gui.views.treemodels import EventModel @@ -97,6 +98,7 @@ class EventView(ListView): EDIT_MSG = _("Edit the selected event") DEL_MSG = _("Delete the selected event") MERGE_MSG = _("Merge the selected events") + CLONE_MSG = _("Clones the selected event") FILTER_TYPE = "Event" QR_CATEGORY = CATEGORY_QR_EVENT @@ -124,6 +126,8 @@ class EventView(ListView): 'BackSpace' : self.key_delete, }) + # Identify the requested action in several sublevel + uistate.action = '' uistate.connect('nameformat-changed', self.build_tree) uistate.connect('placeformat-changed', self.build_tree) @@ -174,6 +178,7 @@ class EventView(ListView): + @@ -188,6 +193,7 @@ class EventView(ListView): + @@ -198,6 +204,7 @@ class EventView(ListView): + @@ -205,8 +212,13 @@ class EventView(ListView): def define_actions(self): ListView.define_actions(self) + self.edit_action.add_actions([ + ('Clone', 'gramps-clone', _('Clone...'), None, + self.CLONE_MSG, self.clone), + ]) self._add_action('FilterEdit', None, - _('Event Filter Editor'), callback=self.filter_editor) + _('Event Filter Editor'), + callback=self.filter_editor,) self._add_action('QuickReport', None, _("Quick View"), None, None, None) @@ -219,6 +231,7 @@ class EventView(ListView): def add(self, obj): try: + self.uistate.action = 'event-add' EditEvent(self.dbstate, self.uistate, [], Event()) except WindowActiveError: pass @@ -282,6 +295,7 @@ class EventView(ListView): for handle in self.selected_handles(): event = self.dbstate.db.get_event_from_handle(handle) try: + self.uistate.action = 'event-edit' EditEvent(self.dbstate, self.uistate, [], event) except WindowActiveError: pass @@ -290,16 +304,38 @@ class EventView(ListView): """ Merge the selected events. """ - mlist = self.selected_handles() + merge_list = self.selected_handles() - if len(mlist) != 2: + if len(merge_list) != 2: msg = _("Cannot merge event objects.") msg2 = _("Exactly two events must be selected to perform a merge. " "A second object can be selected by holding down the " "control key while clicking on the desired event.") ErrorDialog(msg, msg2, parent=self.uistate.window) else: - MergeEvent(self.dbstate, self.uistate, [], mlist[0], mlist[1]) + self.uistate.action = 'event-merge' + MergeEvent(self.dbstate, self.uistate, [], merge_list[0], merge_list[1]) + + def clone(self, obj): + """ + Clones the selected event. + """ + event_list = self.selected_handles() + + if len(event_list) != 1: + msg = _("Cannot clone event object.") + msg2 = _("Exactly one event must be selected to perform a clone.") + ErrorDialog(msg, msg2, parent=self.uistate.window) + else: + event = Event() + event = copy.deepcopy(self.dbstate.db.get_event_from_handle(event_list[0])) + event.gramps_id = None + + try: + self.uistate.action = 'event-clone' + EditEvent(self.dbstate, self.uistate, [], event) + except WindowActiveError: + pass def tag_updated(self, handle_list): """ diff --git a/images/hicolor/16x16/actions/gramps-clone.png b/images/hicolor/16x16/actions/gramps-clone.png new file mode 100644 index 0000000000000000000000000000000000000000..a8b5edaff1638cb91d7439eb55a91c938ef832e9 GIT binary patch literal 727 zcmV;|0x127P)B3e6;4&+Cra#u%2)hSut54D{E24s*;r~w;tb8l~DyMz5V14 z%TJCU3}8^VsftBMM0fJOiB}$7S)R3u1t@m+<|C%xtRO>gJiN&!#JX|c=6I)AK_0<( zpWoN7uKV=oJ&U@W8c<$INxo`M-SmRVJ*)eGqSNALvOrAr)A2ue{7BiVlGmT#FU(&8 zGKv8PzJ2}i?#nAd1`!CCrF>R(M`{ZLDkzv*#A0J?b@K6vm7CY~Wpx3CvM1zz;r#IK z;|G?HUq6V7330J=Le%nd@qb}>|NHlEmirGLow#t?%F+ZV`uE@8H!RO&nH9ywWm#%s zD}X}HYg;;Bcf9)e^5fgjb4r^Lt}65@=-t)*{nz*TIrE{ef>507oL_%_{rckzC(JUa z1Tz!!tJiP7|NP0wzzCJNbocVJFVDXH{C@G;P2tBkDw8M*#_fBl^$-Xmtq zxor~{PW5zj6BQP@bMKz1u_@eaby>AtS9Sqa|Ni;s*E?{UkQA0obxj7MrF)kD7x^zB z!n^Y9$`=n`cciwK&8qs!_Te|fFHs(GJq4pUw`i0|igt)>GTKMcIG+{s9b5=5K6Sei_JWP&5Dm8w>02 zUw?l5{ts0B`_Hd`KmM_@u;SAI1hFpBzyADYVE7M&a^iAmvH-~Q7UgAfHpc(}002ov JPDHLkV1n`!YTW<; literal 0 HcmV?d00001 diff --git a/images/hicolor/22x22/actions/gramps-clone.png b/images/hicolor/22x22/actions/gramps-clone.png new file mode 100644 index 0000000000000000000000000000000000000000..d2144b8adc2b62bfc3109eb62aae70ed186b0817 GIT binary patch literal 1134 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 literal 0 HcmV?d00001 diff --git a/images/hicolor/gramps-clone.png b/images/hicolor/gramps-clone.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c8f029bb14ea108ddccbfb6cd6f10f3755b03e GIT binary patch literal 1239 zcmV;|1StE7P)4V?6Lp-Lwjrb);;N`-shAQ z@{)N4>Vqu47E@Iorr|z)vmChzl0x3fxU;V$@uZ7v*gE2?(PRG^@8l7Wa~*x{q5dzy zb7oz}y^(&@)zdAAc{jvP*x^DOS4QSl=ieT<)j!%Vwc}0unqBm{Ngoxp9Gf5~O43VR z^ph#1-A>si%ZFqh=L^pqDcqGW5{a;xcAz?YfbK_fnY_=|7a#pv(~YLs;Ml1!$x!>x z?qqIuxBapEw z@8C`oHoxVZF(Lm&Pu>{pY_qhd#lABuTwGszefU?5kRY066yDw;FRrLCjexuF)=(dexGQ_ ztLquKg8%{El08Ld+s>{Ie!le5$*x=7YCms^B+bTJ2m#=cPTSUOJO1r)IWAqeM1Oy1 zaO2aPJ%|ZwY8|)wNVN>d5CpM3D$%pzVnISdrwpw?%IW?YAev;wN&%R}PBbrwWBK6$ z8;~hP>KP!bW4yk-Q6cxX*=)O`ljkny|JVcTu({QI<*%!lL_kvH(mXaY23GCQhc_RL z4LARI*-yP-cg#+WLNhnnDL9MGrJcQ9N?$oI;%SZ&RE(6s;qQ+`n8-mnSe{`x7RS9d zM?^wGcBL^hF~i?)$#j<9q2BUKm6)2)5fJ6EsP&nhQiVL(>GhlUCR?jr*v%d_q*olNHvlIQsjizR8(j+{zk)P@LHvXv{A zH(qbV>|#)DI#Ef(sk`KDU;(iij$SMdIFy!WD9hjk&h=%>-k#~k73?}x@WtWa7lPm= za=DC?a!2crhAa;aU$u^)iNofPX&E74PKeqb+S8)mUB4>a#hzX?d$eFVJF?(RDM||p zdPb#GD)jhoLs#3rX>}x)%z~U;0W8vgkA@!9MkNA0sl(-TJ$UVoYfvTiR@IM}r zOezzc7<2?*GU(1dCIyCr|M7^#B5_tihDX0g{6E@}P>zc + + + + + + + + + + + + + + + +