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