diff --git a/src/DdTargets.py b/src/DdTargets.py index 3f588be27..94d0eda4b 100644 --- a/src/DdTargets.py +++ b/src/DdTargets.py @@ -134,6 +134,7 @@ class _DdTargets(object): self.SOURCE_LINK = _DdType(self, 'source-link') self.URL = _DdType(self, 'url') self.SURNAME = _DdType(self, 'surname') + self.CITATION_LINK = _DdType(self, 'citation-link') # List of all types that are used between # gramps widgets but should not be exported @@ -160,7 +161,8 @@ class _DdTargets(object): self.SOURCEREF, self.SOURCE_LINK, self.URL, - self.SURNAME + self.SURNAME, + self.CITATION_LINK ] self.CHILD = _DdType(self, 'child') diff --git a/src/gen/lib/citation.py b/src/gen/lib/citation.py index b16225ba2..8f83302aa 100644 --- a/src/gen/lib/citation.py +++ b/src/gen/lib/citation.py @@ -70,7 +70,7 @@ class Citation(MediaBase, NoteBase, PrimaryObject, DateBase): MediaBase.__init__(self) # 7 NoteBase.__init__(self) # 6 DateBase.__init__(self) # 2 - self.source_handle = None, # 5 + self.source_handle = None # 5 self.page = "" # 3 self.confidence = Citation.CONF_NORMAL # 4 self.datamap = {} # 8 diff --git a/src/gui/editors/displaytabs/citationembedlist.py b/src/gui/editors/displaytabs/citationembedlist.py index 2943b8d50..1243d18d1 100644 --- a/src/gui/editors/displaytabs/citationembedlist.py +++ b/src/gui/editors/displaytabs/citationembedlist.py @@ -180,6 +180,16 @@ class CitationEmbedList(EmbeddedList, DbGUIElement): else: raise ValueError("selection must be either source or citation") + def __blocked_text(self): + """ + Return the common text used when citation cannot be edited + """ + return _("This citation cannot be created at this time. " + "Either the associated Source object is already being " + "edited, or another citation associated with the same " + "source is being edited.\n\nTo edit this " + "citation, you need to close the object.") + def edit_button_clicked(self, obj): """ Get the selected Citation instance and call the EditCitation editor @@ -190,10 +200,8 @@ class CitationEmbedList(EmbeddedList, DbGUIElement): This prevents the dialog from coming up twice on the same object. """ handle = self.get_selected() - LOG.debug('selected handle %s' % handle) if handle: citation = self.dbstate.db.get_citation_from_handle(handle) - LOG.debug("selected citation: %s" % citation) try: from gui.editors import EditCitation EditCitation(self.dbstate, self.uistate, self.track, citation, diff --git a/src/gui/editors/editcitation.py b/src/gui/editors/editcitation.py index ba2e83ade..a59d5f575 100644 --- a/src/gui/editors/editcitation.py +++ b/src/gui/editors/editcitation.py @@ -90,6 +90,37 @@ class EditCitation(EditPrimary): # EditCitation, not only do we need to protect obj (which will be # a Citation, but we also need to protect the associated Source. + def build_window_key(self, obj): + """ + Return a key for the edit window that is opened. + This function overrides the build_window_key in EditPrimary. + + There is a problem with database object locking. The database locking is + handled by the ManagedWindow class, which will only allow one primary + object to be edited at a time. + + Normally, the window key is derived from the obj that is being edited. + However, in the case of EditCitation, there are two objects being + edited, the Citation and the Source. Both must be protected against + against the user trying to edit them twice. + + What we do here is to derive the window key from the Source object, if + one exists. A Citation always points to exactly one Source object, so if + we try to edit the same Citation twice, the associated Source objects + will be the same so this will be prevented. If we try to edit a Source + object and a Citation object that refers to the same Source, then again, + the window key will be the same and this will be prevented. + """ + if obj and obj.get_reference_handle(): + # citation already points to source + return obj.get_reference_handle() + elif self.source and self.source.get_handle(): + # Citation doesn't yet point to source, but source exists and has a + # handle + return self.source.get_handle() + else: + return id(self) + def empty_object(self): """ Return an empty Citation object for comparison for changes. @@ -181,7 +212,6 @@ class EditCitation(EditPrimary): """Connects any signals that need to be connected. Called by the init routine of the base class L{EditPrimary}. - """ self.define_ok_button(self.glade.get_object('ok'), self.save) self.define_cancel_button(self.glade.get_object('cancel')) @@ -191,7 +221,19 @@ class EditCitation(EditPrimary): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). + + What this code does is to check that the object edited is not deleted + whilst editing it. If the object is deleted we need to close the editor + windows and clean up. If the database emits a rebuild signal for the + database object type we also abort the edit. + + The Citation editor edits two primary objects, and therefore we need to + check if either have been deleted. If the source is deleted, the + citation must have been deleted first and will emit a signal, so we + shouldn't have to connect to the source-delete signal. It should not be + necessary to connect to the source- rebuild signal for similar reasons. """ + # FIXME: Should this be modified so that the 'close' routines # are executed not only for the 'Citation', bit also for the 'Source' self._add_db_signal('citation-rebuild', self._do_close) diff --git a/src/gui/grampsgui.py b/src/gui/grampsgui.py index 067d5551d..6de4c589f 100644 --- a/src/gui/grampsgui.py +++ b/src/gui/grampsgui.py @@ -151,6 +151,7 @@ def register_stock_icons (): ('gramps-zoom-out', _('Zoom Out'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-zoom-fit-width', _('Fit Width'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-zoom-best-fit', _('Fit Page'), gtk.gdk.CONTROL_MASK, 0, ''), + ('gramps-citation', _('Citations'), gtk.gdk.CONTROL_MASK, 0, ''), ] # the following icons are not yet in new directory structure # they should be ported in the near future diff --git a/src/images/16x16/Makefile.am b/src/images/16x16/Makefile.am index 39bbef0b2..cb0601126 100644 --- a/src/images/16x16/Makefile.am +++ b/src/images/16x16/Makefile.am @@ -19,6 +19,7 @@ dist_pkgdata_DATA = \ gramps-bookmark-edit.png \ gramps-bookmark-new.png \ gramps-bookmark.png \ + gramps-citation.png \ gramps-config.png \ gramps-date.png \ gramps-date-edit.png \ diff --git a/src/images/16x16/gramps-citation.png b/src/images/16x16/gramps-citation.png new file mode 100644 index 000000000..040991f49 Binary files /dev/null and b/src/images/16x16/gramps-citation.png differ diff --git a/src/images/22x22/Makefile.am b/src/images/22x22/Makefile.am index ed93a36c1..ed9db1b09 100644 --- a/src/images/22x22/Makefile.am +++ b/src/images/22x22/Makefile.am @@ -19,6 +19,7 @@ dist_pkgdata_DATA = \ gramps-bookmark-edit.png \ gramps-bookmark-new.png \ gramps-bookmark.png \ + gramps-citation.png \ gramps-config.png \ gramps-date-edit.png \ gramps-date.png \ diff --git a/src/images/22x22/gramps-citation.png b/src/images/22x22/gramps-citation.png new file mode 100644 index 000000000..8527b728e Binary files /dev/null and b/src/images/22x22/gramps-citation.png differ diff --git a/src/images/48x48/Makefile.am b/src/images/48x48/Makefile.am index dd5c5da37..04b295f28 100644 --- a/src/images/48x48/Makefile.am +++ b/src/images/48x48/Makefile.am @@ -19,6 +19,7 @@ dist_pkgdata_DATA = \ gramps-bookmark-edit.png \ gramps-bookmark-new.png \ gramps-bookmark.png \ + gramps-citation.png \ gramps-config.png \ gramps-date-edit.png \ gramps-date.png \ diff --git a/src/images/48x48/gramps-citation.png b/src/images/48x48/gramps-citation.png new file mode 100644 index 000000000..509bdc201 Binary files /dev/null and b/src/images/48x48/gramps-citation.png differ diff --git a/src/images/scalable/Makefile.am b/src/images/scalable/Makefile.am index 5b5d57e72..169864035 100644 --- a/src/images/scalable/Makefile.am +++ b/src/images/scalable/Makefile.am @@ -19,6 +19,7 @@ dist_pkgdata_DATA = \ gramps-bookmark-edit.svg \ gramps-bookmark-new.svg \ gramps-bookmark.svg \ + gramps-citation.png \ gramps-config.svg \ gramps-date-edit.svg \ gramps-date.svg \ diff --git a/src/images/scalable/gramps-citation.png b/src/images/scalable/gramps-citation.png new file mode 100644 index 000000000..1a597e823 Binary files /dev/null and b/src/images/scalable/gramps-citation.png differ diff --git a/src/plugins/lib/libcitationview.py b/src/plugins/lib/libcitationview.py deleted file mode 100644 index ae07a5d2f..000000000 --- a/src/plugins/lib/libcitationview.py +++ /dev/null @@ -1,427 +0,0 @@ -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2001-2006 Donald N. Allingham -# Copyright (C) 2008 Gary Burton -# Copyright (C) 2011 Tim G L Lyons, Nick Hall -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -# $Id$ - -""" -Citation View -""" -#------------------------------------------------------------------------- -# -# python modules -# -#------------------------------------------------------------------------- -import logging -LOG = logging.getLogger(".citation") - -#------------------------------------------------------------------------- -# -# GTK/Gnome modules -# -#------------------------------------------------------------------------- -import gtk - -#------------------------------------------------------------------------- -# -# gramps modules -# -#------------------------------------------------------------------------- -import gen.lib -from gui.views.listview import ListView -import Utils -import Bookmarks -import Errors -from DdTargets import DdTargets -from QuestionDialog import ErrorDialog -from gui.editors import EditCitation, DeleteCitationQuery, EditSource, \ - DeleteSrcQuery -from Filters.SideBar import SourceSidebarFilter - -#------------------------------------------------------------------------- -# -# internationalization -# -#------------------------------------------------------------------------- -from gen.ggettext import gettext as _ - -#------------------------------------------------------------------------- -# -# CitationView -# -#------------------------------------------------------------------------- -class BaseCitationView(ListView): - """ citation listview class - """ - # The configuration parameters have been moved to CitationTreeView and - # CitationListView, because they differ for the two different views. - - def __init__(self, pdata, dbstate, uistate, title, model, signal_map, - nav_group=0): - - ListView.__init__( - self, title, pdata, dbstate, uistate, - self.COLUMN_NAMES, len(self.COLUMN_NAMES), - model, signal_map, - dbstate.db.get_citation_bookmarks(), - Bookmarks.CitationBookmarks, nav_group, - multiple=True, - filter_class=SourceSidebarFilter) - - self.func_list.update({ - 'J' : self.jump, - 'BackSpace' : self.key_delete, - }) - - self.additional_uis.append(self.additional_ui()) - - def navigation_type(self): - return 'Citation' - - def get_bookmarks(self): - return self.dbstate.db.get_citation_bookmarks() - - def drag_info(self): - return DdTargets.SOURCE_LINK - - def define_actions(self): - """ - This defines the possible actions for the citation views. - Possible actions are: - add_source: Add a new source (this is also available from the - source view) - add: Add a new citation and a new source (this can also be done - by source view add a source, then citation view add a new - citation to an existing source) - share: Add a new citation to an existing source (when a source is - selected) - edit: Edit a source or a citation. - merge: Merge the selected sources or citations. - remove: Delete the selected sources or citations. - - - """ - ListView.define_actions(self) - -# gtk stock icons are at http://www.pygtk.org/docs/pygtk/gtk-stock-items.html - self._add_action('Add source', 'gramps-source', _("Add source..."), - accel=None, - tip=self.ADD_SOURCE_MSG, - callback=self.add_source) - self._add_action('Add citation', 'gramps-source', _("Add citation..."), - accel=None, - tip=self.ADD_CITATION_MSG, - callback=self.share) - - self.all_action = gtk.ActionGroup(self.title + "/CitationAll") - self.edit_action = gtk.ActionGroup(self.title + "/CitationEdit") - - self._add_action('FilterEdit', None, _('Citation Filter Editor'), - callback=self.filter_editor,) - self._add_action('QuickReport', None, _("Quick View"), None, None, None) - self._add_action('Dummy', None, ' ', None, None, self.dummy_report) - - self._add_action_group(self.edit_action) - self._add_action_group(self.all_action) - - def get_stock(self): - return 'gramps-citation' - - def additional_ui(self): - """ - Defines the UI string for UIManager - - This is overridden in citationtreeview because that has additional - popup items for open and close all nodes - """ - return ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ''' - - def dummy_report(self, obj): - """ For the xml UI definition of popup to work, the submenu - Quick Report must have an entry in the xml - As this submenu will be dynamically built, we offer a dummy action - """ - pass - - def add_source(self, obj): - """ - add_source: Add a new source (this is also available from the - source view) - - Create a new Source instance and call the EditSource editor with the - new source. - - Called when the Add_source button is clicked. - If the window already exists (Errors.WindowActiveError), we ignore it. - This prevents the dialog from coming up twice on the same object. - - However, since the window is identified by the Source object, and - we have just created a new one, it seems to be impossible for the - window to already exist, so this is just an extra safety measure. - """ - try: - EditSource(self.dbstate, self.uistate, [], gen.lib.Source()) - except Errors.WindowActiveError: - pass - - def add(self, obj): - """ - add: Add a new citation and a new source (this can also be done - by source view add a source, then citation view add a new - citation to an existing source) - - Create a new Source instance and Citation instance and call the - EditSource editor with the new source. - - Called when the Add button is clicked. - If the window already exists (Errors.WindowActiveError), we ignore it. - This prevents the dialog from coming up twice on the same object. - - However, since the window is identified by the Source object, and - we have just created a new one, it seems to be impossible for the - window to already exist, so this is just an extra safety measure. - """ - try: - EditCitation(self.dbstate, self.uistate, [], gen.lib.Citation(), - gen.lib.Source()) - except Errors.WindowActiveError: - pass - - def share(self, obj): - """ - share: Add a new citation to an existing source (when a source is - selected) - """ - for handle in self.selected_handles(): - # The handle will either be a Source handle or a Citation handle - source = self.dbstate.db.get_source_from_handle(handle) - citation = self.dbstate.db.get_citation_from_handle(handle) - if (not source and not citation) or (source and citation): - raise ValueError("selection must be either source or citation") - if source: - try: - EditCitation(self.dbstate, self.uistate, [], - gen.lib.Citation(), source) - except Errors.WindowActiveError: - from QuestionDialog import WarningDialog - WarningDialog(_("Cannot share this reference"), - self.__blocked_text()) - else: - msg = _("Cannot add citation.") - msg2 = _("In order to add a citation to an existing source, " - " you must select a source.") - ErrorDialog(msg, msg2) -# - def remove(self, obj): - self.remove_selected_objects() - - def remove_object_from_handle(self, handle): - # The handle will either be a Source handle or a Citation handle - source = self.dbstate.db.get_source_from_handle(handle) - citation = self.dbstate.db.get_citation_from_handle(handle) - if (not source and not citation) or (source and citation): - raise ValueError("selection must be either source or citation") - if citation: - the_lists = Utils.get_citation_referents(handle, self.dbstate.db) - object = self.dbstate.db.get_citation_from_handle(handle) - query = DeleteCitationQuery(self.dbstate, self.uistate, object, - the_lists) - is_used = any(the_lists) - return (query, is_used, object) - else: - # FIXME: this is copied from SourceView, because import with - # from plugins.view.sourceview import SourceView doesn't - # seem to work! - the_lists = Utils.get_source_referents(handle, self.dbstate.db) - LOG.debug('source referents %s' % [the_lists]) - citation_referents_list = [] - for citation in the_lists[7]: - LOG.debug('citation %s' % citation) - refs = Utils.get_citation_referents(citation, self.dbstate.db) - citation_referents_list += [(citation, refs)] - LOG.debug('citation_referents_list %s' % [citation_referents_list]) - - (person_list, family_list, event_list, place_list, source_list, - media_list, repo_list, citation_list) = the_lists - the_lists = (person_list, family_list, event_list, place_list, - source_list, media_list, repo_list, citation_list, - citation_referents_list) - - LOG.debug('the_lists %s' % [the_lists]) - - object = self.dbstate.db.get_source_from_handle(handle) - query = DeleteSrcQuery(self.dbstate, self.uistate, object, - the_lists) - is_used = any(the_lists) - return (query, is_used, object) - - def edit(self, obj): - """ - Edit either a Source or a Citation, depending on user selection - """ - for handle in self.selected_handles(): - # The handle will either be a Source handle or a Citation handle - source = self.dbstate.db.get_source_from_handle(handle) - citation = self.dbstate.db.get_citation_from_handle(handle) - if (not source and not citation) or (source and citation): - raise ValueError("selection must be either source or citation") - if citation: - LOG.debug("citation handle %s page %s" % - (handle, citation.page)) - try: - EditCitation(self.dbstate, self.uistate, [], citation) - except Errors.WindowActiveError: - pass - else: - LOG.debug("source handle %s title %s " % - (source, source.title)) - EditSource(self.dbstate, self.uistate, [], source) - - def __blocked_text(self): - """ - Return the common text used when citation cannot be edited - """ - return _("This citation cannot be edited at this time. " - "Either the associated citation is already being " - "edited or another object that is associated with " - "the same citation is being edited.\n\nTo edit this " - "citation, you need to close the object.") - - def merge(self, obj): - """ - Merge the selected citations. - """ - mlist = self.selected_handles() - - if len(mlist) != 2: - msg = _("Cannot merge citations.") - msg2 = _("Exactly two citations must be selected to perform a " - "merge. A second citation can be selected by holding " - "down the control key while clicking on the desired " - "citation.") - ErrorDialog(msg, msg2) - else: - source1 = self.dbstate.db.get_source_from_handle(mlist[0]) - citation1 = self.dbstate.db.get_citation_from_handle(mlist[0]) - if (not source1 and not citation1) or (source1 and citation1): - raise ValueError("selection must be either source or citation") - - source2 = self.dbstate.db.get_source_from_handle(mlist[1]) - citation2 = self.dbstate.db.get_citation_from_handle(mlist[1]) - if (not source2 and not citation2) or (source2 and citation2): - raise ValueError("selection must be either source or citation") - - if citation1 and citation2: - if not citation1.get_reference_handle() == \ - citation2.get_reference_handle(): - msg = _("Cannot merge citations.") - msg2 = _("The two selected citations must have the same " - "source to perform a merge. If you want to merge " - "these two citations, then you must merge the " - "sources first.") - ErrorDialog(msg, msg2) - else: - import Merge - Merge.MergeCitations(self.dbstate, self.uistate, - mlist[0], mlist[1]) - elif source1 and source2: - import Merge - Merge.MergeSources(self.dbstate, self.uistate, - mlist[0], mlist[1]) - else: - msg = _("Cannot perform merge.") - msg2 = _("Both objects must be of the same type, either " - "both must be sources, or both must be " - "citations.") - ErrorDialog(msg, msg2) - - def get_handle_from_gramps_id(self, gid): - obj = self.dbstate.db.get_citation_from_gramps_id(gid) - if obj: - return obj.get_handle() - else: - return None - - def get_default_gramplets(self): - """ - Define the default gramplets for the sidebar and bottombar. - This is overridden for the tree view to give 'Source Filter' - """ - return (("Citation Filter",), - ("Citation Gallery", - "Citation Notes", - "Citation Backlinks")) diff --git a/src/plugins/sidebar/categorysidebar.py b/src/plugins/sidebar/categorysidebar.py index 977f4d92a..96ea892f6 100644 --- a/src/plugins/sidebar/categorysidebar.py +++ b/src/plugins/sidebar/categorysidebar.py @@ -69,7 +69,9 @@ CATEGORY_ICON = { 'Sources': 'gramps-source', 'Repositories': 'gramps-repository', 'Media': 'gramps-media', - 'Notes': 'gramps-notes'} + 'Notes': 'gramps-notes', + 'Citations': 'gramps-citation', +} #------------------------------------------------------------------------- # diff --git a/src/plugins/view/citationlistview.py b/src/plugins/view/citationlistview.py index aac842892..bf4e9b95c 100644 --- a/src/plugins/view/citationlistview.py +++ b/src/plugins/view/citationlistview.py @@ -25,14 +25,37 @@ Citation List View """ +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + #------------------------------------------------------------------------- # # gramps modules # #------------------------------------------------------------------------- -from libcitationview import BaseCitationView from gui.views.treemodels.citationlistmodel import CitationListModel from gen.plug import CATEGORY_QR_SOURCE +import gen.lib +from gui.views.listview import ListView +import Utils +import Bookmarks +import Errors +from DdTargets import DdTargets +from QuestionDialog import ErrorDialog +from gui.editors import EditCitation, DeleteCitationQuery +from Filters.SideBar import CitationSidebarFilter #------------------------------------------------------------------------- # @@ -47,7 +70,7 @@ from gen.ggettext import gettext as _ # CitationView # #------------------------------------------------------------------------- -class CitationListView(BaseCitationView): +class CitationListView(ListView): """ A list view of citations. @@ -69,7 +92,7 @@ class CitationListView(BaseCitationView): COL_SRC_CHAN = 10 # name of the columns COLUMN_NAMES = [ - _('Title/Page'), + _('Volume/Page'), _('ID'), _('Date'), _('Confidence'), @@ -93,8 +116,6 @@ class CitationListView(BaseCitationView): ADD_MSG = _("Add a new citation and a new source") ADD_SOURCE_MSG = _("Add a new source") ADD_CITATION_MSG = _("Add a new citation to an existing source") - # Edit delete and merge messages are overridden for the tree view as - # they can apply to sources or citations EDIT_MSG = _("Edit the selected citation") DEL_MSG = _("Delete the selected citation") MERGE_MSG = _("Merge the selected citations") @@ -110,8 +131,225 @@ class CitationListView(BaseCitationView): 'citation-rebuild' : self.object_build, } - BaseCitationView.__init__(self, pdata, dbstate, uistate, - _('Citation View'), CitationListModel, - signal_map, - nav_group=nav_group) + ListView.__init__( + self, _('Citation View'), pdata, dbstate, uistate, + self.COLUMN_NAMES, len(self.COLUMN_NAMES), + CitationListModel, signal_map, + dbstate.db.get_citation_bookmarks(), + Bookmarks.CitationBookmarks, nav_group, + multiple=True, + filter_class=CitationSidebarFilter) + self.func_list.update({ + 'J' : self.jump, + 'BackSpace' : self.key_delete, + }) + + self.additional_uis.append(self.additional_ui()) + + def navigation_type(self): + return 'Citation' + + def get_bookmarks(self): + return self.dbstate.db.get_citation_bookmarks() + + def drag_info(self): + return DdTargets.CITATION_LINK + + def define_actions(self): + """ + This defines the possible actions for the citation views. + Possible actions are: + add: Add a new citation and a new source (this can also be done + by source view add a source, then citation view add a new + citation to an existing source) + edit: Edit a citation. + merge: Merge the selected citations. + remove: Delete the selected citations. + + + """ + ListView.define_actions(self) + + self.all_action = gtk.ActionGroup(self.title + "/CitationAll") + self.edit_action = gtk.ActionGroup(self.title + "/CitationEdit") + + self._add_action('FilterEdit', None, _('Citation Filter Editor'), + callback=self.filter_editor,) + self._add_action('QuickReport', None, _("Quick View"), None, None, None) + self._add_action('Dummy', None, ' ', None, None, self.dummy_report) + + self._add_action_group(self.edit_action) + self._add_action_group(self.all_action) + + def get_stock(self): + return 'gramps-citation' + + def additional_ui(self): + """ + Defines the UI string for UIManager + """ + return ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' + + def dummy_report(self, obj): + """ For the xml UI definition of popup to work, the submenu + Quick Report must have an entry in the xml + As this submenu will be dynamically built, we offer a dummy action + """ + pass + + def add(self, obj): + """ + add: Add a new citation and a new source (this can also be done + by source view add a source, then citation view add a new + citation to an existing source) + + Create a new Source instance and Citation instance and call the + EditCitation editor with the new source and new citation. + + Called when the Add button is clicked. + If the window already exists (Errors.WindowActiveError), we ignore it. + This prevents the dialog from coming up twice on the same object. + + However, since the window is identified by the Source object, and + we have just created a new one, it seems to be impossible for the + window to already exist, so this is just an extra safety measure. + """ + try: + EditCitation(self.dbstate, self.uistate, [], gen.lib.Citation(), + gen.lib.Source()) + except Errors.WindowActiveError: + pass + + def remove(self, obj): + self.remove_selected_objects() + + def remove_object_from_handle(self, handle): + the_lists = Utils.get_citation_referents(handle, self.dbstate.db) + object = self.dbstate.db.get_citation_from_handle(handle) + query = DeleteCitationQuery(self.dbstate, self.uistate, object, + the_lists) + is_used = any(the_lists) + return (query, is_used, object) + + def edit(self, obj): + """ + Edit a Citation + """ + for handle in self.selected_handles(): + citation = self.dbstate.db.get_citation_from_handle(handle) + try: + EditCitation(self.dbstate, self.uistate, [], citation) + except Errors.WindowActiveError: + pass + + def __blocked_text(self): + """ + Return the common text used when citation cannot be edited + """ + return _("This citation cannot be edited at this time. " + "Either the associated citation is already being " + "edited or another object that is associated with " + "the same citation is being edited.\n\nTo edit this " + "citation, you need to close the object.") + + def merge(self, obj): + """ + Merge the selected citations. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge citations.") + msg2 = _("Exactly two citations must be selected to perform a " + "merge. A second citation can be selected by holding " + "down the control key while clicking on the desired " + "citation.") + ErrorDialog(msg, msg2) + else: + citation1 = self.dbstate.db.get_citation_from_handle(mlist[0]) + citation2 = self.dbstate.db.get_citation_from_handle(mlist[1]) + if not citation1.get_reference_handle() == \ + citation2.get_reference_handle(): + msg = _("Cannot merge citations.") + msg2 = _("The two selected citations must have the same " + "source to perform a merge. If you want to merge " + "these two citations, then you must merge the " + "sources first.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeCitations(self.dbstate, self.uistate, + mlist[0], mlist[1]) + + def get_handle_from_gramps_id(self, gid): + obj = self.dbstate.db.get_citation_from_gramps_id(gid) + if obj: + return obj.get_handle() + else: + return None + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + This is overridden for the tree view to give 'Source Filter' + """ + return (("Citation Filter",), + ("Citation Gallery", + "Citation Notes", + "Citation Backlinks")) diff --git a/src/plugins/view/citationtreeview.py b/src/plugins/view/citationtreeview.py index 75fff2772..219593a59 100644 --- a/src/plugins/view/citationtreeview.py +++ b/src/plugins/view/citationtreeview.py @@ -21,7 +21,8 @@ # $Id$ """ -Citation Tree View +Citation Tree View (or Source tree view). +A view showing all the Sources with child Citations """ #------------------------------------------------------------------------- # @@ -31,15 +32,31 @@ Citation Tree View import logging LOG = logging.getLogger(".citation") +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + #------------------------------------------------------------------------- # # Gramps modules # #------------------------------------------------------------------------- from gui.views.listview import LISTTREE -from libcitationview import BaseCitationView from gui.views.treemodels.citationtreemodel import CitationTreeModel from gen.plug import CATEGORY_QR_SOURCE +import gen.lib +from gui.views.listview import ListView +import Utils +import Bookmarks +import Errors +from DdTargets import DdTargets +from QuestionDialog import ErrorDialog +from gui.editors import EditCitation, DeleteCitationQuery, EditSource, \ + DeleteSrcQuery +from Filters.SideBar import SourceSidebarFilter #------------------------------------------------------------------------- # @@ -53,9 +70,9 @@ from gen.ggettext import gettext as _ # PlaceTreeView # #------------------------------------------------------------------------- -class CitationTreeView(BaseCitationView): +class CitationTreeView(ListView): """ - A hierarchical view of the top three levels of places. + A hierarchical view of sources with citations below them. """ # The data items here have to correspond, in order, to the items in # src/giu.views/treemodels/citationtreemodel.py @@ -69,7 +86,7 @@ class CitationTreeView(BaseCitationView): COL_SRC_PINFO = 7 # name of the columns COLUMN_NAMES = [ - _('Title/Page'), + _('Title or Page'), _('ID'), _('Date'), _('Confidence'), @@ -80,12 +97,12 @@ class CitationTreeView(BaseCitationView): ] # default setting with visible columns, order of the col, and their size CONFIGSETTINGS = ( - ('columns.visible', [COL_TITLE_PAGE, COL_ID, COL_DATE, - COL_CONFIDENCE]), + ('columns.visible', [COL_TITLE_PAGE, COL_ID, COL_SRC_AUTH, + COL_SRC_PINFO]), ('columns.rank', [COL_TITLE_PAGE, COL_ID, COL_DATE, COL_CONFIDENCE, COL_CHAN, COL_SRC_AUTH, COL_SRC_ABBR, COL_SRC_PINFO]), - ('columns.size', [200, 75, 100, 100, 100, 75, 100, 150]) + ('columns.size', [200, 75, 100, 75, 100, 150, 100, 150]) ) ADD_MSG = _("Add a new citation and a new source") ADD_SOURCE_MSG = _("Add a new source") @@ -109,17 +126,40 @@ class CitationTreeView(BaseCitationView): 'source-rebuild' : self.object_build, } - BaseCitationView.__init__(self, pdata, dbstate, uistate, - _('Citation Tree View'), CitationTreeModel, - signal_map, - nav_group=nav_group) + ListView.__init__( + self, _('Citation Tree View'), pdata, dbstate, uistate, + self.COLUMN_NAMES, len(self.COLUMN_NAMES), + CitationTreeModel, signal_map, + dbstate.db.get_citation_bookmarks(), + Bookmarks.CitationBookmarks, nav_group, + multiple=True, + filter_class=SourceSidebarFilter) + self.func_list.update({ + 'J' : self.jump, + 'BackSpace' : self.key_delete, + }) + + self.additional_uis.append(self.additional_ui()) + + def navigation_type(self): + return 'Citation' + + def get_bookmarks(self): + return self.dbstate.db.get_citation_bookmarks() + + def drag_info(self): + return DdTargets.SOURCE_LINK + def type_list(self): """ set the listtype, this governs eg keybinding """ return LISTTREE + def get_stock(self): + return 'gramps-citation' + def get_viewtype_stock(self): """ Override the default icon. Set for hierarchical view. @@ -128,9 +168,43 @@ class CitationTreeView(BaseCitationView): def define_actions(self): """ - Define actions for the popup menu specific to the tree view. + This defines the possible actions for the citation views. + Possible actions are: + add_source: Add a new source (this is also available from the + source view) + add: Add a new citation and a new source (this can also be done + by source view add a source, then citation view add a new + citation to an existing source) + share: Add a new citation to an existing source (when a source is + selected) + edit: Edit a source or a citation. + merge: Merge the selected sources or citations. + remove: Delete the selected sources or citations. + + """ - BaseCitationView.define_actions(self) + ListView.define_actions(self) + + self._add_action('Add source', 'gramps-source', _("Add source..."), + accel=None, + tip=self.ADD_SOURCE_MSG, + callback=self.add_source) + self._add_action('Add citation', 'gramps-citation', + _("Add citation..."), + accel=None, + tip=self.ADD_CITATION_MSG, + callback=self.share) + + self.all_action = gtk.ActionGroup(self.title + "/CitationAll") + self.edit_action = gtk.ActionGroup(self.title + "/CitationEdit") + + self._add_action('FilterEdit', None, _('Citation Filter Editor'), + callback=self.filter_editor,) + self._add_action('QuickReport', None, _("Quick View"), None, None, None) + self._add_action('Dummy', None, ' ', None, None, self.dummy_report) + + self._add_action_group(self.edit_action) + self._add_action_group(self.all_action) self.all_action.add_actions([ ('OpenAllNodes', None, _("Expand all Nodes"), None, None, @@ -206,3 +280,228 @@ class CitationTreeView(BaseCitationView): ''' + def dummy_report(self, obj): + """ For the xml UI definition of popup to work, the submenu + Quick Report must have an entry in the xml + As this submenu will be dynamically built, we offer a dummy action + """ + pass + + def add_source(self, obj): + """ + add_source: Add a new source (this is also available from the + source view) + + Create a new Source instance and call the EditSource editor with the + new source. + + Called when the Add_source button is clicked. + If the window already exists (Errors.WindowActiveError), we ignore it. + This prevents the dialog from coming up twice on the same object. + + However, since the window is identified by the Source object, and + we have just created a new one, it seems to be impossible for the + window to already exist, so this is just an extra safety measure. + """ + try: + EditSource(self.dbstate, self.uistate, [], gen.lib.Source()) + except Errors.WindowActiveError: + pass + + def add(self, obj): + """ + add: Add a new citation and a new source (this can also be done + by source view add a source, then citation view add a new + citation to an existing source) + + Create a new Source instance and Citation instance and call the + EditSource editor with the new source. + + Called when the Add button is clicked. + If the window already exists (Errors.WindowActiveError), we ignore it. + This prevents the dialog from coming up twice on the same object. + + However, since the window is identified by the Source object, and + we have just created a new one, it seems to be impossible for the + window to already exist, so this is just an extra safety measure. + """ + try: + EditCitation(self.dbstate, self.uistate, [], gen.lib.Citation(), + gen.lib.Source()) + except Errors.WindowActiveError: + pass + + def share(self, obj): + """ + share: Add a new citation to an existing source (when a source is + selected) + """ + for handle in self.selected_handles(): + # The handle will either be a Source handle or a Citation handle + source = self.dbstate.db.get_source_from_handle(handle) + citation = self.dbstate.db.get_citation_from_handle(handle) + if (not source and not citation) or (source and citation): + raise ValueError("selection must be either source or citation") + if source: + try: + EditCitation(self.dbstate, self.uistate, [], + gen.lib.Citation(), source) + except Errors.WindowActiveError: + from QuestionDialog import WarningDialog + WarningDialog(_("Cannot share this reference"), + self.__blocked_text()) + else: + msg = _("Cannot add citation.") + msg2 = _("In order to add a citation to an existing source, " + " you must select a source.") + ErrorDialog(msg, msg2) +# + def remove(self, obj): + self.remove_selected_objects() + + def remove_object_from_handle(self, handle): + # The handle will either be a Source handle or a Citation handle + source = self.dbstate.db.get_source_from_handle(handle) + citation = self.dbstate.db.get_citation_from_handle(handle) + if (not source and not citation) or (source and citation): + raise ValueError("selection must be either source or citation") + if citation: + the_lists = Utils.get_citation_referents(handle, self.dbstate.db) + object = self.dbstate.db.get_citation_from_handle(handle) + query = DeleteCitationQuery(self.dbstate, self.uistate, object, + the_lists) + is_used = any(the_lists) + return (query, is_used, object) + else: + # FIXME: this is copied from SourceView, because import with + # from plugins.view.sourceview import SourceView + # doesn't seem to work! + the_lists = Utils.get_source_referents(handle, self.dbstate.db) + LOG.debug('source referents %s' % [the_lists]) + citation_referents_list = [] + for citation in the_lists[7]: + LOG.debug('citation %s' % citation) + refs = Utils.get_citation_referents(citation, self.dbstate.db) + citation_referents_list += [(citation, refs)] + LOG.debug('citation_referents_list %s' % [citation_referents_list]) + + (person_list, family_list, event_list, place_list, source_list, + media_list, repo_list, citation_list) = the_lists + the_lists = (person_list, family_list, event_list, place_list, + source_list, media_list, repo_list, citation_list, + citation_referents_list) + + LOG.debug('the_lists %s' % [the_lists]) + + object = self.dbstate.db.get_source_from_handle(handle) + query = DeleteSrcQuery(self.dbstate, self.uistate, object, + the_lists) + is_used = any(the_lists) + return (query, is_used, object) + + def edit(self, obj): + """ + Edit either a Source or a Citation, depending on user selection + """ + for handle in self.selected_handles(): + # The handle will either be a Source handle or a Citation handle + source = self.dbstate.db.get_source_from_handle(handle) + citation = self.dbstate.db.get_citation_from_handle(handle) + if (not source and not citation) or (source and citation): + raise ValueError("selection must be either source or citation") + if citation: + try: + EditCitation(self.dbstate, self.uistate, [], citation) + except Errors.WindowActiveError: + pass + else: # FIXME need try block here + try: + EditSource(self.dbstate, self.uistate, [], source) + except Errors.WindowActiveError: + from QuestionDialog import WarningDialog + WarningDialog(_("Cannot share this reference"), + self.__blocked_text2()) + + def __blocked_text(self): + """ + Return the common text used when citation cannot be edited + """ + return _("This citation cannot be created at this time. " + "Either the associated Source object is already being " + "edited, or another citation associated with the same " + "source is being edited.\n\nTo edit this " + "citation, you need to close the object.") + + def __blocked_text2(self): + """ + Return the common text used when citation cannot be edited + """ + return _("This source cannot be edited at this time. " + "Either the associated Source object is already being " + "edited, or another citation associated with the same " + "source is being edited.\n\nTo edit this " + "source, you need to close the object.") + + def merge(self, obj): + """ + Merge the selected citations. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge citations.") + msg2 = _("Exactly two citations must be selected to perform a " + "merge. A second citation can be selected by holding " + "down the control key while clicking on the desired " + "citation.") + ErrorDialog(msg, msg2) + else: + source1 = self.dbstate.db.get_source_from_handle(mlist[0]) + citation1 = self.dbstate.db.get_citation_from_handle(mlist[0]) + if (not source1 and not citation1) or (source1 and citation1): + raise ValueError("selection must be either source or citation") + + source2 = self.dbstate.db.get_source_from_handle(mlist[1]) + citation2 = self.dbstate.db.get_citation_from_handle(mlist[1]) + if (not source2 and not citation2) or (source2 and citation2): + raise ValueError("selection must be either source or citation") + + if citation1 and citation2: + if not citation1.get_reference_handle() == \ + citation2.get_reference_handle(): + msg = _("Cannot merge citations.") + msg2 = _("The two selected citations must have the same " + "source to perform a merge. If you want to merge " + "these two citations, then you must merge the " + "sources first.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeCitations(self.dbstate, self.uistate, + mlist[0], mlist[1]) + elif source1 and source2: + import Merge + Merge.MergeSources(self.dbstate, self.uistate, + mlist[0], mlist[1]) + else: + msg = _("Cannot perform merge.") + msg2 = _("Both objects must be of the same type, either " + "both must be sources, or both must be " + "citations.") + ErrorDialog(msg, msg2) + + def get_handle_from_gramps_id(self, gid): + obj = self.dbstate.db.get_citation_from_gramps_id(gid) + if obj: + return obj.get_handle() + else: + return None + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return (("Source Filter",), + ("Citation Gallery", + "Citation Notes", + "Citation Backlinks")) diff --git a/src/plugins/view/view.gpr.py b/src/plugins/view/view.gpr.py index ffa409613..21884309a 100644 --- a/src/plugins/view/view.gpr.py +++ b/src/plugins/view/view.gpr.py @@ -197,20 +197,21 @@ viewclass = 'RepositoryView', order = START, ) -register(VIEW, -id = 'sourceview', -name = _("Source View"), -description = _("The view showing all the sources"), -version = '1.0', -gramps_target_version = '3.4', -status = STABLE, -fname = 'sourceview.py', -authors = [u"The Gramps project"], -authors_email = ["http://gramps-project.org"], -category = ("Sources", _("Sources")), -viewclass = 'SourceView', -order = START, - ) +# FIXME: REDUNDANT As of gramps 3.4, sourceview.py is redundant. +#register(VIEW, +#id = 'sourceview', +#name = _("Source View"), +#description = _("The view showing all the sources"), +#version = '1.0', +#gramps_target_version = '3.4', +#status = STABLE, +#fname = 'sourceview.py', +#authors = [u"The Gramps project"], +#authors_email = ["http://gramps-project.org"], +#category = ("Sources", _("Sources")), +#viewclass = 'SourceView', +#order = START, +# ) register(VIEW, id = 'citationlistview', @@ -237,8 +238,7 @@ status = STABLE, fname = 'citationtreeview.py', authors = [u"Tim G L Lyons", u"Nick Hall"], authors_email = [""], -category = ("Citations", _("Citations")), +category = ("Sources", _("Sources")), viewclass = 'CitationTreeView', -stock_icon = 'gramps-tree-group', order = START, )