1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2000-2007  Donald N. Allingham
5# Copyright (C) 2008       Brian G. Matherly
6# Copyright (C) 2010       Jakim Friant
7# Copyright (C) 2011       Tim G L Lyons
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22#
23
24"""Tools/Family Tree Processing/MergeCitations"""
25
26#------------------------------------------------------------------------
27#
28# Python modules
29#
30#------------------------------------------------------------------------
31import logging
32LOG = logging.getLogger(".citation")
33
34#-------------------------------------------------------------------------
35#
36# GNOME libraries
37#
38#-------------------------------------------------------------------------
39from gi.repository import Gtk
40
41#-------------------------------------------------------------------------
42#
43# Gramps modules
44#
45#-------------------------------------------------------------------------
46from gramps.gen.const import GRAMPS_LOCALE as glocale
47_ = glocale.translation.sgettext
48ngettext = glocale.translation.ngettext # else "nearby" comments are ignored
49from gramps.gen.utils.string import conf_strings
50from gramps.gen.const import URL_MANUAL_PAGE
51from gramps.gui.utils import ProgressMeter
52from gramps.gui.plug import tool
53from gramps.gui.dialog import OkDialog
54from gramps.gui.display import display_help
55from gramps.gen.datehandler import get_date
56from gramps.gui.managedwindow import ManagedWindow
57from gramps.gen.merge import MergeCitationQuery
58
59from gramps.gui.glade import Glade
60from gramps.gen.db import DbTxn
61from gramps.gen.lib import (Person, Family, Event, Place, Media, Citation,
62                     Repository)
63from gramps.gen.errors import MergeError
64
65#-------------------------------------------------------------------------
66#
67# Constants
68#
69#-------------------------------------------------------------------------
70ALL_FIELDS = 0
71IGNORE_DATE = 1
72IGNORE_CONFIDENCE = 2
73IGNORE_BOTH = 3
74
75_val2label = {
76    ALL_FIELDS        : _("Match on Page/Volume, Date and Confidence"),
77    IGNORE_DATE       : _("Ignore Date"),
78    IGNORE_CONFIDENCE : _("Ignore Confidence"),
79    IGNORE_BOTH       : _("Ignore Date and Confidence")
80    }
81
82WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE
83WIKI_HELP_SEC = _('manual|Merge_citations')
84
85#-------------------------------------------------------------------------
86#
87# The Actual tool.
88#
89#-------------------------------------------------------------------------
90class MergeCitations(tool.BatchTool,ManagedWindow):
91
92    def __init__(self, dbstate, user, options_class, name, callback=None):
93        uistate = user.uistate
94        self.user = user
95
96        ManagedWindow.__init__(self, uistate, [], self.__class__)
97        self.dbstate = dbstate
98        self.set_window(Gtk.Window(), Gtk.Label(), '')
99
100        tool.BatchTool.__init__(self, dbstate, user, options_class, name)
101
102        if not self.fail:
103            uistate.set_busy_cursor(True)
104            self.run()
105            uistate.set_busy_cursor(False)
106
107    def run(self):
108
109        top = Glade(toplevel="mergecitations",  also_load=["liststore1"])
110
111        # retrieve options
112        fields = self.options.handler.options_dict['fields']
113        dont_merge_notes = self.options.handler.options_dict['dont_merge_notes']
114
115        my_menu = Gtk.ListStore(str, object)
116        for val in sorted(_val2label):
117            my_menu.append([_val2label[val], val])
118
119        self.notes_obj = top.get_object("notes")
120        self.notes_obj.set_active(dont_merge_notes)
121        self.notes_obj.show()
122
123        self.menu = top.get_object("menu")
124        self.menu.set_model(my_menu)
125        self.menu.set_active(fields)
126
127        window = top.toplevel
128        window.set_transient_for(self.user.uistate.window)
129        window.show()
130#        self.set_window(window, top.get_object('title'),
131#                        _('Merge citations'))
132        self.set_window(window, top.get_object('title2'),
133                        _("Notes, media objects and data-items of matching "
134                          "citations will be combined."))
135        self.setup_configs('interface.mergecitations', 700, 230)
136
137        top.connect_signals({
138            "on_merge_ok_clicked"   : self.on_merge_ok_clicked,
139            "destroy_passed_object" : self.cancel,
140            "on_help_clicked"       : self.on_help_clicked,
141            "on_delete_merge_event" : self.close,
142            "on_delete_event"       : self.close,
143            })
144
145        self.show()
146
147    def cancel(self, obj):
148        """
149        on cancel, update the saved values of the options.
150        """
151        fields = self.menu.get_model()[self.menu.get_active()][1]
152        dont_merge_notes = int(self.notes_obj.get_active())
153        LOG.debug("cancel fields %d dont_merge_notes %d" %
154                  (fields, dont_merge_notes))
155
156        self.options.handler.options_dict['fields'] = fields
157        self.options.handler.options_dict['dont_merge_notes'] = dont_merge_notes
158        # Save options
159        self.options.handler.save_options()
160
161        self.close(obj)
162
163    def build_menu_names(self, obj):
164        return (_("Tool settings"),_("Merge citations tool"))
165
166    def on_help_clicked(self, obj):
167        """Display the relevant portion of Gramps manual"""
168
169        display_help(WIKI_HELP_PAGE , WIKI_HELP_SEC)
170
171    def on_merge_ok_clicked(self, obj):
172        """
173        Performs the actual merge of citations
174        (Derived from ExtractCity)
175        """
176        fields = self.menu.get_model()[self.menu.get_active()][1]
177        dont_merge_notes = int(self.notes_obj.get_active())
178        LOG.debug("fields %d dont_merge_notes %d" % (fields, dont_merge_notes))
179
180        self.options.handler.options_dict['fields'] = fields
181        self.options.handler.options_dict['dont_merge_notes'] = dont_merge_notes
182        # Save options
183        self.options.handler.save_options()
184
185        self.progress = ProgressMeter(_('Checking Sources'), '',
186                                      parent=self.window)
187        self.progress.set_pass(_('Looking for citation fields'),
188                               self.db.get_number_of_citations())
189
190        db = self.dbstate.db
191
192        db.disable_signals()
193        num_merges = 0
194        for handle in db.iter_source_handles():
195            dict = {}
196            citation_handle_list = list(db.find_backlink_handles(handle))
197            for (class_name, citation_handle) in citation_handle_list:
198                if class_name != Citation.__name__:
199                    raise MergeError("Encountered an object of type %s "
200                    "that has a citation reference." % class_name)
201
202                citation = db.get_citation_from_handle(citation_handle)
203                if citation is None:
204                    continue
205                key = citation.get_page()
206                if fields != IGNORE_DATE and fields != IGNORE_BOTH:
207                    key += "\n" + get_date(citation)
208                if fields != IGNORE_CONFIDENCE and fields != IGNORE_BOTH:
209                    key += "\n" + \
210                        conf_strings[citation.get_confidence_level()]
211                if key in dict and \
212                    (not dont_merge_notes or len(citation.note_list) == 0):
213                    citation_match_handle = dict[key]
214                    citation_match = \
215                        db.get_citation_from_handle(citation_match_handle)
216                    try:
217                        query = MergeCitationQuery(
218                                self.dbstate, citation_match, citation)
219                        query.execute()
220                    except AssertionError:
221                        print("Tool/Family Tree processing/MergeCitations", \
222                        "citation1 gramps_id", citation_match.get_gramps_id(), \
223                        "citation2 gramps_id", citation.get_gramps_id() , \
224                        "citation backlink handles", \
225                        list(db.find_backlink_handles(citation.get_handle())))
226                    num_merges += 1
227                elif (not dont_merge_notes or len(citation.note_list) == 0):
228                    dict[key] = citation_handle
229                self.progress.step()
230        db.enable_signals()
231        db.request_rebuild()
232        self.progress.close()
233        OkDialog(_("Number of merges done"),
234                 # translators: leave all/any {...} untranslated
235                 ngettext("{number_of} citation merged",
236                          "{number_of} citations merged", num_merges
237                         ).format(number_of=num_merges),
238                 parent=self.window)
239        self.close(obj)
240
241#------------------------------------------------------------------------
242#
243#
244#
245#------------------------------------------------------------------------
246class MergeCitationsOptions(tool.ToolOptions):
247    """
248    Defines options and provides handling interface.
249    """
250
251    def __init__(self, name,person_id=None):
252        tool.ToolOptions.__init__(self, name,person_id)
253
254        # Options specific for this report
255        self.options_dict = {
256            'fields'   : 1,
257            'dont_merge_notes' : 0,
258        }
259        self.options_help = {
260            'dont_merge_notes'   :
261                ("=0/1","Whether to merge citations if they have notes",
262                 ["Merge citations with notes",
263                  "Do not merge citations with notes"],
264                 False),
265            'fields' : ("=num","Threshold for matching",
266                           "Integer number")
267            }
268