From 82419537658e919470eb05413adb64e06e1fa21e Mon Sep 17 00:00:00 2001
From: Nick Hall
Date: Sun, 2 Mar 2014 19:12:50 +0000
Subject: [PATCH] Simplify citation editor and add source selection
---
gramps/gui/editors/editcitation.py | 318 ++--------
gramps/gui/editors/editsource.py | 4 +-
gramps/gui/editors/objectentries.py | 68 ++-
gramps/gui/glade/editcitation.glade | 869 +++++++++-------------------
4 files changed, 405 insertions(+), 854 deletions(-)
diff --git a/gramps/gui/editors/editcitation.py b/gramps/gui/editors/editcitation.py
index 03294461c..d11de0549 100644
--- a/gramps/gui/editors/editcitation.py
+++ b/gramps/gui/editors/editcitation.py
@@ -3,7 +3,8 @@
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Gary Burton
-# Copyright (C) 2011 Tim G L Lyons, Nick Hall
+# Copyright (C) 2011 Tim G L Lyons
+# Copyright (C) 2011,2014 Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,10 +21,8 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-# $Id$
-
"""
-EditCitation class for GRAMPS.
+EditCitation class for Gramps.
"""
#-------------------------------------------------------------------------
@@ -36,6 +35,13 @@ _ = glocale.translation.gettext
import logging
LOG = logging.getLogger(".citation")
+#-------------------------------------------------------------------------
+#
+# GTK/Gnome modules
+#
+#-------------------------------------------------------------------------
+from gi.repository import Gtk
+
#-------------------------------------------------------------------------
#
# gramps modules
@@ -44,13 +50,11 @@ LOG = logging.getLogger(".citation")
from gramps.gen.lib import Citation, NoteType, Source
from gramps.gen.db import DbTxn
from .editprimary import EditPrimary
-
-from .displaytabs import (NoteTab, GalleryTab, DataEmbedList,
- SourceBackRefList, RepoEmbedList, CitationBackRefList)
+from .objectentries import SourceEntry
+from .displaytabs import NoteTab, GalleryTab, DataEmbedList, CitationBackRefList
from ..widgets import (MonitoredEntry, PrivacyButton, MonitoredMenu,
- MonitoredDate)
+ MonitoredDate)
from ..dialog import ErrorDialog
-from .editreference import RefTab
from ..glade import Glade
#-------------------------------------------------------------------------
@@ -78,49 +82,12 @@ class EditCitation(EditPrimary):
The obj parameter is mandatory. If the source parameter is not
provided, it will be deduced from the obj Citation object.
"""
- if not source and obj.get_reference_handle():
- source = dbstate.db.get_source_from_handle(
- obj.get_reference_handle())
- self.source = source
+ if source:
+ obj.set_reference_handle(source.get_handle())
self.callertitle = callertitle
EditPrimary.__init__(self, dbstate, uistate, track, obj,
dbstate.db.get_citation_from_handle,
dbstate.db.get_citation_from_gramps_id, callback)
- # FIXME: EitPrimary calls ManagedWindow.__init__, which checks whether
- # a window is already open which is editing obj. However, for
- # EditCitation, not only do we need to protect obj (which will be
- # a Citation, but we also need to protect the associated Source.
-
- def build_window_key(self, obj):
- """
- Return a key for the edit window that is opened.
- This function overrides the build_window_key in EditPrimary.
-
- There is a problem with database object locking. The database locking is
- handled by the ManagedWindow class, which will only allow one primary
- object to be edited at a time.
-
- Normally, the window key is derived from the obj that is being edited.
- However, in the case of EditCitation, there are two objects being
- edited, the Citation and the Source. Both must be protected against
- against the user trying to edit them twice.
-
- What we do here is to derive the window key from the Source object, if
- one exists. A Citation always points to exactly one Source object, so if
- we try to edit the same Citation twice, the associated Source objects
- will be the same so this will be prevented. If we try to edit a Source
- object and a Citation object that refers to the same Source, then again,
- the window key will be the same and this will be prevented.
- """
- if obj and obj.get_reference_handle():
- # citation already points to source
- return obj.get_reference_handle()
- elif self.source and self.source.get_handle():
- # Citation doesn't yet point to source, but source exists and has a
- # handle
- return self.source.get_handle()
- else:
- return id(self)
def empty_object(self):
"""
@@ -156,68 +123,27 @@ class EditCitation(EditPrimary):
title = _('New Citation')
return title
- # 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):
- self.warn_box = box
-
- 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)
-
def _local_init(self):
- """Local initialization function.
+ """
+ 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.citation-width'
self.height_key = 'interface.citation-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_warn_box2(self.glade.get_object("warn_box2"))
- 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 _post_init(self):
- title = self.glade.get_object('title')
- volume = self.glade.get_object('volume')
- if not title.get_text_length():
- title.grab_focus();
- elif not volume.get_text_length():
- volume.grab_focus();
+ self.share_btn = self.glade.get_object('select_source')
+ self.add_del_btn = self.glade.get_object('add_del_source')
def _connect_signals(self):
- """Connects any signals that need to be connected.
+ """
+ Connects any signals that need to be connected.
Called by the init routine of the base class L{EditPrimary}.
"""
@@ -234,21 +160,21 @@ class EditCitation(EditPrimary):
whilst editing it. If the object is deleted we need to close the editor
windows and clean up. If the database emits a rebuild signal for the
database object type we also abort the edit.
-
- The Citation editor edits two primary objects, and therefore we need to
- check if either have been deleted. If the source is deleted, the
- citation must have been deleted first and will emit a signal, so we
- shouldn't have to connect to the source-delete signal. It should not be
- necessary to connect to the source- rebuild signal for similar reasons.
"""
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
+ """
+ Get control widgets and attach them to Citation's attributes.
+ """
+ self.source_field = SourceEntry(self.dbstate, self.uistate, self.track,
+ self.glade.get_object("source"),
+ self.obj.set_reference_handle,
+ self.obj.get_reference_handle,
+ self.add_del_btn, self.share_btn,
+ callback=self.source_changed)
self.date = MonitoredDate(
self.glade.get_object("date_entry"),
@@ -259,7 +185,7 @@ class EditCitation(EditPrimary):
self.db.readonly)
self.gid = MonitoredEntry(
- self.glade.get_object('gid2'), self.obj.set_gramps_id,
+ self.glade.get_object('gid'), self.obj.set_gramps_id,
self.obj.get_gramps_id,self.db.readonly)
self.volume = MonitoredEntry(
@@ -279,105 +205,49 @@ class EditCitation(EditPrimary):
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.source, 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)
+ notebook = Gtk.Notebook()
- self.comment_tab = NoteTab(self.dbstate, self.uistate, self.track,
+ self.note_tab = NoteTab(self.dbstate, self.uistate, self.track,
self.obj.get_note_list(), self.get_menu_title(),
notetype=NoteType.CITATION)
- self._add_tab(notebook_ref, self.comment_tab)
- self.track_ref_for_deletion("comment_tab")
+ self._add_tab(notebook, self.note_tab)
+ self.track_ref_for_deletion("note_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._add_tab(notebook, 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._add_tab(notebook, self.data_tab)
self.track_ref_for_deletion("data_tab")
self.citationref_list = CitationBackRefList(self.dbstate, self.uistate,
self.track,
- self.db.find_backlink_handles(self.obj.handle),
- self.enable_warnbox2)
- self._add_tab(notebook_ref, self.citationref_list)
+ self.db.find_backlink_handles(self.obj.handle))
+ self._add_tab(notebook, 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=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")
-
- 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)
- self._setup_notebook_tabs(notebook_src)
- self._setup_notebook_tabs(notebook_ref)
+ notebook.show_all()
+ self.glade.get_object('vbox').pack_start(notebook, True, True, 0)
+
+ def source_changed(self):
+ handle = self.obj.get_reference_handle()
+ if handle:
+ source = self.db.get_source_from_handle(handle)
+ author = source.get_author()
+ else:
+ author = ''
+ self.glade.get_object("author").set_text(author)
def build_menu_names(self, source):
"""
@@ -387,12 +257,13 @@ class EditCitation(EditPrimary):
return (_('Edit Citation'), self.get_menu_title())
def save(self, *obj):
- """Save the data."""
+ """
+ Save the data.
+ """
self.ok_button.set_sensitive(False)
- if self.source_is_empty(self.source):
- ErrorDialog(_("Cannot save source"),
- _("No data exists for this source. Please "
- "enter data or cancel the edit."))
+ if not self.obj.get_reference_handle():
+ ErrorDialog(_("Cannot save citation. No source selected."),
+ _("Please select a source or cancel the edit."))
self.ok_button.set_sensitive(True)
return
@@ -410,78 +281,24 @@ class EditCitation(EditPrimary):
self.ok_button.set_sensitive(True)
return
- (uses_dupe_id, gramps_id) = self.source_uses_duplicate_id(self.source)
- if uses_dupe_id:
- prim_object = self.db.get_source_from_gramps_id(gramps_id)
- name = prim_object.get_title()
- msg1 = _("Cannot save source. ID already exists.")
- msg2 = _("You have attempted to use the existing Gramps ID with "
- "value %(gramps_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.") % {
- 'gramps_id' : gramps_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.set_reference_handle(self.source.handle)
-
- # Now commit the Citation Primary object
if not self.obj.get_handle():
self.db.add_citation(self.obj, trans)
- msg += "\n" + _("Add Citation (%s)") % self.obj.get_page()
+ 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()
+ msg = _("Edit Citation (%s)") % self.obj.get_page()
trans.set_description(msg)
if self.callback:
self.callback(self.obj.get_handle())
self.close()
- def source_is_empty(self, obj):
- empty_object = Source()
- return obj.serialize()[1:] == empty_object.serialize()[1:]
-
- def source_uses_duplicate_id(self, obj):
- """
- Check whether a changed or added GRAMPS ID already exists in the DB.
-
- Return True if a duplicate GRAMPS ID has been detected.
-
- """
- original = self.db.get_source_from_handle(obj.get_handle())
- if original and original.get_gramps_id() == obj.get_gramps_id():
- return (False, 0)
- else:
- idval = obj.get_gramps_id()
- if self.db.get_source_from_gramps_id(idval):
- return (True, idval)
- return (False, 0)
-
def data_has_changed(self):
- return self.citation_data_has_changed() or \
- self.source_data_has_changed()
-
- def citation_data_has_changed(self):
"""
- This checks whether the citation data has changed
-
A date comparison can fail incorrectly because we have made the
decision to store entered text in the date. However, there is no
entered date when importing from a XML file, so we can get an
@@ -500,23 +317,6 @@ class EditCitation(EditPrimary):
cmp_obj = self.empty_object()
return cmp_obj.serialize(True)[1:] != self.obj.serialize()[1:]
- def source_data_has_changed(self):
- """
- This checks whether the source data has changed
- """
- if self.db.readonly:
- return False
- elif self.source.handle:
- orig = self.db.get_source_from_handle(self.source.handle)
- if orig:
- cmp_obj = orig
- else:
- cmp_obj = Source()
- return cmp_obj.serialize()[1:] != self.source.serialize()[1:]
- else:
- cmp_obj = Source()
- return cmp_obj.serialize()[1:] != self.source.serialize()[1:]
-
class DeleteCitationQuery(object):
def __init__(self, dbstate, uistate, citation, the_lists):
self.citation = citation
diff --git a/gramps/gui/editors/editsource.py b/gramps/gui/editors/editsource.py
index 59c627cb0..f33d26c64 100644
--- a/gramps/gui/editors/editsource.py
+++ b/gramps/gui/editors/editsource.py
@@ -67,7 +67,7 @@ class EditSource(EditPrimary):
EditPrimary.__init__(self, dbstate, uistate, track, source,
dbstate.db.get_source_from_handle,
- dbstate.db.get_source_from_gramps_id)
+ dbstate.db.get_source_from_gramps_id, callback)
def empty_object(self):
return Source()
@@ -211,6 +211,8 @@ class EditSource(EditPrimary):
trans.set_description(msg)
self.close()
+ if self.callback:
+ self.callback(self.obj)
class DeleteSrcQuery(object):
def __init__(self, dbstate, uistate, source, the_lists):
diff --git a/gramps/gui/editors/objectentries.py b/gramps/gui/editors/objectentries.py
index e601ebdd4..b7c4355a7 100644
--- a/gramps/gui/editors/objectentries.py
+++ b/gramps/gui/editors/objectentries.py
@@ -50,8 +50,9 @@ from gi.repository import Pango
# Gramps modules
#
#-------------------------------------------------------------------------
-from gramps.gen.lib import (Place, MediaObject, Note)
+from gramps.gen.lib import (Place, Source, MediaObject, Note)
from .editplace import EditPlace
+from .editsource import EditSource
from .editmedia import EditMedia
from .editnote import EditNote
from ..selectors import SelectorFactory
@@ -77,7 +78,7 @@ class ObjEntry(object):
DEL_STR = ""
def __init__(self, dbstate, uistate, track, label, set_val,
- get_val, add_edt, share):
+ get_val, add_edt, share, callback=None):
"""Pass the dbstate and uistate and present track.
label is a Gtk.Label that shows the persent value
set_val is function that is called when handle changes, use it
@@ -97,6 +98,7 @@ class ObjEntry(object):
self.set_val = set_val
self.uistate = uistate
self.track = track
+ self.callback = callback
#connect drag and drop
self._init_dnd()
@@ -140,6 +142,8 @@ class ObjEntry(object):
else:
self.label.set_text(name)
self.label.set_ellipsize(Pango.EllipsizeMode.END)
+ if self.callback:
+ self.callback()
def _init_dnd(self):
"""inheriting objects must set this
@@ -166,6 +170,8 @@ class ObjEntry(object):
def after_edit(self, obj):
name = self.get_label(obj)
self.label.set_text(name)
+ if self.callback:
+ self.callback()
def add_edt_clicked(self, obj):
""" if value, edit, if no value, call editor on new object
@@ -197,6 +203,8 @@ class ObjEntry(object):
self.set_val(data.handle)
self.label.set_text(self.get_label(data))
self.set_button(True)
+ if self.callback:
+ self.callback()
def share_clicked(self, obj):
""" if value, delete connect, in no value, select existing object
@@ -206,6 +214,8 @@ class ObjEntry(object):
self.label.set_text(self.EMPTY_TEXT)
self.label.set_use_markup(True)
self.set_button(False)
+ if self.callback:
+ self.callback()
else:
select = self.call_selector()
obj = select.run()
@@ -300,6 +310,60 @@ class PlaceEntry(ObjEntry):
cls = SelectorFactory('Place')
return cls(self.dbstate, self.uistate, self.track)
+class SourceEntry(ObjEntry):
+ """
+ Handles the selection of a existing or new Source. Supports Drag and Drop
+ to select a source.
+ """
+ EMPTY_TEXT = "%s" % _('To select a source, use drag-and-drop '
+ 'or use the buttons')
+ EMPTY_TEXT_RED = "%s" % _('No place given, click button to select one')
+ EDIT_STR = _('Edit source')
+ SHARE_STR = _('Select an existing source')
+ ADD_STR = _('Add a new source')
+ DEL_STR = _('Remove source')
+
+ def __init__(self, dbstate, uistate, track, label, set_val,
+ get_val, add_edt, share, callback):
+ ObjEntry.__init__(self, dbstate, uistate, track, label, set_val,
+ get_val, add_edt, share, callback)
+
+ def _init_dnd(self):
+ """connect drag and drop of sources
+ """
+ self.label.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
+ tglist = Gtk.TargetList.new([])
+ tglist.add(DdTargets.PLACE_LINK.atom_drag_type,
+ DdTargets.PLACE_LINK.target_flags,
+ DdTargets.PLACE_LINK.app_id)
+ self.label.drag_dest_set_target_list(tglist)
+ self.label.connect('drag_data_received', self.drag_data_received)
+
+ def get_from_handle(self, handle):
+ """ return the object given the handle
+ """
+ return self.db.get_source_from_handle(handle)
+
+ def get_label(self, source):
+ return "%s [%s]" % (source.get_title(), source.gramps_id)
+
+ def call_editor(self, obj=None):
+ if obj is None:
+ source = Source()
+ func = self.obj_added
+ else:
+ source = obj
+ func = self.after_edit
+ try:
+ EditSource(self.dbstate, self.uistate, self.track,
+ source, func)
+ except WindowActiveError:
+ pass
+
+ def call_selector(self):
+ cls = SelectorFactory('Source')
+ return cls(self.dbstate, self.uistate, self.track)
+
# FIXME isn't used anywhere
class MediaEntry(ObjEntry):
"""
diff --git a/gramps/gui/glade/editcitation.glade b/gramps/gui/glade/editcitation.glade
index 96173c944..e96a063e2 100644
--- a/gramps/gui/glade/editcitation.glade
+++ b/gramps/gui/glade/editcitation.glade
@@ -1,6 +1,7 @@
+