From 165625715da38c8ae4f3a1c084436b4be82a0188 Mon Sep 17 00:00:00 2001 From: Tim G L Lyons Date: Tue, 23 Aug 2011 16:54:03 +0000 Subject: [PATCH] Various enhancements: * Modified Event object to include references to Citations * Implemented database upgrade for Events (upgrade for Media had already been done) * Enhanced database upgrade to do backlinks * Modified Media object to remove sourceref * Implemented citations bottombar gramplet * Some minor fixes and improvements svn: r18054 --- src/gen/db/upgrade.py | 243 ++++++++-- src/gen/db/write.py | 8 + src/gen/lib/attribute.py | 24 +- src/gen/lib/citation.py | 135 +++--- src/gen/lib/citationbase.py | 12 +- src/gen/lib/event.py | 35 +- src/gen/lib/mediaobj.py | 50 +- src/gen/lib/mediaref.py | 29 +- src/gui/editors/editattribute.py | 7 +- src/gui/editors/editcitation.py | 17 +- src/gui/editors/editevent.py | 15 +- src/gui/editors/editeventref.py | 10 +- src/gui/editors/editmedia.py | 23 +- src/gui/editors/editmediaref.py | 14 +- src/gui/editors/editsource.py | 4 +- src/gui/views/listview.py | 1 + src/plugins/gramplet/Citations.py | 469 +++++++++++++++++++ src/plugins/gramplet/Makefile.am | 1 + src/plugins/gramplet/PopulateGramplet.gpr.py | 41 ++ src/plugins/gramplet/PopulateGramplet.py | 128 +++++ src/plugins/gramplet/bottombar.gpr.py | 70 +++ src/plugins/lib/libcitationview.py | 79 ++-- src/plugins/view/eventview.py | 2 +- src/plugins/view/mediaview.py | 2 +- 24 files changed, 1158 insertions(+), 261 deletions(-) create mode 100644 src/plugins/gramplet/Citations.py create mode 100644 src/plugins/gramplet/PopulateGramplet.gpr.py create mode 100644 src/plugins/gramplet/PopulateGramplet.py diff --git a/src/gen/db/upgrade.py b/src/gen/db/upgrade.py index fab989f92..4552c1494 100644 --- a/src/gen/db/upgrade.py +++ b/src/gen/db/upgrade.py @@ -36,14 +36,17 @@ if config.get('preferences.use-bsddb3'): from bsddb3 import db else: from bsddb import db -from gen.db import BSDDBTxn +from gen.db import BSDDBTxn, DbTxn from gen.lib.nameorigintype import NameOriginType from gen.db.write import _mkname, SURNAMES +num_citations = 0 + def gramps_upgrade_16(self): """Upgrade database from version 15 to 16. This upgrade converts all SourceRef child objects to Citation Primary objects. """ + global num_citations 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) + @@ -53,62 +56,220 @@ def gramps_upgrade_16(self): LOG.debug("self %s" % self) LOG.debug("self.find_next_citation_gramps_id %s" % self.find_next_citation_gramps_id) + t = time.time() + num_citations = 0 # --------------------------------- # 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") + with DbTxn(_("convert a media record"), self, batch=True, + no_magic=True) as transaction: + # FIXME: This should be a single transaction, so that + # either the whole of the media object is updated or none is + # but it doesn't seem to work like that because if + # update_refernce_map fails, the put of the new_media + # remains committed. + # (1) create each citation + # (2) update the Media to reference the Citations + # (3) remove backlinks for references from Media to Source + # (4) add backlinks for references from Media to Citations + # (5) add backlinks for references from Citation to Source (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_citation_list = convert_source_list_to_citation_list_16( + self, source_list, transaction) + new_attribute_list = upgrade_attribute_list_16( + self, attribute_list, transaction) + new_media = (handle, gramps_id, path, mime, desc, - attribute_list, source_list, note_list, change, - date, tag_list, new_citation_list, private) + new_attribute_list, new_citation_list, note_list, + change, date, tag_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) + txn.put(str(handle), new_media, txn=transaction) + + # (3) remove backlinks for references from Media to Source + # (4) add backlinks for references from Media to Citations + # (get_object is really get_MediaObject !) + LOG.debug(" update ref map media %s" % [handle, + self.get_object_from_handle(handle) ]) + with BSDDBTxn(self.env) as txn: + self.update_reference_map( + self.get_object_from_handle(handle), + transaction, + txn.txn) + 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]) + LOG.debug("Media upgrade %d citations upgraded in %d seconds" % + (num_citations, int(time.time()-t))) - # 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]) + # --------------------------------- + # Modify Events + # --------------------------------- + upgrade_time = 0 + backlink_time = 0 + for event_handle in self.event_map.keys(): + t1 = time.time() + event = self.event_map[event_handle] + with DbTxn(_("convert a media record"), self, batch=True, + no_magic=True) as transaction: + (handle, gramps_id, the_type, date, description, place, + source_list, note_list, media_list, attribute_list, + change, private) = event + if source_list: + new_citation_list = convert_source_list_to_citation_list_16( + self, source_list, transaction) + else: + new_citation_list = [] + if attribute_list: + attribute_list = upgrade_attribute_list_16( + self, attribute_list, transaction) + if media_list: + media_list = upgrade_media_list_16( + self, media_list, transaction) + # FIXME: events also have sources for places + if source_list or attribute_list or media_list: + LOG.debug("upgrade event %s: %s" % (event[1], event [4])) + new_event = (handle, gramps_id, the_type, date, description, place, + new_citation_list, note_list, media_list, + attribute_list, + change, private) + # LOG.debug(" upgrade new_event %s" % [new_event]) + with BSDDBTxn(self.env, self.event_map) as txn: + txn.put(str(handle), new_event, txn=transaction) + t2 = time.time() + upgrade_time += t2 - t1 + # remove backlinks for references from Media to Source + # add backlinks for references from Media to Citations + if source_list or attribute_list or media_list: + LOG.debug(" upgrade backlinks %s" % + [source_list, attribute_list, media_list]) + with BSDDBTxn(self.env) as txn: + self.update_reference_map( + self.get_event_from_handle(handle), + transaction, + txn.txn) + self.update() + t3 = time.time() + backlink_time += t3 - t2 + + LOG.debug("%d events upgraded with %d citations in %d seconds. " + "Backlinks took %d seconds" % + (len(self.event_map.keys()), num_citations, + int(upgrade_time), int(backlink_time))) + +# FIXME: some useful code snipetts for building an information dialogue +# about the speed of datatbase upgrade. +# self.data_newobject = [0] * 9 +# self.data_newobject[self.key2data[key]] += 1 +# key2string = { +# PERSON_KEY : _(' People: %d\n'), +# FAMILY_KEY : _(' Families: %d\n'), +# SOURCE_KEY : _(' Sources: %d\n'), +# EVENT_KEY : _(' Events: %d\n'), +# MEDIA_KEY : _(' Media Objects: %d\n'), +# PLACE_KEY : _(' Places: %d\n'), +# REPOSITORY_KEY : _(' Repositories: %d\n'), +# NOTE_KEY : _(' Notes: %d\n'), +# TAG_KEY : _(' Tags: %d\n'), +# } +# txt = _("Number of new objects imported:\n") +# for key in self.keyorder: +# txt += key2string[key] % self.data_newobject[self.key2data[key]] + # InfoDialog(_('Upgrade Statistics'), infotxt, self.window) + # Example database from repository took: + # 3403 events upgraded with 8 citations in 23 seconds. Backlinks took 1071 seconds + # actually 4 of these citations were from: + # Media upgrade 4 citations upgraded in 4 seconds + # by only doing the backlinks when there might be something to do, + # improved to: + # 3403 events upgraded with 8 citations in 19 seconds. Backlinks took 1348 seconds + # further improved by skipping debug logging: + # 3403 events upgraded with 8 citations in 2 seconds. Backlinks took 167 seconds + # Bump up database version. Separate transaction to save metadata. + with BSDDBTxn(self.env, self.metadata) as txn: + txn.put('version', 16) + +def upgrade_media_list_16(self, media_list, transaction): + new_media_list = [] + for media in media_list: + (privacy, source_list, note_list, attribute_list, ref, rect) = media + new_citation_list = convert_source_list_to_citation_list_16( + self, source_list, transaction) + new_attribute_list = upgrade_attribute_list_16( + self, attribute_list, transaction) + new_media = (privacy, new_citation_list, note_list, new_attribute_list, + ref, rect) + new_media_list.append((new_media)) + return new_media_list + +def upgrade_attribute_list_16(self, attribute_list, transaction): + new_attribute_list = [] + for attribute in attribute_list: + (privacy, source_list, note_list, the_type, + value) = attribute + new_citation_list = convert_source_list_to_citation_list_16( + self, source_list, transaction) + new_attribute = (privacy, new_citation_list, note_list, + the_type, value) + new_attribute_list.append((new_attribute)) + return new_attribute_list + +def convert_source_list_to_citation_list_16(self, source_list, transaction): + global num_citations + citation_list = [] + for source in source_list: + (new_handle, new_citation) = \ + convert_sourceref_to_citation_16(self, source) 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 + txn.put(str(new_handle), new_citation, txn=transaction) + num_citations += 1 + # add backlinks for references from Citation to Source + LOG.debug(" update ref map citation %s" % + [new_handle, + self.get_citation_from_handle(new_handle) ]) + with BSDDBTxn(self.env) as txn: + self.update_reference_map( + self.get_citation_from_handle(new_handle), + transaction, + txn.txn) + citation_list.append((new_handle)) + return citation_list + +def convert_sourceref_to_citation_16(self, source): + LOG.debug(" convert_sourceref_to_citation_16") + 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. + # FIXME: 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 + # FIXME: 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]) + return (new_handle, new_citation) def __find_next_gramps_id(self, prefix, map_index): """ diff --git a/src/gen/db/write.py b/src/gen/db/write.py index a748e88a5..9fc1a45ef 100644 --- a/src/gen/db/write.py +++ b/src/gen/db/write.py @@ -1875,7 +1875,15 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if version < 15: upgrade.gramps_upgrade_15(self) if version < 16: + self.__connect_secondary() + # Open undo database + self.__open_undodb() + self.db_is_open = True upgrade.gramps_upgrade_16(self) + # Close undo database + self.__close_undodb() + self.db_is_open = False + _LOG.debug("Upgrade time: %d seconds" % int(time.time()-t)) diff --git a/src/gen/lib/attribute.py b/src/gen/lib/attribute.py index 306287788..a8ed25bf6 100644 --- a/src/gen/lib/attribute.py +++ b/src/gen/lib/attribute.py @@ -3,6 +3,7 @@ # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2010 Michiel D. Nauta +# 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 @@ -32,7 +33,7 @@ Attribute class for GRAMPS. #------------------------------------------------------------------------- from gen.lib.secondaryobj import SecondaryObject from gen.lib.privacybase import PrivacyBase -from gen.lib.srcbase import SourceBase +from gen.lib.citationbase import CitationBase from gen.lib.notebase import NoteBase from gen.lib.attrtype import AttributeType from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT @@ -42,7 +43,7 @@ from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT # Attribute for Person/Family/MediaObject/MediaRef # #------------------------------------------------------------------------- -class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): +class Attribute(SecondaryObject, PrivacyBase, CitationBase, NoteBase): """ Provide a simple key/value pair for describing properties. Used to store descriptive information. @@ -63,7 +64,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): Create a new Attribute object, copying from the source if provided. """ PrivacyBase.__init__(self, source) - SourceBase.__init__(self, source) + CitationBase.__init__(self, source) NoteBase.__init__(self, source) if source: @@ -78,7 +79,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): Convert the object to a serialized tuple of data. """ return (PrivacyBase.serialize(self), - SourceBase.serialize(self), + CitationBase.serialize(self), NoteBase.serialize(self), self.type.serialize(), self.value) @@ -86,9 +87,9 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): """ Convert a serialized tuple of data to an object. """ - (privacy, source_list, note_list, the_type, self.value) = data + (privacy, citation_list, note_list, the_type, self.value) = data PrivacyBase.unserialize(self, privacy) - SourceBase.unserialize(self, source_list) + CitationBase.unserialize(self, citation_list) NoteBase.unserialize(self, note_list) self.type.unserialize(the_type) return self @@ -109,7 +110,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): :returns: Returns the list of child objects that may carry textual data. :rtype: list """ - return self.source_list + return [] def get_note_child_list(self): """ @@ -119,7 +120,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): refer notes. :rtype: list """ - return self.source_list + return [] def get_handle_referents(self): """ @@ -129,7 +130,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): :returns: Returns the list of objects referencing primary objects. :rtype: list """ - return self.source_list + return [] def get_referenced_handles(self): """ @@ -139,7 +140,8 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): :returns: List of (classname, handle) tuples for referenced objects. :rtype: list """ - return self.get_referenced_note_handles() + return self.get_referenced_note_handles() + \ + self.get_referenced_citation_handles() def is_equivalent(self, other): """ @@ -169,7 +171,7 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): :rtype acquisition: Attribute """ self._merge_privacy(acquisition) - self._merge_source_reference_list(acquisition) + self._merge_citation_list(acquisition) self._merge_note_list(acquisition) def set_type(self, val): diff --git a/src/gen/lib/citation.py b/src/gen/lib/citation.py index ea35a8c99..c868bcca2 100644 --- a/src/gen/lib/citation.py +++ b/src/gen/lib/citation.py @@ -50,7 +50,13 @@ from gen.lib.const import DIFFERENT, EQUAL, IDENTICAL # #------------------------------------------------------------------------- class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): - """A record of a citation of a source of information.""" + """ + A record of a citation of a source of information. + + In GEDCOM this is called a SOURCE_CITATION. + The data provided in the <> structure is source-related + information specific to the data being cited. + """ CONF_VERY_HIGH = 4 CONF_HIGH = 3 @@ -121,12 +127,6 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): 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': @@ -144,8 +144,9 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): :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 + # FIXME: Citations can refer to Notes, MediaObjects and one Source. + # MediaObjects and dealt with in Primary object, + # Notes do not seem to be dealt with at all !! if classname == 'Source' and \ self.get_reference_handle() in handle_list: self.set_reference_handle(None) @@ -161,8 +162,9 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): :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 + # FIXME: Citations can refer to Notes, MediaObjects and one Source. + # MediaObjects and dealt with in Primary object, + # Notes do not seem to be dealt with at all !! if classname == 'Source' and \ RefBase.get_reference_handle(self) == old_handle: self.ref = RefBase.set_reference_handle(self, new_handle) @@ -186,20 +188,18 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): :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_sourcref_child_list(self): +# # FIXME: I think we no longer need to handle source references +# """ +# 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 +# """ +# return self.media_list + self.ref def get_note_child_list(self): """ @@ -209,9 +209,6 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): 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): @@ -222,9 +219,6 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): :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): @@ -235,54 +229,51 @@ class Citation(MediaBase, NoteBase, PrimaryObject, RefBase, DateBase): :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 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): """ diff --git a/src/gen/lib/citationbase.py b/src/gen/lib/citationbase.py index dabd8409a..4ffb80414 100644 --- a/src/gen/lib/citationbase.py +++ b/src/gen/lib/citationbase.py @@ -97,7 +97,7 @@ class CitationBase(object): secondary child objects. :param handle: :class:`~gen.lib.citation.Citation` handle to remove - from the list of citations + from the list of citations :type handle: str """ LOG.debug('enter remove_citation handle %s' % handle) @@ -110,6 +110,16 @@ class CitationBase(object): for item in self.get_citation_child_list(): item.remove_citation(handle) + def get_citation_references(self) : + """ + Return the list of citations associated with the object. + + :returns: Returns the list of :class:`~gen.lib.Citation` handles + associated with the object. + :rtype: list + """ + return self.citation_list + def get_citation_child_list(self): """ Return the list of child secondary objects that may refer citations. diff --git a/src/gen/lib/event.py b/src/gen/lib/event.py index db09164df..e9d8a9509 100644 --- a/src/gen/lib/event.py +++ b/src/gen/lib/event.py @@ -31,7 +31,7 @@ Event object for GRAMPS. # #------------------------------------------------------------------------- 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.mediabase import MediaBase from gen.lib.attrbase import AttributeBase @@ -44,7 +44,7 @@ from gen.lib.eventtype import EventType # Event class # #------------------------------------------------------------------------- -class Event(SourceBase, NoteBase, MediaBase, AttributeBase, +class Event(CitationBase, NoteBase, MediaBase, AttributeBase, DateBase, PlaceBase, PrimaryObject): """ The Event record is used to store information about some type of @@ -67,7 +67,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, """ PrimaryObject.__init__(self, source) - SourceBase.__init__(self, source) + CitationBase.__init__(self, source) NoteBase.__init__(self, source) MediaBase.__init__(self, source) AttributeBase.__init__(self) @@ -102,7 +102,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, return (self.handle, self.gramps_id, self.__type.serialize(), DateBase.serialize(self, no_text_date), self.__description, self.place, - SourceBase.serialize(self), + CitationBase.serialize(self), NoteBase.serialize(self), MediaBase.serialize(self), AttributeBase.serialize(self), @@ -119,7 +119,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, """ (self.handle, self.gramps_id, the_type, date, self.__description, self.place, - source_list, note_list, media_list, attribute_list, + citation_list, note_list, media_list, attribute_list, self.change, self.private) = data self.__type = EventType() @@ -127,7 +127,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, DateBase.unserialize(self, date) MediaBase.unserialize(self, media_list) AttributeBase.unserialize(self, attribute_list) - SourceBase.unserialize(self, source_list) + CitationBase.unserialize(self, citation_list) NoteBase.unserialize(self, note_list) def _has_handle_reference(self, classname, handle): @@ -189,14 +189,14 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, :returns: Returns the list of child objects that may carry textual data. :rtype: list """ - return self.media_list + self.source_list + self.attribute_list + return self.media_list + self.attribute_list - def get_sourcref_child_list(self): + def get_citationref_child_list(self): """ - Return the list of child secondary objects that may refer sources. + Return the list of child secondary objects that may refer citations. :returns: Returns the list of child secondary child objects that may - refer sources. + refer citations. :rtype: list """ return self.media_list + self.attribute_list @@ -209,7 +209,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, refer notes. :rtype: list """ - return self.media_list + self.attribute_list + self.source_list + return self.media_list + self.attribute_list def get_referenced_handles(self): """ @@ -219,7 +219,8 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, :returns: List of (classname, handle) tuples for referenced objects. :rtype: list """ - ret = self.get_referenced_note_handles() + ret = self.get_referenced_note_handles() + \ + self.get_referenced_citation_handles() if self.place: ret.append(('Place', self.place)) return ret @@ -232,7 +233,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, :returns: Returns the list of objects referencing primary objects. :rtype: list """ - return self.get_sourcref_child_list() + self.source_list + return self.get_citationref_child_list() def is_empty(self): """ @@ -265,12 +266,12 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, self.__description != other.__description \ or self.private != other.private or \ (not self.get_date_object().is_equal(other.get_date_object())) or \ - len(self.get_source_references()) != len(other.get_source_references()): + len(self.get_citation_references()) != len(other.get_citation_references()): return False index = 0 - olist = other.get_source_references() - for a in self.get_source_references(): + olist = other.get_citation_references() + for a in self.get_citation_references(): if not a.is_equal(olist[index]): return False index += 1 @@ -289,7 +290,7 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, self._merge_privacy(acquisition) self._merge_attribute_list(acquisition) self._merge_note_list(acquisition) - self._merge_source_reference_list(acquisition) + self._merge_citation_list(acquisition) self._merge_media_list(acquisition) def set_type(self, the_type): diff --git a/src/gen/lib/mediaobj.py b/src/gen/lib/mediaobj.py index 3f2bed914..acd4847b8 100644 --- a/src/gen/lib/mediaobj.py +++ b/src/gen/lib/mediaobj.py @@ -42,7 +42,6 @@ LOG = logging.getLogger(".citation") # #------------------------------------------------------------------------- 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 @@ -54,7 +53,7 @@ from gen.lib.tagbase import TagBase # MediaObject class # #------------------------------------------------------------------------- -class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, +class MediaObject(CitationBase, NoteBase, DateBase, AttributeBase, TagBase, PrimaryObject): """ Container for information about an image file, including location, @@ -72,12 +71,11 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, :type source: MediaObject """ PrimaryObject.__init__(self, source) - SourceBase.__init__(self, source) + CitationBase.__init__(self, source) NoteBase.__init__(self, source) DateBase.__init__(self, source) AttributeBase.__init__(self, source) TagBase.__init__(self) - CitationBase.__init__(self) if source: self.path = source.path @@ -110,12 +108,11 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, """ return (self.handle, self.gramps_id, self.path, self.mime, self.desc, AttributeBase.serialize(self), - SourceBase.serialize(self), + CitationBase.serialize(self), NoteBase.serialize(self), self.change, DateBase.serialize(self, no_text_date), TagBase.serialize(self), - CitationBase.serialize(self), self.private) def unserialize(self, data): @@ -127,17 +124,14 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, :type data: tuple """ (self.handle, self.gramps_id, self.path, self.mime, self.desc, - attribute_list, source_list, note_list, self.change, - date, tag_list, - citation_list, - self.private) = data + attribute_list, citation_list, note_list, self.change, + date, tag_list, self.private) = data AttributeBase.unserialize(self, attribute_list) - SourceBase.unserialize(self, source_list) + CitationBase.unserialize(self, citation_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): """ @@ -157,27 +151,27 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, """ return self.attribute_list + self.source_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 - """ - return self.attribute_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 +# """ +# return self.attribute_list +# def get_citation_child_list(self): """ - Return the list of child secondary objects that may refer sources. + Return the list of child secondary objects that may refer to citations. :returns: Returns the list of child secondary child objects that may - refer sources. + refer to citations. :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 [] + return self.attribute_list def get_note_child_list(self): """ @@ -216,11 +210,11 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, """ LOG.debug ("Media: %s get_handle_referents: %s" % (self.desc, - self.attribute_list + self.source_list)) + self.attribute_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 + return self.attribute_list def merge(self, acquisition): """ @@ -234,7 +228,7 @@ class MediaObject(SourceBase, CitationBase, NoteBase, DateBase, AttributeBase, self._merge_privacy(acquisition) self._merge_attribute_list(acquisition) self._merge_note_list(acquisition) - self._merge_source_reference_list(acquisition) + self._merge_citation_list(acquisition) self._merge_tag_list(acquisition) self.merge_citation_list(acquisition) diff --git a/src/gen/lib/mediaref.py b/src/gen/lib/mediaref.py index 428e5505c..9b1c4afe4 100644 --- a/src/gen/lib/mediaref.py +++ b/src/gen/lib/mediaref.py @@ -32,7 +32,7 @@ Media Reference class for GRAMPS. #------------------------------------------------------------------------- from gen.lib.secondaryobj import SecondaryObject from gen.lib.privacybase import PrivacyBase -from gen.lib.srcbase import SourceBase +from gen.lib.citationbase import CitationBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase from gen.lib.attrbase import AttributeBase @@ -43,12 +43,12 @@ from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT # MediaObject References for Person/Place/Source # #------------------------------------------------------------------------- -class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, +class MediaRef(SecondaryObject, PrivacyBase, CitationBase, NoteBase, RefBase, AttributeBase): """Media reference class.""" def __init__(self, source=None): PrivacyBase.__init__(self, source) - SourceBase.__init__(self, source) + CitationBase.__init__(self, source) NoteBase.__init__(self, source) RefBase.__init__(self, source) AttributeBase.__init__(self, source) @@ -63,7 +63,7 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, Convert the object to a serialized tuple of data. """ return (PrivacyBase.serialize(self), - SourceBase.serialize(self), + CitationBase.serialize(self), NoteBase.serialize(self), AttributeBase.serialize(self), RefBase.serialize(self), @@ -73,9 +73,9 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, """ Convert a serialized tuple of data to an object. """ - (privacy, source_list, note_list,attribute_list,ref,self.rect) = data + (privacy, citation_list, note_list,attribute_list,ref,self.rect) = data PrivacyBase.unserialize(self, privacy) - SourceBase.unserialize(self, source_list) + CitationBase.unserialize(self, citation_list) NoteBase.unserialize(self, note_list) AttributeBase.unserialize(self, attribute_list) RefBase.unserialize(self, ref) @@ -88,14 +88,14 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, :returns: Returns the list of child objects that may carry textual data. :rtype: list """ - return self.attribute_list + self.source_list + return self.attribute_list # + self.source_list - def get_sourcref_child_list(self): + def get_citation_child_list(self): """ - Return the list of child secondary objects that may refer sources. + Return the list of child secondary objects that may refer Citations. :returns: Returns the list of child secondary child objects that may - refer sources. + refer Citations. :rtype: list """ return self.attribute_list @@ -108,7 +108,7 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, refer notes. :rtype: list """ - return self.attribute_list + self.source_list + return self.attribute_list # + self.source_list def get_referenced_handles(self): """ @@ -118,7 +118,8 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, :returns: List of (classname, handle) tuples for referenced objects. :rtype: list """ - ret = self.get_referenced_note_handles() + ret = self.get_referenced_note_handles() + \ + self.get_referenced_citation_handles() if self.ref: ret += [('MediaObject', self.ref)] return ret @@ -131,7 +132,7 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, :returns: Returns the list of objects referencing primary objects. :rtype: list """ - return self.attribute_list + self.source_list + return self.attribute_list # + self.source_list def is_equivalent(self, other): """ @@ -162,7 +163,7 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, """ self._merge_privacy(acquisition) self._merge_attribute_list(acquisition) - self._merge_source_reference_list(acquisition) + self._merge_citation_list(acquisition) self._merge_note_list(acquisition) def set_rectangle(self, coord): diff --git a/src/gui/editors/editattribute.py b/src/gui/editors/editattribute.py index cf022fd0f..747687c45 100644 --- a/src/gui/editors/editattribute.py +++ b/src/gui/editors/editattribute.py @@ -48,7 +48,7 @@ import gtk from editsecondary import EditSecondary from gen.lib import NoteType from glade import Glade -from displaytabs import SourceEmbedList, NoteTab +from displaytabs import CitationEmbedList, NoteTab from gui.widgets import MonitoredEntry, PrivacyButton, MonitoredDataType #------------------------------------------------------------------------- @@ -107,7 +107,10 @@ class EditAttribute(EditSecondary): def _create_tabbed_pages(self): notebook = gtk.Notebook() - self.srcref_list = SourceEmbedList(self.dbstate,self.uistate,self.track,self.obj) + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list()) self._add_tab(notebook, self.srcref_list) self.track_ref_for_deletion("srcref_list") diff --git a/src/gui/editors/editcitation.py b/src/gui/editors/editcitation.py index 076e627f3..2e5d9d8e2 100644 --- a/src/gui/editors/editcitation.py +++ b/src/gui/editors/editcitation.py @@ -110,9 +110,8 @@ class EditCitation(EditPrimary): 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, + # The functions define_warn_box, enable_warn_box and define_expander + # are normally inherited from editreference, # but have to be defined here because this class inherits from # EditPrimary instead def define_warn_box(self,box): @@ -121,6 +120,12 @@ class EditCitation(EditPrimary): def enable_warnbox(self): self.warn_box.show() + def define_warn_box2(self,box): + self.warn_box2 = box + + def enable_warnbox2(self): + self.warn_box2.show() + def define_expander(self,expander): expander.set_expanded(True) @@ -141,6 +146,7 @@ class EditCitation(EditPrimary): self.get_menu_title()) self.define_warn_box(self.glade.get_object("warn_box")) + self.define_warn_box2(self.glade.get_object("warn_box2")) self.define_expander(self.glade.get_object("src_expander")) tblref = self.glade.get_object('table67') @@ -266,11 +272,10 @@ class EditCitation(EditPrimary): 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.citationref_list = SourceBackRefList(self.dbstate, self.uistate, self.track, self.db.find_backlink_handles(self.obj.handle), - self.enable_warnbox) + self.enable_warnbox2) self._add_tab(notebook_ref, self.citationref_list) self.track_ref_for_deletion("citationref_list") diff --git a/src/gui/editors/editevent.py b/src/gui/editors/editevent.py index 2a5486552..8c3588426 100644 --- a/src/gui/editors/editevent.py +++ b/src/gui/editors/editevent.py @@ -48,7 +48,7 @@ from editprimary import EditPrimary from objectentries import PlaceEntry from glade import Glade from QuestionDialog import ErrorDialog -from displaytabs import (SourceEmbedList, NoteTab, GalleryTab, +from displaytabs import (CitationEmbedList, NoteTab, GalleryTab, EventBackRefList, AttrEmbedList) from gui.widgets import (MonitoredEntry, PrivacyButton, MonitoredDataType, MonitoredDate) @@ -172,11 +172,12 @@ class EditEvent(EditPrimary): """ notebook = gtk.Notebook() - self.source_list = SourceEmbedList(self.dbstate, - self.uistate, - self.track, - self.obj) - self._add_tab(notebook, self.source_list) + self.citation_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_citation_list(), + self.get_menu_title()) + self._add_tab(notebook, self.citation_list) self.note_list = NoteTab(self.dbstate, self.uistate, @@ -210,7 +211,7 @@ class EditEvent(EditPrimary): notebook.show_all() self.top.get_object('vbox').pack_start(notebook, True) - self.track_ref_for_deletion("source_list") + self.track_ref_for_deletion("citation_list") self.track_ref_for_deletion("note_list") self.track_ref_for_deletion("gallery_list") self.track_ref_for_deletion("attr_list") diff --git a/src/gui/editors/editeventref.py b/src/gui/editors/editeventref.py index c7faca611..9a9238825 100644 --- a/src/gui/editors/editeventref.py +++ b/src/gui/editors/editeventref.py @@ -36,7 +36,7 @@ from gen.ggettext import gettext as _ import gen.lib from gen.db import DbTxn from glade import Glade -from displaytabs import (SourceEmbedList, NoteTab, GalleryTab, +from displaytabs import (CitationEmbedList, NoteTab, GalleryTab, EventBackRefList, AttrEmbedList) from gui.widgets import (PrivacyButton, MonitoredEntry, MonitoredDate, MonitoredDataType) @@ -172,10 +172,10 @@ class EditEventRef(EditReference): self.track_ref_for_deletion("primtab") self.track_ref_for_deletion("reftab") - self.srcref_list = SourceEmbedList(self.dbstate, - self.uistate, - self.track, - self.source) + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source.get_citation_list()) self._add_tab(notebook, self.srcref_list) self.track_ref_for_deletion("srcref_list") diff --git a/src/gui/editors/editmedia.py b/src/gui/editors/editmedia.py index dd409989a..aa6eca4c1 100644 --- a/src/gui/editors/editmedia.py +++ b/src/gui/editors/editmedia.py @@ -192,13 +192,14 @@ class EditMedia(EditPrimary): def _create_tabbed_pages(self): notebook = gtk.Notebook() - self.src_tab = SourceEmbedList(self.dbstate, - self.uistate, - self.track, - self.obj) - self._add_tab(notebook, self.src_tab) - self.track_ref_for_deletion("src_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.attr_tab = AttrEmbedList(self.dbstate, self.uistate, self.track, @@ -214,14 +215,6 @@ 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/editmediaref.py b/src/gui/editors/editmediaref.py index 3121ce997..9cd98db2c 100644 --- a/src/gui/editors/editmediaref.py +++ b/src/gui/editors/editmediaref.py @@ -49,7 +49,7 @@ import Utils from gen.lib import NoteType from gen.db import DbTxn from glade import Glade -from displaytabs import (SourceEmbedList, AttrEmbedList, MediaBackRefList, +from displaytabs import (CitationEmbedList, AttrEmbedList, MediaBackRefList, NoteTab) from gui.widgets import MonitoredSpinButton, MonitoredEntry, PrivacyButton from editreference import RefTab, EditReference @@ -543,8 +543,10 @@ class EditMediaRef(EditReference): self._add_tab(notebook_src, self.primtab) self._add_tab(notebook_ref, self.reftab) - self.srcref_list = SourceEmbedList(self.dbstate,self.uistate,self.track, - self.source_ref) + self.srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source_ref.get_citation_list()) self._add_tab(notebook_ref, self.srcref_list) self.track_ref_for_deletion("srcref_list") @@ -566,8 +568,10 @@ class EditMediaRef(EditReference): self._add_tab(notebook_ref, self.note_ref_tab) self.track_ref_for_deletion("note_ref_tab") - self.src_srcref_list = SourceEmbedList(self.dbstate,self.uistate, - self.track, self.source) + self.src_srcref_list = CitationEmbedList(self.dbstate, + self.uistate, + self.track, + self.source.get_citation_list()) self._add_tab(notebook_src, self.src_srcref_list) self.track_ref_for_deletion("src_srcref_list") diff --git a/src/gui/editors/editsource.py b/src/gui/editors/editsource.py index 9da2b4753..80d5bb48e 100644 --- a/src/gui/editors/editsource.py +++ b/src/gui/editors/editsource.py @@ -49,7 +49,7 @@ from gen.db import DbTxn from editprimary import EditPrimary from displaytabs import (NoteTab, GalleryTab, DataEmbedList, - SourceBackRefList, RepoEmbedList) + CitationBackRefList, RepoEmbedList) from gui.widgets import MonitoredEntry, PrivacyButton from QuestionDialog import ErrorDialog from glade import Glade @@ -160,7 +160,7 @@ class EditSource(EditPrimary): self._add_tab(notebook, self.repo_tab) self.track_ref_for_deletion("repo_tab") - self.backref_list = SourceBackRefList(self.dbstate, + self.backref_list = CitationBackRefList(self.dbstate, self.uistate, self.track, self.db.find_backlink_handles(self.obj.handle)) diff --git a/src/gui/views/listview.py b/src/gui/views/listview.py index 206700a58..d58b53d0d 100644 --- a/src/gui/views/listview.py +++ b/src/gui/views/listview.py @@ -36,6 +36,7 @@ import time import logging _LOG = logging.getLogger('.gui.listview') +LOG = logging.getLogger(".citation") #---------------------------------------------------------------- # diff --git a/src/plugins/gramplet/Citations.py b/src/plugins/gramplet/Citations.py new file mode 100644 index 000000000..24c37e2b9 --- /dev/null +++ b/src/plugins/gramplet/Citations.py @@ -0,0 +1,469 @@ +# 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$ +# + +from gui.editors import EditCitation +from ListModel import ListModel, NOSORT +from gen.plug import Gramplet +from gen.ggettext import gettext as _ +import Errors +import gtk + +class Citations(Gramplet): + """ + Displays the citations for an object. + """ + def init(self): + self.gui.WIDGET = self.build_gui() + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET) + self.gui.WIDGET.show() + + def build_gui(self): + """ + Build the GUI interface. + """ + tip = _('Double-click on a row to edit the selected citation.') + self.set_tooltip(tip) + top = gtk.TreeView() + titles = [('', NOSORT, 50,), + (_('Source'), 1, 200), + (_('Reference'), 2, 300), + (_('Author'), 3, 100)] + self.model = ListModel(top, titles, event_func=self.edit_citation) + return top + + def add_citations(self, obj): + for citation_handle in obj.get_citation_references(): + self.add_citation_ref(citation_handle) + + def add_name_citations(self, obj): + names = [obj.get_primary_name()] + obj.get_alternate_names() + for name in names: + self.add_citations(name) + + def add_attribute_citations(self, obj): + for attr in obj.get_attribute_list(): + self.add_citations(attr) + + def add_mediaref_citations(self, obj): + for media_ref in obj.get_media_list(): + self.add_citations(media_ref) + self.add_attribute_citations(media_ref) + media = self.dbstate.db.get_object_from_handle(media_ref.ref) + self.add_media_citations(media) + + def add_media_citations(self, media): + self.add_citations(media) + self.add_attribute_citations(media) + + def add_eventref_citations(self, obj): + for event_ref in obj.get_event_ref_list(): + self.add_attribute_citations(event_ref) + event = self.dbstate.db.get_event_from_handle(event_ref.ref) + self.add_event_citations(event) + + def add_event_citations(self, event): + self.add_citations(event) + self.add_attribute_citations(event) + self.add_mediaref_citations(event) + place_handle = event.get_place_handle() + place = self.dbstate.db.get_place_from_handle(place_handle) + if place: + self.add_place_citations(place) + + def add_place_citations(self, place): + self.add_citations(place) + self.add_mediaref_citations(place) + + def add_address_citations(self, obj): + for address in obj.get_address_list(): + self.add_citations(address) + + def add_lds_citations(self, obj): + for lds in obj.get_lds_ord_list(): + self.add_citations(lds) + place_handle = lds.get_place_handle() + place = self.dbstate.db.get_place_from_handle(place_handle) + if place: + self.add_place_citations(place) + + def add_association_citations(self, obj): + for assoc in obj.get_person_ref_list(): + self.add_citations(assoc) + + def add_citation_ref(self, citation_handle): + """ + Add a citation to the model. + """ + citation = self.dbstate.db.get_citation_from_handle(citation_handle) + page = citation.get_page() + source = self.dbstate.db.get_source_from_handle(citation.ref) + title = source.get_title() + author = source.get_author() + self.model.add((citation_handle, title, page, author)) + + def check_citations(self, obj): + return True if obj.get_citation_references() else False + + def check_name_citations(self, obj): + names = [obj.get_primary_name()] + obj.get_alternate_names() + for name in names: + if self.check_citations(name): + return True + return False + + def check_attribute_citations(self, obj): + for attr in obj.get_attribute_list(): + if self.check_citations(attr): + return True + return False + + def check_mediaref_citations(self, obj): + for media_ref in obj.get_media_list(): + if self.check_citations(media_ref): + return True + if self.check_attribute_citations(media_ref): + return True + media = self.dbstate.db.get_object_from_handle(media_ref.ref) + if self.check_media_citations(media): + return True + return False + + def check_media_citations(self, media): + if self.check_citations(media): + return True + if self.check_attribute_citations(media): + return True + return False + + def check_eventref_citations(self, obj): + for event_ref in obj.get_event_ref_list(): + if self.check_attribute_citations(event_ref): + return True + event = self.dbstate.db.get_event_from_handle(event_ref.ref) + if self.check_event_citations(event): + return True + return False + + def check_event_citations(self, event): + if self.check_citations(event): + return True + if self.check_attribute_citations(event): + return True + if self.check_mediaref_citations(event): + return True + place_handle = event.get_place_handle() + place = self.dbstate.db.get_place_from_handle(place_handle) + if place and self.check_place_citations(place): + return True + return False + + def check_place_citations(self, place): + if self.check_citations(place): + return True + if self.check_mediaref_citations(place): + return True + return False + + def check_address_citations(self, obj): + for address in obj.get_address_list(): + if self.check_citations(address): + return True + return False + + def check_lds_citations(self, obj): + for lds in obj.get_lds_ord_list(): + if self.check_citations(lds): + return True + place_handle = lds.get_place_handle() + place = self.dbstate.db.get_place_from_handle(place_handle) + if place and self.check_place_citations(place): + return True + return False + + def check_association_citations(self, obj): + for assoc in obj.get_person_ref_list(): + if self.check_citations(assoc): + return True + return False + + def edit_citation(self, treeview): + """ + Edit the selected citation. + """ + model, iter_ = treeview.get_selection().get_selected() + if iter_: + handle = model.get_value(iter_, 0) + try: + citation = self.dbstate.db.get_citation_from_handle(handle) + source = self.dbstate.db.get_source_from_handle(citation.ref) + EditCitation(self.dbstate, self.uistate, [], citation, source) + except Errors.WindowActiveError: + pass + +class PersonCitations(Citations): + """ + Displays the citations for a person. + """ + def db_changed(self): + self.dbstate.db.connect('person-update', self.update) + self.update() + + def active_changed(self, handle): + self.update() + + def update_has_data(self): + active_handle = self.get_active('Person') + active = self.dbstate.db.get_person_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Person') + active = self.dbstate.db.get_person_from_handle(active_handle) + + self.model.clear() + if active: + self.display_citations(active) + else: + self.set_has_data(False) + + def display_citations(self, person): + """ + Display the citations for the active person. + """ + self.add_citations(person) + self.add_eventref_citations(person) + for handle in person.get_family_handle_list(): + family = self.dbstate.db.get_family_from_handle(handle) + self.add_eventref_citations(family) + self.add_name_citations(person) + self.add_attribute_citations(person) + self.add_address_citations(person) + self.add_mediaref_citations(person) + self.add_association_citations(person) + self.add_lds_citations(person) + + self.set_has_data(self.model.count > 0) + + def get_has_data(self, person): + """ + Return True if the gramplet has data, else return False. + """ + if person is None: + return False + if self.check_citations(person): + return True + if self.check_eventref_citations(person): + return True + for handle in person.get_family_handle_list(): + family = self.dbstate.db.get_family_from_handle(handle) + if self.check_eventref_citations(family): + return True + if self.check_name_citations(person): + return True + if self.check_attribute_citations(person): + return True + if self.check_address_citations(person): + return True + if self.check_mediaref_citations(person): + return True + if self.check_association_citations(person): + return True + if self.check_lds_citations(person): + return True + return False + +class EventCitations(Citations): + """ + Displays the citations for an event. + """ + def db_changed(self): + self.dbstate.db.connect('event-update', self.update) + self.connect_signal('Event', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Event') + active = self.dbstate.db.get_event_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Event') + active = self.dbstate.db.get_event_from_handle(active_handle) + + self.model.clear() + if active: + self.display_citations(active) + else: + self.set_has_data(False) + + def display_citations(self, event): + """ + Display the citations for the active event. + """ + self.add_event_citations(event) + self.set_has_data(self.model.count > 0) + + def get_has_data(self, event): + """ + Return True if the gramplet has data, else return False. + """ + if event is None: + return False + if self.check_event_citations(event): + return True + return False + +class FamilyCitations(Citations): + """ + Displays the citations for a family. + """ + def db_changed(self): + self.dbstate.db.connect('family-update', self.update) + self.connect_signal('Family', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Family') + active = self.dbstate.db.get_family_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Family') + active = self.dbstate.db.get_family_from_handle(active_handle) + + self.model.clear() + if active: + self.display_citations(active) + else: + self.set_has_data(False) + + def display_citations(self, family): + """ + Display the citations for the active family. + """ + self.add_citations(family) + self.add_eventref_citations(family) + self.add_attribute_citations(family) + self.add_mediaref_citations(family) + self.add_lds_citations(family) + + self.set_has_data(self.model.count > 0) + + def get_has_data(self, family): + """ + Return True if the gramplet has data, else return False. + """ + if family is None: + return False + if self.check_citations(family): + return True + if self.check_eventref_citations(family): + return True + if self.check_attribute_citations(family): + return True + if self.check_mediaref_citations(family): + return True + if self.check_lds_citations(family): + return True + return False + +class PlaceCitations(Citations): + """ + Displays the citations for a place. + """ + def db_changed(self): + self.dbstate.db.connect('place-update', self.update) + self.connect_signal('Place', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Place') + active = self.dbstate.db.get_place_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Place') + active = self.dbstate.db.get_place_from_handle(active_handle) + + self.model.clear() + if active: + self.display_citations(active) + else: + self.set_has_data(False) + + def display_citations(self, place): + """ + Display the citations for the active place. + """ + self.add_place_citations(place) + self.set_has_data(self.model.count > 0) + + def get_has_data(self, place): + """ + Return True if the gramplet has data, else return False. + """ + if place is None: + return False + if self.check_place_citations(place): + return True + return False + +class MediaCitations(Citations): + """ + Displays the citations for a media object. + """ + def db_changed(self): + self.dbstate.db.connect('media-update', self.update) + self.connect_signal('Media', self.update) + self.update() + + def update_has_data(self): + active_handle = self.get_active('Media') + active = self.dbstate.db.get_object_from_handle(active_handle) + self.set_has_data(self.get_has_data(active)) + + def main(self): + active_handle = self.get_active('Media') + active = self.dbstate.db.get_object_from_handle(active_handle) + + self.model.clear() + if active: + self.display_citations(active) + else: + self.set_has_data(False) + + def display_citations(self, media): + """ + Display the citations for the active media object. + """ + self.add_media_citations(media) + self.set_has_data(self.model.count > 0) + + def get_has_data(self, media): + """ + Return True if the gramplet has data, else return False. + """ + if media is None: + return False + if self.check_media_citations(media): + return True + return False diff --git a/src/plugins/gramplet/Makefile.am b/src/plugins/gramplet/Makefile.am index 52d61e08d..35831f59e 100644 --- a/src/plugins/gramplet/Makefile.am +++ b/src/plugins/gramplet/Makefile.am @@ -14,6 +14,7 @@ pkgdata_PYTHON = \ bottombar.gpr.py \ CalendarGramplet.py \ Children.py \ + Citations.py \ DescendGramplet.py \ EditExifMetadata.py \ Events.py \ diff --git a/src/plugins/gramplet/PopulateGramplet.gpr.py b/src/plugins/gramplet/PopulateGramplet.gpr.py new file mode 100644 index 000000000..b096df8cb --- /dev/null +++ b/src/plugins/gramplet/PopulateGramplet.gpr.py @@ -0,0 +1,41 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Benny Malengier +# +# 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$ + +#------------------------------------------------------------------------ +# +# Register Gramplet +# +#------------------------------------------------------------------------ +register(GRAMPLET, + id="Populate", + name=_("Populate data"), + description = _("Gramplet to populate database"), + version="2.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="PopulateGramplet.py", + height=200, + gramplet = 'PopulateGramplet', + gramplet_title=_("Populate data"), + ) + + diff --git a/src/plugins/gramplet/PopulateGramplet.py b/src/plugins/gramplet/PopulateGramplet.py new file mode 100644 index 000000000..ca6635f46 --- /dev/null +++ b/src/plugins/gramplet/PopulateGramplet.py @@ -0,0 +1,128 @@ +# 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$ + +""" +Gramplet that populates the database with sources and citations. +""" + +#------------------------------------------------------------------------ +# +# Python modules +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------ +# +# GRAMPS modules +# +#------------------------------------------------------------------------ +from gen.plug import Gramplet +from gen.ggettext import sgettext as _ +import DateHandler +from QuickReports import run_quick_report_by_name +import gen.lib +from gen.db import DbTxn + +#------------------------------------------------------------------------ +# +# Gramplet class +# +#------------------------------------------------------------------------ +class PopulateGramplet(Gramplet): + """ + Gramplet that populates the database with sources and citations. + """ + def init(self): + """ + Constructs the GUI, consisting of a message, an entry, and + a Run button. + """ + import gtk + # GUI setup: + self.set_tooltip(_("Enter a date, click Run")) + vbox = gtk.VBox() + hbox = gtk.HBox() + # label, entry + description = gtk.TextView() + description.set_wrap_mode(gtk.WRAP_WORD) + description.set_editable(False) + buffer = description.get_buffer() + buffer.set_text(_("Enter a valid number of sources and citations." + " This will create the requested number of sources," + " and for each source, will create the requested" + " number of citations.")) + label_sources = gtk.Label() + label_sources.set_text(_("Number of sources") + ":") + self.num_sources = gtk.Entry() + label_citations = gtk.Label() + label_citations.set_text(_("Number of citations") + ":") + self.num_citations = gtk.Entry() + button = gtk.Button(_("Run")) + button.connect("clicked", self.run) + ##self.filter = + hbox.pack_start(label_sources, False) + hbox.pack_start(self.num_sources, True) + hbox.pack_start(label_citations, False) + hbox.pack_start(self.num_citations, True) + vbox.pack_start(description, True) + vbox.pack_start(hbox, False) + vbox.pack_start(button, False) + self.gui.get_container_widget().remove(self.gui.textview) + self.gui.get_container_widget().add_with_viewport(vbox) + vbox.show_all() + + def post_init(self): + self.disconnect("active-changed") + + def run(self, obj): + """ + Method that is run when you click the Run button. + The date is retrieved from the entry box, parsed as a date, + and then handed to the quick report. + """ + num_sources_text = self.num_sources.get_text() + num_sources = int(num_sources_text) + num_citations_text = self.num_citations.get_text() + num_citations = int(num_citations_text) + + LOG.debug("sources %04d citations %04d" % (num_sources, + num_citations)) + + source = gen.lib.Source() + citation = gen.lib.Citation() + db = self.gui.dbstate.db + + for i in range(num_sources): + source.gramps_id = None + source.handle = None + source.title = "Source %04d" % i + with DbTxn('savesource', db) as trans: + db.add_source(source, trans) + + for j in range(num_citations): + citation.gramps_id = None + citation.handle = None + citation.ref = source.handle + citation.page = "Page %04d" % j + with DbTxn('savecitation', db) as trans: + db.add_citation(citation, trans) + diff --git a/src/plugins/gramplet/bottombar.gpr.py b/src/plugins/gramplet/bottombar.gpr.py index a17808d3e..4c38edecc 100644 --- a/src/plugins/gramplet/bottombar.gpr.py +++ b/src/plugins/gramplet/bottombar.gpr.py @@ -470,6 +470,76 @@ register(GRAMPLET, navtypes=["Media"], ) +register(GRAMPLET, + id="Person Citations", + name=_("Person Citations"), + description = _("Gramplet showing the citations for a person"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Citations.py", + height=200, + gramplet = 'PersonCitations', + gramplet_title=_("Citations"), + navtypes=["Person"], + ) + +register(GRAMPLET, + id="Event Citations", + name=_("Event Citations"), + description = _("Gramplet showing the citations for an event"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Citations.py", + height=200, + gramplet = 'EventCitations', + gramplet_title=_("Citations"), + navtypes=["Event"], + ) + +register(GRAMPLET, + id="Family Citations", + name=_("Family Citations"), + description = _("Gramplet showing the citations for a family"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Citations.py", + height=200, + gramplet = 'FamilyCitations', + gramplet_title=_("Citations"), + navtypes=["Family"], + ) + +register(GRAMPLET, + id="Place Citations", + name=_("Place Citations"), + description = _("Gramplet showing the citations for a place"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Citations.py", + height=200, + gramplet = 'PlaceCitations', + gramplet_title=_("Citations"), + navtypes=["Place"], + ) + +register(GRAMPLET, + id="Media Citations", + name=_("Media Citations"), + description = _("Gramplet showing the citations for a media object"), + version="1.0.0", + gramps_target_version="3.4", + status = STABLE, + fname="Citations.py", + height=200, + gramplet = 'MediaCitations', + gramplet_title=_("Citations"), + navtypes=["Media"], + ) + register(GRAMPLET, id="Person Children", name=_("Person Children"), diff --git a/src/plugins/lib/libcitationview.py b/src/plugins/lib/libcitationview.py index 6933385e9..5b12a32cf 100644 --- a/src/plugins/lib/libcitationview.py +++ b/src/plugins/lib/libcitationview.py @@ -52,7 +52,7 @@ import Errors from DdTargets import DdTargets from gui.selectors import SelectorFactory from QuestionDialog import ErrorDialog -from gui.editors import EditCitation, DeleteCitationQuery +from gui.editors import EditCitation, DeleteCitationQuery, EditSource from Filters.SideBar import SourceSidebarFilter from gen.plug import CATEGORY_QR_SOURCE @@ -238,6 +238,9 @@ class BaseCitationView(ListView): pass def add(self, obj): + """ + Add a new Citation to a user selected source + """ SelectSource = SelectorFactory('Source') sel = SelectSource(self.dbstate, self.uistate) source = sel.run() @@ -250,38 +253,6 @@ class BaseCitationView(ListView): 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) @@ -295,6 +266,48 @@ class BaseCitationView(ListView): # 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): + """ + Edit either a Source or a Citation, depending on user selection + """ + for handle in self.selected_handles(): + # The handle will either be a Source handle or a Citation handle + citation = self.dbstate.db.get_citation_from_handle(handle) + if citation: + LOG.debug("citation handle %s page %s" % + (handle, citation.page)) + source = self.dbstate.db.get_source_from_handle(citation.ref) + try: + EditCitation(self.dbstate, self.uistate, [], citation, source) + except Errors.WindowActiveError: + pass + else: + source = self.dbstate.db.get_source_from_handle(handle) + LOG.debug("source handle %s title %s " % + (source, source.title)) + EditSource(self.dbstate, self.uistate, [], source) + + def __blocked_text(self): + """ + Return the common text used when 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 merge(self, obj): """ Merge the selected citations. diff --git a/src/plugins/view/eventview.py b/src/plugins/view/eventview.py index 8f7c6310e..0dde539e4 100644 --- a/src/plugins/view/eventview.py +++ b/src/plugins/view/eventview.py @@ -291,7 +291,7 @@ class EventView(ListView): """ return (("Event Filter",), ("Event Gallery", - "Event Sources", + "Event Citations", "Event Notes", "Event Attributes", "Event Backlinks")) diff --git a/src/plugins/view/mediaview.py b/src/plugins/view/mediaview.py index 8f98e8c59..cfb394d7d 100644 --- a/src/plugins/view/mediaview.py +++ b/src/plugins/view/mediaview.py @@ -425,7 +425,7 @@ class MediaView(ListView): """ return (("Media Filter",), ("Media Preview", - "Media Sources", + "Media Citations" "Media Notes", "Media Attributes", "Metadata Viewer",