From 9ff46d9eab6116b5096a932a5d9ee14f7af1c68e Mon Sep 17 00:00:00 2001 From: Tim G L Lyons Date: Sun, 24 Jul 2011 18:30:28 +0000 Subject: [PATCH] Initial prototype. * Changed database to introduce Citations * Introduced new Citation Primary object and CitationBase (equivalent to CitationRef) child object * Implemented CitationTreeModel and CitationListModel * Implemented CitationTreeView and CitationListView for new citation views in the navigator * Implemented EditCitation which is used both for the citation views in the navigator and for the citations of an object * Implemented the CitationEmbedList to display the citations of an object * Modified the bottom bar gramplets to support citations * Implemented the citation selector. * Modified Media object to include references to Citations * Initial work on deleting citations svn: r17960 --- src/Bookmarks.py | 13 + src/DbState.py | 2 + src/DisplayState.py | 1 + src/Filters/SideBar/Makefile.am | 1 + src/Filters/SideBar/_CitationSidebarFilter.py | 141 +++++ src/Filters/SideBar/__init__.py | 2 + src/Utils.py | 24 +- src/config.py | 2 + src/gen/db/base.py | 45 ++ src/gen/db/dbconst.py | 8 +- src/gen/db/read.py | 127 +++- src/gen/db/upgrade.py | 90 +++ src/gen/db/write.py | 65 +- src/gen/lib/Makefile.am | 2 + src/gen/lib/__init__.py | 2 + src/gen/lib/citation.py | 336 +++++++++++ src/gen/lib/citationbase.py | 208 +++++++ src/gen/lib/mediaobj.py | 40 +- src/gen/utils/callman.py | 9 +- src/glade/Makefile.am | 1 + src/glade/editcitation.glade | 565 ++++++++++++++++++ src/gui/editors/Makefile.am | 1 + src/gui/editors/__init__.py | 3 + src/gui/editors/displaytabs/Makefile.am | 3 + src/gui/editors/displaytabs/__init__.py | 3 + src/gui/editors/displaytabs/backrefmodel.py | 8 + .../displaytabs/citationbackreflist.py | 39 ++ .../editors/displaytabs/citationembedlist.py | 220 +++++++ .../editors/displaytabs/citationrefmodel.py | 44 ++ src/gui/editors/editcitation.py | 438 ++++++++++++++ src/gui/editors/editmedia.py | 13 +- src/gui/editors/editsource.py | 59 +- src/gui/selectors/Makefile.am | 1 + src/gui/selectors/selectcitation.py | 69 +++ src/gui/selectors/selectorfactory.py | 3 + src/gui/views/treemodels/Makefile.am | 1 + src/gui/views/treemodels/__init__.py | 2 + src/gui/views/treemodels/citationmodel.py | 317 ++++++++++ src/plugins/gramplet/Backlinks.py | 22 + src/plugins/gramplet/Filter.py | 13 + src/plugins/gramplet/Gallery.py | 24 + src/plugins/gramplet/Notes.py | 24 + src/plugins/gramplet/bottombar.gpr.py | 57 ++ src/plugins/lib/libcitationview.py | 332 ++++++++++ src/plugins/view/Makefile.am | 3 + src/plugins/view/citationlistview.py | 55 ++ src/plugins/view/citationtreeview.gpr.py | 14 + src/plugins/view/citationtreeview.py | 155 +++++ src/plugins/view/sourceview.py | 33 + src/plugins/view/view.gpr.py | 15 + 50 files changed, 3629 insertions(+), 26 deletions(-) create mode 100644 src/Filters/SideBar/_CitationSidebarFilter.py create mode 100644 src/gen/lib/citation.py create mode 100644 src/gen/lib/citationbase.py create mode 100644 src/glade/editcitation.glade create mode 100644 src/gui/editors/displaytabs/citationbackreflist.py create mode 100644 src/gui/editors/displaytabs/citationembedlist.py create mode 100644 src/gui/editors/displaytabs/citationrefmodel.py create mode 100644 src/gui/editors/editcitation.py create mode 100644 src/gui/selectors/selectcitation.py create mode 100644 src/gui/views/treemodels/citationmodel.py create mode 100644 src/plugins/lib/libcitationview.py create mode 100644 src/plugins/view/citationlistview.py create mode 100644 src/plugins/view/citationtreeview.gpr.py create mode 100644 src/plugins/view/citationtreeview.py diff --git a/src/Bookmarks.py b/src/Bookmarks.py index c7954262f..9f156c249 100644 --- a/src/Bookmarks.py +++ b/src/Bookmarks.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -364,6 +365,18 @@ class SourceBookmarks(ListBookmarks) : def connect_signals(self): self.dbstate.db.connect('source-delete', self.remove_handles) +class CitationBookmarks(ListBookmarks) : + "Handle the bookmarks interface for Gramps." + def __init__(self, dbstate, uistate, bookmarks, goto_handle): + ListBookmarks.__init__(self, dbstate, uistate, bookmarks, + goto_handle) + + def make_label(self, handle): + return Utils.navigation_label(self.dbstate.db, 'Citation', handle) + + def connect_signals(self): + self.dbstate.db.connect('citation-delete', self.remove_handles) + class MediaBookmarks(ListBookmarks) : "Handle the bookmarks interface for Gramps." diff --git a/src/DbState.py b/src/DbState.py index 51b6fac8a..9f5c01e69 100644 --- a/src/DbState.py +++ b/src/DbState.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -63,6 +64,7 @@ class DbState(Callback): config.get('preferences.oprefix'), config.get('preferences.fprefix'), config.get('preferences.sprefix'), + config.get('preferences.cprefix'), config.get('preferences.pprefix'), config.get('preferences.eprefix'), config.get('preferences.rprefix'), diff --git a/src/DisplayState.py b/src/DisplayState.py index 35566bba1..ada84df2c 100644 --- a/src/DisplayState.py +++ b/src/DisplayState.py @@ -365,6 +365,7 @@ class DisplayState(gen.utils.Callback): 'Event': _("No active event"), 'Place': _("No active place"), 'Source': _("No active source"), + 'Citation': _("No active citation"), 'Repository': _("No active repository"), 'Media': _("No active media"), 'Note': _("No active note"), diff --git a/src/Filters/SideBar/Makefile.am b/src/Filters/SideBar/Makefile.am index 404497a04..23dc08615 100644 --- a/src/Filters/SideBar/Makefile.am +++ b/src/Filters/SideBar/Makefile.am @@ -9,6 +9,7 @@ pkgdata_PYTHON = \ _SidebarFilter.py \ _PersonSidebarFilter.py\ _SourceSidebarFilter.py\ + _CitationSidebarFilter.py\ _PlaceSidebarFilter.py\ _MediaSidebarFilter.py\ _RepoSidebarFilter.py\ diff --git a/src/Filters/SideBar/_CitationSidebarFilter.py b/src/Filters/SideBar/_CitationSidebarFilter.py new file mode 100644 index 000000000..c0fc0050f --- /dev/null +++ b/src/Filters/SideBar/_CitationSidebarFilter.py @@ -0,0 +1,141 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2002-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: _SourceSidebarFilter.py 17555 2011-05-24 20:31:54Z m_d_n $ + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# gtk +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from Filters.SideBar import SidebarFilter +from Filters import GenericFilterFactory, build_filter_model, Rules +from Filters.Rules.Source import (RegExpIdOf, HasIdOf, HasSource, + HasNoteMatchingSubstringOf, HasNoteRegexp, + MatchesFilter) +# FIXME: need to add Citation filter rules. +GenericSourceFilter = GenericFilterFactory('Citation') +#------------------------------------------------------------------------- +# +# PersonSidebarFilter class +# +#------------------------------------------------------------------------- +class CitationSidebarFilter(SidebarFilter): + + def __init__(self, dbstate, uistate, clicked): + self.clicked_func = clicked + self.filter_id = gtk.Entry() + self.filter_title = gtk.Entry() + self.filter_author = gtk.Entry() + self.filter_pub = gtk.Entry() + self.filter_note = gtk.Entry() + + self.filter_regex = gtk.CheckButton(_('Use regular expressions')) + + self.generic = gtk.ComboBox() + + SidebarFilter.__init__(self, dbstate, uistate, "Source") + + def create_widget(self): + cell = gtk.CellRendererText() + cell.set_property('width', self._FILTER_WIDTH) + cell.set_property('ellipsize', self._FILTER_ELLIPSIZE) + self.generic.pack_start(cell, True) + self.generic.add_attribute(cell, 'text', 0) + self.on_filters_changed('Source') + + self.add_text_entry(_('ID'), self.filter_id) + self.add_text_entry(_('Title'), self.filter_title) + self.add_text_entry(_('Author'), self.filter_author) + self.add_text_entry(_('Publication'), self.filter_pub) + self.add_text_entry(_('Note'), self.filter_note) + self.add_filter_entry(_('Custom filter'), self.generic) + self.add_entry(None, self.filter_regex) + + def clear(self, obj): + self.filter_id.set_text('') + self.filter_title.set_text('') + self.filter_author.set_text('') + self.filter_pub.set_text('') + self.filter_note.set_text('') + self.generic.set_active(0) + + def get_filter(self): + gid = unicode(self.filter_id.get_text()).strip() + title = unicode(self.filter_title.get_text()).strip() + author = unicode(self.filter_author.get_text()).strip() + pub = unicode(self.filter_pub.get_text()).strip() + note = unicode(self.filter_note.get_text()).strip() + regex = self.filter_regex.get_active() + gen = self.generic.get_active() > 0 + + empty = not (gid or title or author or pub or note or regex or gen) + if empty: + generic_filter = None + else: + generic_filter = GenericSourceFilter() + if gid: + if regex: + rule = RegExpIdOf([gid]) + else: + rule = HasIdOf([gid]) + generic_filter.add_rule(rule) + + rule = HasSource([title, author, pub], use_regex=regex) + generic_filter.add_rule(rule) + + if note: + if regex: + rule = HasNoteRegexp([note]) + else: + rule = HasNoteMatchingSubstringOf([note]) + generic_filter.add_rule(rule) + + if self.generic.get_active() != 0: + model = self.generic.get_model() + node = self.generic.get_active_iter() + obj = unicode(model.get_value(node, 0)) + rule = MatchesFilter([obj]) + generic_filter.add_rule(rule) + + return generic_filter + + def on_filters_changed(self, name_space): + if name_space == 'Source': + all_filter = GenericSourceFilter() + all_filter.set_name(_("None")) + all_filter.add_rule(Rules.Source.AllSources([])) + self.generic.set_model(build_filter_model('Source', [all_filter])) + self.generic.set_active(0) diff --git a/src/Filters/SideBar/__init__.py b/src/Filters/SideBar/__init__.py index 758147b8d..03a4525c5 100644 --- a/src/Filters/SideBar/__init__.py +++ b/src/Filters/SideBar/__init__.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2002-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,6 +30,7 @@ from _PersonSidebarFilter import PersonSidebarFilter from _FamilySidebarFilter import FamilySidebarFilter from _EventSidebarFilter import EventSidebarFilter from _SourceSidebarFilter import SourceSidebarFilter +from _CitationSidebarFilter import CitationSidebarFilter from _PlaceSidebarFilter import PlaceSidebarFilter from _MediaSidebarFilter import MediaSidebarFilter from _RepoSidebarFilter import RepoSidebarFilter diff --git a/src/Utils.py b/src/Utils.py index 86c46b45d..4d9c19486 100644 --- a/src/Utils.py +++ b/src/Utils.py @@ -3,6 +3,7 @@ # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2009 Gary Burton +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -974,10 +975,22 @@ def get_source_referents(source_handle, db): """ _primaries = ('Person', 'Family', 'Event', 'Place', - 'Source', 'MediaObject', 'Repository') + 'Source', 'MediaObject', 'Repository', 'Citation') return (get_referents(source_handle, db, _primaries)) +def get_citation_referents(citation_handle, db): + """ Find objects that refer the citation. + + This function finds all primary objects that refer (directly or through + secondary child-objects) to a given citation handle in a given database. + + """ + _primaries = ('Person', 'Family', 'Event', 'Place', + 'Source', 'MediaObject', 'Repository') + + return (get_referents(citation_handle, db, _primaries)) + def get_media_referents(media_handle, db): """ Find objects that refer the media object. @@ -1456,11 +1469,18 @@ def navigation_label(db, nav_type, handle): obj = db.get_source_from_handle(handle) if obj: label = obj.get_title() + elif nav_type == 'Citation': + obj = db.get_citation_from_handle(handle) + if obj: + label = obj.get_page() + src = db.get_source_from_handle(obj.ref) + if src: + label = src.get_title() + " " + label elif nav_type == 'Repository': obj = db.get_repository_from_handle(handle) if obj: label = obj.get_name() - elif nav_type == 'Media': + elif nav_type == 'Media' or nav_type == 'MediaObject': obj = db.get_object_from_handle(handle) if obj: label = obj.get_description() diff --git a/src/config.py b/src/config.py index 1b624c574..7318a1cdf 100644 --- a/src/config.py +++ b/src/config.py @@ -5,6 +5,7 @@ # Copyright (C) 2005-2007 Donald N. Allingham # Copyright (C) 2008-2009 Gary Burton # Copyright (C) 2009 Doug Blank +# Copyright (C) 20111 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -266,6 +267,7 @@ register('paths.quick-backup-filename', register('preferences.date-format', 0) register('preferences.calendar-format-report', 0) +register('preferences.cprefix', 'C%04d') register('preferences.default-source', False) register('preferences.eprefix', 'E%04d') register('preferences.family-warn', True) diff --git a/src/gen/db/base.py b/src/gen/db/base.py index bb72e74bb..64b287902 100644 --- a/src/gen/db/base.py +++ b/src/gen/db/base.py @@ -3,6 +3,7 @@ # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -610,6 +611,12 @@ class DbReadBase(object): """ raise NotImplementedError + def get_raw_citation_data(self, handle): + """ + Return raw (serialized and pickled) Citation object from handle + """ + raise NotImplementedError + def get_raw_tag_data(self, handle): """ Return raw (serialized and pickled) Tag object from handle @@ -735,6 +742,44 @@ class DbReadBase(object): """ raise NotImplementedError + def get_citation_bookmarks(self): + """ + Return the list of Citation handles in the bookmarks. + """ + raise NotImplementedError + + def get_citation_cursor(self): + """ + Return a reference to a cursor over Citation objects + """ + raise NotImplementedError + + def get_citation_from_gramps_id(self, val): + """ + Find a Citation in the database from the passed gramps' ID. + + If no such Citation exists, None is returned. + Needs to be overridden by the derived class. + """ + raise NotImplementedError + + def get_citation_from_handle(self, handle): + """ + Find a Citation in the database from the passed gramps' ID. + + If no such Citation exists, None is returned. + """ + raise NotImplementedError + + def get_citation_handles(self, sort_handles=False): + """ + Return a list of database handles, one handle for each Citation in + the database. + + If sort_handles is True, the list is sorted by Citation title. + """ + raise NotImplementedError + def get_surname_list(self): """ Return the list of locale-sorted surnames contained in the database. diff --git a/src/gen/db/dbconst.py b/src/gen/db/dbconst.py index a62f8553b..3b8c469a2 100644 --- a/src/gen/db/dbconst.py +++ b/src/gen/db/dbconst.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2004-2007 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,9 +42,9 @@ __all__ = ( 'DBRECOVFN', 'DBLOGNAME', 'DBFLAGS_O', 'DBFLAGS_R', 'DBFLAGS_D', ) + - ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'EVENT_KEY', - 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', 'NOTE_KEY', - 'REFERENCE_KEY', 'TAG_KEY' + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', + 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', + 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY' ) + ('TXNADD', 'TXNUPD', 'TXNDEL') @@ -82,5 +83,6 @@ REPOSITORY_KEY = 6 REFERENCE_KEY = 7 NOTE_KEY = 8 TAG_KEY = 9 +CITATION_KEY = 10 TXNADD, TXNUPD, TXNDEL = 0, 1, 2 diff --git a/src/gen/db/read.py b/src/gen/db/read.py index fd497307e..80d7c2973 100644 --- a/src/gen/db/read.py +++ b/src/gen/db/read.py @@ -3,6 +3,7 @@ # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,8 +53,8 @@ import logging # GRAMPS libraries # #------------------------------------------------------------------------- -from gen.lib import (MediaObject, Person, Family, Source, Event, Place, - Repository, Note, Tag, GenderStats, Researcher, +from gen.lib import (MediaObject, Person, Family, Source, Citation, Event, + Place, Repository, Note, Tag, GenderStats, Researcher, NameOriginType) from gen.db.dbconst import * from gen.utils.callback import Callback @@ -62,6 +63,7 @@ from Utils import create_id import Errors LOG = logging.getLogger(DBLOGNAME) +LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- # # constants @@ -69,8 +71,9 @@ LOG = logging.getLogger(DBLOGNAME) #------------------------------------------------------------------------- from gen.db.dbconst import * -_SIGBASE = ('person', 'family', 'source', 'event', - 'media', 'place', 'repository', 'reference', 'note', 'tag') +_SIGBASE = ('person', 'family', 'source', 'citation', + 'event', 'media', 'place', 'repository', + 'reference', 'note', 'tag') DBERRS = (db.DBRunRecoveryError, db.DBAccessError, db.DBPageNotFoundError, db.DBInvalidArgError) @@ -153,9 +156,10 @@ class DbReadCursor(BsddbBaseCursor): class DbBsddbRead(DbReadBase, Callback): """ Read class for the GRAMPS databases. Implements methods necessary to read - the various object classes. Currently, there are eight (8) classes: + the various object classes. Currently, there are nine (9) classes: - Person, Family, Event, Place, Source, MediaObject, Repository and Note + Person, Family, Event, Place, Source, Citation, MediaObject, + Repository and Note For each object class, there are methods to retrieve data in various ways. In the methods described below, can be one of person, family, @@ -240,6 +244,13 @@ class DbBsddbRead(DbReadBase, Callback): "class_func": Source, "cursor_func": self.get_source_cursor, }, + 'Citation': + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": Citation, + "cursor_func": self.get_citation_cursor, + }, 'Event': { "handle_func": self.get_event_from_handle, @@ -288,6 +299,7 @@ class DbBsddbRead(DbReadBase, Callback): self.set_object_id_prefix('O%04d') self.set_family_id_prefix('F%04d') self.set_source_id_prefix('S%04d') + self.set_citation_id_prefix('C%04d') self.set_place_id_prefix('P%04d') self.set_event_id_prefix('E%04d') self.set_repository_id_prefix('R%04d') @@ -296,6 +308,7 @@ class DbBsddbRead(DbReadBase, Callback): self.readonly = False self.rand = random.Random(time.time()) self.smap_index = 0 + self.cmap_index = 0 self.emap_index = 0 self.pmap_index = 0 self.fmap_index = 0 @@ -328,6 +341,7 @@ class DbBsddbRead(DbReadBase, Callback): self.fid_trans = {} self.pid_trans = {} self.sid_trans = {} + self.cid_trans = {} self.oid_trans = {} self.rid_trans = {} self.nid_trans = {} @@ -338,6 +352,7 @@ class DbBsddbRead(DbReadBase, Callback): self.family_map = {} self.place_map = {} self.source_map = {} + self.citation_map = {} self.repository_map = {} self.note_map = {} self.media_map = {} @@ -361,6 +376,7 @@ class DbBsddbRead(DbReadBase, Callback): self.event_bookmarks = DbBookmarks() self.place_bookmarks = DbBookmarks() self.source_bookmarks = DbBookmarks() + self.citation_bookmarks = DbBookmarks() self.repo_bookmarks = DbBookmarks() self.media_bookmarks = DbBookmarks() self.note_bookmarks = DbBookmarks() @@ -370,12 +386,13 @@ class DbBsddbRead(DbReadBase, Callback): self.txn = None self.has_changed = False - def set_prefixes(self, person, media, family, source, place, event, - repository, note): + def set_prefixes(self, person, media, family, source, citation, place, + event, repository, note): self.set_person_id_prefix(person) self.set_object_id_prefix(media) self.set_family_id_prefix(family) self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) self.set_place_id_prefix(place) self.set_event_id_prefix(event) self.set_repository_id_prefix(repository) @@ -418,6 +435,9 @@ class DbBsddbRead(DbReadBase, Callback): def get_source_cursor(self, *args, **kwargs): return self.get_cursor(self.source_map, *args, **kwargs) + def get_citation_cursor(self, *args, **kwargs): + return self.get_cursor(self.citation_map, *args, **kwargs) + def get_media_cursor(self, *args, **kwargs): return self.get_cursor(self.media_map, *args, **kwargs) @@ -451,6 +471,7 @@ class DbBsddbRead(DbReadBase, Callback): ## self.event_bookmarks = None ## self.place_bookmarks = None ## self.source_bookmarks = None +## self.citation_bookmarks = None ## self.repo_bookmarks = None ## self.media_bookmarks = None ## self.note_bookmarks = None @@ -471,6 +492,7 @@ class DbBsddbRead(DbReadBase, Callback): self.emit('family-rebuild') self.emit('place-rebuild') self.emit('source-rebuild') + self.emit('citation-rebuild') self.emit('media-rebuild') self.emit('event-rebuild') self.emit('repository-rebuild') @@ -533,6 +555,17 @@ class DbBsddbRead(DbReadBase, Callback): self.smap_index, self.sid_trans) return gid + def find_next_citation_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Source object based off the + source ID prefix. + """ + LOG.debug("cid_index %s" % [self.cid_trans]) + self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix, + self.cmap_index, self.cid_trans) + LOG.debug("gid %s" % gid) + return gid + def find_next_family_gramps_id(self): """ Return the next available GRAMPS' ID for a Family object based off the @@ -613,6 +646,14 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.get_from_handle(handle, Source, self.source_map) + def get_citation_from_handle(self, handle): + """ + Find a Citation in the database from the passed handle. + + If no such Citation exists, None is returned. + """ + return self.get_from_handle(handle, Citation, self.citation_map) + def get_object_from_handle(self, handle): """ Find an Object in the database from the passed handle. @@ -735,6 +776,15 @@ class DbBsddbRead(DbReadBase, Callback): return self.__get_obj_from_gramps_id(val, self.sid_trans, Source, self.source_map) + def get_citation_from_gramps_id(self, val): + """ + Find a Citation in the database from the passed gramps' ID. + + If no such Citation exists, None is returned. + """ + return self.__get_obj_from_gramps_id(val, self.cid_trans, Citation, + self.citation_map) + def get_object_from_gramps_id(self, val): """ Find a MediaObject in the database from the passed gramps' ID. @@ -829,6 +879,12 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.get_number_of_records(self.source_map) + def get_number_of_citations(self): + """ + Return the number of citations currently in the database. + """ + return self.get_number_of_records(self.citation_map) + def get_number_of_media_objects(self): """ Return the number of media objects currently in the database. @@ -899,6 +955,20 @@ class DbBsddbRead(DbReadBase, Callback): return handle_list return [] + def get_citation_handles(self, sort_handles=False): + """ + Return a list of database handles, one handle for each Citation in + the database. + + If sort_handles is True, the list is sorted by Citation Volume/Page. + """ + if self.db_is_open: + handle_list = self.all_handles(self.citation_map) + if sort_handles: + handle_list.sort(key=self.__sortbycitation_key) + return handle_list + return [] + def get_media_object_handles(self, sort_handles=False): """ Return a list of database handles, one handle for each MediaObject in @@ -980,6 +1050,7 @@ class DbBsddbRead(DbReadBase, Callback): iter_event_handles = _f(get_event_cursor) iter_place_handles = _f(get_place_cursor) iter_source_handles = _f(get_source_cursor) + iter_citation_handles = _f(get_citation_cursor) iter_media_object_handles = _f(get_media_cursor) iter_repository_handles = _f(get_repository_cursor) iter_note_handles = _f(get_note_cursor) @@ -1005,6 +1076,7 @@ class DbBsddbRead(DbReadBase, Callback): iter_events = _f(get_event_cursor, Event) iter_places = _f(get_place_cursor, Place) iter_sources = _f(get_source_cursor, Source) + iter_citations = _f(get_citation_cursor, Citation) iter_media_objects = _f(get_media_cursor, MediaObject) iter_repositories = _f(get_repository_cursor, Repository) iter_notes = _f(get_note_cursor, Note) @@ -1016,6 +1088,7 @@ class DbBsddbRead(DbReadBase, Callback): PERSON_KEY: self.id_trans, FAMILY_KEY: self.fid_trans, SOURCE_KEY: self.sid_trans, + CITATION_KEY: self.cid_trans, EVENT_KEY: self.eid_trans, MEDIA_KEY: self.oid_trans, PLACE_KEY: self.pid_trans, @@ -1031,6 +1104,7 @@ class DbBsddbRead(DbReadBase, Callback): PERSON_KEY: self.id_trans, FAMILY_KEY: self.fid_trans, SOURCE_KEY: self.sid_trans, + CITATION_KEY: self.cid_trans, EVENT_KEY: self.eid_trans, MEDIA_KEY: self.oid_trans, PLACE_KEY: self.pid_trans, @@ -1119,6 +1193,17 @@ class DbBsddbRead(DbReadBase, Callback): self.source_prefix = self._validated_id_prefix(val, "S") self.sid2user_format = self.__id2user_format(self.source_prefix) + def set_citation_id_prefix(self, val): + """ + Set the naming template for GRAMPS Citation ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as C%d or C%04d. + """ + self.citation_prefix = self._validated_id_prefix(val, "C") + self.cid2user_format = self.__id2user_format(self.citation_prefix) + def set_object_id_prefix(self, val): """ Set the naming template for GRAMPS MediaObject ID values. @@ -1230,6 +1315,10 @@ class DbBsddbRead(DbReadBase, Callback): """Return the list of Person handles in the bookmarks.""" return self.source_bookmarks + def get_citation_bookmarks(self): + """Return the list of Citation handles in the bookmarks.""" + return self.citation_bookmarks + def get_media_bookmarks(self): """Return the list of Person handles in the bookmarks.""" return self.media_bookmarks @@ -1405,6 +1494,9 @@ class DbBsddbRead(DbReadBase, Callback): def get_raw_source_data(self, handle): return self.__get_raw_data(self.source_map, handle) + def get_raw_citation_data(self, handle): + return self.__get_raw_data(self.citation_map, handle) + def get_raw_repository_data(self, handle): return self.__get_raw_data(self.repository_map, handle) @@ -1472,6 +1564,12 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.__has_handle(self.source_map, handle) + def has_citation_handle(self, handle): + """ + Return True if the handle exists in the current Citation database. + """ + return self.__has_handle(self.citation_map, handle) + def has_tag_handle(self, handle): """ Return True if the handle exists in the current Tag database. @@ -1498,6 +1596,15 @@ class DbBsddbRead(DbReadBase, Callback): source = unicode(self.source_map[str(key)][2]) return locale.strxfrm(source) + def __sortbycitation(self, first, second): + citation1 = unicode(self.citation_map[str(first)][3]) + citation2 = unicode(self.citation_map[str(second)][3]) + return locale.strcoll(citation1, citation2) + + def __sortbycitation_key(self, key): + citation = unicode(self.citation_map[str(key)][3]) + return locale.strxfrm(citation) + def __sortbymedia(self, first, second): media1 = self.media_map[str(first)][4] media2 = self.media_map[str(second)][4] @@ -1573,6 +1680,10 @@ class DbBsddbRead(DbReadBase, Callback): 'cursor_func': self.get_source_cursor, 'class_func': Source, }, + 'Citation': { + 'cursor_func': self.get_citation_cursor, + 'class_func': Citation, + }, 'MediaObject': { 'cursor_func': self.get_media_cursor, 'class_func': MediaObject, diff --git a/src/gen/db/upgrade.py b/src/gen/db/upgrade.py index bc8834b7e..267518632 100644 --- a/src/gen/db/upgrade.py +++ b/src/gen/db/upgrade.py @@ -25,6 +25,8 @@ from __future__ import with_statement from gen.lib.markertype import MarkerType from gen.lib.tag import Tag import time +import logging +LOG = logging.getLogger(".citation") """ methods to upgrade a database from version 13 to current version @@ -38,6 +40,94 @@ from gen.db import BSDDBTxn from gen.lib.nameorigintype import NameOriginType from gen.db.write import _mkname, SURNAMES +def gramps_upgrade_16(self): + """Upgrade database from version 15 to 16. This upgrade converts all + SourceRef child objects to Citation Primary objects. + """ + length = (len(self.note_map) + len(self.person_map) + + len(self.event_map) + len(self.family_map) + + len(self.repository_map) + len(self.media_map) + + len(self.place_map) + len(self.source_map)) + 10 + self.set_total(length) + + LOG.debug("self %s" % self) + LOG.debug("self.find_next_citation_gramps_id %s" % self.find_next_citation_gramps_id) + # --------------------------------- + # Modify Media + # --------------------------------- + for media_handle in self.media_map.keys(): + media = self.media_map[media_handle] + LOG.debug("upgrade media %s" % media[4]) + if len(media) == 12: + LOG.debug(" len == 12") + (handle, gramps_id, path, mime, desc, + attribute_list, source_list, note_list, change, + date, tag_list, private) = media + new_citation_list = convert_sourceref_to_citation_15(self, source_list) + new_media = (handle, gramps_id, path, mime, desc, + attribute_list, source_list, note_list, change, + date, tag_list, new_citation_list, private) + LOG.debug(" upgrade new_media %s" % [new_media]) + with BSDDBTxn(self.env, self.media_map) as txn: + txn.put(str(handle), new_media) + self.update() + +def convert_sourceref_to_citation_15(self, source_list): + new_citation_list = [] + LOG.debug(" convert_sourceref_to_citation_15") + for source in source_list: + LOG.debug(" old sourceref %s" % [source]) + (date, private, note_list, confidence, ref, page) = source + new_handle = self.create_id() + new_media_list = [] + new_data_map = {} + new_change = time.time() + LOG.debug(" self %s" % [self]) + + # FIXME: I don't understand why I can't use find_next_citation_gramps_id. + # Attempting to use it fails. This seems to be because cid_trans + # is not initialised properly. However I don't understand how this + # is ever initialised. + # Also, self.cmap_index does not seem to be initialised, but + # again I don't see how it is initialised for find_next_citation_gramps_id + # Should self.citation_map and/or cmap_index be committed to the + # database after being updated? + LOG.debug(" cmap_index %s" % self.cmap_index) + LOG.debug(" len(self.citation_map) %s" % len(self.citation_map)) + (self.cmap_index, new_gramps_id) = \ + __find_next_gramps_id(self, self.citation_prefix, + self.cmap_index) + LOG.debug(" new_gramps_id %s" % new_gramps_id) + new_citation = (new_handle, new_gramps_id, + date, page, confidence, ref, note_list, new_media_list, + new_data_map, new_change, private) + LOG.debug(" new_citation %s" % [new_citation]) + with BSDDBTxn(self.env, self.citation_map) as txn: + txn.put(str(new_handle), new_citation) + new_citation_list.append((new_handle)) + return new_citation_list + +def __find_next_gramps_id(self, prefix, map_index): + """ + Helper function for find_next__gramps_id methods + """ + index = prefix % map_index + # This uses a generator expression, see PEP 289: + # http://www.python.org/dev/peps/pep-0289/ + # This avoids evaluating a whole list at once. + # This is equivalent to: + # used_ids = {} + # for handle in self.citation_map.keys() + # used_ids += self.citation_map[handle][1] + used_ids = (self.citation_map[handle][1] for handle in self.citation_map.keys()) + for i in used_ids: + LOG.debug(" used_ids %s" % i) + while index in used_ids: + map_index += 1 + index = prefix % map_index + map_index += 1 + return (map_index, index) + def gramps_upgrade_15(self): """Upgrade database from version 14 to 15. This upgrade adds: * tagging diff --git a/src/gen/db/write.py b/src/gen/db/write.py index 213293952..a748e88a5 100644 --- a/src/gen/db/write.py +++ b/src/gen/db/write.py @@ -3,6 +3,7 @@ # # Copyright (C) 2000-2008 Donald N. Allingham # Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -54,7 +55,7 @@ else: # #------------------------------------------------------------------------- from gen.lib import (GenderStats, Person, Family, Event, Place, Source, - MediaObject, Repository, Note, Tag) + Citation, MediaObject, Repository, Note, Tag) from gen.db import (DbBsddbRead, DbWriteBase, BSDDBTxn, DbTxn, BsddbBaseCursor, DbVersionError, DbEnvironmentError, DbUpgradeRequiredError, find_surname, find_surname_name, @@ -66,8 +67,9 @@ import Errors import constfunc _LOG = logging.getLogger(DBLOGNAME) +LOG = logging.getLogger(".citation") _MINVERSION = 9 -_DBVERSION = 15 +_DBVERSION = 16 IDTRANS = "person_id" FIDTRANS = "family_id" @@ -77,6 +79,7 @@ EIDTRANS = "event_id" RIDTRANS = "repo_id" NIDTRANS = "note_id" SIDTRANS = "source_id" +CIDTRANS = "citation_id" TAGTRANS = "tag_name" SURNAMES = "surnames" NAME_GROUP = "name_group" @@ -85,6 +88,7 @@ META = "meta_data" FAMILY_TBL = "family" PLACES_TBL = "place" SOURCES_TBL = "source" +CITATIONS_TBL = "citation" MEDIA_TBL = "media" EVENTS_TBL = "event" PERSON_TBL = "person" @@ -108,6 +112,7 @@ DBERRS = (db.DBRunRecoveryError, db.DBAccessError, CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, Family.__name__: FAMILY_KEY, Source.__name__: SOURCE_KEY, + Citation.__name__: CITATION_KEY, Event.__name__: EVENT_KEY, MediaObject.__name__: MEDIA_KEY, Place.__name__: PLACE_KEY, @@ -118,6 +123,7 @@ CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, FAMILY_KEY: Family.__name__, SOURCE_KEY: Source.__name__, + CITATION_KEY: Citation.__name__, EVENT_KEY: Event.__name__, MEDIA_KEY: MediaObject.__name__, PLACE_KEY: Place.__name__, @@ -129,6 +135,7 @@ KEY_TO_NAME_MAP = {PERSON_KEY: 'person', FAMILY_KEY: 'family', EVENT_KEY: 'event', SOURCE_KEY: 'source', + CITATION_KEY: 'citation', PLACE_KEY: 'place', MEDIA_KEY: 'media', REPOSITORY_KEY: 'repository', @@ -195,7 +202,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): __signals__ = dict((obj+'-'+op, signal) for obj in ['person', 'family', 'event', 'place', - 'source', 'media', 'note', 'repository', 'tag'] + 'source', 'citation', 'media', 'note', 'repository', 'tag'] for op, signal in zip( ['add', 'update', 'delete', 'rebuild'], [(list,), (list,), (list,), None] @@ -370,8 +377,9 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return True # See if we lack write access to any files in the directory - for base in [FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, - EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, REF_MAP, META]: + for base in [FAMILY_TBL, PLACES_TBL, SOURCES_TBL, CITATIONS_TBL, + MEDIA_TBL, EVENTS_TBL, PERSON_TBL, REPO_TBL, + NOTE_TBL, REF_MAP, META]: path = os.path.join(name, base + DBEXT) if os.path.isfile(path) and not os.access(path, os.W_OK): return True @@ -475,6 +483,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ("family_map", FAMILY_TBL, db.DB_HASH), ("place_map", PLACES_TBL, db.DB_HASH), ("source_map", SOURCES_TBL, db.DB_HASH), + ("citation_map", CITATIONS_TBL, db.DB_HASH), ("media_map", MEDIA_TBL, db.DB_HASH), ("event_map", EVENTS_TBL, db.DB_HASH), ("person_map", PERSON_TBL, db.DB_HASH), @@ -576,6 +585,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.family_bookmarks.set(meta('family_bookmarks')) self.event_bookmarks.set(meta('event_bookmarks')) self.source_bookmarks.set(meta('source_bookmarks')) + self.citation_bookmarks.set(meta('citation_bookmarks')) self.repo_bookmarks.set(meta('repo_bookmarks')) self.media_bookmarks.set(meta('media_bookmarks')) self.place_bookmarks.set(meta('place_bookmarks')) @@ -622,6 +632,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ("eid_trans", EIDTRANS, db.DB_HASH, 0), ("pid_trans", PIDTRANS, db.DB_HASH, 0), ("sid_trans", SIDTRANS, db.DB_HASH, 0), + ("cid_trans", CIDTRANS, db.DB_HASH, 0), ("oid_trans", OIDTRANS, db.DB_HASH, 0), ("rid_trans", RIDTRANS, db.DB_HASH, 0), ("nid_trans", NIDTRANS, db.DB_HASH, 0), @@ -644,6 +655,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): (self.event_map, self.eid_trans, find_idmap), (self.place_map, self.pid_trans, find_idmap), (self.source_map, self.sid_trans, find_idmap), + (self.citation_map, self.cid_trans, find_idmap), (self.media_map, self.oid_trans, find_idmap), (self.repository_map, self.rid_trans, find_idmap), (self.note_map, self.nid_trans, find_idmap), @@ -660,6 +672,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.secondary_connected = True self.smap_index = len(self.source_map) + self.cmap_index = len(self.citation_map) self.emap_index = len(self.event_map) self.pmap_index = len(self.person_map) self.fmap_index = len(self.family_map) @@ -686,6 +699,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ( self.eid_trans, EIDTRANS ), ( self.rid_trans, RIDTRANS ), ( self.nid_trans, NIDTRANS ), + ( self.cid_trans, CIDTRANS ), ( self.tag_trans, TAGTRANS ), ( self.reference_map_primary_map, REF_PRI), ( self.reference_map_referenced_map, REF_REF), @@ -935,6 +949,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): (self.get_event_cursor, Event), (self.get_place_cursor, Place), (self.get_source_cursor, Source), + (self.get_citation_cursor, Citation), (self.get_media_cursor, MediaObject), (self.get_repository_cursor, Repository), (self.get_note_cursor, Note), @@ -979,6 +994,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): txn.put('family_bookmarks', self.family_bookmarks.get()) txn.put('event_bookmarks', self.event_bookmarks.get()) txn.put('source_bookmarks', self.source_bookmarks.get()) + txn.put('citation_bookmarks', self.citation_bookmarks.get()) txn.put('place_bookmarks', self.place_bookmarks.get()) txn.put('repo_bookmarks', self.repo_bookmarks.get()) txn.put('media_bookmarks', self.media_bookmarks.get()) @@ -1044,6 +1060,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.nid_trans.close() self.oid_trans.close() self.sid_trans.close() + self.cid_trans.close() self.pid_trans.close() self.tag_trans.close() self.reference_map_primary_map.close() @@ -1059,6 +1076,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.note_map.close() self.place_map.close() self.source_map.close() + self.citation_map.close() self.media_map.close() self.event_map.close() self.tag_map.close() @@ -1071,6 +1089,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.note_map = None self.place_map = None self.source_map = None + self.citation_map = None self.media_map = None self.event_map = None self.tag_map = None @@ -1088,6 +1107,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.note_map = None self.place_map = None self.source_map = None + self.citation_map = None self.media_map = None self.event_map = None self.tag_map = None @@ -1151,6 +1171,17 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.find_next_source_gramps_id if set_gid else None, self.commit_source) + def add_citation(self, citation, transaction, set_gid=True): + """ + Add a Citation to the database, assigning internal IDs if they have + not already been defined. + + If not set_gid, then gramps_id is not set. + """ + return self.__add_object(citation, transaction, + self.find_next_citation_gramps_id if set_gid else None, + self.commit_citation) + def add_event(self, event, transaction, set_gid=True): """ Add an Event to the database, assigning internal IDs if they have @@ -1278,6 +1309,14 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.__do_remove(handle, transaction, self.source_map, SOURCE_KEY) + def remove_citation(self, handle, transaction): + """ + Remove the Citation specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.citation_map, + CITATION_KEY) + def remove_event(self, handle, transaction): """ Remove the Event specified by the database handle from the @@ -1515,6 +1554,20 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if attr.type.is_custom() and str(attr.type)] self.media_attributes.update(attr_list) + def commit_citation(self, citation, transaction, change_time=None): + """ + Commit the specified Citation to the database, storing the changes as + part of the transaction. + """ + self.commit_base(citation, self.citation_map, CITATION_KEY, + transaction, change_time) + + attr_list = [] + for mref in citation.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) + def commit_place(self, place, transaction, change_time=None): """ Commit the specified Place to the database, storing the changes as @@ -1821,6 +1874,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): upgrade.gramps_upgrade_14(self) if version < 15: upgrade.gramps_upgrade_15(self) + if version < 16: + upgrade.gramps_upgrade_16(self) _LOG.debug("Upgrade time: %d seconds" % int(time.time()-t)) diff --git a/src/gen/lib/Makefile.am b/src/gen/lib/Makefile.am index a8fc70c6c..01804bd6e 100644 --- a/src/gen/lib/Makefile.am +++ b/src/gen/lib/Makefile.am @@ -15,6 +15,8 @@ pkgdata_PYTHON = \ calendar.py \ childref.py \ childreftype.py \ + citation.py \ + citationbase.py \ const.py \ datebase.py \ date.py \ diff --git a/src/gen/lib/__init__.py b/src/gen/lib/__init__.py index a5d487b9a..354da33f1 100644 --- a/src/gen/lib/__init__.py +++ b/src/gen/lib/__init__.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,6 +53,7 @@ from gen.lib.src import Source from gen.lib.mediaobj import MediaObject from gen.lib.repo import Repository from gen.lib.note import Note +from gen.lib.citation import Citation # Table objects from gen.lib.tag import Tag diff --git a/src/gen/lib/citation.py b/src/gen/lib/citation.py new file mode 100644 index 000000000..94a1d55ee --- /dev/null +++ b/src/gen/lib/citation.py @@ -0,0 +1,336 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: src.py 16425 2011-01-21 16:27:54Z gbritton $ + +""" +Citation object for GRAMPS. +""" + +#------------------------------------------------------------------------- +# +# standard python modules +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from gen.lib.primaryobj import PrimaryObject +from gen.lib.mediabase import MediaBase +from gen.lib.notebase import NoteBase +from gen.lib.datebase import DateBase +from gen.lib.refbase import RefBase +from gen.lib.const import DIFFERENT, EQUAL, IDENTICAL + +#------------------------------------------------------------------------- +# +# Citation class +# +#------------------------------------------------------------------------- +class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): + """A record of a citation of a source of information.""" + + CONF_VERY_HIGH = 4 + CONF_HIGH = 3 + CONF_NORMAL = 2 + CONF_LOW = 1 + CONF_VERY_LOW = 0 + + def __init__(self): + """Create a new Citation instance.""" + PrimaryObject.__init__(self) + MediaBase.__init__(self) # 7 + NoteBase.__init__(self) # 6 + DateBase.__init__(self) # 2 + RefBase.__init__(self) # 5 + self.page = "" # 3 + self.confidence = Citation.CONF_NORMAL # 4 + self.datamap = {} # 8 + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return (self.handle, # 0 + self.gramps_id, # 1 + DateBase.serialize(self), # 2 + unicode(self.page), # 3 + self.confidence, # 4 + RefBase.serialize(self), # 5 + NoteBase.serialize(self), # 6 + MediaBase.serialize(self), # 7 + self.datamap, # 8 + self.change, # 9 + self.private) # 10 + + def unserialize(self, data): + """ + Convert the data held in a tuple created by the serialize method + back into the data in a Citation structure. + """ + (self.handle, # 0 + self.gramps_id, # 1 + date, # 2 + self.page, # 3 + self.confidence, # 4 + ref, # 5 + note_list, # 6 + media_list, # 7 + self.datamap, # 8 + self.change, # 9 + self.private # 10 + ) = data + + DateBase.unserialize(self, date) + NoteBase.unserialize(self, note_list) + MediaBase.unserialize(self, media_list) + RefBase.unserialize(self, ref) + + def _has_handle_reference(self, classname, handle): + """ + Return True if the object has reference to a given handle of given + primary object type. + + :param classname: The name of the primary object class. + :type classname: str + :param handle: The handle to be checked. + :type handle: str + :returns: Returns whether the object has reference to this handle of + this object type. + :rtype: bool + """ + # FIXME: it appears that this is only called for 'Event', 'Person', + # 'Place' and 'Repository', hence this is untested and may be + # unnecessary. + + # FIXME: and libgrdb find_backlink_handles for all primary types + # should add 'Note', 'Media', 'Source' + if classname == 'Note': + return handle in [ref.ref for ref in self.note_list] + elif classname == 'Media': + return handle in [ref.ref for ref in self.media_list] + elif classname == 'Source': + return handle == self.get_reference_handle() + return False + + def _remove_handle_references(self, classname, handle_list): + """ + Remove all references in this object to object handles in the list. + + :param classname: The name of the primary object class. + :type classname: str + :param handle_list: The list of handles to be removed. + :type handle_list: str + """ + # FIXME: The following primary objects can refer to Citations: + # Person, Family, Event, MediaObject, Place + if classname == 'Source' and \ + self.get_reference_handle() in handle_list: + self.set_reference_handle(None) + + def _replace_handle_reference(self, classname, old_handle, new_handle): + """ + Replace all references to old handle with those to the new handle. + + :param classname: The name of the primary object class. + :type classname: str + :param old_handle: The handle to be replaced. + :type old_handle: str + :param new_handle: The handle to replace the old one with. + :type new_handle: str + """ + # FIXME: The following primary objects can refer to Citations: + # Person, Family, Event, MediaObject, Place + if classname == 'Source' and \ + RefBase.get_reference_handle(self) == old_handle: + self.ref = RefBase.set_reference_handle(self, new_handle) + + def get_text_data_list(self): + """ + Return the list of all textual attributes of the object. + + :returns: Returns the list of all textual attributes of the object. + :rtype: list + """ + # FIXME: Presumably this does not include references to primary objects + # (Notes, Media, Source) that contain text + return [self.page, + self.gramps_id] + self.datamap.keys() + self.datamap.values() + + def get_text_data_child_list(self): + """ + Return the list of child objects that may carry textual data. + + :returns: Returns the list of child objects that may carry textual data. + :rtype: list + """ + # FIXME: Apparently does not include 'Note' child objects + return self.media_list + + def get_sourcref_child_list(self): + """ + Return the list of child secondary objects that may refer sources. + + :returns: Returns the list of child secondary child objects that may + refer sources. + :rtype: list + """ + # FIXME: should this also return the source reference child + # secondary object as this will refer to a Source Primary object + return self.media_list + self.ref + + def get_note_child_list(self): + """ + Return the list of child secondary objects that may refer notes. + + :returns: Returns the list of child secondary child objects that may + refer notes. + :rtype: list + """ + # FIXME: should this also return the source reference child + # secondary object as this will refer to a Source Primary object + # that can itself refer to notes + return self.media_list + + def get_handle_referents(self): + """ + Return the list of child objects which may, directly or through + their children, reference primary objects. + + :returns: Returns the list of objects referencing primary objects. + :rtype: list + """ + # FIXME: should this also return the source reference child + # secondary object as this will refer to a Primary objects, + # namely the Source object? + return self.media_list + + def get_referenced_handles(self): + """ + Return the list of (classname, handle) tuples for all directly + referenced primary objects. + + :returns: List of (classname, handle) tuples for referenced objects. + :rtype: list + """ + # FIXME: Apparently this does not include 'Media' + ret = self.get_referenced_note_handles() + if self.ref: + ret += [('Source', self.ref)] + LOG.debug ("Citation: %s get_referenced_handles: %s" % + (self.page, ret)) + return ret + + def has_source_reference(self, src_handle) : + """ + Return True if any of the child objects has reference to this source + handle. + + :param src_handle: The source handle to be checked. + :type src_handle: str + :returns: Returns whether any of it's child objects has reference to + this source handle. + :rtype: bool + """ + for item in self.get_sourcref_child_list(): + if item.has_source_reference(src_handle): + return True + + return False + + def remove_source_references(self, src_handle_list): + """ + Remove references to all source handles in the list in all child + objects. + + :param src_handle_list: The list of source handles to be removed. + :type src_handle_list: list + """ + for item in self.get_sourcref_child_list(): + item.remove_source_references(src_handle_list) + + def replace_source_references(self, old_handle, new_handle): + """ + Replace references to source_handles in the list in this object and + all child objects and merge equivalent entries. + + :param old_handle: The source handle to be replaced. + :type old_handle: str + :param new_handle: The source handle to replace the old one with. + :type new_handle: str + """ + for item in self.get_sourcref_child_list(): + item.replace_source_references(old_handle, new_handle) + + def merge(self, acquisition): + """ + Merge the content of acquisition into this source. + + :param acquisition: The source to merge with the present source. + :rtype acquisition: Source + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_media_list(acquisition) + # merge confidence + level_priority = [0, 4, 1, 3, 2] + idx = min(level_priority.index(self.confidence), + level_priority.index(acquisition.confidence)) + self.confidence = level_priority[idx] + my_datamap = self.get_data_map() + acquisition_map = acquisition.get_data_map() + for key in acquisition.get_data_map(): + if key not in my_datamap: + self.datamap[key] = acquisition_map[key] + # N.B. a Citation can refer to only one 'Source', so the + # 'Source' from acquisition cannot be merged in + + def get_data_map(self): + """Return the data map of attributes for the source.""" + return self.datamap + + def set_data_map(self, datamap): + """Set the data map of attributes for the source.""" + self.datamap = datamap + + def set_data_item(self, key, value): + """Set the particular data item in the attribute data map.""" + self.datamap[key] = value + + def set_confidence_level(self, val): + """Set the confidence level.""" + self.confidence = val + + def get_confidence_level(self): + """Return the confidence level.""" + return self.confidence + + def set_page(self, page): + """Set the page indicator of the SourceRef.""" + self.page = page + + def get_page(self): + """Get the page indicator of the SourceRef.""" + return self.page diff --git a/src/gen/lib/citationbase.py b/src/gen/lib/citationbase.py new file mode 100644 index 000000000..6c8000412 --- /dev/null +++ b/src/gen/lib/citationbase.py @@ -0,0 +1,208 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: notebase.py 15645 2010-07-22 02:16:32Z dsblank $ + +""" +CitationBase class for GRAMPS. +""" + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# CitationBase class +# +#------------------------------------------------------------------------- +class CitationBase(object): + """ + Base class for storing citations. + + Starting in 3.4, the objects may have multiple citations. + Internally, this class maintains a list of Citation handles, + as a citation_list attribute of the CitationBase object. + This class is analogous to the notebase class. + Both these have no attributes of their own; in this respect, they differ + from classes like MediaRef, which does have attributes (in that case, + privacy, sources, notes and attributes). + + This class, together with the Citation class, replaces the old SourceRef class. + I.e. SourceRef = CitationBase + Citation + """ + def __init__(self, source=None): + """ + Create a new CitationBase, copying from source if not None. + + :param source: Object used to initialize the new object + :type source: CitationBase + """ + self.citation_list = list(source.citation_list) if source else [] + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return self.citation_list + + def unserialize(self, data): + """ + Convert a serialized tuple of data to an object. + """ + self.citation_list = list(data) + + def add_citation(self, handle): + """ + Add the :class:`~gen.lib.citation.Citation` handle to the list of citation handles. + + :param handle: :class:`~gen.lib.citation.Citation` handle to add the list of citations + :type handle: str + + :returns: True if handle was added, False if it already was in the list + :rtype: bool + """ + if handle in self.citation_list: + return False + else: + self.citation_list.append(handle) + return True + + def remove_citation(self, handle): + """ + Remove the specified handle from the list of citation handles, and all + secondary child objects. + + :param handle: :class:`~gen.lib.citation.Citation` handle to remove from the list of citations + :type handle: str + """ + LOG.debug('enter remove_citation handle %s' % handle) + if handle in self.citation_list: + LOG.debug('remove handle %s from citation_list %s' % (handle, self.citation_list)) + self.citation_list.remove(handle) + LOG.debug('get_citation_child_list %s' % self.get_citation_child_list()) + for item in self.get_citation_child_list(): + item.remove_citation(handle) + + def get_citation_child_list(self): + """ + Return the list of child secondary objects that may refer citations. + + All methods which inherit from CitationBase and have other child objects + with citations, should return here a list of child objects which are + CitationBase + + :returns: Returns the list of child secondary child objects that may + refer citations. + :rtype: list + """ + return [] + + def get_citation_list(self): + """ + Return the list of :class:`~gen.lib.citation.Citation` handles associated with the object. + + :returns: The list of :class:`~gen.lib.citation.Citation` handles + :rtype: list + """ + return self.citation_list + + def has_citation_reference(self, citation_handle): + """ + Return True if the object or any of its child objects has reference + to this citation handle. + + :param citation_handle: The citation handle to be checked. + :type citation_handle: str + :returns: Returns whether the object or any of its child objects has + reference to this citation handle. + :rtype: bool + """ + for citation_ref in self.citation_list: + if citation_ref == citation_handle: + return True + + for item in self.get_citation_child_list(): + if item.has_citation_reference(citation_handle): + return True + + return False + + def set_citation_list(self, citation_list): + """ + Assign the passed list to be object's list of :class:`~gen.lib.citation.Citation` handles. + + :param citation_list: List of :class:`~gen.lib.citation.Citation` handles to be set on the object + :type citation_list: list + """ + self.citation_list = citation_list + + def _merge_citation_list(self, acquisition): + """ + Merge the list of citations from acquisition with our own. + + :param acquisition: The citation list of this object will be merged with + the current citation list. + :rtype acquisition: CitationBase + """ + for addendum in acquisition.citation_list: + self.add_citation(addendum) + + def get_referenced_citation_handles(self): + """ + Return the list of (classname, handle) tuples for all referenced citations. + + This method should be used to get the :class:`~gen.lib.citation.Citation` portion of the list + by objects that store citation lists. + + :returns: List of (classname, handle) tuples for referenced objects. + :rtype: list + """ + return [('Citation', handle) for handle in self.citation_list] + + def replace_citation_references(self, old_handle, new_handle): + """ + Replace references to citation handles in the list of this object and + all child objects and merge equivalent entries. + + :param old_handle: The citation handle to be replaced. + :type old_handle: str + :param new_handle: The citation handle to replace the old one with. + :type new_handle: str + """ + refs_list = self.citation_list[:] + new_ref = None + if new_handle in self.citation_list: + new_ref = new_handle + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + if new_ref: + self.citation_list.pop(idx) + refs_list.pop(idx) + else: + self.citation_list[idx] = new_handle + + for item in self.get_citation_child_list(): + item.replace_citation_references(old_handle, new_handle) diff --git a/src/gen/lib/mediaobj.py b/src/gen/lib/mediaobj.py index a0af0e54d..fe782e046 100644 --- a/src/gen/lib/mediaobj.py +++ b/src/gen/lib/mediaobj.py @@ -32,6 +32,8 @@ Media object for GRAMPS. # #------------------------------------------------------------------------- import os +import logging +LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- # @@ -40,6 +42,7 @@ import os #------------------------------------------------------------------------- from gen.lib.primaryobj import PrimaryObject from gen.lib.srcbase import SourceBase +from gen.lib.citationbase import CitationBase from gen.lib.notebase import NoteBase from gen.lib.datebase import DateBase from gen.lib.attrbase import AttributeBase @@ -50,7 +53,7 @@ from gen.lib.tagbase import TagBase # MediaObject class # #------------------------------------------------------------------------- -class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, +class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, TagBase, PrimaryObject): """ Container for information about an image file, including location, @@ -73,6 +76,7 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, DateBase.__init__(self, source) AttributeBase.__init__(self, source) TagBase.__init__(self) + CitationBase.__init__(self) if source: self.path = source.path @@ -110,6 +114,7 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, self.change, DateBase.serialize(self, no_text_date), TagBase.serialize(self), + CitationBase.serialize(self), self.private) def unserialize(self, data): @@ -122,13 +127,16 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, """ (self.handle, self.gramps_id, self.path, self.mime, self.desc, attribute_list, source_list, note_list, self.change, - date, tag_list, self.private) = data + date, tag_list, + citation_list, + self.private) = data AttributeBase.unserialize(self, attribute_list) SourceBase.unserialize(self, source_list) NoteBase.unserialize(self, note_list) DateBase.unserialize(self, date) TagBase.unserialize(self, tag_list) + CitationBase.unserialize(self, citation_list) def get_text_data_list(self): """ @@ -158,6 +166,18 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, """ return self.attribute_list + def get_citation_child_list(self): + """ + Return the list of child secondary objects that may refer sources. + + :returns: Returns the list of child secondary child objects that may + refer sources. + :rtype: list + """ + # N.B. the citation_list of the media object is not a child object + # it is a direct reference from Media to a citation. + return [] + def get_note_child_list(self): """ Return the list of child secondary objects that may refer notes. @@ -166,7 +186,7 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, refer notes. :rtype: list """ - return self.attribute_list + self.source_list + return self.attribute_list + self.source_list + self.citation_list def get_referenced_handles(self): """ @@ -176,8 +196,14 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, :returns: List of (classname, handle) tuples for referenced objects. :rtype: list """ + LOG.debug ("Media: %s get_referenced_handles: %s" % + (self.desc, + self.get_referenced_note_handles() + + self.get_referenced_tag_handles() + + self.get_referenced_citation_handles())) return self.get_referenced_note_handles() + \ - self.get_referenced_tag_handles() + self.get_referenced_tag_handles() + \ + self.get_referenced_citation_handles() def get_handle_referents(self): """ @@ -187,6 +213,11 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, :returns: Returns the list of objects referencing primary objects. :rtype: list """ + LOG.debug ("Media: %s get_handle_referents: %s" % + (self.desc, + self.attribute_list + self.source_list)) +# FIXME: This is wrong, because it returns the handle, when it should return the +# citation object. This is probably because the citation unpack has not been done. return self.attribute_list + self.source_list def merge(self, acquisition): @@ -203,6 +234,7 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, self._merge_note_list(acquisition) self._merge_source_reference_list(acquisition) self._merge_tag_list(acquisition) + self.merge_citation_list(acquisition) def set_mime_type(self, mime_type): """ diff --git a/src/gen/utils/callman.py b/src/gen/utils/callman.py index e595917f7..74a2210a4 100644 --- a/src/gen/utils/callman.py +++ b/src/gen/utils/callman.py @@ -44,6 +44,7 @@ EVENTKEY = 'event' PLACEKEY = 'place' MEDIAKEY = 'media' SOURCEKEY = 'source' +CITATIONKEY = 'citation' REPOKEY = 'repository' NOTEKEY = 'note' @@ -53,7 +54,7 @@ DELETE = '-delete' REBUILD = '-rebuild' KEYS = [PERSONKEY, FAMILYKEY, EVENTKEY, PLACEKEY, MEDIAKEY, SOURCEKEY, - REPOKEY, NOTEKEY] + CITATIONKEY, REPOKEY, NOTEKEY] METHODS = [ADD, UPDATE, DELETE, REBUILD] METHODS_LIST = [ADD, UPDATE, DELETE] @@ -65,6 +66,7 @@ EVENTCLASS = 'Event' PLACECLASS = 'Place' MEDIACLASS = 'MediaObject' SOURCECLASS = 'Source' +CITATIONCLASS = 'Citation' REPOCLASS = 'Repository' NOTECLASS = 'Note' @@ -75,6 +77,7 @@ CLASS2KEY = { PLACECLASS: PLACEKEY, MEDIACLASS: MEDIAKEY, SOURCECLASS: SOURCEKEY, + CITATIONCLASS: CITATIONKEY, REPOCLASS: REPOKEY, NOTECLASS: NOTEKEY } @@ -116,6 +119,7 @@ class CallbackManager(object): PLACEKEY: [], MEDIAKEY: [], SOURCEKEY: [], + CITATIONKEY: [], REPOKEY: [], NOTEKEY: [], } @@ -198,6 +202,7 @@ class CallbackManager(object): PLACEKEY: [], MEDIAKEY: [], SOURCEKEY: [], + CITATIONKEY: [], REPOKEY: [], NOTEKEY: [], } @@ -334,6 +339,7 @@ def directhandledict(baseobj): PLACEKEY: [], MEDIAKEY: [], SOURCEKEY: [], + CITATIONKEY: [], REPOKEY: [], NOTEKEY: [], } @@ -353,6 +359,7 @@ def handledict(baseobj): PLACEKEY: [], MEDIAKEY: [], SOURCEKEY: [], + CITATIONKEY: [], REPOKEY: [], NOTEKEY: [], } diff --git a/src/glade/Makefile.am b/src/glade/Makefile.am index c46c81aab..b1b4c7881 100644 --- a/src/glade/Makefile.am +++ b/src/glade/Makefile.am @@ -21,6 +21,7 @@ dist_pkgdata_DATA = \ configure.glade \ dateedit.glade \ editsource.glade \ + editcitation.glade \ styleeditor.glade \ dbman.glade \ editurl.glade \ diff --git a/src/glade/editcitation.glade b/src/glade/editcitation.glade new file mode 100644 index 000000000..0750c485a --- /dev/null +++ b/src/glade/editcitation.glade @@ -0,0 +1,565 @@ + + + + + + + 600 + dialog + False + + + True + + + True + 0 + 6 + 3 + <b>Reference information</b> + True + True + center + + + False + False + 1 + + + + + True + True + 6 + + + True + 12 + 3 + 3 + 12 + 6 + + + True + 1 + _Date: + True + center + date_entry + + + GTK_FILL + + + + + + True + True + True + True + Invoke date editor + none + + + True + gramps-date + + + + + 2 + 3 + GTK_FILL + + + + + + True + True + Specific location within the information referenced. For a published work, this could include the volume of a multi-volume work and the page number(s). For a periodical, it could include volume, issue, and page numbers. For a newspaper, it could include a column number and page number. For an unpublished source, this could be a sheet number, page number, frame number, etc. A census record might have a line number or dwelling and family numbers in addition to the page number. + + + + 1 + 2 + 1 + 2 + + + + + + True + 1 + _Volume/Page: + True + center + volume + + + 1 + 2 + GTK_FILL + + + + + + True + 1 + Con_fidence: + True + center + confidence + + + 2 + 3 + GTK_FILL + + + + + + True + True + True + none + + + True + gramps-unlock + + + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + True + Conveys the submitter's quantitative evaluation of the credibility of a piece of information, based upon its supporting evidence. It is not intended to eliminate the receiver's need to evaluate the evidence for themselves. +Very Low =Unreliable evidence or estimated data +Low =Questionable reliability of evidence (interviews, census, oral genealogies, or potential for bias for example, an autobiography) +High =Secondary evidence, data officially recorded sometime after event +Very High =Direct and primary evidence used, or by dominance of the evidence + confidence_model + + + + 0 + + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + True + True + The date of the entry in the source you are referencing, e.g. the date a house was visited during a census, or the date an entry was made in a birth log/registry. + + + + 1 + 2 + + + + + + + + + + + True + <b>General</b> + True + + + False + + + + + 2 + + + + + True + True + 6 + True + 6 + + + True + True + False + + + True + 12 + 6 + 2 + 12 + 6 + + + True + 0 + _Title: + True + center + title + + + GTK_FILL + + + + + + True + 0 + _Author: + True + center + author + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + 3 + A_bbreviation: + True + center + abbrev + + + 3 + 4 + GTK_FILL + + + + + + True + 0 + _Pub. Info.: + True + center + pub_info + + + 4 + 5 + GTK_FILL + + + + + + True + True + Authors of the source. + + + + 1 + 2 + 1 + 2 + + + + + + 6 + 12 + + + True + gtk-dialog-warning + 6 + + + False + False + 0 + + + + + 500 + True + 0 + 3 + <b>Note:</b> Any changes in the shared source information will be reflected in the source itself, for all items that reference the source. + True + True + True + + + 1 + + + + + 2 + 5 + 6 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + True + True + Provide a short title used for sorting, filing, and retrieving source records. + + + + 1 + 2 + 3 + 4 + + + + + + True + True + Publication Information, such as city and year of publication, name of publisher, ... + + + + 1 + 2 + 4 + 5 + + + + + + True + 0 + _ID: + True + gid + + + 2 + 3 + GTK_FILL + + + + + + True + 12 + + + True + True + A unique ID to identify the source + + + + 0 + + + + + True + True + True + none + + + True + gramps-unlock + + + + + False + False + 1 + + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + True + True + Title of the source. + + + + 1 + 2 + + + + + + False + + + + + True + + + gtk-file + 1 + + + 0 + + + + + True + <b>General</b> + True + center + + + False + False + 1 + + + + + False + + + + + + + True + <b>Shared source information</b> + True + + + + + 3 + + + + + True + end + + + gtk-help + True + True + True + True + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + True + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + True + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + help + cancel + ok + + + + + + + + + diff --git a/src/gui/editors/Makefile.am b/src/gui/editors/Makefile.am index 9dfb1d2dc..2a79e2b6e 100644 --- a/src/gui/editors/Makefile.am +++ b/src/gui/editors/Makefile.am @@ -14,6 +14,7 @@ pkgdata_PYTHON = \ editaddress.py \ editattribute.py \ editchildref.py \ + editcitation.py \ editevent.py \ editeventref.py \ editfamily.py \ diff --git a/src/gui/editors/__init__.py b/src/gui/editors/__init__.py index 9904dea7a..9a94d7c8d 100644 --- a/src/gui/editors/__init__.py +++ b/src/gui/editors/__init__.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,6 +22,7 @@ from editaddress import EditAddress from editattribute import EditAttribute, EditFamilyAttribute from editchildref import EditChildRef +from editcitation import EditCitation, DeleteCitationQuery from editevent import EditEvent, DeleteEventQuery from editeventref import EditEventRef, EditFamilyEventRef from editfamily import EditFamily @@ -47,6 +49,7 @@ EDITORS = { 'Family': EditFamily, 'Media': EditMedia, 'Source': EditSource, + 'Citation': EditCitation, 'Place': EditPlace, 'Repository': EditRepository, 'Note': EditNote, diff --git a/src/gui/editors/displaytabs/Makefile.am b/src/gui/editors/displaytabs/Makefile.am index 615a008b1..c01ccd1e6 100644 --- a/src/gui/editors/displaytabs/Makefile.am +++ b/src/gui/editors/displaytabs/Makefile.am @@ -11,6 +11,9 @@ pkgdata_PYTHON = \ backrefmodel.py \ buttontab.py \ childmodel.py \ + citationbackreflist.py \ + citationenbededlist.py \ + citationrefmodel.py \ dataembedlist.py \ datamodel.py \ embeddedlist.py \ diff --git a/src/gui/editors/displaytabs/__init__.py b/src/gui/editors/displaytabs/__init__.py index 119114911..9b2c02047 100644 --- a/src/gui/editors/displaytabs/__init__.py +++ b/src/gui/editors/displaytabs/__init__.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,6 +51,8 @@ from mediabackreflist import MediaBackRefList from nameembedlist import NameEmbedList from notebackreflist import NoteBackRefList from notetab import NoteTab +from citationbackreflist import CitationBackRefList +from citationembedlist import CitationEmbedList from personeventembedlist import PersonEventEmbedList from personrefembedlist import PersonRefEmbedList from personbackreflist import PersonBackRefList diff --git a/src/gui/editors/displaytabs/backrefmodel.py b/src/gui/editors/displaytabs/backrefmodel.py index 0efd1b77e..8adc510d7 100644 --- a/src/gui/editors/displaytabs/backrefmodel.py +++ b/src/gui/editors/displaytabs/backrefmodel.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -91,6 +92,13 @@ class BackRefModel(gtk.ListStore): gid = p.gramps_id handle = p.handle name = p.get_title() + elif dtype == 'Citation': + p = self.db.get_citation_from_handle(ref[1]) + if not p: + continue + gid = p.gramps_id + handle = p.handle + name = p.get_page() elif dtype == 'Event': p = self.db.get_event_from_handle(ref[1]) if not p: diff --git a/src/gui/editors/displaytabs/citationbackreflist.py b/src/gui/editors/displaytabs/citationbackreflist.py new file mode 100644 index 000000000..74acdcf2c --- /dev/null +++ b/src/gui/editors/displaytabs/citationbackreflist.py @@ -0,0 +1,39 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: sourcebackreflist.py 13821 2009-12-16 06:11:06Z pez4brian $ + +#------------------------------------------------------------------------- +# +# GRAMPS classes +# +#------------------------------------------------------------------------- +from backrefmodel import BackRefModel +from backreflist import BackRefList + +class CitationBackRefList(BackRefList): + + def __init__(self, dbstate, uistate, track, obj, callback=None): + BackRefList.__init__(self, dbstate, uistate, track, obj, + BackRefModel, callback=callback) + + def get_icon_name(self): + return 'gramps-citation' diff --git a/src/gui/editors/displaytabs/citationembedlist.py b/src/gui/editors/displaytabs/citationembedlist.py new file mode 100644 index 000000000..d76eece4d --- /dev/null +++ b/src/gui/editors/displaytabs/citationembedlist.py @@ -0,0 +1,220 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# +# 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: notetab.py 14091 2010-01-18 04:42:17Z pez4brian $ + +#------------------------------------------------------------------------- +# +# Python classes +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# +# GRAMPS classes +# +#------------------------------------------------------------------------- +import Errors +import gen.lib +from gui.dbguielement import DbGUIElement +from gui.selectors import SelectorFactory +from citationrefmodel import CitationRefModel +from embeddedlist import EmbeddedList +from DdTargets import DdTargets +from gen.lib.refbase import RefBase + +#------------------------------------------------------------------------- +# +# CitationEmbedList +# +#------------------------------------------------------------------------- +class CitationEmbedList(EmbeddedList, DbGUIElement): + """ + Citation List display tab for edit dialogs. + + Derives from the EmbeddedList class. + """ + + _HANDLE_COL = 4 # Column number from CitationRefModel + _DND_TYPE = DdTargets.NOTE_LINK + + _MSG = { + 'add' : _('Create and add a new citation'), + 'del' : _('Remove the existing citation'), + 'edit' : _('Edit the selected citation'), + 'share' : _('Add an existing citation'), + 'up' : _('Move the selected citation upwards'), + 'down' : _('Move the selected citation downwards'), + } + + #index = column in model. Value = + # (name, sortcol in model, width, markup/text, weigth_col + _column_names = [ + (_('Title'), 0, 200, 0, -1), + (_('Author'), 1, 125, 0, -1), + (_('Page'), 2, 100, 0, -1), + (_('ID'), 3, 75, 0, -1), + ] + + def __init__(self, dbstate, uistate, track, data, callertitle=None): + self.data = data + self.callertitle = callertitle + EmbeddedList.__init__(self, dbstate, uistate, track, _("_Citations"), + CitationRefModel, share_button=True, + move_buttons=True) + DbGUIElement.__init__(self, dbstate.db) + self.callman.register_handles({'citation': self.data}) + + def _connect_db_signals(self): + """ + Implement base class DbGUIElement method + """ + #citation: citation-rebuild closes the editors, so no need to connect to it + self.callman.register_callbacks( + {'citation-delete': self.citation_delete, # delete a citation we track + 'citation-update': self.citation_update, # change a citation we track + }) + self.callman.connect_all(keys=['citation']) + + def get_icon_name(self): + """ + Return the stock-id icon name associated with the display tab + """ + return 'gramps-citations' + + def get_data(self): + """ + Return the data associated with display tab + """ + return self.data + + def column_order(self): + """ + Return the column order of the columns in the display tab. + """ + return ((1, 0), (1, 1), (1, 2), (1, 3)) + + def add_button_clicked(self, obj): + """ + Create a new Citation instance and call the EditCitation editor with the 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. + """ + citation = gen.lib.Citation() + try: + from gui.editors import EditCitation + EditCitation(self.dbstate, self.uistate, self.track, + gen.lib.Citation(), gen.lib.Source(), + self.add_callback, self.callertitle) + except Errors.WindowActiveError: + pass + + def add_callback(self, value): + """ + Called to update the screen when a new citation is added + """ + self.get_data().append(value) + self.callman.register_handles({'citation': [value]}) + self.changed = True + self.rebuild() + + def share_button_clicked(self, obj): + SelectCitation = SelectorFactory('Citation') + + sel = SelectCitation(self.dbstate, self.uistate, self.track) + citation = sel.run() + LOG.debug("selected citation: %s" % citation) + if citation: + try: + source = self.dbstate.db.get_source_from_handle(citation.ref) + from gui.editors import EditCitation + EditCitation(self.dbstate, self.uistate, self.track, + citation, source, self.add_callback, + self.callertitle) + except Errors.WindowActiveError: + from QuestionDialog import WarningDialog + WarningDialog(_("Cannot share this reference"), + self.__blocked_text()) + + def edit_button_clicked(self, obj): + """ + Get the selected Citation instance and call the EditCitation editor with the + citation. + + Called when the Edit 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. + """ + 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) + source = self.dbstate.db.get_source_from_handle(citation.ref) + try: + from gui.editors import EditCitation + EditCitation(self.dbstate, self.uistate, self.track, citation, + source, callertitle = self.callertitle) + except Errors.WindowActiveError: + pass + + def citation_delete(self, del_citation_handle_list): + """ + Outside of this tab citation objects have been deleted. Check if tab + and object must be changed. + Note: delete of object will cause reference on database to be removed, + so this method need not do this + """ + rebuild = False + for handle in del_citation_handle_list : + while self.data.count(handle) > 0: + self.data.remove(handle) + rebuild = True + if rebuild: + self.rebuild() + + def citation_update(self, upd_citation_handle_list): + """ + Outside of this tab citation objects have been updated. Check if tab + and object must be updated. + """ + for handle in upd_citation_handle_list : + if handle in self.data: + self.rebuild() + break + +# FIXME: Are these functions needed for citations? +# def get_editor(self): +# pass +# +# def get_user_values(self): +# return [] diff --git a/src/gui/editors/displaytabs/citationrefmodel.py b/src/gui/editors/displaytabs/citationrefmodel.py new file mode 100644 index 000000000..ee2e2cbcb --- /dev/null +++ b/src/gui/editors/displaytabs/citationrefmodel.py @@ -0,0 +1,44 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: notemodel.py 15597 2010-06-28 07:43:41Z ldnp $ + +#------------------------------------------------------------------------- +# +# GTK libraries +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# CitationModel +# +#------------------------------------------------------------------------- +class CitationRefModel(gtk.ListStore): + + def __init__(self, citation_list, db): + gtk.ListStore.__init__(self, str, str, str, str, object) + self.db = db + for handle in citation_list: + citation = self.db.get_citation_from_handle(handle) + src = self.db.get_source_from_handle(citation.get_reference_handle()) + self.append(row=[src.title, src.author, citation.page, + citation.gramps_id, handle, ]) diff --git a/src/gui/editors/editcitation.py b/src/gui/editors/editcitation.py new file mode 100644 index 000000000..127603953 --- /dev/null +++ b/src/gui/editors/editcitation.py @@ -0,0 +1,438 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: editsource.py 16680 2011-02-20 10:52:06Z bmcage $ + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +import gen.lib +from gen.db import DbTxn +from editprimary import EditPrimary + +from displaytabs import (NoteTab, GalleryTab, DataEmbedList, + SourceBackRefList, RepoEmbedList) +from gui.widgets import (MonitoredEntry, PrivacyButton, MonitoredMenu, + MonitoredDate) +from QuestionDialog import ErrorDialog +from editreference import RefTab +from glade import Glade + +#------------------------------------------------------------------------- +# +# EditCitationclass +# +#------------------------------------------------------------------------- + +class EditCitation(EditPrimary): + + def __init__(self, dbstate, uistate, track, obj, source, callback=None, + callertitle = None): + """ + Create an EditCitation window. Associate a citation with the window. + + This class is called both to edit the Citation Primary object + and to edit references from other objects to citation. + It is called from gui.editors.__init__ for editing the primary object + and is called from CitationEmbedList for editing references + + @param callertitle: Text passed by calling object to add to title + @type callertitle: str + """ + self.source = source + self.callertitle = callertitle + EditPrimary.__init__(self, dbstate, uistate, track, obj, + dbstate.db.get_citation_from_handle, + dbstate.db.get_citation_from_gramps_id, callback) + + def empty_object(self): + """ + Return an empty Citation object for comparison for changes. + + It is used by the base class L{EditPrimary}. + """ + return gen.lib.Citation() + + def get_menu_title(self): + title = self.obj.get_page() + if title: + if self.callertitle: + title = _('Citation') + \ + (': %(id)s - %(context)s' % { + 'id' : title, + 'context' : self.callertitle + }) + else: + title = _('Citation') + ": " + title + else: + if self.callertitle: + title = _('New Citation') + \ + (': %(id)s - %(context)s' % { + 'id' : title, + 'context' : self.callertitle + }) + else: + title = _('New Citation') + return title + + # FIXME: There will have to be two warnings, + # one because Source may be shared and one because Citation may be shared. + # These three functions are normally inherited from editreference, + # but have to be defined here because this class inherits from + # EditPrimary instead + def define_warn_box(self,box): + self.warn_box = box + + def enable_warnbox(self): + self.warn_box.show() + + def define_expander(self,expander): + expander.set_expanded(True) + + def _local_init(self): + """Local initialization function. + + Perform basic initialization, including setting up widgets + and the glade interface. It is called by the base class L{EditPrimary}, + and overridden here. + + """ + self.width_key = 'interface.source-width' + self.height_key = 'interface.source-height' + assert(self.obj) + + self.glade = Glade() + self.set_window(self.glade.toplevel, None, + self.get_menu_title()) + + self.define_warn_box(self.glade.get_object("warn_box")) + self.define_expander(self.glade.get_object("src_expander")) + + tblref = self.glade.get_object('table67') + notebook = self.glade.get_object('notebook_ref') + #recreate start page as GrampsTab + notebook.remove_page(0) + self.reftab = RefTab(self.dbstate, self.uistate, self.track, + _('General'), tblref) + tblref = self.glade.get_object('table68') + notebook = self.glade.get_object('notebook_src') + #recreate start page as GrampsTab + notebook.remove_page(0) + self.primtab = RefTab(self.dbstate, self.uistate, self.track, + _('General'), tblref) + + + def _connect_signals(self): + """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')) + self.define_help_button(self.glade.get_object('help')) + + def _connect_db_signals(self): + """ + Connect any signals that need to be connected. + Called by the init routine of the base class (_EditPrimary). + """ + # 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) + self._add_db_signal('citation-delete', self.check_for_close) + + def _setup_fields(self): + """Get control widgets and attach them to Citation's attributes.""" + + # Populate the Citation section + + self.date = MonitoredDate( + self.glade.get_object("date_entry"), + self.glade.get_object("date_stat"), + self.obj.get_date_object(), + self.uistate, + self.track, + self.db.readonly) + + # FIXME: This needs to be changed to reflect the correct names + # and uncommented when src/glade/editcitation.glade + # has been amended to include an id in the Citation section. +# self.gid = MonitoredEntry( +# self.glade.get_object('gid'), self.obj.set_gramps_id, +# self.obj.get_gramps_id,self.db.readonly) + + self.volume = MonitoredEntry( + self.glade.get_object("volume"), self.obj.set_page, + self.obj.get_page, self.db.readonly) + + self.type_mon = MonitoredMenu( + self.glade.get_object('confidence'), + self.obj.set_confidence_level, + self.obj.get_confidence_level, [ + (_('Very Low'), gen.lib.Citation.CONF_VERY_LOW), + (_('Low'), gen.lib.Citation.CONF_LOW), + (_('Normal'), gen.lib.Citation.CONF_NORMAL), + (_('High'), gen.lib.Citation.CONF_HIGH), + (_('Very High'), gen.lib.Citation.CONF_VERY_HIGH)], + self.db.readonly) + + self.ref_privacy = PrivacyButton( + self.glade.get_object('privacy'), self.obj, self.db.readonly) + + # Populate the Source section + + self.title = MonitoredEntry( + self.glade.get_object('title'), + self.source.set_title, + self.source.get_title, + self.db.readonly) + + self.author = MonitoredEntry( + self.glade.get_object('author'), self.source.set_author, + self.source.get_author,self.db.readonly) + + self.gid = MonitoredEntry( + self.glade.get_object('gid'), self.source.set_gramps_id, + self.source.get_gramps_id,self.db.readonly) + + self.source_privacy = PrivacyButton( + self.glade.get_object("private"), + self.obj, self.db.readonly) + + self.abbrev = MonitoredEntry( + self.glade.get_object('abbrev'), self.source.set_abbreviation, + self.source.get_abbreviation,self.db.readonly) + + self.pubinfo = MonitoredEntry( + self.glade.get_object('pub_info'), self.source.set_publication_info, + self.source.get_publication_info,self.db.readonly) + + def _create_tabbed_pages(self): + """ + Create the notebook tabs and inserts them into the main + window. + """ + # create notebook tabs for Citation + + notebook_ref = self.glade.get_object('notebook_ref') + self._add_tab(notebook_ref, self.reftab) + + self.comment_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.obj.get_note_list(), self.get_menu_title(), + notetype=gen.lib.NoteType.SOURCEREF) + self._add_tab(notebook_ref, self.comment_tab) + self.track_ref_for_deletion("comment_tab") + + self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track, + self.obj.get_media_list()) + self._add_tab(notebook_ref, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.data_tab = DataEmbedList(self.dbstate, self.uistate, self.track, + self.obj) + self._add_tab(notebook_ref, self.data_tab) + self.track_ref_for_deletion("data_tab") + + # FIXME: This needs to enable the shared Citation warning box + self.citationref_list = SourceBackRefList(self.dbstate,self.uistate, self.track, + self.db.find_backlink_handles(self.obj.handle), + self.enable_warnbox + ) + self._add_tab(notebook_ref, self.citationref_list) + self.track_ref_for_deletion("citationref_list") + + # Create notebook tabs for Source + + notebook_src = self.glade.get_object('notebook_src') + + self._add_tab(notebook_src, self.primtab) + + self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, + self.source.get_note_list(), self.get_menu_title(), + notetype=gen.lib.NoteType.SOURCE) + self._add_tab(notebook_src, self.note_tab) + self.track_ref_for_deletion("note_tab") + + self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track, + self.source.get_media_list()) + self._add_tab(notebook_src, self.gallery_tab) + self.track_ref_for_deletion("gallery_tab") + + self.data_tab = DataEmbedList(self.dbstate, self.uistate, self.track, + self.source) + self._add_tab(notebook_src, self.data_tab) + self.track_ref_for_deletion("data_tab") + + self.repo_tab = RepoEmbedList(self.dbstate, self.uistate, self.track, + self.source.get_reporef_list()) + self._add_tab(notebook_src, self.repo_tab) + self.track_ref_for_deletion("repo_tab") + + # FIXME: + # SourceBackrefList inherits from BackrefList inherits from EmbeddedList + # inherits from ButtonTab + # _create_buttons is defined in ButtonTab, and overridden in BackRefList. + # But needs to be overriden here so that there is no edit button for + # References to Source, because they will all be citations, + # and the Citations will be displayed in the top part of the + # edit dialogue. + self.srcref_list = SourceBackRefList(self.dbstate,self.uistate, self.track, + self.db.find_backlink_handles(self.source.handle), + self.enable_warnbox + ) + self._add_tab(notebook_src, self.srcref_list) + self.track_ref_for_deletion("srcref_list") + + self._setup_notebook_tabs(notebook_src) + self._setup_notebook_tabs(notebook_ref) + + def build_menu_names(self, source): + """ + Provide the information needed by the base class to define the + window management menu entries. + """ + return (_('Edit Citation'), self.get_menu_title()) + + def save(self, *obj): + """Save the data.""" + self.ok_button.set_sensitive(False) + if self.object_is_empty(): + ErrorDialog(_("Cannot save citation"), + _("No data exists for this citation. Please " + "enter data or cancel the edit.")) + self.ok_button.set_sensitive(True) + return + + (uses_dupe_id, id) = self._uses_duplicate_id() + if uses_dupe_id: + prim_object = self.get_from_gramps_id(id) + name = prim_object.get_page() + msg1 = _("Cannot save citation. ID already exists.") + msg2 = _("You have attempted to use the existing Gramps ID with " + "value %(id)s. This value is already used by '" + "%(prim_object)s'. Please enter a different ID or leave " + "blank to get the next available ID value.") % { + 'id' : id, 'prim_object' : name } + ErrorDialog(msg1, msg2) + self.ok_button.set_sensitive(True) + return + + with DbTxn('', self.db) as trans: + # First commit the Source Primary object + if not self.source.get_handle(): + self.db.add_source(self.source, trans) + msg = _("Add Source (%s)") % self.source.get_title() + else: + if not self.source.get_gramps_id(): + self.source.set_gramps_id(self.db.find_next_source_gramps_id()) + self.db.commit_source(self.source, trans) + msg = _("Edit Source (%s)") % self.source.get_title() + + self.obj.ref = self.source.handle + + # Now commit the Citation Primary object + if not self.obj.get_handle(): + self.db.add_citation(self.obj, trans) + msg += _(" " + "Add Citation (%s)") % self.obj.get_page() + else: + if not self.obj.get_gramps_id(): + self.obj.set_gramps_id(self.db.find_next_citation_gramps_id()) + self.db.commit_citation(self.obj, trans) + msg += _("\n" + "Edit Citation (%s)") % self.obj.get_page() + trans.set_description(msg) + LOG.debug(msg) + + if self.callback: + self.callback(self.obj.get_handle()) + self.close() + +class DeleteCitationQuery(object): + def __init__(self, dbstate, uistate, citation, the_lists): + self.citation = citation + self.db = dbstate.db + self.uistate = uistate + self.the_lists = the_lists + + def query_response(self): + with DbTxn(_("Delete Citation (%s)") % self.citation.get_page(), + self.db) as trans: + self.db.disable_signals() + + (person_list, family_list, event_list, place_list, source_list, + media_list, repo_list) = self.the_lists + + ctn_handle_list = [self.citation.get_handle()] + + for handle in person_list: + person = self.db.get_person_from_handle(handle) + person.remove_citation_references(ctn_handle_list) + self.db.commit_person(person, trans) + + for handle in family_list: + family = self.db.get_family_from_handle(handle) + family.remove_citation_references(ctn_handle_list) + self.db.commit_family(family, trans) + + for handle in event_list: + event = self.db.get_event_from_handle(handle) + event.remove_citation_references(ctn_handle_list) + self.db.commit_event(event, trans) + + for handle in place_list: + place = self.db.get_place_from_handle(handle) + place.remove_citation_references(ctn_handle_list) + self.db.commit_place(place, trans) + + for handle in source_list: + source = self.db.get_source_from_handle(handle) + source.remove_citation_references(ctn_handle_list) + self.db.commit_source(source, trans) + + for handle in media_list: + media = self.db.get_object_from_handle(handle) + media.remove_citation_references(ctn_handle_list) + self.db.commit_media_object(media, trans) + + for handle in repo_list: + repo = self.db.get_repository_from_handle(handle) + repo.remove_citation_references(ctn_handle_list) + self.db.commit_repository(repo, trans) + + self.db.enable_signals() + self.db.remove_citation(self.citation.get_handle(), trans) diff --git a/src/gui/editors/editmedia.py b/src/gui/editors/editmedia.py index 752b26a6d..dd409989a 100644 --- a/src/gui/editors/editmedia.py +++ b/src/gui/editors/editmedia.py @@ -4,6 +4,7 @@ # Copyright (C) 2000-2006 Donald N. Allingham # Copyright (C) 2009 Gary Burton # Copyright (C) 2010 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,8 +51,8 @@ import Utils from editprimary import EditPrimary from gui.widgets import (MonitoredDate, MonitoredEntry, PrivacyButton, MonitoredTagList) -from displaytabs import (SourceEmbedList, AttrEmbedList, NoteTab, - MediaBackRefList) +from displaytabs import (SourceEmbedList, AttrEmbedList, NoteTab, + CitationEmbedList, MediaBackRefList) from addmedia import AddMediaObject from QuestionDialog import ErrorDialog from glade import Glade @@ -213,6 +214,14 @@ class EditMedia(EditPrimary): self._add_tab(notebook, self.note_tab) self.track_ref_for_deletion("note_tab") + self.citation_tab = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.citation_tab) + self.track_ref_for_deletion("citation_tab") + self.backref_tab = MediaBackRefList(self.dbstate, self.uistate, self.track, diff --git a/src/gui/editors/editsource.py b/src/gui/editors/editsource.py index 48d795c44..1c7ceb087 100644 --- a/src/gui/editors/editsource.py +++ b/src/gui/editors/editsource.py @@ -29,6 +29,7 @@ from gen.ggettext import gettext as _ import logging log = logging.getLogger(".") +LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- # @@ -220,11 +221,63 @@ class DeleteSrcQuery(object): with DbTxn(_("Delete Source (%s)") % self.source.get_title(), self.db) as trans: self.db.disable_signals() + + # we can have: + # object(CitationBase) -> Citation(RefBase) -> Source + # We first have to remove the (person_list, family_list, event_list, place_list, source_list, - media_list, repo_list) = self.the_lists + media_list, repo_list, citation_list, citation_referents_list) = self.the_lists + # (1) delete the references to the citation + for (citation_handle, refs) in citation_referents_list: + LOG.debug('delete citation %s references %s' % (citation_handle, refs)) + (person_list, family_list, event_list, place_list, source_list, + media_list, repo_list) = refs + +# for handle in person_list: +# person = self.db.get_person_from_handle(handle) +# person.remove_citation(citation_handle) +# self.db.commit_person(person, trans) +# +# for handle in family_list: +# family = self.db.get_family_from_handle(handle) +# family.remove_citation(citation_handle) +# self.db.commit_family(family, trans) +# +# for handle in event_list: +# event = self.db.get_event_from_handle(handle) +# event.remove_citation(citation_handle) +# self.db.commit_event(event, trans) +# +# for handle in place_list: +# place = self.db.get_place_from_handle(handle) +# place.remove_citation(citation_handle) +# self.db.commit_place(place, trans) +# +# for handle in source_list: +# source = self.db.get_source_from_handle(handle) +# source.remove_citation(citation_handle) +# self.db.commit_source(source, trans) +# + for handle in media_list: + media = self.db.get_object_from_handle(handle) + media.remove_citation(citation_handle) + self.db.commit_media_object(media, trans) + +# for handle in repo_list: +# repo = self.db.get_repository_from_handle(handle) +# repo.remove_citation(citation_handle) +# self.db.commit_repository(repo, trans) + + # (2) delete the actual citation + LOG.debug('remove the actual citations %s' % citation_list) + for citation_handle in citation_list: + self.db.remove_citation(citation_handle, trans) + + # (3) delete the references to the source src_handle_list = [self.source.get_handle()] + LOG.debug('remove the source references to %s' % src_handle_list) for handle in person_list: person = self.db.get_person_from_handle(handle) @@ -261,5 +314,9 @@ class DeleteSrcQuery(object): repo.remove_source_references(src_handle_list) self.db.commit_repository(repo, trans) +# FIXME: we need to remove all the citations that point to this source object, +# and before that, all the CitationBase pointers in all objects that point to +# this source. + self.db.enable_signals() self.db.remove_source(self.source.get_handle(), trans) diff --git a/src/gui/selectors/Makefile.am b/src/gui/selectors/Makefile.am index ef318b790..0c5c666ff 100644 --- a/src/gui/selectors/Makefile.am +++ b/src/gui/selectors/Makefile.am @@ -8,6 +8,7 @@ pkgdatadir = $(datadir)/@PACKAGE@/gui/selectors pkgdata_PYTHON = \ __init__.py \ baseselector.py \ + selectcitation.py \ selectevent.py \ selectfamily.py \ selectnote.py \ diff --git a/src/gui/selectors/selectcitation.py b/src/gui/selectors/selectcitation.py new file mode 100644 index 000000000..49b91fe58 --- /dev/null +++ b/src/gui/selectors/selectcitation.py @@ -0,0 +1,69 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2003-2006 Donald N. Allingham +# 2009 Gary Burton +# +# 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: selectsource.py 14091 2010-01-18 04:42:17Z pez4brian $ + +#------------------------------------------------------------------------- +# +# internationalization +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gui.views.treemodels import CitationTreeModel +from baseselector import BaseSelector + +#------------------------------------------------------------------------- +# +# SelectSource +# +#------------------------------------------------------------------------- +class SelectCitation(BaseSelector): + + def _local_init(self): + """ + Perform local initialisation for this class + """ + self.width_key = 'interface.source-sel-width' + self.height_key = 'interface.source-sel-height' + + def get_window_title(self): + return _("Select Source or Citation") + + def get_model_class(self): + return CitationTreeModel + + def get_column_titles(self): + return [ + (_('Page'), 350, BaseSelector.TEXT, 0), + (_('ID'), 75, BaseSelector.TEXT, 1) + ] + + def get_from_handle_func(self): + return self.db.get_citation_from_handle + + def get_handle_column(self): + return 11 diff --git a/src/gui/selectors/selectorfactory.py b/src/gui/selectors/selectorfactory.py index 875f09238..46e958569 100644 --- a/src/gui/selectors/selectorfactory.py +++ b/src/gui/selectors/selectorfactory.py @@ -38,6 +38,9 @@ def SelectorFactory(classname): elif classname == 'Source': from selectsource import SelectSource cls = SelectSource + elif classname == 'Citation': + from selectcitation import SelectCitation + cls = SelectCitation elif classname in ['MediaObject', 'Media']: from selectobject import SelectObject cls = SelectObject diff --git a/src/gui/views/treemodels/Makefile.am b/src/gui/views/treemodels/Makefile.am index 8499b308c..0dc601d5c 100644 --- a/src/gui/views/treemodels/Makefile.am +++ b/src/gui/views/treemodels/Makefile.am @@ -16,6 +16,7 @@ pkgdata_PYTHON = \ placemodel.py \ repomodel.py \ sourcemodel.py \ + citationmodel.py \ treebasemodel.py pkgpyexecdir = @pkgpyexecdir@/gui/views/treemodels diff --git a/src/gui/views/treemodels/__init__.py b/src/gui/views/treemodels/__init__.py index e7c1faafb..967c69cda 100644 --- a/src/gui/views/treemodels/__init__.py +++ b/src/gui/views/treemodels/__init__.py @@ -1,6 +1,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2009 Benny Malengier +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -31,3 +32,4 @@ from placemodel import PlaceBaseModel, PlaceListModel, PlaceTreeModel from mediamodel import MediaModel from repomodel import RepositoryModel from notemodel import NoteModel +from citationmodel import CitationBaseModel, CitationListModel, CitationTreeModel diff --git a/src/gui/views/treemodels/citationmodel.py b/src/gui/views/treemodels/citationmodel.py new file mode 100644 index 000000000..2d4cdd7c1 --- /dev/null +++ b/src/gui/views/treemodels/citationmodel.py @@ -0,0 +1,317 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# $Id:_SourceModel.py 9912 2008-01-22 09:17:46Z acraphae $ + +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import cgi +import logging +log = logging.getLogger(".") +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GNOME/GTK modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import const +import ToolTips +import DateHandler +import gen.lib +from Utils import confidence, format_time +import config +from gui.views.treemodels.flatbasemodel import FlatBaseModel +from gui.views.treemodels.treebasemodel import TreeBaseModel + +#------------------------------------------------------------------------- +# +# COLUMN constants +# +#------------------------------------------------------------------------- +# These are the column numbers in the serialize/unserialize interfaces in +# the Citation object +COLUMN_HANDLE = 0 +COLUMN_ID = 1 +COLUMN_DATE = 2 +COLUMN_PAGE = 3 +COLUMN_CONFIDENCE = 4 +COLUMN_SOURCE = 5 +COLUMN_CHANGE = 9 + +INVALID_DATE_FORMAT = config.get('preferences.invalid-date-format') + +#------------------------------------------------------------------------- +# +# CitationModel +# +#------------------------------------------------------------------------- +class CitationBaseModel(object): + + def __init__(self,db): + self.map = db.get_raw_citation_data + self.gen_cursor = db.get_citation_cursor + self.fmap = [ + self.column_page, + self.column_id, + self.column_date, + self.column_confidence, + self.column_change, + self.column_src_title, + self.column_src_id, + self.column_src_auth, + self.column_src_abbr, + self.column_src_pinfo, + self.column_src_chan, + self.column_handle, + self.column_tooltip + ] + self.smap = [ + self.column_page, + self.column_id, + self.column_date, + self.column_confidence, + self.sort_change, + self.column_src_title, + self.column_src_id, + self.column_src_auth, + self.column_src_abbr, + self.column_src_pinfo, + self.column_src_chan, + self.column_handle, + self.column_tooltip + ] + + 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 on_get_n_columns(self): + return len(self.fmap)+1 + + def column_date(self,data): + if data[COLUMN_DATE]: + citation = gen.lib.Citation() + citation.unserialize(data) + date_str = DateHandler.get_date(citation) + if date_str != "": + retval = cgi.escape(date_str) + if not DateHandler.get_date_valid(citation): + return INVALID_DATE_FORMAT % retval + else: + return retval + return u'' + + def column_id(self,data): + return unicode(data[COLUMN_ID]) + + def column_page(self,data): + return unicode(data[COLUMN_PAGE]) + + def column_confidence(self,data): + return unicode(confidence[data[COLUMN_CONFIDENCE]]) + + def column_handle(self,data): + return unicode(data[COLUMN_HANDLE]) + + def column_change(self,data): + return format_time(data[COLUMN_CHANGE]) + + def sort_change(self,data): + return "%012x" % data[8] + + def column_src_title(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return unicode(source.get_title()) + except: + return u'' + + def column_src_id(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return unicode(source.gramps_id) + except: + return u'' + + def column_src_auth(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return unicode(source.get_author()) + except: + return u'' + + def column_src_abbr(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return unicode(source.get_abbreviation()) + except: + return u'' + + def column_src_pinfo(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return unicode(source.get_publication_info()) + except: + return u'' + + def column_src_chan(self,data): + source_handle = data[COLUMN_SOURCE] + try: + source = self.db.get_source_from_handle(source_handle) + return format_time(source.change) + except: + return u'' + + def column_tooltip(self,data): + if const.USE_TIPS: + try: + t = ToolTips.TipFromFunction(self.db, lambda: + self.db.get_citation_from_handle(data[0])) + except: + log.error("Failed to create tooltip.",exc_info=True) + return t + else: + return u'' + +#------------------------------------------------------------------------- +# +# CitationListModel +# +#------------------------------------------------------------------------- +class CitationListModel(CitationBaseModel, FlatBaseModel): + """ + Flat citation model. (Original code in CitationBaseModel). + """ + def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None, + skip=set(), sort_map=None): + + CitationBaseModel.__init__(self, db) + FlatBaseModel.__init__(self, db, scol, order, tooltip_column=12, + search=search, skip=skip, sort_map=sort_map) + + def destroy(self): + """ + Unset all elements that can prevent garbage collection + """ + CitationBaseModel.destroy(self) + FlatBaseModel.destroy(self) + +#------------------------------------------------------------------------- +# +# CitationTreeModel +# +#------------------------------------------------------------------------- +class CitationTreeModel(CitationBaseModel, TreeBaseModel): + """ + Hierarchical citation model. + """ + def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None, + skip=set(), sort_map=None): + + CitationBaseModel.__init__(self, db) + TreeBaseModel.__init__(self, db, scol=scol, order=order, + tooltip_column=12, + search=search, skip=skip, sort_map=sort_map, + nrgroups = 1, + group_can_have_handle = True) + + 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 +# Can't call FlatBaseModel.destroy(self), because it fails when a treemodel +# is passed, so can't just do: +# CitationBaseModel.destroy(self) + self.hmap = None + self.number_items = None + TreeBaseModel.destroy(self) + + def _set_base_data(self): + """See TreeBaseModel, for place, most have been set in init of + CitationBaseModel + """ + self.number_items = self.db.get_number_of_citations + # FIXME: What should the number in the next line be? + # FIXME: Only the first element of hmap ever seems to be populated. + # Why is this, and is it correct? + self.hmap = [self.column_header] + [None]*14 + + def get_tree_levels(self): + """ + Return the headings of the levels in the hierarchy. + """ + return [_('Source'), _('Citation')] + + def add_row(self, handle, data): + """ + Add nodes to the node map for a single citation. + + handle The handle of the gramps object. + data The object data. + """ + source_handle = data[COLUMN_SOURCE] + if source_handle: + source = self.db.get_source_from_handle(source_handle) + if source: + source_name = source.get_title() + sort_key = self.sort_func(data) + # add as node: parent, child, sortkey, handle; parent and child are + # nodes in the treebasemodel, and will be used as iters + self.add_node(source_name, handle, sort_key, handle) + else: + log.warn("Citation %s still has a pointer (handle %s) to a deleted source" % + (data[COLUMN_ID], source_handle)) + else: + log.warn("Citation %s does not have a source" % unicode(data[COLUMN_PAGE]), + exc_info=True) + + def column_header(self, node): + """ + Return a column heading. This is called for nodes with no associated + Gramps handle. + """ + return node.name diff --git a/src/plugins/gramplet/Backlinks.py b/src/plugins/gramplet/Backlinks.py index f2b55a80a..80f9f8b24 100644 --- a/src/plugins/gramplet/Backlinks.py +++ b/src/plugins/gramplet/Backlinks.py @@ -1,6 +1,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2011 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -172,6 +173,27 @@ class SourceBacklinks(Backlinks): else: self.set_has_data(False) +class CitationBacklinks(Backlinks): + """ + Displays the back references for a Citation,. + """ + def db_changed(self): + self.dbstate.db.connect('citation-update', self.update) + self.connect_signal('Citation', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Citation') + self.set_has_data(self.get_has_data(active_handle)) + + def main(self): + active_handle = self.get_active('Citation') + self.model.clear() + if active_handle: + self.display_backlinks(active_handle) + else: + self.set_has_data(False) + class RepositoryBacklinks(Backlinks): """ Displays the back references for a repository. diff --git a/src/plugins/gramplet/Filter.py b/src/plugins/gramplet/Filter.py index 81bb9e07d..f9a672cb7 100644 --- a/src/plugins/gramplet/Filter.py +++ b/src/plugins/gramplet/Filter.py @@ -2,6 +2,7 @@ # # Copyright (C) 2010 Doug Blank # Copyright (C) 2011 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +29,7 @@ from gen.plug import Gramplet from Filters.SideBar import (PersonSidebarFilter, FamilySidebarFilter, EventSidebarFilter, SourceSidebarFilter, + CitationSidebarFilter, PlaceSidebarFilter, MediaSidebarFilter, RepoSidebarFilter, NoteSidebarFilter) @@ -101,6 +103,17 @@ class SourceFilter(Filter): """ FILTER_CLASS = SourceSidebarFilter +#------------------------------------------------------------------------- +# +# CitationFilter class +# +#------------------------------------------------------------------------- +class CitationFilter(Filter): + """ + A gramplet providing a Citation Filter. + """ + FILTER_CLASS = CitationSidebarFilter + #------------------------------------------------------------------------- # # PlaceFilter class diff --git a/src/plugins/gramplet/Gallery.py b/src/plugins/gramplet/Gallery.py index 5f7619de5..849923315 100644 --- a/src/plugins/gramplet/Gallery.py +++ b/src/plugins/gramplet/Gallery.py @@ -206,3 +206,27 @@ class SourceGallery(Gallery): else: self.set_has_data(False) +class CitationGallery(Gallery): + """ + Displays a gallery of media objects for a Citation. + """ + def db_changed(self): + self.dbstate.db.connect('event-update', self.update) + self.connect_signal('Citation', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Citation') + active = self.dbstate.db.get_citation_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Citation') + active = self.dbstate.db.get_citation_from_handle(active_handle) + + self.clear_images() + if active: + self.load_images(active) + else: + self.set_has_data(False) + diff --git a/src/plugins/gramplet/Notes.py b/src/plugins/gramplet/Notes.py index d4023e974..5265af396 100644 --- a/src/plugins/gramplet/Notes.py +++ b/src/plugins/gramplet/Notes.py @@ -1,6 +1,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2011 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -248,6 +249,29 @@ class SourceNotes(Notes): self.clear_text() self.set_has_data(False) +class CitationNotes(Notes): + """ + Displays the notes for a Citation. + """ + def db_changed(self): + self.dbstate.db.connect('citation-update', self.update) + self.connect_signal('Citation', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Citation') + active = self.dbstate.db.get_citation_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Citation') + active = self.dbstate.db.get_citation_from_handle(active_handle) + if active: + self.get_notes(active) + else: + self.clear_text() + self.set_has_data(False) + class RepositoryNotes(Notes): """ Displays the notes for a repository. diff --git a/src/plugins/gramplet/bottombar.gpr.py b/src/plugins/gramplet/bottombar.gpr.py index 77a47f1bc..a17808d3e 100644 --- a/src/plugins/gramplet/bottombar.gpr.py +++ b/src/plugins/gramplet/bottombar.gpr.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2011 Nick Hall +# Copyright (C) 2011 Tim G L Lyons # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -217,6 +218,20 @@ register(GRAMPLET, navtypes=["Source"], ) +register(GRAMPLET, + id="Citation Gallery", + name=_("Citation Gallery"), + description = _("Gramplet showing media objects for a citation"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Gallery.py", + height=200, + gramplet = 'CitationGallery', + gramplet_title=_("Gallery"), + navtypes=["Citation"], + ) + register(GRAMPLET, id="Person Attributes", name=_("Person Attributes"), @@ -343,6 +358,20 @@ register(GRAMPLET, navtypes=["Source"], ) +register(GRAMPLET, + id="Citation Notes", + name=_("Citation Notes"), + description = _("Gramplet showing the notes for a citation"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Notes.py", + height=200, + gramplet = 'CitationNotes', + gramplet_title=_("Notes"), + navtypes=["Citation"], + ) + register(GRAMPLET, id="Repository Notes", name=_("Repository Notes"), @@ -539,6 +568,20 @@ register(GRAMPLET, navtypes=["Source"], ) +register(GRAMPLET, + id="Citation Backlinks", + name=_("Citation Backlinks"), + description = _("Gramplet showing the backlinks for a citation"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Backlinks.py", + height=200, + gramplet = 'CitationBacklinks', + gramplet_title=_("References"), + navtypes=["Citation"], + ) + register(GRAMPLET, id="Repository Backlinks", name=_("Repository Backlinks"), @@ -637,6 +680,20 @@ register(GRAMPLET, navtypes=["Source"], ) +register(GRAMPLET, + id="Citation Filter", + name=_("Citation Filter"), + description = _("Gramplet providing a citation filter"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Filter.py", + height=200, + gramplet = 'CitationFilter', + gramplet_title=_("Filter"), + navtypes=["Citation"], + ) + register(GRAMPLET, id="Place Filter", name=_("Place Filter"), diff --git a/src/plugins/lib/libcitationview.py b/src/plugins/lib/libcitationview.py new file mode 100644 index 000000000..16d3bbbb3 --- /dev/null +++ b/src/plugins/lib/libcitationview.py @@ -0,0 +1,332 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: sourceview.py 17447 2011-05-07 18:42:31Z nick-h $ + +""" +Citation View +""" +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +import gen.lib +import config +from gui.views.listview import ListView +from gui.views.treemodels import CitationListModel +import Utils +import Bookmarks +import Errors +from DdTargets import DdTargets +from gui.selectors import SelectorFactory +from QuestionDialog import ErrorDialog +from gui.editors import EditCitation, DeleteCitationQuery +from Filters.SideBar import SourceSidebarFilter +from gen.plug import CATEGORY_QR_SOURCE + +#------------------------------------------------------------------------- +# +# internationalization +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + + +#------------------------------------------------------------------------- +# +# CitationView +# +#------------------------------------------------------------------------- +class BaseCitationView(ListView): + """ citation listview class + """ + # The data items here have to correspond, in order, to the items in + # src/giu.views/treemodels/citationmodel.py + COL_PAGE = 0 + COL_ID = 1 + COL_DATE = 2 + COL_CONFIDENCE = 3 + COL_CHAN = 4 + + COL_SRC_TITLE = 5 + COL_SRC_ID = 6 + COL_SRC_AUTH = 7 + COL_SRC_ABBR = 8 + COL_SRC_PINFO = 9 + COL_SRC_CHAN = 10 + # name of the columns + COLUMN_NAMES = [ + _('Volume/Page'), + _('ID'), + _('Date'), + _('Confidence'), + _('Last Changed'), + _('Source: Title'), + _('Source: ID'), + _('Source: Author'), + _('Source: Abbreviation'), + _('Source: Publication Information'), + _('Source: Last Changed'), + ] + # default setting with visible columns, order of the col, and their size + CONFIGSETTINGS = ( + ('columns.visible', [COL_SRC_TITLE, + COL_PAGE, COL_ID, COL_DATE, COL_CONFIDENCE]), + ('columns.rank', [COL_SRC_TITLE, COL_SRC_ID, COL_SRC_AUTH, + COL_SRC_ABBR, COL_SRC_PINFO, COL_SRC_CHAN, + COL_PAGE, COL_ID, COL_DATE, COL_CONFIDENCE, + COL_CHAN]), + ('columns.size', [200, 75, 150, + 100, 150, 100, + 75, 75, 100, 100, + 100]) + ) + ADD_MSG = _("Add a new citation") + EDIT_MSG = _("Edit the selected citation") + SHARE_MSG = _("Share the selected source") + DEL_MSG = _("Delete the selected citation") + MERGE_MSG = _("Merge the selected citations") + FILTER_TYPE = "Citation" + QR_CATEGORY = CATEGORY_QR_SOURCE + + def __init__(self, pdata, dbstate, uistate, title, model, nav_group=0): + + signal_map = { + 'citation-add' : self.row_add, + 'citation-update' : self.row_update, + 'citation-delete' : self.row_delete, + 'citation-rebuild' : self.object_build, + } + + ListView.__init__( + self, title, pdata, dbstate, uistate, + BaseCitationView.COLUMN_NAMES, len(BaseCitationView.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): + ListView.define_actions(self) + +# self._add_action('Share', gtk.STOCK_EDIT, _("Share..."), +# accel=None, +# tip=self.SHARE_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): + 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): + SelectSource = SelectorFactory('Source') + sel = SelectSource(self.dbstate,self.uistate) + source = sel.run() + 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()) + + 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): + for handle in self.selected_handles(): + citation = self.dbstate.db.get_citation_from_handle(handle) + try: + source = self.dbstate.db.get_source_from_handle(citation.ref) + EditCitation(self.dbstate, self.uistate, [], citation, source) + except Errors.WindowActiveError: + pass + except: + LOG.warn("failed to find a Source for the selected Citation") + + def __blocked_text(self): + """ + Return the common text used when mediaref cannot be edited + """ + return _("This media reference cannot be edited at this time. " + "Either the associated media object is already being " + "edited or another media reference that is associated with " + "the same media object is being edited.\n\nTo edit this " + "media reference, you need to close the media object.") + +# def share(self, obj): +# SelectSource = SelectorFactory('Source') +# sel = SelectSource(self.dbstate,self.uistate) +# source = sel.run() +# 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()) +# + 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: + 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. + """ + return (("Source Filter",), + ("Citation Gallery", + "Citation Notes", + "Citation Backlinks")) diff --git a/src/plugins/view/Makefile.am b/src/plugins/view/Makefile.am index bfa7b61ab..9f24089ec 100644 --- a/src/plugins/view/Makefile.am +++ b/src/plugins/view/Makefile.am @@ -6,6 +6,9 @@ pkgdatadir = $(datadir)/@PACKAGE@/plugins/view pkgdata_PYTHON = \ + citationlistview.py \ + citationtreeview.grp.py \ + citationtreeview.py \ eventview.py \ familyview.py \ fanchartview.gpr.py \ diff --git a/src/plugins/view/citationlistview.py b/src/plugins/view/citationlistview.py new file mode 100644 index 000000000..1cfbbd633 --- /dev/null +++ b/src/plugins/view/citationlistview.py @@ -0,0 +1,55 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: sourceview.py 17447 2011-05-07 18:42:31Z nick-h $ + +""" +Citation List View +""" + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from libcitationview import BaseCitationView +from gui.views.treemodels.citationmodel import CitationListModel + +#------------------------------------------------------------------------- +# +# internationalization +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + + +#------------------------------------------------------------------------- +# +# CitationView +# +#------------------------------------------------------------------------- +class CitationListView(BaseCitationView): + """ + A list view of citations. + """ + def __init__(self, pdata, dbstate, uistate, nav_group=0): + BaseCitationView.__init__(self, pdata, dbstate, uistate, + _('Citation View'), CitationListModel, + nav_group=nav_group) + diff --git a/src/plugins/view/citationtreeview.gpr.py b/src/plugins/view/citationtreeview.gpr.py new file mode 100644 index 000000000..26f05fe1f --- /dev/null +++ b/src/plugins/view/citationtreeview.gpr.py @@ -0,0 +1,14 @@ +register(VIEW, + id = 'citationtreeview', + name = _("Citation Tree View"), + description = _("A view displaying citations in a tree format."), + version = '1.0', + gramps_target_version = '3.4', + status = STABLE, + fname = 'citationtreeview.py', + authors = [u"Tim G L Lyons", u"Nick Hall"], + authors_email = [""], + category = ("Citations", _("Citations")), + viewclass = 'CitationTreeView', + stock_icon = 'gramps-tree-group', + ) diff --git a/src/plugins/view/citationtreeview.py b/src/plugins/view/citationtreeview.py new file mode 100644 index 000000000..5d28f5ecc --- /dev/null +++ b/src/plugins/view/citationtreeview.py @@ -0,0 +1,155 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: placetreeview.py 14176 2010-02-01 07:01:45Z bmcage $ + +""" +Citation Tree View +""" +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gui.views.listview import LISTTREE +from libcitationview import BaseCitationView +from gui.views.treemodels.citationmodel import CitationTreeModel +import gen.lib +import Errors +from gui.editors import EditCitation +from Utils import preset_name + +#------------------------------------------------------------------------- +# +# Internationalization +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# PlaceTreeView +# +#------------------------------------------------------------------------- +class CitationTreeView(BaseCitationView): + """ + A hierarchical view of the top three levels of places. + """ + def __init__(self, pdata, dbstate, uistate, nav_group=0): + + BaseCitationView.__init__(self, pdata, dbstate, uistate, + _('Citation Tree View'), CitationTreeModel, + nav_group=nav_group) + + def type_list(self): + """ + set the listtype, this governs eg keybinding + """ + return LISTTREE + + def get_viewtype_stock(self): + """ + Override the default icon. Set for hierarchical view. + """ + return 'gramps-tree-group' + + def define_actions(self): + """ + Define actions for the popup menu specific to the tree view. + """ + BaseCitationView.define_actions(self) + + self.all_action.add_actions([ + ('OpenAllNodes', None, _("Expand all Nodes"), None, None, + self.open_all_nodes), + ('CloseAllNodes', None, _("Collapse all Nodes"), None, None, + self.close_all_nodes), + ]) + + def additional_ui(self): + """ + Defines the UI string for UIManager + """ + return ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' + diff --git a/src/plugins/view/sourceview.py b/src/plugins/view/sourceview.py index 4d42ce542..7160b8871 100644 --- a/src/plugins/view/sourceview.py +++ b/src/plugins/view/sourceview.py @@ -30,6 +30,8 @@ Source View # #------------------------------------------------------------------------- import gtk +import logging +LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- # @@ -211,6 +213,37 @@ class SourceView(ListView): def remove_object_from_handle(self, handle): the_lists = Utils.get_source_referents(handle, self.dbstate.db) + LOG.debug('source referents %s' % [the_lists]) +# per = [] +# fam = [] +# eve = [] +# pla = [] +# sou = [] +# med = [] +# rep = [] + 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)] +# (per2 , fam2, eve2, pla2, sou2, med2, rep2) = refs +# per += per2 +# fam += fam2 +# eve += eve2 +# pla += pla2 +# sou += sou2 +# med += med2 +# rep += rep2 +# citation_referents_list = (per , fam, eve, pla, sou, med, rep) + 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) diff --git a/src/plugins/view/view.gpr.py b/src/plugins/view/view.gpr.py index d2976d8d2..07531fc78 100644 --- a/src/plugins/view/view.gpr.py +++ b/src/plugins/view/view.gpr.py @@ -211,3 +211,18 @@ category = ("Sources", _("Sources")), viewclass = 'SourceView', order = START, ) + +register(VIEW, +id = 'citationlistview', +name = _("Citation View"), +description = _("The view showing all the citations"), +version = '1.0', +gramps_target_version = '3.4', +status = STABLE, +fname = 'citationlistview.py', +authors = [u"The Gramps project"], +authors_email = ["http://gramps-project.org"], +category = ("Citations", _("Citations")), +viewclass = 'CitationListView', +order = START, + )