1# Gramps - a GTK+/GNOME based genealogy program 2# 3# Copyright (C) 2001-2007 Donald N. Allingham 4# Copyright (C) 2009-2010 Gary Burton 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 20 21""" 22Relationship View 23""" 24 25#------------------------------------------------------------------------- 26# 27# Python modules 28# 29#------------------------------------------------------------------------- 30from html import escape 31import pickle 32 33#------------------------------------------------------------------------- 34# 35# Set up logging 36# 37#------------------------------------------------------------------------- 38import logging 39_LOG = logging.getLogger("plugin.relview") 40 41#------------------------------------------------------------------------- 42# 43# GTK/Gnome modules 44# 45#------------------------------------------------------------------------- 46from gi.repository import Gdk 47from gi.repository import Gtk 48from gi.repository import Pango 49 50#------------------------------------------------------------------------- 51# 52# Gramps Modules 53# 54#------------------------------------------------------------------------- 55from gramps.gen.const import GRAMPS_LOCALE as glocale 56_ = glocale.translation.sgettext 57ngettext = glocale.translation.ngettext # else "nearby" comments are ignored 58from gramps.gen.lib import (ChildRef, EventRoleType, EventType, Family, 59 FamilyRelType, Name, Person, Surname) 60from gramps.gen.lib.date import Today 61from gramps.gen.db import DbTxn 62from gramps.gui.views.navigationview import NavigationView 63from gramps.gui.uimanager import ActionGroup 64from gramps.gui.editors import EditPerson, EditFamily 65from gramps.gui.editors import FilterEditor 66from gramps.gen.display.name import displayer as name_displayer 67from gramps.gen.display.place import displayer as place_displayer 68from gramps.gen.utils.file import media_path_full 69from gramps.gen.utils.alive import probably_alive 70from gramps.gui.utils import open_file_with_default_application 71from gramps.gen.datehandler import displayer, get_date 72from gramps.gen.utils.thumbnails import get_thumbnail_image 73from gramps.gen.config import config 74from gramps.gui import widgets 75from gramps.gui.widgets.reorderfam import Reorder 76from gramps.gui.selectors import SelectorFactory 77from gramps.gen.errors import WindowActiveError 78from gramps.gui.views.bookmarks import PersonBookmarks 79from gramps.gen.const import CUSTOM_FILTERS 80from gramps.gen.utils.db import (get_birth_or_fallback, get_death_or_fallback, 81 preset_name) 82from gramps.gui.ddtargets import DdTargets 83from gramps.gen.utils.symbols import Symbols 84 85_NAME_START = 0 86_LABEL_START = 0 87_LABEL_STOP = 1 88_DATA_START = _LABEL_STOP 89_DATA_STOP = _DATA_START+1 90_BTN_START = _DATA_STOP 91_BTN_STOP = _BTN_START+2 92_PLABEL_START = 1 93_PLABEL_STOP = _PLABEL_START+1 94_PDATA_START = _PLABEL_STOP 95_PDATA_STOP = _PDATA_START+2 96_PDTLS_START = _PLABEL_STOP 97_PDTLS_STOP = _PDTLS_START+2 98_CLABEL_START = _PLABEL_START+1 99_CLABEL_STOP = _CLABEL_START+1 100_CDATA_START = _CLABEL_STOP 101_CDATA_STOP = _CDATA_START+1 102_CDTLS_START = _CDATA_START 103_CDTLS_STOP = _CDTLS_START+1 104_ALABEL_START = 0 105_ALABEL_STOP = _ALABEL_START+1 106_ADATA_START = _ALABEL_STOP 107_ADATA_STOP = _ADATA_START+3 108_SDATA_START = 2 109_SDATA_STOP = 4 110_RETURN = Gdk.keyval_from_name("Return") 111_KP_ENTER = Gdk.keyval_from_name("KP_Enter") 112_SPACE = Gdk.keyval_from_name("space") 113_LEFT_BUTTON = 1 114_RIGHT_BUTTON = 3 115 116class RelationshipView(NavigationView): 117 """ 118 View showing a textual representation of the relationships of the 119 active person 120 """ 121 #settings in the config file 122 CONFIGSETTINGS = ( 123 ('preferences.family-siblings', True), 124 ('preferences.family-details', True), 125 ('preferences.relation-display-theme', "CLASSIC"), 126 ('preferences.relation-shade', True), 127 ('preferences.releditbtn', True), 128 ) 129 130 def __init__(self, pdata, dbstate, uistate, nav_group=0): 131 NavigationView.__init__(self, _('Relationships'), 132 pdata, dbstate, uistate, 133 PersonBookmarks, 134 nav_group) 135 136 dbstate.connect('database-changed', self.change_db) 137 uistate.connect('nameformat-changed', self.build_tree) 138 uistate.connect('placeformat-changed', self.build_tree) 139 uistate.connect('font-changed', self.font_changed) 140 self.redrawing = False 141 142 self.child = None 143 self.old_handle = None 144 145 self.reorder_sensitive = False 146 self.collapsed_items = {} 147 148 self.additional_uis.append(self.additional_ui) 149 150 self.show_siblings = self._config.get('preferences.family-siblings') 151 self.show_details = self._config.get('preferences.family-details') 152 self.use_shade = self._config.get('preferences.relation-shade') 153 self.theme = self._config.get('preferences.relation-display-theme') 154 self.toolbar_visible = config.get('interface.toolbar-on') 155 self.age_precision = config.get('preferences.age-display-precision') 156 self.symbols = Symbols() 157 self.reload_symbols() 158 159 def get_handle_from_gramps_id(self, gid): 160 """ 161 returns the handle of the specified object 162 """ 163 obj = self.dbstate.db.get_person_from_gramps_id(gid) 164 if obj: 165 return obj.get_handle() 166 else: 167 return None 168 169 def _connect_db_signals(self): 170 """ 171 implement from base class DbGUIElement 172 Register the callbacks we need. 173 """ 174 # Add a signal to pick up event changes, bug #1416 175 self.callman.add_db_signal('event-update', self.family_update) 176 177 self.callman.add_db_signal('person-update', self.person_update) 178 self.callman.add_db_signal('person-rebuild', self.person_rebuild) 179 self.callman.add_db_signal('family-update', self.family_update) 180 self.callman.add_db_signal('family-add', self.family_add) 181 self.callman.add_db_signal('family-delete', self.family_delete) 182 self.callman.add_db_signal('family-rebuild', self.family_rebuild) 183 184 self.callman.add_db_signal('person-delete', self.redraw) 185 186 def reload_symbols(self): 187 if self.uistate and self.uistate.symbols: 188 gsfs = self.symbols.get_symbol_for_string 189 self.male = gsfs(self.symbols.SYMBOL_MALE) 190 self.female = gsfs(self.symbols.SYMBOL_FEMALE) 191 self.bth = gsfs(self.symbols.SYMBOL_BIRTH) 192 self.bptsm = gsfs(self.symbols.SYMBOL_BAPTISM) 193 self.marriage = gsfs(self.symbols.SYMBOL_MARRIAGE) 194 self.marr = gsfs(self.symbols.SYMBOL_HETEROSEXUAL) 195 self.homom = gsfs(self.symbols.SYMBOL_MALE_HOMOSEXUAL) 196 self.homof = gsfs(self.symbols.SYMBOL_LESBIAN) 197 self.divorce = gsfs(self.symbols.SYMBOL_DIVORCE) 198 self.unmarr = gsfs(self.symbols.SYMBOL_UNMARRIED_PARTNERSHIP) 199 death_idx = self.uistate.death_symbol 200 self.dth = self.symbols.get_death_symbol_for_char(death_idx) 201 self.burial = gsfs(self.symbols.SYMBOL_BURIED) 202 self.cremation = gsfs(self.symbols.SYMBOL_CREMATED) 203 else: 204 gsf = self.symbols.get_symbol_fallback 205 self.male = gsf(self.symbols.SYMBOL_MALE) 206 self.female = gsf(self.symbols.SYMBOL_FEMALE) 207 self.bth = gsf(self.symbols.SYMBOL_BIRTH) 208 self.bptsm = gsf(self.symbols.SYMBOL_BAPTISM) 209 self.marriage = gsf(self.symbols.SYMBOL_MARRIAGE) 210 self.marr = gsf(self.symbols.SYMBOL_HETEROSEXUAL) 211 self.homom = gsf(self.symbols.SYMBOL_MALE_HOMOSEXUAL) 212 self.homof = gsf(self.symbols.SYMBOL_LESBIAN) 213 self.divorce = gsf(self.symbols.SYMBOL_DIVORCE) 214 self.unmarr = gsf(self.symbols.SYMBOL_UNMARRIED_PARTNERSHIP) 215 death_idx = self.symbols.DEATH_SYMBOL_LATIN_CROSS 216 self.dth = self.symbols.get_death_symbol_fallback(death_idx) 217 self.burial = gsf(self.symbols.SYMBOL_BURIED) 218 self.cremation = gsf(self.symbols.SYMBOL_CREMATED) 219 220 def font_changed(self): 221 self.reload_symbols() 222 self.build_tree() 223 224 def navigation_type(self): 225 return 'Person' 226 227 def can_configure(self): 228 """ 229 See :class:`~gui.views.pageview.PageView 230 :return: bool 231 """ 232 return True 233 234 def goto_handle(self, handle): 235 self.change_person(handle) 236 237 def shade_update(self, client, cnxn_id, entry, data): 238 self.use_shade = self._config.get('preferences.relation-shade') 239 self.toolbar_visible = config.get('interface.toolbar-on') 240 self.uistate.modify_statusbar(self.dbstate) 241 self.redraw() 242 243 def config_update(self, client, cnxn_id, entry, data): 244 self.show_siblings = self._config.get('preferences.family-siblings') 245 self.show_details = self._config.get('preferences.family-details') 246 self.redraw() 247 248 def build_tree(self): 249 self.redraw() 250 251 def person_update(self, handle_list): 252 if self.active: 253 person = self.get_active() 254 if person: 255 while not self.change_person(person): 256 pass 257 else: 258 self.change_person(None) 259 else: 260 self.dirty = True 261 262 def person_rebuild(self): 263 """Large change to person database""" 264 if self.active: 265 self.bookmarks.redraw() 266 person = self.get_active() 267 if person: 268 while not self.change_person(person): 269 pass 270 else: 271 self.change_person(None) 272 else: 273 self.dirty = True 274 275 def family_update(self, handle_list): 276 if self.active: 277 person = self.get_active() 278 if person: 279 while not self.change_person(person): 280 pass 281 else: 282 self.change_person(None) 283 else: 284 self.dirty = True 285 286 def family_add(self, handle_list): 287 if self.active: 288 person = self.get_active() 289 if person: 290 while not self.change_person(person): 291 pass 292 else: 293 self.change_person(None) 294 else: 295 self.dirty = True 296 297 def family_delete(self, handle_list): 298 if self.active: 299 person = self.get_active() 300 if person: 301 while not self.change_person(person): 302 pass 303 else: 304 self.change_person(None) 305 else: 306 self.dirty = True 307 308 def family_rebuild(self): 309 if self.active: 310 person = self.get_active() 311 if person: 312 while not self.change_person(person): 313 pass 314 else: 315 self.change_person(None) 316 else: 317 self.dirty = True 318 319 def change_page(self): 320 NavigationView.change_page(self) 321 self.uistate.clear_filter_results() 322 323 def get_stock(self): 324 """ 325 Return the name of the stock icon to use for the display. 326 This assumes that this icon has already been registered with 327 GNOME as a stock icon. 328 """ 329 return 'gramps-relation' 330 331 def get_viewtype_stock(self): 332 """Type of view in category 333 """ 334 return 'gramps-relation' 335 336 def build_widget(self): 337 """ 338 Build the widget that contains the view, see 339 :class:`~gui.views.pageview.PageView 340 """ 341 container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 342 container.set_border_width(12) 343 344 self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 345 self.vbox.show() 346 347 self.header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 348 self.header.show() 349 350 self.child = None 351 352 self.scroll = Gtk.ScrolledWindow() 353 self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, 354 Gtk.PolicyType.AUTOMATIC) 355 self.scroll.show() 356 357 vp = Gtk.Viewport() 358 vp.set_shadow_type(Gtk.ShadowType.NONE) 359 vp.add(self.vbox) 360 361 self.scroll.add(vp) 362 self.scroll.show_all() 363 364 container.set_spacing(6) 365 container.pack_start(self.header, False, False, 0) 366 container.pack_start(Gtk.Separator(), False, False, 0) 367 container.pack_start(self.scroll, True, True, 0) 368 container.show_all() 369 return container 370 371 additional_ui = [ # Defines the UI string for UIManager 372 ''' 373 <placeholder id="CommonGo"> 374 <section> 375 <item> 376 <attribute name="action">win.Back</attribute> 377 <attribute name="label" translatable="yes">_Add Bookmark</attribute> 378 </item> 379 <item> 380 <attribute name="action">win.Forward</attribute> 381 <attribute name="label" translatable="yes">''' 382 '''Organize Bookmarks...</attribute> 383 </item> 384 </section> 385 <section> 386 <item> 387 <attribute name="action">win.HomePerson</attribute> 388 <attribute name="label" translatable="yes">_Home</attribute> 389 </item> 390 </section> 391 </placeholder> 392''', 393 ''' 394 <placeholder id='otheredit'> 395 <item> 396 <attribute name="action">win.Edit</attribute> 397 <attribute name="label" translatable="yes">Edit...</attribute> 398 </item> 399 <item> 400 <attribute name="action">win.AddParents</attribute> 401 <attribute name="label" translatable="yes">''' 402 '''Add New Parents...</attribute> 403 </item> 404 <item> 405 <attribute name="action">win.ShareFamily</attribute> 406 <attribute name="label" translatable="yes">''' 407 '''Add Existing Parents...</attribute> 408 </item> 409 <item> 410 <attribute name="action">win.AddSpouse</attribute> 411 <attribute name="label" translatable="yes">Add Partner...</attribute> 412 </item> 413 <item> 414 <attribute name="action">win.ChangeOrder</attribute> 415 <attribute name="label" translatable="yes">_Reorder</attribute> 416 </item> 417 <item> 418 <attribute name="action">win.FilterEdit</attribute> 419 <attribute name="label" translatable="yes">''' 420 '''Person Filter Editor</attribute> 421 </item> 422 </placeholder> 423''', 424 ''' 425 <section id="AddEditBook"> 426 <item> 427 <attribute name="action">win.AddBook</attribute> 428 <attribute name="label" translatable="yes">_Add Bookmark</attribute> 429 </item> 430 <item> 431 <attribute name="action">win.EditBook</attribute> 432 <attribute name="label" translatable="no">%s...</attribute> 433 </item> 434 </section> 435''' % _('Organize Bookmarks'), # Following are the Toolbar items 436 ''' 437 <placeholder id='CommonNavigation'> 438 <child groups='RO'> 439 <object class="GtkToolButton"> 440 <property name="icon-name">go-previous</property> 441 <property name="action-name">win.Back</property> 442 <property name="tooltip_text" translatable="yes">''' 443 '''Go to the previous object in the history</property> 444 <property name="label" translatable="yes">_Back</property> 445 <property name="use-underline">True</property> 446 </object> 447 <packing> 448 <property name="homogeneous">False</property> 449 </packing> 450 </child> 451 <child groups='RO'> 452 <object class="GtkToolButton"> 453 <property name="icon-name">go-next</property> 454 <property name="action-name">win.Forward</property> 455 <property name="tooltip_text" translatable="yes">''' 456 '''Go to the next object in the history</property> 457 <property name="label" translatable="yes">_Forward</property> 458 <property name="use-underline">True</property> 459 </object> 460 <packing> 461 <property name="homogeneous">False</property> 462 </packing> 463 </child> 464 <child groups='RO'> 465 <object class="GtkToolButton"> 466 <property name="icon-name">go-home</property> 467 <property name="action-name">win.HomePerson</property> 468 <property name="tooltip_text" translatable="yes">''' 469 '''Go to the home person</property> 470 <property name="label" translatable="yes">_Home</property> 471 <property name="use-underline">True</property> 472 </object> 473 <packing> 474 <property name="homogeneous">False</property> 475 </packing> 476 </child> 477 </placeholder> 478''', 479 ''' 480 <placeholder id='BarCommonEdit'> 481 <child groups='RW'> 482 <object class="GtkToolButton"> 483 <property name="icon-name">gtk-edit</property> 484 <property name="action-name">win.Edit</property> 485 <property name="tooltip_text" translatable="yes">''' 486 '''Edit the active person</property> 487 <property name="label" translatable="yes">Edit...</property> 488 </object> 489 <packing> 490 <property name="homogeneous">False</property> 491 </packing> 492 </child> 493 <child groups='RW'> 494 <object class="GtkToolButton"> 495 <property name="icon-name">gramps-parents-add</property> 496 <property name="action-name">win.AddParents</property> 497 <property name="tooltip_text" translatable="yes">''' 498 '''Add a new set of parents</property> 499 <property name="label" translatable="yes">Add</property> 500 </object> 501 <packing> 502 <property name="homogeneous">False</property> 503 </packing> 504 </child> 505 <child groups='RW'> 506 <object class="GtkToolButton"> 507 <property name="icon-name">gramps-parents-open</property> 508 <property name="action-name">win.ShareFamily</property> 509 <property name="tooltip_text" translatable="yes">''' 510 '''Add person as child to an existing family</property> 511 <property name="label" translatable="yes">Share</property> 512 </object> 513 <packing> 514 <property name="homogeneous">False</property> 515 </packing> 516 </child> 517 <child groups='RW'> 518 <object class="GtkToolButton"> 519 <property name="icon-name">gramps-spouse</property> 520 <property name="action-name">win.AddSpouse</property> 521 <property name="tooltip_text" translatable="yes">''' 522 '''Add a new family with person as parent</property> 523 <property name="label" translatable="yes">Partner</property> 524 </object> 525 <packing> 526 <property name="homogeneous">False</property> 527 </packing> 528 </child> 529 <child groups='RW'> 530 <object class="GtkToolButton"> 531 <property name="icon-name">view-sort-ascending</property> 532 <property name="action-name">win.ChangeOrder</property> 533 <property name="tooltip_text" translatable="yes">''' 534 '''Change order of parents and families</property> 535 <property name="label" translatable="yes">_Reorder</property> 536 <property name="use-underline">True</property> 537 </object> 538 <packing> 539 <property name="homogeneous">False</property> 540 </packing> 541 </child> 542 </placeholder> 543 '''] 544 545 def define_actions(self): 546 NavigationView.define_actions(self) 547 548 self.order_action = ActionGroup(name=self.title + '/ChangeOrder') 549 self.order_action.add_actions([ 550 ('ChangeOrder', self.reorder)]) 551 552 self.family_action = ActionGroup(name=self.title + '/Family') 553 self.family_action.add_actions([ 554 ('Edit', self.edit_active, "<PRIMARY>Return"), 555 ('AddSpouse', self.add_spouse), 556 ('AddParents', self.add_parents), 557 ('ShareFamily', self.select_parents)]) 558 559 self._add_action('FilterEdit', callback=self.filter_editor) 560 self._add_action('PRIMARY-J', self.jump, '<PRIMARY>J') 561 562 self._add_action_group(self.order_action) 563 self._add_action_group(self.family_action) 564 565 self.uimanager.set_actions_sensitive(self.order_action, 566 self.reorder_sensitive) 567 self.uimanager.set_actions_sensitive(self.family_action, False) 568 569 def filter_editor(self, *obj): 570 try: 571 FilterEditor('Person', CUSTOM_FILTERS, 572 self.dbstate, self.uistate) 573 except WindowActiveError: 574 return 575 576 def change_db(self, db): 577 #reset the connects 578 self._change_db(db) 579 if self.child: 580 list(map(self.vbox.remove, self.vbox.get_children())) 581 list(map(self.header.remove, self.header.get_children())) 582 self.child = None 583 if self.active: 584 self.bookmarks.redraw() 585 self.redraw() 586 587 def get_name(self, handle, use_gender=False): 588 if handle: 589 person = self.dbstate.db.get_person_from_handle(handle) 590 name = name_displayer.display(person) 591 if use_gender: 592 gender = self.symbols.get_symbol_for_string(person.gender) 593 else: 594 gender = "" 595 return (name, gender) 596 else: 597 return (_("Unknown"), "") 598 599 def redraw(self, *obj): 600 active_person = self.get_active() 601 if active_person: 602 self.change_person(active_person) 603 else: 604 self.change_person(None) 605 606 def change_person(self, obj): 607 self.change_active(obj) 608 try: 609 return self._change_person(obj) 610 except AttributeError as msg: 611 import traceback 612 exc = traceback.format_exc() 613 _LOG.error(str(msg) +"\n" + exc) 614 from gramps.gui.dialog import RunDatabaseRepair 615 RunDatabaseRepair(str(msg), 616 parent=self.uistate.window) 617 self.redrawing = False 618 return True 619 620 def _change_person(self, obj): 621 if obj == self.old_handle: 622 #same object, keep present scroll position 623 old_vadjust = self.scroll.get_vadjustment().get_value() 624 self.old_handle = obj 625 else: 626 #different object, scroll to top 627 old_vadjust = self.scroll.get_vadjustment().get_lower() 628 self.old_handle = obj 629 self.scroll.get_vadjustment().set_value( 630 self.scroll.get_vadjustment().get_lower()) 631 if self.redrawing: 632 return False 633 self.redrawing = True 634 635 for old_child in self.vbox.get_children(): 636 self.vbox.remove(old_child) 637 for old_child in self.header.get_children(): 638 self.header.remove(old_child) 639 640 person = None 641 if obj: 642 person = self.dbstate.db.get_person_from_handle(obj) 643 if not person: 644 self.uimanager.set_actions_sensitive(self.family_action, False) 645 self.uimanager.set_actions_sensitive(self.order_action, False) 646 self.redrawing = False 647 return 648 self.uimanager.set_actions_sensitive(self.family_action, True) 649 650 self.write_title(person) 651 652 self.child = Gtk.Grid() 653 self.child.set_border_width(12) 654 self.child.set_column_spacing(12) 655 self.child.set_row_spacing(0) 656 self.row = 0 657 658 family_handle_list = person.get_parent_family_handle_list() 659 660 self.reorder_sensitive = len(family_handle_list)> 1 661 662 if family_handle_list: 663 for family_handle in family_handle_list: 664 if family_handle: 665 self.write_parents(family_handle, person) 666 else: 667 self.write_label(_("%s:") % _('Parents'), None, True, person) 668 self.row += 1 669 670 family_handle_list = person.get_family_handle_list() 671 672 if not self.reorder_sensitive: 673 self.reorder_sensitive = len(family_handle_list)> 1 674 675 if family_handle_list: 676 for family_handle in family_handle_list: 677 if family_handle: 678 self.write_family(family_handle, person) 679 680 self.child.show_all() 681 682 self.vbox.pack_start(self.child, False, True, 0) 683 #reset scroll position as it was before 684 self.scroll.get_vadjustment().set_value(old_vadjust) 685 self.redrawing = False 686 self.uistate.modify_statusbar(self.dbstate) 687 688 self.uimanager.set_actions_sensitive(self.order_action, 689 self.reorder_sensitive) 690 self.dirty = False 691 692 return True 693 694 def write_title(self, person): 695 696 list(map(self.header.remove, self.header.get_children())) 697 grid = Gtk.Grid() 698 grid.set_column_spacing(12) 699 grid.set_row_spacing(0) 700 701 # name and edit button 702 name = name_displayer.display(person) 703 fmt = '<span size="larger" weight="bold">%s</span>' 704 text = fmt % escape(name) 705 gender_code = self.symbols.get_symbol_for_string(person.gender) 706 label = widgets.DualMarkupLabel(text, gender_code, 707 halign=Gtk.Align.END) 708 if self._config.get('preferences.releditbtn'): 709 button = widgets.IconButton(self.edit_button_press, 710 person.handle) 711 button.set_tooltip_text(_('Edit Person (%s)') % name) 712 else: 713 button = None 714 eventbox = Gtk.EventBox() 715 eventbox.set_visible_window(False) 716 self._set_draggable_person(eventbox, person.get_handle()) 717 hbox = widgets.LinkBox(label, button) 718 eventbox.add(hbox) 719 720 grid.attach(eventbox, 0, 0, 2, 1) 721 722 eventbox = widgets.ShadeBox(self.use_shade) 723 grid.attach(eventbox, 1, 1, 1, 1) 724 subgrid = Gtk.Grid() 725 subgrid.set_column_spacing(12) 726 subgrid.set_row_spacing(0) 727 eventbox.add(subgrid) 728 self._set_draggable_person(eventbox, person.get_handle()) 729 # Gramps ID 730 731 subgrid.attach(widgets.BasicLabel(_("%s:") % _('ID')), 1, 0, 1, 1) 732 label = widgets.BasicLabel(person.gramps_id) 733 label.set_hexpand(True) 734 subgrid.attach(label, 2, 0, 1, 1) 735 736 # Birth event. 737 birth = get_birth_or_fallback(self.dbstate.db, person) 738 if birth: 739 if birth.get_type() == EventType.BAPTISM: 740 birth_title = self.bptsm 741 else: 742 birth_title = self.bth 743 else: 744 birth_title = self.bth 745 746 subgrid.attach(widgets.BasicLabel(_("%s") % birth_title), 1, 1, 1, 1) 747 birthwidget = widgets.BasicLabel(self.format_event(birth)) 748 birthwidget.set_selectable(True) 749 subgrid.attach(birthwidget, 2, 1, 1, 1) 750 751 death = get_death_or_fallback(self.dbstate.db, person) 752 if death: 753 if death.get_type() == EventType.BURIAL: 754 death_title = self.burial 755 elif death.get_type() == EventType.CREMATION: 756 death_title = self.cremation 757 else: 758 death_title = self.dth 759 else: 760 death_title = self.dth 761 762 showed_death = False 763 if birth: 764 birth_date = birth.get_date_object() 765 if (birth_date and birth_date.get_valid()): 766 if death: 767 death_date = death.get_date_object() 768 if (death_date and death_date.get_valid()): 769 age = (death_date - birth_date).format( 770 precision=self.age_precision) 771 subgrid.attach(widgets.BasicLabel( 772 _("%s") % death_title), 1, 2, 1, 1) 773 deathwidget = widgets.BasicLabel( 774 "%s (%s)" % (self.format_event(death), age), 775 Pango.EllipsizeMode.END) 776 deathwidget.set_selectable(True) 777 subgrid.attach(deathwidget, 2, 2, 1, 1) 778 showed_death = True 779 if not showed_death: 780 age = (Today() - birth_date).format( 781 precision=self.age_precision) 782 if probably_alive(person, self.dbstate.db): 783 subgrid.attach(widgets.BasicLabel( 784 _("%s:") % _("Alive")), 1, 2, 1, 1) 785 subgrid.attach(widgets.BasicLabel( 786 "(%s)" % age, Pango.EllipsizeMode.END), 2, 2, 1, 1) 787 else: 788 subgrid.attach(widgets.BasicLabel( 789 _("%s") % self.dth), 1, 2, 1, 1) 790 subgrid.attach(widgets.BasicLabel( 791 "%s (%s)" % (_("unknown"), age), 792 Pango.EllipsizeMode.END), 2, 2, 1, 1) 793 showed_death = True 794 795 if not showed_death: 796 subgrid.attach(widgets.BasicLabel(_("%s") % death_title), 797 1, 2, 1, 1) 798 deathwidget = widgets.BasicLabel(self.format_event(death)) 799 deathwidget.set_selectable(True) 800 subgrid.attach(deathwidget, 801 2, 2, 1, 1) 802 803 mbox = Gtk.Box() 804 mbox.add(grid) 805 806 # image 807 image_list = person.get_media_list() 808 if image_list: 809 mobj = self.dbstate.db.get_media_from_handle(image_list[0].ref) 810 if mobj and mobj.get_mime_type()[0:5] == "image": 811 pixbuf = get_thumbnail_image( 812 media_path_full(self.dbstate.db, 813 mobj.get_path()), 814 rectangle=image_list[0].get_rectangle()) 815 image = Gtk.Image() 816 image.set_from_pixbuf(pixbuf) 817 button = Gtk.Button() 818 button.add(image) 819 button.connect("clicked", lambda obj: self.view_photo(mobj)) 820 mbox.pack_end(button, False, True, 0) 821 822 mbox.show_all() 823 self.header.pack_start(mbox, False, True, 0) 824 825 def view_photo(self, photo): 826 """ 827 Open this picture in the default picture viewer. 828 """ 829 photo_path = media_path_full(self.dbstate.db, photo.get_path()) 830 open_file_with_default_application(photo_path, self.uistate) 831 832 def write_person_event(self, ename, event): 833 if event: 834 dobj = event.get_date_object() 835 phandle = event.get_place_handle() 836 if phandle: 837 pname = place_displayer.display_event(self.dbstate.db, event) 838 else: 839 pname = None 840 841 value = { 842 'date' : displayer.display(dobj), 843 'place' : pname, 844 } 845 else: 846 pname = None 847 dobj = None 848 849 if dobj: 850 if pname: 851 self.write_person_data(ename, 852 _('%(date)s in %(place)s') % value) 853 else: 854 self.write_person_data(ename, '%(date)s' % value) 855 elif pname: 856 self.write_person_data(ename, pname) 857 else: 858 self.write_person_data(ename, '') 859 860 def format_event(self, event): 861 if event: 862 dobj = event.get_date_object() 863 phandle = event.get_place_handle() 864 if phandle: 865 pname = place_displayer.display_event(self.dbstate.db, event) 866 else: 867 pname = None 868 869 value = { 870 'date' : displayer.display(dobj), 871 'place' : pname, 872 } 873 else: 874 pname = None 875 dobj = None 876 877 if dobj: 878 if pname: 879 return _('%(date)s in %(place)s') % value 880 else: 881 return '%(date)s' % value 882 elif pname: 883 return pname 884 else: 885 return '' 886 887 def write_person_data(self, title, data): 888 self.child.attach(widgets.BasicLabel(title), _ALABEL_START, self.row, 889 _ALABEL_STOP-_ALABEL_START, 1) 890 self.child.attach(widgets.BasicLabel(data), _ADATA_START, self.row, 891 _ADATA_STOP-_ADATA_START, 1) 892 self.row += 1 893 894 def marriage_symbol(self, family, markup=True): 895 if family: 896 father = mother = None 897 hdl1 = family.get_father_handle() 898 if hdl1: 899 father = self.dbstate.db.get_person_from_handle(hdl1).gender 900 hdl2 = family.get_mother_handle() 901 if hdl2: 902 mother = self.dbstate.db.get_person_from_handle(hdl2).gender 903 if father != mother: 904 symbol = self.marr 905 elif father == Person.MALE: 906 symbol = self.homom 907 else: 908 symbol = self.homof 909 if markup: 910 msg = '<span size="12000" >%s</span>' % symbol 911 else: 912 msg = symbol 913 else: 914 msg = "" 915 return msg 916 917 def write_label(self, title, family, is_parent, person = None): 918 """ 919 Write a Family header row 920 Shows following elements: 921 (collapse/expand arrow, Parents/Family title label, Family gramps_id, and add-choose-edit-delete buttons) 922 """ 923 msg = '<span style="italic" weight="heavy">%s</span>' % escape(title) 924 hbox = Gtk.Box() 925 label = widgets.MarkupLabel(msg, halign=Gtk.Align.END) 926 # Draw the collapse/expand button: 927 if family is not None: 928 if self.check_collapsed(person.handle, family.handle): 929 arrow = widgets.ExpandCollapseArrow(True, 930 self.expand_collapse_press, 931 (person, family.handle)) 932 else: 933 arrow = widgets.ExpandCollapseArrow(False, 934 self.expand_collapse_press, 935 (person, family.handle)) 936 else : 937 arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.RIGHT, 938 shadow_type=Gtk.ShadowType.OUT) 939 hbox.pack_start(arrow, False, True, 0) 940 hbox.pack_start(label, True, True, 0) 941 # Allow to drag this family 942 eventbox = Gtk.EventBox() 943 if family is not None: 944 self._set_draggable_family(eventbox, family.handle) 945 eventbox.add(hbox) 946 self.child.attach(eventbox, _LABEL_START, self.row, 947 _LABEL_STOP-_LABEL_START, 1) 948 949 if family: 950 value = family.gramps_id 951 else: 952 value = "" 953 eventbox = Gtk.EventBox() 954 if family is not None: 955 self._set_draggable_family(eventbox, family.handle) 956 eventbox.add(widgets.BasicLabel(value)) 957 self.child.attach(eventbox, _DATA_START, self.row, 958 _DATA_STOP-_DATA_START, 1) 959 960 if family and self.check_collapsed(person.handle, family.handle): 961 # show family names later 962 pass 963 else: 964 hbox = Gtk.Box() 965 hbox.set_spacing(12) 966 hbox.set_hexpand(True) 967 if self.uistate and self.uistate.symbols: 968 msg = self.marriage_symbol(family) 969 marriage = widgets.MarkupLabel(msg) 970 hbox.pack_start(marriage, False, True, 0) 971 if is_parent: 972 call_fcn = self.add_parent_family 973 del_fcn = self.delete_parent_family 974 add_msg = _('Add a new set of parents') 975 sel_msg = _('Add person as child to an existing family') 976 edit_msg = _('Edit parents') 977 ord_msg = _('Reorder parents') 978 del_msg = _('Remove person as child of these parents') 979 else: 980 add_msg = _('Add a new family with person as parent') 981 sel_msg = None 982 edit_msg = _('Edit family') 983 ord_msg = _('Reorder families') 984 del_msg = _('Remove person as parent in this family') 985 call_fcn = self.add_family 986 del_fcn = self.delete_family 987 988 if not self.dbstate.db.readonly: 989 # Show edit-Buttons only if db is not readonly 990 if self.reorder_sensitive: 991 add = widgets.IconButton(self.reorder_button_press, None, 992 'view-sort-ascending') 993 add.set_tooltip_text(ord_msg) 994 hbox.pack_start(add, False, True, 0) 995 996 add = widgets.IconButton(call_fcn, None, 'list-add') 997 add.set_tooltip_text(add_msg) 998 hbox.pack_start(add, False, True, 0) 999 1000 if is_parent: 1001 add = widgets.IconButton(self.select_family, None, 1002 'gtk-index') 1003 add.set_tooltip_text(sel_msg) 1004 hbox.pack_start(add, False, True, 0) 1005 1006 if family: 1007 edit = widgets.IconButton(self.edit_family, family.handle, 1008 'gtk-edit') 1009 edit.set_tooltip_text(edit_msg) 1010 hbox.pack_start(edit, False, True, 0) 1011 if not self.dbstate.db.readonly: 1012 delete = widgets.IconButton(del_fcn, family.handle, 1013 'list-remove') 1014 delete.set_tooltip_text(del_msg) 1015 hbox.pack_start(delete, False, True, 0) 1016 1017 eventbox = Gtk.EventBox() 1018 if family is not None: 1019 self._set_draggable_family(eventbox, family.handle) 1020 eventbox.add(hbox) 1021 self.child.attach(eventbox, _BTN_START, self.row, 1022 _BTN_STOP-_BTN_START, 1) 1023 self.row += 1 1024 1025###################################################################### 1026 1027 def write_parents(self, family_handle, person = None): 1028 family = self.dbstate.db.get_family_from_handle(family_handle) 1029 if not family: 1030 return 1031 if person and self.check_collapsed(person.handle, family_handle): 1032 # don't show rest 1033 self.write_label(_("%s:") % _('Parents'), family, True, person) 1034 self.row -= 1 # back up one row for summary names 1035 active = self.get_active() 1036 child_list = [ref.ref for ref in family.get_child_ref_list() 1037 if ref.ref != active] 1038 if child_list: 1039 count = len(child_list) 1040 else: 1041 count = 0 1042 if count > 1 : 1043 # translators: leave all/any {...} untranslated 1044 childmsg = ngettext(" ({number_of} sibling)", 1045 " ({number_of} siblings)", count 1046 ).format(number_of=count) 1047 elif count == 1 : 1048 gender = self.dbstate.db.get_person_from_handle( 1049 child_list[0]).gender 1050 if gender == Person.MALE : 1051 childmsg = _(" (1 brother)") 1052 elif gender == Person.FEMALE : 1053 childmsg = _(" (1 sister)") 1054 else : 1055 childmsg = _(" (1 sibling)") 1056 else : 1057 childmsg = _(" (only child)") 1058 self.family = family 1059 box = self.get_people_box(family.get_father_handle(), 1060 family.get_mother_handle(), 1061 post_msg=childmsg) 1062 eventbox = widgets.ShadeBox(self.use_shade) 1063 eventbox.add(box) 1064 self.child.attach(eventbox, _PDATA_START, self.row, 1065 _PDATA_STOP-_PDATA_START, 1) 1066 self.row += 1 # now advance it 1067 else: 1068 self.write_label(_("%s:") % _('Parents'), family, True, person) 1069 self.write_person(_('Father'), family.get_father_handle()) 1070 self.write_person(_('Mother'), family.get_mother_handle()) 1071 1072 if self.show_siblings: 1073 active = self.get_active() 1074 hbox = Gtk.Box() 1075 if self.check_collapsed(person.handle, "SIBLINGS"): 1076 arrow = widgets.ExpandCollapseArrow(True, 1077 self.expand_collapse_press, 1078 (person, "SIBLINGS")) 1079 else: 1080 arrow = widgets.ExpandCollapseArrow(False, 1081 self.expand_collapse_press, 1082 (person, "SIBLINGS")) 1083 hbox.pack_start(arrow, False, True, 0) 1084 label_cell = self.build_label_cell(_('Siblings')) 1085 hbox.pack_start(label_cell, True, True, 0) 1086 self.child.attach(hbox, _CLABEL_START-1, self.row, 1087 _CLABEL_STOP-_CLABEL_START, 1) 1088 1089 if self.check_collapsed(person.handle, "SIBLINGS"): 1090 hbox = Gtk.Box() 1091 child_list = [ref.ref for ref in family.get_child_ref_list() 1092 if ref.ref != active] 1093 if child_list: 1094 count = len(child_list) 1095 else: 1096 count = 0 1097 if count > 1 : 1098 # translators: leave all/any {...} untranslated 1099 childmsg = ngettext(" ({number_of} sibling)", 1100 " ({number_of} siblings)", count 1101 ).format(number_of=count) 1102 elif count == 1 : 1103 gender = self.dbstate.db.get_person_from_handle( 1104 child_list[0]).gender 1105 if gender == Person.MALE : 1106 childmsg = _(" (1 brother)") 1107 elif gender == Person.FEMALE : 1108 childmsg = _(" (1 sister)") 1109 else : 1110 childmsg = _(" (1 sibling)") 1111 else : 1112 childmsg = _(" (only child)") 1113 self.family = None 1114 box = self.get_people_box(post_msg=childmsg) 1115 eventbox = widgets.ShadeBox(self.use_shade) 1116 eventbox.add(box) 1117 self.child.attach(eventbox, _PDATA_START, self.row, 1118 _PDATA_STOP-_PDATA_START, 1) 1119 self.row += 1 # now advance it 1120 else: 1121 hbox = Gtk.Box() 1122 addchild = widgets.IconButton(self.add_child_to_fam, 1123 family.handle, 1124 'list-add') 1125 addchild.set_tooltip_text(_('Add new child to family')) 1126 selchild = widgets.IconButton(self.sel_child_to_fam, 1127 family.handle, 1128 'gtk-index') 1129 selchild.set_tooltip_text(_('Add existing child to family')) 1130 hbox.pack_start(addchild, False, True, 0) 1131 hbox.pack_start(selchild, False, True, 0) 1132 1133 self.child.attach(hbox, _CLABEL_START, self.row, 1134 _CLABEL_STOP-_CLABEL_START, 1) 1135 self.row += 1 1136 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 1137 i = 1 1138 child_list = [ref.ref for ref in family.get_child_ref_list()] 1139 for child_handle in child_list: 1140 child_should_be_linked = (child_handle != active) 1141 self.write_child(vbox, child_handle, i, child_should_be_linked) 1142 i += 1 1143 eventbox = widgets.ShadeBox(self.use_shade) 1144 eventbox.add(vbox) 1145 self.child.attach(eventbox, _CDATA_START-1, self.row, 1146 _CDATA_STOP-_CDATA_START+1, 1) 1147 1148 self.row += 1 1149 1150 def get_people_box(self, *handles, **kwargs): 1151 hbox = Gtk.Box() 1152 initial_name = True 1153 if self.uistate and self.uistate.symbols: 1154 msg = self.marriage_symbol(self.family) + " " 1155 marriage = widgets.MarkupLabel(msg) 1156 hbox.pack_start(marriage, False, True, 0) 1157 for handle in handles: 1158 if not initial_name: 1159 link_label = Gtk.Label(label=" %s " % _('and')) 1160 link_label.show() 1161 hbox.pack_start(link_label, False, True, 0) 1162 initial_name = False 1163 if handle: 1164 name = self.get_name(handle, True) 1165 link_label = widgets.LinkLabel(name, self._button_press, 1166 handle, theme=self.theme) 1167 if self._config.get('preferences.releditbtn'): 1168 button = widgets.IconButton(self.edit_button_press, 1169 handle) 1170 button.set_tooltip_text(_('Edit Person (%s)') % name[0]) 1171 else: 1172 button = None 1173 hbox.pack_start(widgets.LinkBox(link_label, button), 1174 False, True, 0) 1175 else: 1176 link_label = Gtk.Label(label=_('Unknown')) 1177 link_label.show() 1178 hbox.pack_start(link_label, False, True, 0) 1179 if "post_msg" in kwargs and kwargs["post_msg"]: 1180 link_label = Gtk.Label(label=kwargs["post_msg"]) 1181 link_label.show() 1182 hbox.pack_start(link_label, False, True, 0) 1183 return hbox 1184 1185 def write_person(self, title, handle): 1186 """ 1187 Create and show a person cell with a "Father/Mother/Spouse" label in the GUI at the current row 1188 @param title: left column label ("Father/Mother/Spouse") 1189 @param handle: person handle 1190 """ 1191 if title: 1192 format = '<span weight="bold">%s: </span>' 1193 else: 1194 format = "%s" 1195 1196 label = widgets.MarkupLabel(format % escape(title), 1197 halign=Gtk.Align.END) 1198 if self._config.get('preferences.releditbtn'): 1199 label.set_padding(0, 5) 1200 1201 eventbox = Gtk.EventBox() 1202 if handle is not None: 1203 self._set_draggable_person(eventbox, handle) 1204 eventbox.add(label) 1205 self.child.attach(eventbox, _PLABEL_START, self.row, 1206 _PLABEL_STOP-_PLABEL_START, 1) 1207 1208 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 1209 eventbox = widgets.ShadeBox(self.use_shade) 1210 if handle: 1211 name = self.get_name(handle, True) 1212 person = self.dbstate.db.get_person_from_handle(handle) 1213 parent = len(person.get_parent_family_handle_list()) > 0 1214 format = '' 1215 relation_display_theme = self._config.get( 1216 'preferences.relation-display-theme') 1217 if parent: 1218 emph = True 1219 else: 1220 emph = False 1221 link_label = widgets.LinkLabel(name, self._button_press, 1222 handle, emph, theme=self.theme) 1223 if self._config.get('preferences.releditbtn'): 1224 button = widgets.IconButton(self.edit_button_press, handle) 1225 button.set_tooltip_text(_('Edit Person (%s)') % name[0]) 1226 else: 1227 button = None 1228 vbox.pack_start(widgets.LinkBox(link_label, button), True, True, 0) 1229 self._set_draggable_person(eventbox, handle) 1230 else: 1231 link_label = Gtk.Label(label=_('Unknown')) 1232 link_label.set_halign(Gtk.Align.START) 1233 link_label.show() 1234 vbox.pack_start(link_label, True, True, 0) 1235 1236 if self.show_details and handle: 1237 value = self.info_string(handle) 1238 if value: 1239 vbox.pack_start(widgets.MarkupLabel(value), True, True, 0) 1240 1241 eventbox.add(vbox) 1242 1243 self.child.attach(eventbox, _PDATA_START, self.row, 1244 _PDATA_STOP-_PDATA_START, 1) 1245 self.row += 1 1246 return vbox 1247 1248 def _set_draggable_person(self, eventbox, person_handle): 1249 """ 1250 Register the given eventbox as a drag_source with given person_handle 1251 """ 1252 self._set_draggable(eventbox, person_handle, DdTargets.PERSON_LINK, 'gramps-person') 1253 1254 def _set_draggable_family(self, eventbox, family_handle): 1255 """ 1256 Register the given eventbox as a drag_source with given person_handle 1257 """ 1258 self._set_draggable(eventbox, family_handle, DdTargets.FAMILY_LINK, 'gramps-family') 1259 1260 def _set_draggable(self, eventbox, object_h, dnd_type, stock_icon): 1261 """ 1262 Register the given eventbox as a drag_source with given object_h 1263 """ 1264 eventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, 1265 [dnd_type.target()], Gdk.DragAction.COPY) 1266 eventbox.drag_source_set_icon_name(stock_icon) 1267 eventbox.connect('drag_data_get', 1268 self._make_drag_data_get_func(object_h, dnd_type)) 1269 1270 def _make_drag_data_get_func(self, object_h, dnd_type): 1271 """ 1272 Generate at runtime a drag_data_get function returning the given dnd_type and object_h 1273 """ 1274 def drag_data_get(widget, context, sel_data, info, time): 1275 if info == dnd_type.app_id: 1276 data = (dnd_type.drag_type, id(self), object_h, 0) 1277 sel_data.set(dnd_type.atom_drag_type, 8, pickle.dumps(data)) 1278 return drag_data_get 1279 1280 def build_label_cell(self, title): 1281 if title: 1282 format = '<span weight="bold">%s: </span>' 1283 else: 1284 format = "%s" 1285 1286 lbl = widgets.MarkupLabel(format % escape(title), 1287 halign=Gtk.Align.END) 1288 if self._config.get('preferences.releditbtn'): 1289 lbl.set_padding(0, 5) 1290 return lbl 1291 1292 def write_child(self, vbox, handle, index, child_should_be_linked): 1293 """ 1294 Write a child cell (used for children and siblings of active person) 1295 """ 1296 original_vbox = vbox 1297 # Always create a transparent eventbox to allow dnd drag 1298 ev = Gtk.EventBox() 1299 ev.set_visible_window(False) 1300 if handle: 1301 self._set_draggable_person(ev, handle) 1302 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 1303 ev.add(vbox) 1304 1305 if not child_should_be_linked: 1306 frame = Gtk.Frame() 1307 frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN) 1308 frame.add(ev) 1309 original_vbox.pack_start(frame, True, True, 0) 1310 else: 1311 original_vbox.pack_start(ev, True, True, 0) 1312 1313 parent = has_children(self.dbstate.db, 1314 self.dbstate.db.get_person_from_handle(handle)) 1315 1316 format = '' 1317 relation_display_theme = self._config.get( 1318 'preferences.relation-display-theme') 1319 emph = False 1320 if child_should_be_linked and parent: 1321 emph = True 1322 elif child_should_be_linked and not parent: 1323 emph = False 1324 elif parent and not child_should_be_linked: 1325 emph = None 1326 1327 if child_should_be_linked: 1328 link_func = self._button_press 1329 else: 1330 link_func = None 1331 1332 name = self.get_name(handle, True) 1333 link_label = widgets.LinkLabel(name, link_func, handle, emph, 1334 theme=self.theme) 1335 link_label.set_padding(3, 0) 1336 if child_should_be_linked and self._config.get( 1337 'preferences.releditbtn'): 1338 button = widgets.IconButton(self.edit_button_press, handle) 1339 button.set_tooltip_text(_('Edit Person (%s)') % name[0]) 1340 else: 1341 button = None 1342 1343 hbox = Gtk.Box() 1344 l = widgets.BasicLabel("%d." % index) 1345 l.set_width_chars(3) 1346 l.set_halign(Gtk.Align.END) 1347 hbox.pack_start(l, False, False, 0) 1348 hbox.pack_start(widgets.LinkBox(link_label, button), 1349 False, False, 4) 1350 hbox.show() 1351 vbox.pack_start(hbox, True, True, 0) 1352 1353 if self.show_details: 1354 value = self.info_string(handle) 1355 if value: 1356 l = widgets.MarkupLabel(value) 1357 l.set_padding(48, 0) 1358 vbox.add(l) 1359 1360 def write_data(self, box, title, start_col=_SDATA_START, 1361 stop_col=_SDATA_STOP, selectable=False): 1362 label=widgets.BasicLabel(title) 1363 label.set_selectable(selectable) 1364 box.add(label) 1365 1366 def info_string(self, handle): 1367 person = self.dbstate.db.get_person_from_handle(handle) 1368 if not person: 1369 return None 1370 1371 birth = get_birth_or_fallback(self.dbstate.db, person) 1372 if birth: 1373 if birth.get_type() == EventType.BAPTISM: 1374 s_birth = self.bptsm 1375 else: 1376 s_birth = self.bth 1377 if birth and birth.get_type() != EventType.BIRTH: 1378 sdate = get_date(birth) 1379 if sdate: 1380 bdate = "<i>%s</i>" % escape(sdate) 1381 else: 1382 bdate = "" 1383 elif birth: 1384 bdate = escape(get_date(birth)) 1385 else: 1386 bdate = "" 1387 1388 death = get_death_or_fallback(self.dbstate.db, person) 1389 if death: 1390 if death.get_type() == EventType.BURIAL: 1391 s_death = self.burial 1392 elif death.get_type() == EventType.CREMATION: 1393 s_death = self.cremation 1394 else: 1395 s_death = self.dth 1396 if death and death.get_type() != EventType.DEATH: 1397 sdate = get_date(death) 1398 if sdate: 1399 ddate = "<i>%s</i>" % escape(sdate) 1400 else: 1401 ddate = "" 1402 elif death: 1403 ddate = escape(get_date(death)) 1404 else: 1405 ddate = "" 1406 1407 if bdate and ddate: 1408 value = _("%(birthabbrev)s %(birthdate)s, %(deathabbrev)s %(deathdate)s") % { 1409 'birthabbrev': s_birth, 1410 'deathabbrev': s_death, 1411 'birthdate' : bdate, 1412 'deathdate' : ddate 1413 } 1414 elif bdate: 1415 value = _("%(event)s %(date)s") % {'event': s_birth, 'date': bdate} 1416 elif ddate: 1417 value = _("%(event)s %(date)s") % {'event': s_death, 'date': ddate} 1418 else: 1419 value = "" 1420 return value 1421 1422 def check_collapsed(self, person_handle, handle): 1423 """ Return true if collapsed. """ 1424 return (handle in self.collapsed_items.get(person_handle, [])) 1425 1426 def expand_collapse_press(self, obj, event, pair): 1427 """ Calback function for ExpandCollapseArrow, user param is 1428 pair, which is a tuple (object, handle) which handles the 1429 section of which the collapse state must change, so a 1430 parent, siblings id, family id, family children id, etc. 1431 NOTE: object must be a thing that has a handle field. 1432 """ 1433 if button_activated(event, _LEFT_BUTTON): 1434 object, handle = pair 1435 if object.handle in self.collapsed_items: 1436 if handle in self.collapsed_items[object.handle]: 1437 self.collapsed_items[object.handle].remove(handle) 1438 else: 1439 self.collapsed_items[object.handle].append(handle) 1440 else: 1441 self.collapsed_items[object.handle] = [handle] 1442 self.redraw() 1443 1444 def _button_press(self, obj, event, handle): 1445 if button_activated(event, _LEFT_BUTTON): 1446 self.change_active(handle) 1447 elif button_activated(event, _RIGHT_BUTTON): 1448 self.my_menu = Gtk.Menu() 1449 self.my_menu.set_reserve_toggle_size(False) 1450 self.my_menu.append(self.build_menu_item(handle)) 1451 self.my_menu.popup(None, None, None, None, event.button, event.time) 1452 1453 def build_menu_item(self, handle): 1454 person = self.dbstate.db.get_person_from_handle(handle) 1455 name = name_displayer.display(person) 1456 1457 item = Gtk.MenuItem() 1458 label = Gtk.Label(label=_("Edit Person (%s)") % name) 1459 label.show() 1460 label.set_halign(Gtk.Align.START) 1461 1462 item.add(label) 1463 1464 item.connect('activate', self.edit_menu, handle) 1465 item.show() 1466 return item 1467 1468 def edit_menu(self, obj, handle): 1469 person = self.dbstate.db.get_person_from_handle(handle) 1470 try: 1471 EditPerson(self.dbstate, self.uistate, [], person) 1472 except WindowActiveError: 1473 pass 1474 1475 def write_relationship(self, box, family): 1476 msg = _('Relationship type: %s') % escape(str(family.get_relationship())) 1477 box.add(widgets.MarkupLabel(msg)) 1478 1479 def write_relationship_events(self, vbox, family): 1480 value = False 1481 for event_ref in family.get_event_ref_list(): 1482 handle = event_ref.ref 1483 event = self.dbstate.db.get_event_from_handle(handle) 1484 if (event and event.get_type().is_relationship_event() and 1485 (event_ref.get_role() == EventRoleType.FAMILY or 1486 event_ref.get_role() == EventRoleType.PRIMARY)): 1487 if event.get_type() == EventType.MARRIAGE: 1488 etype = self.marriage 1489 elif event.get_type() == EventType.DIVORCE: 1490 etype = self.divorce 1491 else: 1492 etype = event.get_type().string 1493 self.write_event_ref(vbox, etype, event) 1494 value = True 1495 return value 1496 1497 def write_event_ref(self, vbox, ename, event, start_col=_SDATA_START, 1498 stop_col=_SDATA_STOP): 1499 if event: 1500 dobj = event.get_date_object() 1501 phandle = event.get_place_handle() 1502 if phandle: 1503 pname = place_displayer.display_event(self.dbstate.db, event) 1504 else: 1505 pname = None 1506 1507 value = { 1508 'date' : displayer.display(dobj), 1509 'place' : pname, 1510 'event_type' : ename, 1511 } 1512 else: 1513 pname = None 1514 dobj = None 1515 value = { 'event_type' : ename, } 1516 1517 if dobj: 1518 if pname: 1519 self.write_data( 1520 vbox, _('%(event_type)s %(date)s in %(place)s') % 1521 value, start_col, stop_col, True) 1522 else: 1523 self.write_data( 1524 vbox, _('%(event_type)s %(date)s') % value, 1525 start_col, stop_col) 1526 elif pname: 1527 self.write_data( 1528 vbox, _('%(event_type)s %(place)s') % value, 1529 start_col, stop_col) 1530 else: 1531 self.write_data( 1532 vbox, '%(event_type)s' % value, start_col, stop_col) 1533 1534 def write_family(self, family_handle, person = None): 1535 family = self.dbstate.db.get_family_from_handle(family_handle) 1536 if family is None: 1537 from gramps.gui.dialog import WarningDialog 1538 WarningDialog( 1539 _('Broken family detected'), 1540 _('Please run the Check and Repair Database tool'), 1541 parent=self.uistate.window) 1542 return 1543 1544 father_handle = family.get_father_handle() 1545 mother_handle = family.get_mother_handle() 1546 if self.get_active() == father_handle: 1547 handle = mother_handle 1548 else: 1549 handle = father_handle 1550 1551 # collapse button 1552 if self.check_collapsed(person.handle, family_handle): 1553 # show "> Family: ..." and nothing else 1554 self.write_label(_("%s:") % _('Family'), family, False, person) 1555 self.row -= 1 # back up 1556 child_list = family.get_child_ref_list() 1557 if child_list: 1558 count = len(child_list) 1559 else: 1560 count = 0 1561 if count >= 1 : 1562 # translators: leave all/any {...} untranslated 1563 childmsg = ngettext(" ({number_of} child)", 1564 " ({number_of} children)", count 1565 ).format(number_of=count) 1566 else : 1567 childmsg = _(" (no children)") 1568 self.family = family 1569 box = self.get_people_box(handle, post_msg=childmsg) 1570 eventbox = widgets.ShadeBox(self.use_shade) 1571 eventbox.add(box) 1572 self.child.attach(eventbox, _PDATA_START, self.row, 1573 _PDATA_STOP-_PDATA_START, 1) 1574 self.row += 1 # now advance it 1575 else: 1576 # show "V Family: ..." and the rest 1577 self.write_label(_("%s:") % _('Family'), family, False, person) 1578 if (handle or 1579 family.get_relationship() != FamilyRelType.UNKNOWN): 1580 box = self.write_person(_('Spouse'), handle) 1581 1582 if not self.write_relationship_events(box, family): 1583 self.write_relationship(box, family) 1584 1585 hbox = Gtk.Box() 1586 if self.check_collapsed(family.handle, "CHILDREN"): 1587 arrow = widgets.ExpandCollapseArrow(True, 1588 self.expand_collapse_press, 1589 (family, "CHILDREN")) 1590 else: 1591 arrow = widgets.ExpandCollapseArrow(False, 1592 self.expand_collapse_press, 1593 (family, "CHILDREN")) 1594 hbox.pack_start(arrow, False, True, 0) 1595 label_cell = self.build_label_cell(_('Children')) 1596 hbox.pack_start(label_cell, True, True, 0) 1597 self.child.attach(hbox, _CLABEL_START-1, self.row, 1598 _CLABEL_STOP-_CLABEL_START, 1) 1599 1600 if self.check_collapsed(family.handle, "CHILDREN"): 1601 hbox = Gtk.Box() 1602 child_list = family.get_child_ref_list() 1603 if child_list: 1604 count = len(child_list) 1605 else: 1606 count = 0 1607 if count >= 1 : 1608 # translators: leave all/any {...} untranslated 1609 childmsg = ngettext(" ({number_of} child)", 1610 " ({number_of} children)", count 1611 ).format(number_of=count) 1612 else : 1613 childmsg = _(" (no children)") 1614 self.family = None 1615 box = self.get_people_box(post_msg=childmsg) 1616 eventbox = widgets.ShadeBox(self.use_shade) 1617 eventbox.add(box) 1618 self.child.attach(eventbox, _PDATA_START, self.row, 1619 _PDATA_STOP-_PDATA_START, 1) 1620 self.row += 1 # now advance it 1621 else: 1622 hbox = Gtk.Box() 1623 addchild = widgets.IconButton(self.add_child_to_fam, 1624 family.handle, 1625 'list-add') 1626 addchild.set_tooltip_text(_('Add new child to family')) 1627 selchild = widgets.IconButton(self.sel_child_to_fam, 1628 family.handle, 1629 'gtk-index') 1630 selchild.set_tooltip_text(_('Add existing child to family')) 1631 hbox.pack_start(addchild, False, True, 0) 1632 hbox.pack_start(selchild, False, True, 0) 1633 self.child.attach(hbox, _CLABEL_START, self.row, 1634 _CLABEL_STOP-_CLABEL_START, 1) 1635 1636 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 1637 i = 1 1638 child_list = family.get_child_ref_list() 1639 for child_ref in child_list: 1640 self.write_child(vbox, child_ref.ref, i, True) 1641 i += 1 1642 1643 self.row += 1 1644 eventbox = widgets.ShadeBox(self.use_shade) 1645 eventbox.add(vbox) 1646 self.child.attach(eventbox, _CDATA_START-1, self.row, 1647 _CDATA_STOP-_CDATA_START+1, 1) 1648 self.row += 1 1649 1650 def edit_button_press(self, obj, event, handle): 1651 if button_activated(event, _LEFT_BUTTON): 1652 self.edit_person(obj, handle) 1653 1654 def edit_person(self, obj, handle): 1655 person = self.dbstate.db.get_person_from_handle(handle) 1656 try: 1657 EditPerson(self.dbstate, self.uistate, [], person) 1658 except WindowActiveError: 1659 pass 1660 1661 def edit_family(self, obj, event, handle): 1662 if button_activated(event, _LEFT_BUTTON): 1663 family = self.dbstate.db.get_family_from_handle(handle) 1664 try: 1665 EditFamily(self.dbstate, self.uistate, [], family) 1666 except WindowActiveError: 1667 pass 1668 1669 def add_family(self, obj, event, handle): 1670 if button_activated(event, _LEFT_BUTTON): 1671 family = Family() 1672 person = self.dbstate.db.get_person_from_handle(self.get_active()) 1673 if not person: 1674 return 1675 1676 if person.gender == Person.MALE: 1677 family.set_father_handle(person.handle) 1678 else: 1679 family.set_mother_handle(person.handle) 1680 1681 try: 1682 EditFamily(self.dbstate, self.uistate, [], family) 1683 except WindowActiveError: 1684 pass 1685 1686 def add_spouse(self, *obj): 1687 family = Family() 1688 person = self.dbstate.db.get_person_from_handle(self.get_active()) 1689 1690 if not person: 1691 return 1692 1693 if person.gender == Person.MALE: 1694 family.set_father_handle(person.handle) 1695 else: 1696 family.set_mother_handle(person.handle) 1697 1698 try: 1699 EditFamily(self.dbstate, self.uistate, [], family) 1700 except WindowActiveError: 1701 pass 1702 1703 def edit_active(self, obj, value): 1704 phandle = self.get_active() 1705 self.edit_person(obj, phandle) 1706 1707 def add_child_to_fam(self, obj, event, handle): 1708 if button_activated(event, _LEFT_BUTTON): 1709 callback = lambda x: self.callback_add_child(x, handle) 1710 person = Person() 1711 name = Name() 1712 #the editor requires a surname 1713 name.add_surname(Surname()) 1714 name.set_primary_surname(0) 1715 family = self.dbstate.db.get_family_from_handle(handle) 1716 father_h = family.get_father_handle() 1717 if father_h: 1718 father = self.dbstate.db.get_person_from_handle(father_h) 1719 if father: 1720 preset_name(father, name) 1721 person.set_primary_name(name) 1722 try: 1723 EditPerson(self.dbstate, self.uistate, [], person, 1724 callback=callback) 1725 except WindowActiveError: 1726 pass 1727 1728 def callback_add_child(self, person, family_handle): 1729 ref = ChildRef() 1730 ref.ref = person.get_handle() 1731 family = self.dbstate.db.get_family_from_handle(family_handle) 1732 family.add_child_ref(ref) 1733 1734 with DbTxn(_("Add Child to Family"), self.dbstate.db) as trans: 1735 #add parentref to child 1736 person.add_parent_family_handle(family_handle) 1737 #default relationship is used 1738 self.dbstate.db.commit_person(person, trans) 1739 #add child to family 1740 self.dbstate.db.commit_family(family, trans) 1741 1742 def sel_child_to_fam(self, obj, event, handle, surname=None): 1743 if button_activated(event, _LEFT_BUTTON): 1744 SelectPerson = SelectorFactory('Person') 1745 family = self.dbstate.db.get_family_from_handle(handle) 1746 # it only makes sense to skip those who are already in the family 1747 skip_list = [family.get_father_handle(), 1748 family.get_mother_handle()] 1749 skip_list.extend(x.ref for x in family.get_child_ref_list()) 1750 1751 sel = SelectPerson(self.dbstate, self.uistate, [], 1752 _("Select Child"), skip=skip_list) 1753 person = sel.run() 1754 1755 if person: 1756 self.callback_add_child(person, handle) 1757 1758 def select_family(self, obj, event, handle): 1759 if button_activated(event, _LEFT_BUTTON): 1760 SelectFamily = SelectorFactory('Family') 1761 1762 phandle = self.get_active() 1763 person = self.dbstate.db.get_person_from_handle(phandle) 1764 skip = set(person.get_family_handle_list()) 1765 1766 dialog = SelectFamily(self.dbstate, self.uistate, skip=skip) 1767 family = dialog.run() 1768 1769 if family: 1770 child = self.dbstate.db.get_person_from_handle(self.get_active()) 1771 1772 self.dbstate.db.add_child_to_family(family, child) 1773 1774 def select_parents(self, *obj): 1775 SelectFamily = SelectorFactory('Family') 1776 1777 phandle = self.get_active() 1778 person = self.dbstate.db.get_person_from_handle(phandle) 1779 skip = set(person.get_family_handle_list()+ 1780 person.get_parent_family_handle_list()) 1781 1782 dialog = SelectFamily(self.dbstate, self.uistate, skip=skip) 1783 family = dialog.run() 1784 1785 if family: 1786 child = self.dbstate.db.get_person_from_handle(self.get_active()) 1787 1788 self.dbstate.db.add_child_to_family(family, child) 1789 1790 def add_parents(self, *obj): 1791 family = Family() 1792 person = self.dbstate.db.get_person_from_handle(self.get_active()) 1793 1794 if not person: 1795 return 1796 1797 ref = ChildRef() 1798 ref.ref = person.handle 1799 family.add_child_ref(ref) 1800 1801 try: 1802 EditFamily(self.dbstate, self.uistate, [], family) 1803 except WindowActiveError: 1804 pass 1805 1806 def add_parent_family(self, obj, event, handle): 1807 if button_activated(event, _LEFT_BUTTON): 1808 family = Family() 1809 person = self.dbstate.db.get_person_from_handle(self.get_active()) 1810 1811 ref = ChildRef() 1812 ref.ref = person.handle 1813 family.add_child_ref(ref) 1814 1815 try: 1816 EditFamily(self.dbstate, self.uistate, [], family) 1817 except WindowActiveError: 1818 pass 1819 1820 def delete_family(self, obj, event, handle): 1821 if button_activated(event, _LEFT_BUTTON): 1822 self.dbstate.db.remove_parent_from_family(self.get_active(), handle) 1823 1824 def delete_parent_family(self, obj, event, handle): 1825 if button_activated(event, _LEFT_BUTTON): 1826 self.dbstate.db.remove_child_from_family(self.get_active(), handle) 1827 1828 def change_to(self, obj, handle): 1829 self.change_active(handle) 1830 1831 def reorder_button_press(self, obj, event, handle): 1832 if button_activated(event, _LEFT_BUTTON): 1833 self.reorder(obj) 1834 1835 def reorder(self, *obj): 1836 if self.get_active(): 1837 try: 1838 Reorder(self.dbstate, self.uistate, [], self.get_active()) 1839 except WindowActiveError: 1840 pass 1841 1842 def config_connect(self): 1843 """ 1844 Overwriten from :class:`~gui.views.pageview.PageView method 1845 This method will be called after the ini file is initialized, 1846 use it to monitor changes in the ini file 1847 """ 1848 self._config.connect("preferences.relation-shade", 1849 self.shade_update) 1850 self._config.connect("preferences.releditbtn", 1851 self.config_update) 1852 self._config.connect("preferences.relation-display-theme", 1853 self.config_update) 1854 self._config.connect("preferences.family-siblings", 1855 self.config_update) 1856 self._config.connect("preferences.family-details", 1857 self.config_update) 1858 config.connect("interface.toolbar-on", 1859 self.shade_update) 1860 1861 def config_panel(self, configdialog): 1862 """ 1863 Function that builds the widget in the configuration dialog 1864 """ 1865 grid = Gtk.Grid() 1866 grid.set_border_width(12) 1867 grid.set_column_spacing(6) 1868 grid.set_row_spacing(6) 1869 1870 configdialog.add_checkbox(grid, 1871 _('Use shading'), 1872 0, 'preferences.relation-shade') 1873 configdialog.add_checkbox(grid, 1874 _('Display edit buttons'), 1875 1, 'preferences.releditbtn') 1876 checkbox = Gtk.CheckButton(label=_('View links as website links')) 1877 theme = self._config.get('preferences.relation-display-theme') 1878 checkbox.set_active(theme == 'WEBPAGE') 1879 checkbox.connect('toggled', self._config_update_theme) 1880 grid.attach(checkbox, 1, 2, 8, 1) 1881 1882 return _('Layout'), grid 1883 1884 def content_panel(self, configdialog): 1885 """ 1886 Function that builds the widget in the configuration dialog 1887 """ 1888 grid = Gtk.Grid() 1889 grid.set_border_width(12) 1890 grid.set_column_spacing(6) 1891 grid.set_row_spacing(6) 1892 configdialog.add_checkbox(grid, 1893 _('Show Details'), 1894 0, 'preferences.family-details') 1895 configdialog.add_checkbox(grid, 1896 _('Show Siblings'), 1897 1, 'preferences.family-siblings') 1898 1899 return _('Content'), grid 1900 1901 def _config_update_theme(self, obj): 1902 """ 1903 callback from the theme checkbox 1904 """ 1905 if obj.get_active(): 1906 self.theme = 'WEBPAGE' 1907 self._config.set('preferences.relation-display-theme', 1908 'WEBPAGE') 1909 else: 1910 self.theme = 'CLASSIC' 1911 self._config.set('preferences.relation-display-theme', 1912 'CLASSIC') 1913 1914 def _get_configure_page_funcs(self): 1915 """ 1916 Return a list of functions that create gtk elements to use in the 1917 notebook pages of the Configure dialog 1918 1919 :return: list of functions 1920 """ 1921 return [self.content_panel, self.config_panel] 1922 1923#------------------------------------------------------------------------- 1924# 1925# Function to return if person has children 1926# 1927#------------------------------------------------------------------------- 1928def has_children(db,p): 1929 """ 1930 Return if a person has children. 1931 """ 1932 for family_handle in p.get_family_handle_list(): 1933 family = db.get_family_from_handle(family_handle) 1934 if not family: 1935 continue 1936 childlist = family.get_child_ref_list() 1937 if childlist and len(childlist) > 0: 1938 return True 1939 return False 1940 1941def button_activated(event, mouse_button): 1942 if (event.type == Gdk.EventType.BUTTON_PRESS and 1943 event.button == mouse_button) or \ 1944 (event.type == Gdk.EventType.KEY_PRESS and 1945 event.keyval in (_RETURN, _KP_ENTER, _SPACE)): 1946 return True 1947 else: 1948 return False 1949 1950