1# Gramps - a GTK+/GNOME based genealogy program 2# 3# Copyright (C) 2001-2006 Donald N. Allingham 4# Copyright (C) 2010 Nick Hall 5# Copyright (C) 2011 Tim G L Lyons 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20# 21 22""" 23Family View. 24""" 25 26#------------------------------------------------------------------------- 27# 28# Standard python modules 29# 30#------------------------------------------------------------------------- 31from gramps.gen.const import GRAMPS_LOCALE as glocale 32_ = glocale.translation.sgettext 33import logging 34_LOG = logging.getLogger(".plugins.eventview") 35#------------------------------------------------------------------------- 36# 37# GNOME/GTK+ modules 38# 39#------------------------------------------------------------------------- 40from gi.repository import Gtk 41 42#------------------------------------------------------------------------- 43# 44# gramps modules 45# 46#------------------------------------------------------------------------- 47from gramps.gen.lib import Family 48from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON 49from gramps.gui.views.treemodels import FamilyModel 50from gramps.gui.editors import EditFamily 51from gramps.gui.views.bookmarks import FamilyBookmarks 52from gramps.gen.errors import WindowActiveError 53from gramps.gen.config import config 54from gramps.gui.dialog import ErrorDialog 55from gramps.gui.filters.sidebar import FamilySidebarFilter 56from gramps.gui.merge import MergeFamily 57from gramps.gen.plug import CATEGORY_QR_FAMILY 58from gramps.gui.ddtargets import DdTargets 59 60#------------------------------------------------------------------------- 61# 62# FamilyView 63# 64#------------------------------------------------------------------------- 65class FamilyView(ListView): 66 """ FamilyView class, derived from the ListView 67 """ 68 # columns in the model used in view 69 COL_ID = 0 70 COL_FATHER = 1 71 COL_MOTHER = 2 72 COL_REL = 3 73 COL_MARDATE = 4 74 COL_PRIV = 5 75 COL_TAGS = 6 76 COL_CHAN = 7 77 # column definitions 78 COLUMNS = [ 79 (_('ID'), TEXT, None), 80 (_('Father'), TEXT, None), 81 (_('Mother'), TEXT, None), 82 (_('Relationship'), TEXT, None), 83 (_('Marriage Date'), MARKUP, None), 84 (_('Private'), ICON, 'gramps-lock'), 85 (_('Tags'), TEXT, None), 86 (_('Last Changed'), TEXT, None), 87 ] 88 #default setting with visible columns, order of the col, and their size 89 CONFIGSETTINGS = ( 90 ('columns.visible', [COL_ID, COL_FATHER, COL_MOTHER, COL_REL, 91 COL_MARDATE]), 92 ('columns.rank', [COL_ID, COL_FATHER, COL_MOTHER, COL_REL, 93 COL_MARDATE, COL_PRIV, COL_TAGS, COL_CHAN]), 94 ('columns.size', [75, 200, 200, 100, 100, 40, 100, 100]) 95 ) 96 97 ADD_MSG = _("Add a new family") 98 EDIT_MSG = _("Edit the selected family") 99 DEL_MSG = _("Delete the selected family") 100 MERGE_MSG = _("Merge the selected families") 101 FILTER_TYPE = "Family" 102 QR_CATEGORY = CATEGORY_QR_FAMILY 103 104 def __init__(self, pdata, dbstate, uistate, nav_group=0): 105 106 signal_map = { 107 'family-add' : self.row_add, 108 'family-update' : self.row_update, 109 'family-delete' : self.row_delete, 110 'family-rebuild' : self.object_build, 111 'event-update' : self.related_update, 112 'person-update' : self.related_update, 113 } 114 115 ListView.__init__( 116 self, _('Families'), pdata, dbstate, uistate, 117 FamilyModel, 118 signal_map, 119 FamilyBookmarks, nav_group, 120 multiple=True, 121 filter_class=FamilySidebarFilter) 122 123 uistate.connect('nameformat-changed', self.build_tree) 124 125 self.additional_uis.append(self.additional_ui) 126 127 def navigation_type(self): 128 return 'Family' 129 130 def get_stock(self): 131 return 'gramps-family' 132 133 additional_ui = [ # Defines the UI string for UIManager 134 ''' 135 <placeholder id="LocalExport"> 136 <item> 137 <attribute name="action">win.ExportTab</attribute> 138 <attribute name="label" translatable="yes">Export View...</attribute> 139 </item> 140 </placeholder> 141''', 142 ''' 143 <section id="AddEditBook"> 144 <item> 145 <attribute name="action">win.AddBook</attribute> 146 <attribute name="label" translatable="yes">_Add Bookmark</attribute> 147 </item> 148 <item> 149 <attribute name="action">win.EditBook</attribute> 150 <attribute name="label" translatable="no">%s...</attribute> 151 </item> 152 </section> 153''' % _('Organize Bookmarks'), 154 ''' 155 <placeholder id="CommonGo"> 156 <section> 157 <item> 158 <attribute name="action">win.Back</attribute> 159 <attribute name="label" translatable="yes">_Back</attribute> 160 </item> 161 <item> 162 <attribute name="action">win.Forward</attribute> 163 <attribute name="label" translatable="yes">_Forward</attribute> 164 </item> 165 </section> 166 </placeholder> 167''', 168 ''' 169 <section id='CommonEdit' groups='RW'> 170 <item> 171 <attribute name="action">win.Add</attribute> 172 <attribute name="label" translatable="yes">_Add...</attribute> 173 </item> 174 <item> 175 <attribute name="action">win.Edit</attribute> 176 <attribute name="label">%s</attribute> 177 </item> 178 <item> 179 <attribute name="action">win.Remove</attribute> 180 <attribute name="label" translatable="yes">_Delete</attribute> 181 </item> 182 <item> 183 <attribute name="action">win.Merge</attribute> 184 <attribute name="label" translatable="yes">_Merge...</attribute> 185 </item> 186 </section> 187''' % _("action|_Edit..."), # to use sgettext() 188 ''' 189 <placeholder id='otheredit'> 190 <item> 191 <attribute name="action">win.FilterEdit</attribute> 192 <attribute name="label" translatable="yes">''' 193 '''Family Filter Editor</attribute> 194 </item> 195 </placeholder> 196''', # Following are the Toolbar items 197 ''' 198 <placeholder id='CommonNavigation'> 199 <child groups='RO'> 200 <object class="GtkToolButton"> 201 <property name="icon-name">go-previous</property> 202 <property name="action-name">win.Back</property> 203 <property name="tooltip_text" translatable="yes">''' 204 '''Go to the previous object in the history</property> 205 <property name="label" translatable="yes">_Back</property> 206 <property name="use-underline">True</property> 207 </object> 208 <packing> 209 <property name="homogeneous">False</property> 210 </packing> 211 </child> 212 <child groups='RO'> 213 <object class="GtkToolButton"> 214 <property name="icon-name">go-next</property> 215 <property name="action-name">win.Forward</property> 216 <property name="tooltip_text" translatable="yes">''' 217 '''Go to the next object in the history</property> 218 <property name="label" translatable="yes">_Forward</property> 219 <property name="use-underline">True</property> 220 </object> 221 <packing> 222 <property name="homogeneous">False</property> 223 </packing> 224 </child> 225 </placeholder> 226''', 227 ''' 228 <placeholder id='BarCommonEdit'> 229 <child groups='RW'> 230 <object class="GtkToolButton"> 231 <property name="icon-name">list-add</property> 232 <property name="action-name">win.Add</property> 233 <property name="tooltip_text">%s</property> 234 <property name="label" translatable="yes">_Add...</property> 235 <property name="use-underline">True</property> 236 </object> 237 <packing> 238 <property name="homogeneous">False</property> 239 </packing> 240 </child> 241 <child groups='RW'> 242 <object class="GtkToolButton"> 243 <property name="icon-name">gtk-edit</property> 244 <property name="action-name">win.Edit</property> 245 <property name="tooltip_text">%s</property> 246 <property name="label" translatable="yes">Edit...</property> 247 <property name="use-underline">True</property> 248 </object> 249 <packing> 250 <property name="homogeneous">False</property> 251 </packing> 252 </child> 253 <child groups='RW'> 254 <object class="GtkToolButton"> 255 <property name="icon-name">list-remove</property> 256 <property name="action-name">win.Remove</property> 257 <property name="tooltip_text">%s</property> 258 <property name="label" translatable="yes">_Delete</property> 259 <property name="use-underline">True</property> 260 </object> 261 <packing> 262 <property name="homogeneous">False</property> 263 </packing> 264 </child> 265 <child groups='RW'> 266 <object class="GtkToolButton"> 267 <property name="icon-name">gramps-merge</property> 268 <property name="action-name">win.Merge</property> 269 <property name="tooltip_text">%s</property> 270 <property name="label" translatable="yes">_Merge...</property> 271 <property name="use-underline">True</property> 272 </object> 273 <packing> 274 <property name="homogeneous">False</property> 275 </packing> 276 </child> 277 </placeholder> 278''' % (ADD_MSG, EDIT_MSG, DEL_MSG, MERGE_MSG), 279 ''' 280 <menu id="Popup"> 281 <section> 282 <item> 283 <attribute name="action">win.Back</attribute> 284 <attribute name="label" translatable="yes">_Back</attribute> 285 </item> 286 <item> 287 <attribute name="action">win.Forward</attribute> 288 <attribute name="label" translatable="yes">Forward</attribute> 289 </item> 290 </section> 291 <section id="PopUpTree"> 292 </section> 293 <section> 294 <item> 295 <attribute name="action">win.Add</attribute> 296 <attribute name="label" translatable="yes">_Add...</attribute> 297 </item> 298 <item> 299 <attribute name="action">win.Edit</attribute> 300 <attribute name="label">%s</attribute> 301 </item> 302 <item> 303 <attribute name="action">win.Remove</attribute> 304 <attribute name="label" translatable="yes">_Delete</attribute> 305 </item> 306 <item> 307 <attribute name="action">win.Merge</attribute> 308 <attribute name="label" translatable="yes">_Merge...</attribute> 309 </item> 310 </section> 311 <section> 312 <item> 313 <attribute name="action">win.MakeFatherActive</attribute> 314 <attribute name="label" translatable="yes">''' 315 '''Make Father Active Person</attribute> 316 </item> 317 <item> 318 <attribute name="action">win.MakeMotherActive</attribute> 319 <attribute name="label" translatable="yes">''' 320 '''Make Mother Active Person</attribute> 321 </item> 322 </section> 323 <section> 324 <placeholder id='QuickReport'> 325 </placeholder> 326 </section> 327 </menu> 328''' % _('action|_Edit...') # to use sgettext() 329 ] 330 331 def define_actions(self): 332 """Add the Forward action group to handle the Forward button.""" 333 334 ListView.define_actions(self) 335 336 self.action_list.extend([ 337 ('MakeFatherActive', self._make_father_active), 338 ('MakeMotherActive', self._make_mother_active), ]) 339 340 def add_bookmark(self, *obj): 341 mlist = self.selected_handles() 342 if mlist: 343 self.bookmarks.add(mlist[0]) 344 else: 345 from gramps.gui.dialog import WarningDialog 346 WarningDialog( 347 _("Could Not Set a Bookmark"), 348 _("A bookmark could not be set because " 349 "no one was selected."), parent=self.uistate.window) 350 351 def add(self, *obj): 352 family = Family() 353 try: 354 EditFamily(self.dbstate, self.uistate, [], family) 355 except WindowActiveError: 356 pass 357 358 def remove(self, *obj): 359 """ 360 Method called when deleting a family from a family view. 361 """ 362 from gramps.gui.dialog import QuestionDialog, MultiSelectDialog 363 from gramps.gen.utils.string import data_recover_msg 364 handles = self.selected_handles() 365 if len(handles) == 1: 366 family = self.dbstate.db.get_family_from_handle(handles[0]) 367 msg1 = self._message1_format(family) 368 msg2 = self._message2_format(family) 369 msg2 = "%s %s" % (msg2, data_recover_msg) 370 QuestionDialog(msg1, 371 msg2, 372 _('_Delete Family'), 373 lambda: self.delete_family_response(family), 374 parent=self.uistate.window) 375 else: 376 MultiSelectDialog(self._message1_format, 377 self._message2_format, 378 handles, 379 self.dbstate.db.get_family_from_handle, 380 yes_func=self.delete_family_response, 381 parent=self.uistate.window) 382 383 def _message1_format(self, family): 384 """ 385 Header format for remove dialogs. 386 """ 387 return _('Delete %s?') % (_('family') + 388 (" [%s]" % family.gramps_id)) 389 390 def _message2_format(self, family): 391 """ 392 Detailed message format for the remove dialogs. 393 """ 394 return _('Deleting item will remove it from the database.') 395 396 def delete_family_response(self, family): 397 """ 398 Deletes the family from the database. Callback to remove 399 dialogs. 400 """ 401 from gramps.gen.db import DbTxn 402 # set the busy cursor, so the user knows that we are working 403 self.uistate.set_busy_cursor(True) 404 # create the transaction 405 with DbTxn('', self.dbstate.db) as trans: 406 gramps_id = family.gramps_id 407 self.dbstate.db.remove_family_relationships(family.handle, trans) 408 trans.set_description(_("Family [%s]") % gramps_id) 409 self.uistate.set_busy_cursor(False) 410 411 def remove_object_from_handle(self, handle): 412 """ 413 The remove_selected_objects method is not called in this view. 414 """ 415 pass 416 417 def edit(self, *obj): 418 for handle in self.selected_handles(): 419 family = self.dbstate.db.get_family_from_handle(handle) 420 try: 421 EditFamily(self.dbstate, self.uistate, [], family) 422 except WindowActiveError: 423 pass 424 425 def merge(self, *obj): 426 """ 427 Merge the selected families. 428 """ 429 mlist = self.selected_handles() 430 431 if len(mlist) != 2: 432 msg = _("Cannot merge families.") 433 msg2 = _("Exactly two families must be selected to perform a merge." 434 " A second family can be selected by holding down the " 435 "control key while clicking on the desired family.") 436 ErrorDialog(msg, msg2, parent=self.uistate.window) 437 else: 438 MergeFamily(self.dbstate, self.uistate, [], mlist[0], mlist[1]) 439 440 def _make_father_active(self, *obj): 441 """ 442 Make the father of the family the active person. 443 """ 444 fhandle = self.first_selected() 445 if fhandle: 446 family = self.dbstate.db.get_family_from_handle(fhandle) 447 if family: 448 self.uistate.set_active(family.father_handle, 'Person') 449 450 def _make_mother_active(self, *obj): 451 """ 452 Make the mother of the family the active person. 453 """ 454 fhandle = self.first_selected() 455 if fhandle: 456 family = self.dbstate.db.get_family_from_handle(fhandle) 457 if family: 458 self.uistate.set_active(family.mother_handle, 'Person') 459 460 def drag_info(self): 461 """ 462 Indicate that the drag type is a FAMILY_LINK 463 """ 464 return DdTargets.FAMILY_LINK 465 466 def tag_updated(self, handle_list): 467 """ 468 Update tagged rows when a tag color changes. 469 """ 470 all_links = set([]) 471 for tag_handle in handle_list: 472 links = set([link[1] for link in 473 self.dbstate.db.find_backlink_handles(tag_handle, 474 include_classes='Family')]) 475 all_links = all_links.union(links) 476 self.row_update(list(all_links)) 477 478 def add_tag(self, transaction, family_handle, tag_handle): 479 """ 480 Add the given tag to the given family. 481 """ 482 family = self.dbstate.db.get_family_from_handle(family_handle) 483 family.add_tag(tag_handle) 484 self.dbstate.db.commit_family(family, transaction) 485 486 def get_default_gramplets(self): 487 """ 488 Define the default gramplets for the sidebar and bottombar. 489 """ 490 return (("Family Filter",), 491 ("Family Gallery", 492 "Family Events", 493 "Family Children", 494 "Family Citations", 495 "Family Notes", 496 "Family Attributes", 497 "Family Backlinks")) 498