From 0b88f0cbbe921353fbe915d7e2b3e364197e177f Mon Sep 17 00:00:00 2001 From: Christopher Horn <cdhorn@embarqmail.com> Date: Sat, 11 Mar 2023 01:29:27 -0500 Subject: [PATCH] Add tag list view, enable tag drag and drop --- gramps/gen/utils/db.py | 4 + gramps/gui/clipboard.py | 40 +- gramps/gui/ddtargets.py | 4 +- gramps/gui/navigator.py | 1 + gramps/gui/views/listview.py | 6 +- gramps/gui/views/navigationview.py | 16 +- gramps/gui/widgets/monitoredwidgets.py | 21 +- gramps/plugins/view/tagview.py | 583 +++++++++++++++++++++++++ gramps/plugins/view/view.gpr.py | 17 + 9 files changed, 679 insertions(+), 13 deletions(-) create mode 100644 gramps/plugins/view/tagview.py diff --git a/gramps/gen/utils/db.py b/gramps/gen/utils/db.py index bb842275f..d1ca39cd2 100644 --- a/gramps/gen/utils/db.py +++ b/gramps/gen/utils/db.py @@ -369,6 +369,10 @@ def navigation_label(db, nav_type, handle): label = " ".join(label.split()) if len(label) > 40: label = label[:40] + "..." + elif nav_type == 'Tag': + obj = db.get_tag_from_handle(handle) + if obj: + return ('[%s] %s' % (_('Tag'), obj.get_name()), obj) if label and obj: label = '[%s] %s' % (obj.get_gramps_id(), label) diff --git a/gramps/gui/clipboard.py b/gramps/gui/clipboard.py index e04f88f41..6fef3cb4a 100644 --- a/gramps/gui/clipboard.py +++ b/gramps/gui/clipboard.py @@ -94,7 +94,8 @@ for (name, icon) in (("media", "gramps-media"), ('source', 'gramps-source'), ('citation', 'gramps-citation'), ('text', 'gramps-font'), - ('url', 'gramps-geo')): + ('url', 'gramps-geo'), + ('tag', 'gramps-tag')): ICONS[name] = theme.load_icon(icon, 16, 0) @@ -118,6 +119,7 @@ def map2class(target): 'place-link': ClipPlace, 'placeref': ClipPlaceRef, 'note-link': ClipNote, + 'tag': ClipTag, 'TEXT': ClipText} return _d_[target] if target in _d_ else None @@ -131,7 +133,8 @@ def obj2class(target): 'Event': ClipEvent, 'Media': ClipMediaObj, 'Place': ClipPlace, - 'Note': ClipNote} + 'Note': ClipNote, + 'Tag': ClipTag} return _d_[target] if target in _d_ else None OBJ2TARGET = {"Person": Gdk.atom_intern('person-link', False), @@ -142,7 +145,8 @@ OBJ2TARGET = {"Person": Gdk.atom_intern('person-link', False), 'Event': Gdk.atom_intern('pevent', False), 'Media': Gdk.atom_intern('media', False), 'Place': Gdk.atom_intern('place-link', False), - 'Note': Gdk.atom_intern('note-link', False)} + 'Note': Gdk.atom_intern('note-link', False), + "Tag": Gdk.atom_intern('tag', False)} def obj2target(target): @@ -280,7 +284,7 @@ class ClipObjWrapper(ClipWrapper): return False for (clname, handle) in self._obj.get_referenced_handles_recursively(): - if obj2class(clname): # a class we care about (not tag) + if obj2class(clname): # a class we care about if not clipdb.method("has_%s_handle", clname)(handle): return False @@ -424,6 +428,26 @@ class ClipUrl(ClipObjWrapper): self._value = self._obj.get_description() +class ClipTag(ClipHandleWrapper): + + DROP_TARGETS = [DdTargets.TAG_LINK] + DRAG_TARGET = DdTargets.TAG_LINK + ICON = ICONS['tag'] + + def __init__(self, obj): + super(ClipTag, self).__init__(obj) + self._type = _("Tag") + self._objclass = "Tag" + self.refresh() + + def refresh(self): + if self._handle: + value = clipdb.get_tag_from_handle(self._handle) + if value: + self._title = value.get_name() + self._value = value.get_color() + + class ClipAttribute(ClipObjWrapper): DROP_TARGETS = [DdTargets.ATTRIBUTE] @@ -993,7 +1017,9 @@ class ClipboardListView: 'event-rebuild', 'repository-update', 'repository-rebuild', - 'note-rebuild') + 'note-rebuild', + 'tag-update', + 'tag-rebuild') for signal in db_signals: clipdb.connect(signal, self.refresh_objects) @@ -1022,6 +1048,8 @@ class ClipboardListView: gen_del_obj(self.delete_object, 'place-link')) clipdb.connect('note-delete', gen_del_obj(self.delete_object, 'note-link')) + clipdb.connect('tag-delete', + gen_del_obj(self.delete_object, 'tag')) # family-delete not needed, cannot be dragged! self.refresh_objects() @@ -1087,6 +1115,7 @@ class ClipboardListView: self.register_wrapper_class(ClipChildRef) self.register_wrapper_class(ClipText) self.register_wrapper_class(ClipNote) + self.register_wrapper_class(ClipTag) def register_wrapper_class(self, wrapper_class): for drop_target in wrapper_class.DROP_TARGETS: @@ -1593,6 +1622,7 @@ class MultiTreeView(Gtk.TreeView): from .editors import (EditPerson, EditEvent, EditFamily, EditSource, EditPlace, EditRepository, EditNote, EditMedia, EditCitation) + from .views.tags import EditTag if obj2class(objclass): # make sure it is an editable object if self.dbstate.db.method('has_%s_handle', objclass)(handle): g_object = self.dbstate.db.method( diff --git a/gramps/gui/ddtargets.py b/gramps/gui/ddtargets.py index cdb1ee937..5b1dee327 100644 --- a/gramps/gui/ddtargets.py +++ b/gramps/gui/ddtargets.py @@ -154,6 +154,7 @@ class _DdTargets: self.URL = _DdType(self, 'url') self.SURNAME = _DdType(self, 'surname') self.CITATION_LINK = _DdType(self, 'citation-link') + self.TAG_LINK = _DdType(self, 'tag') # List of all types that are used between # gramps widgets but should not be exported @@ -185,7 +186,8 @@ class _DdTargets: self.SRCATTRIBUTE, self.URL, self.SURNAME, - self.CITATION_LINK + self.CITATION_LINK, + self.TAG_LINK, ] self.CHILD = _DdType(self, 'child') diff --git a/gramps/gui/navigator.py b/gramps/gui/navigator.py index faaf18f3a..4c2f3078f 100644 --- a/gramps/gui/navigator.py +++ b/gramps/gui/navigator.py @@ -68,6 +68,7 @@ CATEGORY_ICON = { 'Media': 'gramps-media', 'Notes': 'gramps-notes', 'Citations': 'gramps-citation', + 'Tags': 'gramps-tag' } #------------------------------------------------------------------------- diff --git a/gramps/gui/views/listview.py b/gramps/gui/views/listview.py index 4032d10dd..a3011440a 100644 --- a/gramps/gui/views/listview.py +++ b/gramps/gui/views/listview.py @@ -781,7 +781,8 @@ class ListView(NavigationView): #force rebuild of the model on build of tree self.dirty = True self.build_tree() - self.bookmarks.redraw() + if self.bookmarks: + self.bookmarks.redraw() else: self.dirty = True @@ -894,7 +895,8 @@ class ListView(NavigationView): if self.active: # Save the currently selected handles, if any: selected_ids = self.selected_handles() - self.bookmarks.redraw() + if self.bookmarks: + self.bookmarks.redraw() self.build_tree() # Reselect one, if it still exists after rebuild: nav_type = self.navigation_type() diff --git a/gramps/gui/views/navigationview.py b/gramps/gui/views/navigationview.py index df5a3fdcb..495b3c894 100644 --- a/gramps/gui/views/navigationview.py +++ b/gramps/gui/views/navigationview.py @@ -75,7 +75,12 @@ class NavigationView(PageView): def __init__(self, title, pdata, state, uistate, bm_type, nav_group): PageView.__init__(self, title, pdata, state, uistate) - self.bookmarks = bm_type(self.dbstate, self.uistate, self.change_active) + if bm_type: + self.bookmarks = bm_type( + self.dbstate, self.uistate, self.change_active + ) + else: + self.bookmarks = None self.fwd_action = None self.back_action = None @@ -103,7 +108,8 @@ class NavigationView(PageView): Define menu actions. """ PageView.define_actions(self) - self.bookmark_actions() + if self.bookmarks: + self.bookmark_actions() self.navigation_actions() def disable_action_group(self): @@ -151,7 +157,8 @@ class NavigationView(PageView): Called when the page becomes active (displayed). """ PageView.set_active(self) - self.bookmarks.display() + if self.bookmarks: + self.bookmarks.display() hobj = self.get_history() self.active_signal = hobj.connect('active-changed', self.goto_active) @@ -166,7 +173,8 @@ class NavigationView(PageView): """ if self.active: PageView.set_inactive(self) - self.bookmarks.undisplay() + if self.bookmarks: + self.bookmarks.undisplay() hobj = self.get_history() hobj.disconnect(self.active_signal) hobj.disconnect(self.mru_signal) diff --git a/gramps/gui/widgets/monitoredwidgets.py b/gramps/gui/widgets/monitoredwidgets.py index 3b694f690..05c52d51c 100644 --- a/gramps/gui/widgets/monitoredwidgets.py +++ b/gramps/gui/widgets/monitoredwidgets.py @@ -30,6 +30,7 @@ __all__ = ["MonitoredCheckbox", "MonitoredEntry", # Standard python modules # #------------------------------------------------------------------------- +import pickle import logging _LOG = logging.getLogger(".widgets.monitoredwidgets") @@ -54,13 +55,13 @@ from ..autocomp import StandardCustomSelector, fill_entry from gramps.gen.datehandler import displayer, parser from gramps.gen.lib.date import Date, NextYear from gramps.gen.errors import ValidationError +from gramps.gui.ddtargets import DdTargets #------------------------------------------------------------------------- # # constants # #------------------------------------------------------------------------ - _RETURN = Gdk.keyval_from_name("Return") _KP_ENTER = Gdk.keyval_from_name("KP_Enter") @@ -813,6 +814,8 @@ class MonitoredTagList: self.label = label self.label.set_halign(Gtk.Align.START) self.label.set_ellipsize(Pango.EllipsizeMode.END) + self.label.drag_dest_set(Gtk.DestDefaults.ALL, [DdTargets.TAG_LINK.target()], Gdk.DragAction.COPY) + self.label.connect('drag_data_received', self.tag_dropped) image = Gtk.Image() image.set_from_icon_name('gramps-tag', Gtk.IconSize.MENU) button.set_image (image) @@ -856,3 +859,19 @@ class MonitoredTagList: self.set_list([item[0] for item in self.tag_list]) return True return False + + def tag_dropped(self, _dummy_widget, _dummy_context, _dummy_x, + _dummy_y, data, _dummy_info, _dummy_time): + """ + Add dropped tag if not in list. + """ + if data and data.get_data(): + (dnd_type, obj_id, handle, val) = pickle.loads(data.get_data()) + for item in self.tag_list: + if item[0] == handle: + return True + tag = self.db.get_tag_from_handle(handle) + self.tag_list.append((handle, tag.get_name())) + self._display() + self.set_list([item[0] for item in self.tag_list]) + return True diff --git a/gramps/plugins/view/tagview.py b/gramps/plugins/view/tagview.py new file mode 100644 index 000000000..770d1ff57 --- /dev/null +++ b/gramps/plugins/view/tagview.py @@ -0,0 +1,583 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2001-2006 Donald N. Allingham +# Copyright (C) 2008 Gary Burton +# Copyright (C) 2010 Nick Hall +# Copyright (C) 2022 Christopher Horn +# +# 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. +# + +""" +Tag View. +""" + +# ------------------------------------------------------------------------- +# +# GTK/Gnome Modules +# +# ------------------------------------------------------------------------- +from gi.repository import Gtk + +# ------------------------------------------------------------------------- +# +# Gramps Modules +# +# ------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.datehandler import format_time +from gramps.gen.db import DbTxn +from gramps.gen.errors import WindowActiveError +from gramps.gen.lib import Tag +from gramps.gui.ddtargets import DdTargets +from gramps.gui.dialog import QuestionDialog2 +from gramps.gui.views.listview import ListView, TEXT +from gramps.gui.views.tags import EditTag, OrganizeTagsDialog +from gramps.gui.views.treemodels.flatbasemodel import FlatBaseModel +import gramps.gui.widgets.progressdialog as progressdlg + +_ = glocale.translation.sgettext + + +(POS_HANDLE, POS_NAME, POS_COLOR, POS_PRIORITY, POS_CHANGE) = list(range(5)) + + +# ------------------------------------------------------------------------- +# +# TagModel +# +# ------------------------------------------------------------------------- +class TagModel(FlatBaseModel): + """ + Basic model for a Tag list + """ + + def __init__( + self, + db, + uistate, + scol=0, + order=Gtk.SortType.ASCENDING, + search=None, + skip=None, + sort_map=None, + ): + """Setup initial values for instance variables.""" + skip = skip or set() + self.gen_cursor = db.get_tag_cursor + self.map = db.get_raw_tag_data + self.fmap = [ + self.column_name, + self.column_color, + self.column_priority, + self.column_change, + self.column_count, + ] + self.smap = [ + self.column_name, + self.column_color, + self.column_priority, + self.sort_change, + self.sort_count, + ] + FlatBaseModel.__init__( + self, + db, + uistate, + scol, + order, + search=search, + skip=skip, + sort_map=sort_map, + ) + + def destroy(self): + """ + Unset all elements that can prevent garbage collection + """ + self.db = None + self.gen_cursor = None + self.map = None + self.fmap = None + self.smap = None + FlatBaseModel.destroy(self) + + def color_column(self): + """ + Return the color column. + """ + return 1 + + def on_get_n_columns(self): + """ + Return the column number of the Tag tab. + """ + return len(self.fmap) + 1 + + def column_handle(self, data): + """ + Return the handle of the Tag. + """ + return data[POS_HANDLE] + + def column_name(self, data): + """ + Return the name of the Tag in readable format. + """ + return data[POS_NAME] + + def column_priority(self, data): + """ + Return the priority of the Tag. + """ + return "%03d" % data[POS_PRIORITY] + + def column_color(self, data): + """ + Return the color. + """ + return data[POS_COLOR] + + def sort_change(self, data): + """ + Return sort value for change. + """ + return "%012x" % data[POS_CHANGE] + + def column_change(self, data): + """ + Return formatted change time. + """ + return format_time(data[POS_CHANGE]) + + def sort_count(self, data): + """ + Return sort value for count of tagged items. + """ + return "%012d" % len( + list(self.db.find_backlink_handles(data[POS_HANDLE])) + ) + + def column_count(self, data): + """ + Return count of tagged items. + """ + return int(len(list(self.db.find_backlink_handles(data[POS_HANDLE])))) + + +# ------------------------------------------------------------------------- +# +# TagView +# +# ------------------------------------------------------------------------- +class TagView(ListView): + """ + TagView, a normal flat listview for the tags + """ + + COL_NAME = 0 + COL_COLO = 1 + COL_PRIO = 2 + COL_CHAN = 3 + COL_COUNT = 4 + + # column definitions + COLUMNS = [ + (_("Name"), TEXT, None), + (_("Color"), TEXT, None), + (_("Priority"), TEXT, None), + (_("Last Changed"), TEXT, None), + (_("Tagged Items"), TEXT, None), + ] + # default setting with visible columns, order of the col, and their size + CONFIGSETTINGS = ( + ( + "columns.visible", + [COL_NAME, COL_COLO, COL_PRIO, COL_CHAN, COL_COUNT], + ), + ("columns.rank", [COL_NAME, COL_COLO, COL_PRIO, COL_CHAN, COL_COUNT]), + ("columns.size", [330, 150, 70, 200, 50]), + ) + + ADD_MSG = _("Add a new tag") + EDIT_MSG = _("Edit the selected tag") + DEL_MSG = _("Delete the selected tag") + ORGANIZE_MSG = _("Organize tags") + + FILTER_TYPE = "Tag" + QR_CATEGORY = -1 + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + signal_map = { + "tag-add": self.row_add, + "tag-update": self.row_update, + "tag-delete": self.row_delete, + "tag-rebuild": self.object_build, + } + + # Work around for modify_statusbar issues + if "Tag" not in uistate.NAV2MES: + uistate.NAV2MES["Tag"] = "" + + ListView.__init__( + self, + _("Tags"), + pdata, + dbstate, + uistate, + TagModel, + signal_map, + None, + nav_group, + filter_class=None, + multiple=False, + ) + + self.additional_uis.append(self.additional_ui) + + def navigation_type(self): + """ + Return the navigation type. + """ + return "Tag" + + def drag_info(self): + """ + Return a drag type of TAG_LINK + """ + return DdTargets.TAG_LINK + + def get_stock(self): + """ + Return the gramps-tag stock icon + """ + return "gramps-tag" + + additional_ui = [ # Defines the UI string for UIManager + """ + <placeholder id="LocalExport"> + <item> + <attribute name="action">win.ExportTab</attribute> + <attribute name="label" translatable="yes">Export View...</attribute> + </item> + </placeholder> +""", + """ + <placeholder id="CommonGo"> + <section> + <item> + <attribute name="action">win.Back</attribute> + <attribute name="label" translatable="yes">_Back</attribute> + </item> + <item> + <attribute name="action">win.Forward</attribute> + <attribute name="label" translatable="yes">_Forward</attribute> + </item> + </section> + </placeholder> +""", + """ + <section id='CommonEdit' groups='RW'> + <item> + <attribute name="action">win.Add</attribute> + <attribute name="label" translatable="yes">_Add...</attribute> + </item> + <item> + <attribute name="action">win.Edit</attribute> + <attribute name="label">%s</attribute> + </item> + <item> + <attribute name="action">win.Remove</attribute> + <attribute name="label" translatable="yes">_Delete</attribute> + </item> + <item> + <attribute name="action">win.Organize</attribute> + <attribute name="label" translatable="yes">_Organize...</attribute> + </item> + </section> +""" + % _( + "_Edit...", "action" + ), # to use sgettext() # Following are the Toolbar items + """ + <placeholder id='CommonNavigation'> + <child groups='RO'> + <object class="GtkToolButton"> + <property name="icon-name">go-previous</property> + <property name="action-name">win.Back</property> + <property name="tooltip_text" translatable="yes">""" + """Go to the previous object in the history</property> + <property name="label" translatable="yes">_Back</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + <child groups='RO'> + <object class="GtkToolButton"> + <property name="icon-name">go-next</property> + <property name="action-name">win.Forward</property> + <property name="tooltip_text" translatable="yes">""" + """Go to the next object in the history</property> + <property name="label" translatable="yes">_Forward</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + </placeholder> +""", + """ + <placeholder id='BarCommonEdit'> + <child groups='RW'> + <object class="GtkToolButton"> + <property name="icon-name">list-add</property> + <property name="action-name">win.Add</property> + <property name="tooltip_text">%s</property> + <property name="label" translatable="yes">_Add...</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + <child groups='RW'> + <object class="GtkToolButton"> + <property name="icon-name">gtk-edit</property> + <property name="action-name">win.Edit</property> + <property name="tooltip_text">%s</property> + <property name="label" translatable="yes">Edit...</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + <child groups='RW'> + <object class="GtkToolButton"> + <property name="icon-name">list-remove</property> + <property name="action-name">win.Remove</property> + <property name="tooltip_text">%s</property> + <property name="label" translatable="yes">_Delete</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + <child groups='RW'> + <object class="GtkToolButton"> + <property name="icon-name">view-sort-descending</property> + <property name="action-name">win.Organize</property> + <property name="tooltip_text">%s</property> + <property name="label" translatable="yes">_Organize</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="homogeneous">False</property> + </packing> + </child> + </placeholder> +""" + % (ADD_MSG, EDIT_MSG, DEL_MSG, ORGANIZE_MSG), + """ + <menu id="Popup"> + <section> + <item> + <attribute name="action">win.Back</attribute> + <attribute name="label" translatable="yes">_Back</attribute> + </item> + <item> + <attribute name="action">win.Forward</attribute> + <attribute name="label" translatable="yes">Forward</attribute> + </item> + </section> + <section id="PopUpTree"> + </section> + <section> + <item> + <attribute name="action">win.Add</attribute> + <attribute name="label" translatable="yes">_Add...</attribute> + </item> + <item> + <attribute name="action">win.Edit</attribute> + <attribute name="label">%s</attribute> + </item> + <item> + <attribute name="action">win.Remove</attribute> + <attribute name="label" translatable="yes">_Delete</attribute> + </item> + <item> + <attribute name="action">win.Organize</attribute> + <attribute name="label" translatable="yes">_Organize...</attribute> + </item> + </section> + <section> + <placeholder id='QuickReport'> + </placeholder> + </section> + </menu> + """ + % _("_Edit...", "action"), # to use sgettext() + ] + # Leave QuickReport as placeholder + + def define_actions(self): + """ + Define actions for the view. + """ + ListView.define_actions(self) + self.edit_action.add_actions( + [("Organize", self.organize, "<PRIMARY>Home")] + ) + + def set_active(self): + """ + Set view active. + """ + ListView.set_active(self) + self.uistate.viewmanager.tags.tag_disable() + + def set_inactive(self): + """ + Set view inactive. + """ + ListView.set_inactive(self) + self.uistate.viewmanager.tags.tag_enable(update_menu=False) + + def get_handle_from_gramps_id(self, gid): + """ + Not applicable. + """ + return None + + def add(self, *obj): + """ + Add new tag. + """ + try: + EditTag(self.dbstate.db, self.uistate, [], Tag()) + except WindowActiveError: + pass + + def remove(self, *obj): + """ + Remove selected tag. + """ + handles = self.selected_handles() + if handles: + tag = self.dbstate.db.get_tag_from_handle(handles[0]) + delete_tag(self.uistate.window, self.dbstate.db, tag) + + def edit(self, *obj): + """ + Edit selected tag. + """ + for handle in self.selected_handles(): + tag = self.dbstate.db.get_tag_from_handle(handle) + try: + EditTag(self.dbstate.db, self.uistate, [], tag) + except WindowActiveError: + pass + + def organize(self, *_dummy_obj): + """ + Launch organize tool. + """ + try: + OrganizeTagsDialog(self.dbstate.db, self.uistate, []) + except WindowActiveError: + pass + + def merge(self, *obj): + """ + Not supported for now. + """ + + def tag_updated(self, handle_list): + """ + Not applicable. + """ + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return ((), ()) + + def remove_object_from_handle(self, *args, **kwargs): + """ + Not applicable. + """ + + +def delete_tag(window, db, tag): + """ + Handle tag deletion, extracted from OrganizeTagsDialog. + """ + yes_no = QuestionDialog2( + _("Remove tag '%s'?") % tag.name, + _( + "The tag definition will be removed. The tag will be also " + "removed from all objects in the database." + ), + _("Yes"), + _("No"), + parent=window, + ) + prompt = yes_no.run() + if prompt: + fnc = { + "Person": (db.get_person_from_handle, db.commit_person), + "Family": (db.get_family_from_handle, db.commit_family), + "Event": (db.get_event_from_handle, db.commit_event), + "Place": (db.get_place_from_handle, db.commit_place), + "Source": (db.get_source_from_handle, db.commit_source), + "Citation": (db.get_citation_from_handle, db.commit_citation), + "Repository": ( + db.get_repository_from_handle, + db.commit_repository, + ), + "Media": (db.get_media_from_handle, db.commit_media), + "Note": (db.get_note_from_handle, db.commit_note), + } + + links = list(db.find_backlink_handles(tag.handle)) + # Make the dialog modal so that the user can't start another + # database transaction while the one removing tags is still running. + pmon = progressdlg.ProgressMonitor( + progressdlg.GtkProgressDialog, + ("", window, Gtk.DialogFlags.MODAL), + popup_time=2, + ) + status = progressdlg.LongOpStatus( + msg=_("Removing Tags"), + total_steps=len(links), + interval=len(links) // 20, + ) + pmon.add_op(status) + + msg = _("Delete Tag (%s)") % tag.name + with DbTxn(msg, db) as trans: + for classname, handle in links: + status.heartbeat() + obj = fnc[classname][0](handle) # get from handle + obj.remove_tag(tag.handle) + fnc[classname][1](obj, trans) # commit + + db.remove_tag(tag.handle, trans) + status.end() diff --git a/gramps/plugins/view/view.gpr.py b/gramps/plugins/view/view.gpr.py index 53cc78908..27b410004 100644 --- a/gramps/plugins/view/view.gpr.py +++ b/gramps/plugins/view/view.gpr.py @@ -307,3 +307,20 @@ category = ("Sources", _("Sources")), viewclass = 'CitationTreeView', stock_icon = 'gramps-tree-select', ) + +register( + VIEW, + id="tagview", + name=_("Tags"), + description=_("The view showing all the tags"), + version="1.0", + gramps_target_version=MODULE_VERSION, + status=STABLE, + fname="tagview.py", + authors=["The Gramps project"], + authors_email=["http://gramps-project.org"], + category=("Tags", _("Tags")), + stock_icon="gramps-tag", + viewclass="TagView", + order=START, +)