1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2007-2008 Brian G. Matherly 5# Copyright (C) 2008,2010 Gary Burton 6# Copyright (C) 2008 Craig J. Anderson 7# Copyright (C) 2009 Nick Hall 8# Copyright (C) 2010 Jakim Friant 9# Copyright (C) 2011 Adam Stein <adam@csh.rit.edu> 10# Copyright (C) 2011-2013 Paul Franklin 11# 12# This program is free software; you can redistribute it and/or modify 13# it under the terms of the GNU General Public License as published by 14# the Free Software Foundation; either version 2 of the License, or 15# (at your option) any later version. 16# 17# This program is distributed in the hope that it will be useful, 18# but WITHOUT ANY WARRANTY; without even the implied warranty of 19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20# GNU General Public License for more details. 21# 22# You should have received a copy of the GNU General Public License 23# along with this program; if not, write to the Free Software 24# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25# 26 27""" 28Specific option handling for a GUI. 29""" 30 31#------------------------------------------------------------------------ 32# 33# python modules 34# 35#------------------------------------------------------------------------ 36import os 37 38#------------------------------------------------------------------------- 39# 40# gtk modules 41# 42#------------------------------------------------------------------------- 43from gi.repository import Gtk 44from gi.repository import Gdk 45from gi.repository import GObject 46 47#------------------------------------------------------------------------- 48# 49# gramps modules 50# 51#------------------------------------------------------------------------- 52from ..utils import ProgressMeter 53from ..pluginmanager import GuiPluginManager 54from .. import widgets 55from ..managedwindow import ManagedWindow 56from ..dialog import OptionDialog 57from ..selectors import SelectorFactory 58from gramps.gen.errors import HandleError 59from gramps.gen.display.name import displayer as _nd 60from gramps.gen.display.place import displayer as _pd 61from gramps.gen.filters import GenericFilterFactory, GenericFilter, rules 62from gramps.gen.constfunc import get_curr_dir 63from gramps.gen.const import GRAMPS_LOCALE as glocale 64_ = glocale.translation.gettext 65 66#------------------------------------------------------------------------ 67# 68# Dialog window used to select a surname 69# 70#------------------------------------------------------------------------ 71class LastNameDialog(ManagedWindow): 72 """ 73 A dialog that allows the selection of a surname from the database. 74 """ 75 def __init__(self, database, uistate, track, surnames, skip_list=set()): 76 77 ManagedWindow.__init__(self, uistate, track, self, modal=True) 78 flags = Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT 79 buttons = (_('_Cancel'), Gtk.ResponseType.REJECT, 80 _('_OK'), Gtk.ResponseType.ACCEPT) 81 self.__dlg = Gtk.Dialog(None, uistate.window, flags, buttons) 82 self.set_window(self.__dlg, None, _('Select surname')) 83 self.setup_configs('interface.lastnamedialog', 400, 400) 84 85 # build up a container to display all of the people of interest 86 self.__model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT) 87 self.__tree_view = Gtk.TreeView(self.__model) 88 col1 = Gtk.TreeViewColumn(_('Surname'), Gtk.CellRendererText(), text=0) 89 col2 = Gtk.TreeViewColumn(_('Count'), Gtk.CellRendererText(), text=1) 90 col1.set_resizable(True) 91 col2.set_resizable(True) 92 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 93 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 94 col1.set_sort_column_id(0) 95 col2.set_sort_column_id(1) 96 self.__tree_view.append_column(col1) 97 self.__tree_view.append_column(col2) 98 scrolled_window = Gtk.ScrolledWindow() 99 scrolled_window.add(self.__tree_view) 100 scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 101 Gtk.PolicyType.AUTOMATIC) 102 scrolled_window.set_shadow_type(Gtk.ShadowType.OUT) 103 self.__dlg.vbox.pack_start(scrolled_window, True, True, 0) 104 self.show() 105 106 if len(surnames) == 0: 107 # we could use database.get_surname_list(), but if we do that 108 # all we get is a list of names without a count...therefore 109 # we'll traverse the entire database ourself and build up a 110 # list that we can use 111# for name in database.get_surname_list(): 112# self.__model.append([name, 0]) 113 114 # build up the list of surnames, keeping track of the count for each 115 # name (this can be a lengthy process, so by passing in the 116 # dictionary we can be certain we only do this once) 117 progress = ProgressMeter( 118 _('Finding Surnames'), parent=uistate.window) 119 progress.set_pass(_('Finding surnames'), 120 database.get_number_of_people()) 121 for person in database.iter_people(): 122 progress.step() 123 key = person.get_primary_name().get_surname() 124 count = 0 125 if key in surnames: 126 count = surnames[key] 127 surnames[key] = count + 1 128 progress.close() 129 130 # insert the names and count into the model 131 for key in surnames: 132 if key.encode('iso-8859-1', 'xmlcharrefreplace') not in skip_list: 133 self.__model.append([key, surnames[key]]) 134 135 # keep the list sorted starting with the most popular last name 136 # (but after sorting the whole list alphabetically first, so 137 # that surnames with the same number of people will be alphabetical) 138 self.__model.set_sort_column_id(0, Gtk.SortType.ASCENDING) 139 self.__model.set_sort_column_id(1, Gtk.SortType.DESCENDING) 140 141 # the "OK" button should be enabled/disabled based on the selection of 142 # a row 143 self.__tree_selection = self.__tree_view.get_selection() 144 self.__tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE) 145 self.__tree_selection.select_path(0) 146 147 def run(self): 148 """ 149 Display the dialog and return the selected surnames when done. 150 """ 151 response = self.__dlg.run() 152 surname_set = set() 153 if response == Gtk.ResponseType.ACCEPT: 154 (mode, paths) = self.__tree_selection.get_selected_rows() 155 for path in paths: 156 tree_iter = self.__model.get_iter(path) 157 surname = self.__model.get_value(tree_iter, 0) 158 surname_set.add(surname) 159 if response != Gtk.ResponseType.DELETE_EVENT: 160 # ManagedWindow: set the parent dialog to be modal again 161 self.close() 162 return surname_set 163 164 def build_menu_names(self, obj): 165 return (_('Select surname'), None) 166 167#------------------------------------------------------------------------- 168# 169# GuiStringOption class 170# 171#------------------------------------------------------------------------- 172class GuiStringOption(Gtk.Entry): 173 """ 174 This class displays an option that is a simple one-line string. 175 """ 176 def __init__(self, option, dbstate, uistate, track, override): 177 """ 178 @param option: The option to display. 179 @type option: gen.plug.menu.StringOption 180 @return: nothing 181 """ 182 Gtk.Entry.__init__(self) 183 self.__option = option 184 self.set_text(self.__option.get_value()) 185 186 # Set up signal handlers when the widget value is changed 187 # from user interaction or programmatically. When handling 188 # a specific signal, we need to temporarily block the signal 189 # that would call the other signal handler. 190 self.changekey = self.connect('changed', self.__text_changed) 191 self.valuekey = self.__option.connect('value-changed', 192 self.__value_changed) 193 194 self.conkey = self.__option.connect('avail-changed', 195 self.__update_avail) 196 self.__update_avail() 197 198 self.set_tooltip_text(self.__option.get_help()) 199 200 def __text_changed(self, obj): # IGNORE:W0613 - obj is unused 201 """ 202 Handle the change of the value made by the user. 203 """ 204 self.__option.disable_signals() 205 self.__option.set_value(self.get_text()) 206 self.__option.enable_signals() 207 208 def __update_avail(self): 209 """ 210 Update the availability (sensitivity) of this widget. 211 """ 212 avail = self.__option.get_available() 213 self.set_sensitive(avail) 214 215 def __value_changed(self): 216 """ 217 Handle the change made programmatically 218 """ 219 self.handler_block(self.changekey) 220 self.set_text(self.__option.get_value()) 221 self.handler_unblock(self.changekey) 222 223 def clean_up(self): 224 """ 225 remove stuff that blocks garbage collection 226 """ 227 self.__option.disconnect(self.valuekey) 228 self.__option.disconnect(self.conkey) 229 self.__option = None 230 231#------------------------------------------------------------------------- 232# 233# GuiColorOption class 234# 235#------------------------------------------------------------------------- 236class GuiColorOption(Gtk.ColorButton): 237 """ 238 This class displays an option that allows the selection of a colour. 239 """ 240 def __init__(self, option, dbstate, uistate, track, override): 241 self.__option = option 242 Gtk.ColorButton.__init__(self) 243 rgba = Gdk.RGBA() 244 rgba.parse(self.__option.get_value()) 245 self.set_rgba(rgba) 246 247 # Set up signal handlers when the widget value is changed 248 # from user interaction or programmatically. When handling 249 # a specific signal, we need to temporarily block the signal 250 # that would call the other signal handler. 251 self.changekey = self.connect('color-set', self.__color_changed) 252 self.valuekey = self.__option.connect('value-changed', 253 self.__value_changed) 254 255 self.conkey = self.__option.connect('avail-changed', 256 self.__update_avail) 257 self.__update_avail() 258 259 self.set_tooltip_text(self.__option.get_help()) 260 261 def __color_changed(self, obj): # IGNORE:W0613 - obj is unused 262 """ 263 Handle the change of color made by the user. 264 """ 265 rgba = self.get_rgba() 266 value = '#%02x%02x%02x' % (int(rgba.red * 255), 267 int(rgba.green * 255), 268 int(rgba.blue * 255)) 269 270 self.__option.disable_signals() 271 self.__option.set_value(value) 272 self.__option.enable_signals() 273 274 def __update_avail(self): 275 """ 276 Update the availability (sensitivity) of this widget. 277 """ 278 avail = self.__option.get_available() 279 self.set_sensitive(avail) 280 281 def __value_changed(self): 282 """ 283 Handle the change made programmatically 284 """ 285 self.handler_block(self.changekey) 286 rgba = Gdk.RGBA() 287 rgba.parse(self.__option.get_value()) 288 self.set_rgba(rgba) 289 self.handler_unblock(self.changekey) 290 291 def clean_up(self): 292 """ 293 remove stuff that blocks garbage collection 294 """ 295 self.__option.disconnect(self.valuekey) 296 self.__option.disconnect(self.conkey) 297 self.__option = None 298 299#------------------------------------------------------------------------- 300# 301# GuiNumberOption class 302# 303#------------------------------------------------------------------------- 304class GuiNumberOption(Gtk.SpinButton): 305 """ 306 This class displays an option that is a simple number with defined maximum 307 and minimum values. 308 """ 309 def __init__(self, option, dbstate, uistate, track, override): 310 self.__option = option 311 312 decimals = 0 313 step = self.__option.get_step() 314 adj = Gtk.Adjustment(value=1, 315 lower=self.__option.get_min(), 316 upper=self.__option.get_max(), 317 step_increment=step) 318 319 # Calculate the number of decimal places if necessary 320 if step < 1: 321 import math 322 decimals = int(math.log10(step) * -1) 323 324 Gtk.SpinButton.__init__(self, adjustment=adj, 325 climb_rate=1, digits=decimals) 326 Gtk.SpinButton.set_numeric(self, True) 327 328 self.set_value(self.__option.get_value()) 329 330 # Set up signal handlers when the widget value is changed 331 # from user interaction or programmatically. When handling 332 # a specific signal, we need to temporarily block the signal 333 # that would call the other signal handler. 334 self.changekey = self.connect('value_changed', 335 self.__number_changed) 336 self.valuekey = self.__option.connect('value-changed', 337 self.__value_changed) 338 339 self.conkey = self.__option.connect('avail-changed', 340 self.__update_avail) 341 self.__update_avail() 342 343 self.set_tooltip_text(self.__option.get_help()) 344 345 def __number_changed(self, obj): # IGNORE:W0613 - obj is unused 346 """ 347 Handle the change of the value made by the user. 348 """ 349 vtype = type(self.__option.get_value()) 350 351 self.__option.set_value(vtype(self.get_value())) 352 353 def __update_avail(self): 354 """ 355 Update the availability (sensitivity) of this widget. 356 """ 357 avail = self.__option.get_available() 358 self.set_sensitive(avail) 359 360 def __value_changed(self): 361 """ 362 Handle the change made programmatically 363 """ 364 self.handler_block(self.changekey) 365 self.set_value(self.__option.get_value()) 366 self.handler_unblock(self.changekey) 367 368 def clean_up(self): 369 """ 370 remove stuff that blocks garbage collection 371 """ 372 self.__option.disconnect(self.valuekey) 373 self.__option.disconnect(self.conkey) 374 self.__option = None 375 376#------------------------------------------------------------------------- 377# 378# GuiTextOption class 379# 380#------------------------------------------------------------------------- 381class GuiTextOption(Gtk.ScrolledWindow): 382 """ 383 This class displays an option that is a multi-line string. 384 """ 385 def __init__(self, option, dbstate, uistate, track, override): 386 self.__option = option 387 Gtk.ScrolledWindow.__init__(self) 388 self.set_shadow_type(Gtk.ShadowType.IN) 389 self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 390 self.set_vexpand(True) 391 392 # Add a TextView 393 value = self.__option.get_value() 394 gtext = Gtk.TextView() 395 gtext.set_size_request(-1, 70) 396 gtext.get_buffer().set_text("\n".join(value)) 397 gtext.set_editable(1) 398 gtext.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) 399 self.add(gtext) 400 self.__buff = gtext.get_buffer() 401 402 # Set up signal handlers when the widget value is changed 403 # from user interaction or programmatically. When handling 404 # a specific signal, we need to temporarily block the signal 405 # that would call the other signal handler. 406 self.bufcon = self.__buff.connect('changed', self.__text_changed) 407 self.valuekey = self.__option.connect('value-changed', 408 self.__value_changed) 409 410 # Required for tooltip 411 gtext.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK) 412 gtext.add_events(Gdk.EventMask.LEAVE_NOTIFY_MASK) 413 gtext.set_tooltip_text(self.__option.get_help()) 414 415 def __text_changed(self, obj): # IGNORE:W0613 - obj is unused 416 """ 417 Handle the change of the value made by the user. 418 """ 419 text_val = str(self.__buff.get_text(self.__buff.get_start_iter(), 420 self.__buff.get_end_iter(), 421 False)) 422 423 self.__option.disable_signals() 424 self.__option.set_value(text_val.split('\n')) 425 self.__option.enable_signals() 426 427 def __value_changed(self): 428 """ 429 Handle the change made programmatically 430 """ 431 self.__buff.handler_block(self.bufcon) 432 433 value = self.__option.get_value() 434 435 # Can only set using a string. If we have a string value, 436 # we'll use that. If not, we'll assume a list and convert 437 # it into a single string by assuming each list element 438 # is separated by a newline. 439 if isinstance(value, str): 440 self.__buff.set_text(value) 441 442 # Need to manually call the other handler so that the option 443 # value is changed to be a list. If left as a string, 444 # it would be treated as a list, meaning each character 445 # becomes a list element -- not what we want. 446 self.__text_changed(None) 447 else: 448 self.__buff.set_text("\n".join(value)) 449 450 self.__buff.handler_unblock(self.bufcon) 451 452 def clean_up(self): 453 """ 454 remove stuff that blocks garbage collection 455 """ 456 self.__option.disconnect(self.valuekey) 457 self.__option = None 458 459 self.__buff.disconnect(self.bufcon) 460 self.__buff = None 461 462#------------------------------------------------------------------------- 463# 464# GuiBooleanOption class 465# 466#------------------------------------------------------------------------- 467class GuiBooleanOption(Gtk.CheckButton): 468 """ 469 This class displays an option that is a boolean (True or False). 470 """ 471 def __init__(self, option, dbstate, uistate, track, override): 472 self.__option = option 473 Gtk.CheckButton.__init__(self) 474 self.set_label(self.__option.get_label()) 475 self.set_active(self.__option.get_value()) 476 477 # Set up signal handlers when the widget value is changed 478 # from user interaction or programmatically. When handling 479 # a specific signal, we need to temporarily block the signal 480 # that would call the other signal handler. 481 self.changekey = self.connect('toggled', self.__state_changed) 482 self.valuekey = self.__option.connect('value-changed', 483 self.__value_changed) 484 485 self.conkey = self.__option.connect('avail-changed', 486 self.__update_avail) 487 self.__update_avail() 488 489 self.set_tooltip_text(self.__option.get_help()) 490 491 def __state_changed(self, obj): # IGNORE:W0613 - obj is unused 492 """ 493 Handle the change of the value made by the user. 494 """ 495 self.__option.set_value(self.get_active()) 496 497 def __update_avail(self): 498 """ 499 Update the availability (sensitivity) of this widget. 500 """ 501 avail = self.__option.get_available() 502 self.set_sensitive(avail) 503 504 def __value_changed(self): 505 """ 506 Handle the change made programmatically 507 """ 508 self.handler_block(self.changekey) 509 self.set_active(self.__option.get_value()) 510 self.handler_unblock(self.changekey) 511 512 def clean_up(self): 513 """ 514 remove stuff that blocks garbage collection 515 """ 516 self.__option.disconnect(self.valuekey) 517 self.__option.disconnect(self.conkey) 518 self.__option = None 519 520#------------------------------------------------------------------------- 521# 522# GuiEnumeratedListOption class 523# 524#------------------------------------------------------------------------- 525class GuiEnumeratedListOption(Gtk.Box): 526 """ 527 This class displays an option that provides a finite number of values. 528 Each possible value is assigned a value and a description. 529 """ 530 def __init__(self, option, dbstate, uistate, track, override): 531 Gtk.Box.__init__(self) 532 evt_box = Gtk.EventBox() 533 self.__option = option 534 self.__combo = Gtk.ComboBoxText() 535 if len(option.get_items()) > 18: 536 self.__combo.set_popup_fixed_width(False) 537 self.__combo.set_wrap_width(3) 538 evt_box.add(self.__combo) 539 self.pack_start(evt_box, True, True, 0) 540 541 self.__update_options() 542 543 # Set up signal handlers when the widget value is changed 544 # from user interaction or programmatically. When handling 545 # a specific signal, we need to temporarily block the signal 546 # that would call the other signal handler. 547 self.changekey = self.__combo.connect('changed', 548 self.__selection_changed) 549 self.valuekey = self.__option.connect('value-changed', 550 self.__value_changed) 551 552 self.conkey1 = self.__option.connect('options-changed', 553 self.__update_options) 554 self.conkey2 = self.__option.connect('avail-changed', 555 self.__update_avail) 556 self.__update_avail() 557 558 self.set_tooltip_text(self.__option.get_help()) 559 560 def __selection_changed(self, obj): # IGNORE:W0613 - obj is unused 561 """ 562 Handle the change of the value made by the user. 563 """ 564 index = self.__combo.get_active() 565 if index < 0: 566 return 567 items = self.__option.get_items() 568 value, description = items[index] # IGNORE:W0612 - description is unused 569 570 # Don't disable the __option signals as is normally done for 571 # the other widgets or bad things happen (like other needed 572 # signals don't fire) 573 574 self.__option.set_value(value) 575 self.value_changed() # Allow overriding so that another class 576 # can add functionality 577 578 def value_changed(self): 579 """ Allow overriding so that another class can add functionality """ 580 pass 581 582 def __update_options(self): 583 """ 584 Handle the change of the available options. 585 """ 586 self.__combo.remove_all() 587 #self.__combo.get_model().clear() 588 cur_val = self.__option.get_value() 589 active_index = 0 590 current_index = 0 591 for (value, description) in self.__option.get_items(): 592 self.__combo.append_text(description) 593 if value == cur_val: 594 active_index = current_index 595 current_index += 1 596 self.__combo.set_active(active_index) 597 598 def __update_avail(self): 599 """ 600 Update the availability (sensitivity) of this widget. 601 """ 602 avail = self.__option.get_available() 603 self.set_sensitive(avail) 604 605 def __value_changed(self): 606 """ 607 Handle the change made programmatically 608 """ 609 self.__combo.handler_block(self.changekey) 610 self.__update_options() 611 self.__combo.handler_unblock(self.changekey) 612 613 def clean_up(self): 614 """ 615 remove stuff that blocks garbage collection 616 """ 617 self.__option.disconnect(self.valuekey) 618 self.__option.disconnect(self.conkey1) 619 self.__option.disconnect(self.conkey2) 620 self.__option = None 621 622#------------------------------------------------------------------------- 623# 624# GuiPersonOption class 625# 626#------------------------------------------------------------------------- 627class GuiPersonOption(Gtk.Box): 628 """ 629 This class displays an option that allows a person from the 630 database to be selected. 631 """ 632 def __init__(self, option, dbstate, uistate, track, override): 633 """ 634 @param option: The option to display. 635 @type option: gen.plug.menu.PersonOption 636 @return: nothing 637 """ 638 Gtk.Box.__init__(self) 639 self.__option = option 640 self.__dbstate = dbstate 641 self.__db = dbstate.get_database() 642 self.__uistate = uistate 643 self.__track = track 644 self.__person_label = Gtk.Label() 645 self.__person_label.set_halign(Gtk.Align.START) 646 647 pevt = Gtk.EventBox() 648 pevt.add(self.__person_label) 649 person_button = widgets.SimpleButton('gtk-index', 650 self.__get_person_clicked) 651 person_button.set_relief(Gtk.ReliefStyle.NORMAL) 652 653 self.pack_start(pevt, False, True, 0) 654 self.pack_end(person_button, False, True, 0) 655 656 gid = self.__option.get_value() 657 658 # Pick up the active person 659 try: 660 person_handle = self.__uistate.get_active('Person') 661 person = self.__dbstate.db.get_person_from_handle(person_handle) 662 663 if override or not person: 664 # Pick up the stored option value if there is one 665 person = self.__db.get_person_from_gramps_id(gid) 666 667 if not person: 668 # If all else fails, get the default person to avoid bad values 669 person = self.__db.get_default_person() 670 671 if not person: 672 person = self.__db.find_initial_person() 673 except HandleError: 674 person = None 675 676 self.__update_person(person) 677 678 self.valuekey = self.__option.connect('value-changed', 679 self.__value_changed) 680 681 self.conkey = self.__option.connect('avail-changed', 682 self.__update_avail) 683 self.__update_avail() 684 685 pevt.set_tooltip_text(self.__option.get_help()) 686 person_button.set_tooltip_text(_('Select a different person')) 687 688 def __get_person_clicked(self, obj): # IGNORE:W0613 - obj is unused 689 """ 690 Handle the button to choose a different person. 691 """ 692 # Create a filter for the person selector. 693 rfilter = GenericFilter() 694 rfilter.set_logical_op('or') 695 rfilter.add_rule(rules.person.IsBookmarked([])) 696 rfilter.add_rule(rules.person.HasIdOf([self.__option.get_value()])) 697 698 # Add the database home person if one exists. 699 default_person = self.__db.get_default_person() 700 if default_person: 701 gid = default_person.get_gramps_id() 702 rfilter.add_rule(rules.person.HasIdOf([gid])) 703 704 # Add the selected person if one exists. 705 person_handle = self.__uistate.get_active('Person') 706 active_person = self.__dbstate.db.get_person_from_handle(person_handle) 707 if active_person: 708 gid = active_person.get_gramps_id() 709 rfilter.add_rule(rules.person.HasIdOf([gid])) 710 711 select_class = SelectorFactory('Person') 712 sel = select_class(self.__dbstate, self.__uistate, self.__track, 713 title=_('Select a person for the report'), 714 filter=rfilter) 715 person = sel.run() 716 self.__update_person(person) 717 718 def __update_person(self, person): 719 """ 720 Update the currently selected person. 721 """ 722 if person: 723 name = _nd.display(person) 724 gid = person.get_gramps_id() 725 self.__person_label.set_text("%s (%s)" % (name, gid)) 726 self.__option.set_value(gid) 727 728 def __update_avail(self): 729 """ 730 Update the availability (sensitivity) of this widget. 731 """ 732 avail = self.__option.get_available() 733 self.set_sensitive(avail) 734 735 def __value_changed(self): 736 """ 737 Handle the change made programmatically 738 """ 739 gid = self.__option.get_value() 740 name = _nd.display(self.__db.get_person_from_gramps_id(gid)) 741 742 self.__person_label.set_text("%s (%s)" % (name, gid)) 743 744 def clean_up(self): 745 """ 746 remove stuff that blocks garbage collection 747 """ 748 self.__option.disconnect(self.valuekey) 749 self.__option.disconnect(self.conkey) 750 self.__option = None 751 752#------------------------------------------------------------------------- 753# 754# GuiFamilyOption class 755# 756#------------------------------------------------------------------------- 757class GuiFamilyOption(Gtk.Box): 758 """ 759 This class displays an option that allows a family from the 760 database to be selected. 761 """ 762 def __init__(self, option, dbstate, uistate, track, override): 763 """ 764 @param option: The option to display. 765 @type option: gen.plug.menu.FamilyOption 766 @return: nothing 767 """ 768 Gtk.Box.__init__(self) 769 self.__option = option 770 self.__dbstate = dbstate 771 self.__db = dbstate.get_database() 772 self.__uistate = uistate 773 self.__track = track 774 self.__family_label = Gtk.Label() 775 self.__family_label.set_halign(Gtk.Align.START) 776 777 pevt = Gtk.EventBox() 778 pevt.add(self.__family_label) 779 family_button = widgets.SimpleButton('gtk-index', 780 self.__get_family_clicked) 781 family_button.set_relief(Gtk.ReliefStyle.NORMAL) 782 783 self.pack_start(pevt, False, True, 0) 784 self.pack_end(family_button, False, True, 0) 785 786 self.__initialize_family(override) 787 788 self.valuekey = self.__option.connect('value-changed', 789 self.__value_changed) 790 791 self.conkey = self.__option.connect('avail-changed', 792 self.__update_avail) 793 self.__update_avail() 794 795 pevt.set_tooltip_text(self.__option.get_help()) 796 family_button.set_tooltip_text(_('Select a different family')) 797 798 def __initialize_family(self, override): 799 """ 800 Find a family to initialize the option with. If there is no specified 801 family, try to find a family that the user is likely interested in. 802 """ 803 family_list = [] 804 805 fid = self.__option.get_value() 806 fid_family = self.__db.get_family_from_gramps_id(fid) 807 active_family = self.__uistate.get_active('Family') 808 809 if override and fid_family: 810 # Use the stored option value if there is one 811 family_list = [fid_family.get_handle()] 812 813 if active_family and not family_list: 814 # Use the active family if one is selected 815 family_list = [active_family] 816 817 if not family_list: 818 # Next try the family of the active person 819 person_handle = self.__uistate.get_active('Person') 820 person = self.__dbstate.db.get_person_from_handle(person_handle) 821 if person: 822 family_list = person.get_family_handle_list() 823 824 if fid_family and not family_list: 825 # Next try the stored option value if there is one 826 family_list = [fid_family.get_handle()] 827 828 if not family_list: 829 # Next try the family of the default person in the database. 830 person = self.__db.get_default_person() 831 if person: 832 family_list = person.get_family_handle_list() 833 834 if not family_list: 835 # Finally, take any family you can find. 836 for family in self.__db.iter_family_handles(): 837 self.__update_family(family) 838 break 839 else: 840 self.__update_family(family_list[0]) 841 842 def __get_family_clicked(self, obj): # IGNORE:W0613 - obj is unused 843 """ 844 Handle the button to choose a different family. 845 """ 846 # Create a filter for the person selector. 847 rfilter = GenericFilterFactory('Family')() 848 rfilter.set_logical_op('or') 849 850 # Add the current family 851 rfilter.add_rule(rules.family.HasIdOf([self.__option.get_value()])) 852 853 # Add all bookmarked families 854 rfilter.add_rule(rules.family.IsBookmarked([])) 855 856 # Add the families of the database home person if one exists. 857 default_person = self.__db.get_default_person() 858 if default_person: 859 family_list = default_person.get_family_handle_list() 860 for family_handle in family_list: 861 family = self.__db.get_family_from_handle(family_handle) 862 gid = family.get_gramps_id() 863 rfilter.add_rule(rules.family.HasIdOf([gid])) 864 865 # Add the families of the selected person if one exists. 866 # Same code as above one ! See bug #5032 feature request #5038 867 ### active_person = self.__uistate.get_active('Person') ### 868 #active_person = self.__db.get_default_person() 869 #if active_person: 870 #family_list = active_person.get_family_handle_list() 871 #for family_handle in family_list: 872 #family = self.__db.get_family_from_handle(family_handle) 873 #gid = family.get_gramps_id() 874 #rfilter.add_rule(rules.family.HasIdOf([gid])) 875 876 select_class = SelectorFactory('Family') 877 sel = select_class(self.__dbstate, self.__uistate, self.__track, 878 filter=rfilter) 879 family = sel.run() 880 if family: 881 self.__update_family(family.get_handle()) 882 883 def __update_family(self, handle): 884 """ 885 Update the currently selected family. 886 """ 887 if handle: 888 family = self.__dbstate.db.get_family_from_handle(handle) 889 family_id = family.get_gramps_id() 890 fhandle = family.get_father_handle() 891 mhandle = family.get_mother_handle() 892 893 if fhandle: 894 father = self.__db.get_person_from_handle(fhandle) 895 father_name = _nd.display(father) 896 else: 897 father_name = _("unknown father") 898 899 if mhandle: 900 mother = self.__db.get_person_from_handle(mhandle) 901 mother_name = _nd.display(mother) 902 else: 903 mother_name = _("unknown mother") 904 905 name = _("%(father_name)s and %(mother_name)s (%(family_id)s)" 906 ) % {'father_name' : father_name, 907 'mother_name' : mother_name, 908 'family_id' : family_id} 909 910 self.__family_label.set_text(name) 911 self.__option.set_value(family_id) 912 913 def __update_avail(self): 914 """ 915 Update the availability (sensitivity) of this widget. 916 """ 917 avail = self.__option.get_available() 918 self.set_sensitive(avail) 919 920 def __value_changed(self): 921 """ 922 Handle the change made programmatically 923 """ 924 fid = self.__option.get_value() 925 handle = self.__db.get_family_from_gramps_id(fid).get_handle() 926 927 # Need to disable signals as __update_family() calls set_value() 928 # which would launch the 'value-changed' signal which is what 929 # we are reacting to here in the first place (don't need the 930 # signal repeated) 931 self.__option.disable_signals() 932 self.__update_family(handle) 933 self.__option.enable_signals() 934 935 def clean_up(self): 936 """ 937 remove stuff that blocks garbage collection 938 """ 939 self.__option.disconnect(self.valuekey) 940 self.__option.disconnect(self.conkey) 941 self.__option = None 942 943#------------------------------------------------------------------------- 944# 945# GuiNoteOption class 946# 947#------------------------------------------------------------------------- 948class GuiNoteOption(Gtk.Box): 949 """ 950 This class displays an option that allows a note from the 951 database to be selected. 952 """ 953 def __init__(self, option, dbstate, uistate, track, override): 954 """ 955 @param option: The option to display. 956 @type option: gen.plug.menu.NoteOption 957 @return: nothing 958 """ 959 Gtk.Box.__init__(self) 960 self.__option = option 961 self.__dbstate = dbstate 962 self.__db = dbstate.get_database() 963 self.__uistate = uistate 964 self.__track = track 965 self.__note_label = Gtk.Label() 966 self.__note_label.set_halign(Gtk.Align.START) 967 968 pevt = Gtk.EventBox() 969 pevt.add(self.__note_label) 970 note_button = widgets.SimpleButton('gtk-index', 971 self.__get_note_clicked) 972 note_button.set_relief(Gtk.ReliefStyle.NORMAL) 973 974 self.pack_start(pevt, False, True, 0) 975 self.pack_end(note_button, False, True, 0) 976 977 # Initialize to the current value 978 nid = self.__option.get_value() 979 note = self.__db.get_note_from_gramps_id(nid) 980 self.__update_note(note) 981 982 self.valuekey = self.__option.connect('value-changed', 983 self.__value_changed) 984 985 self.__option.connect('avail-changed', self.__update_avail) 986 self.__update_avail() 987 988 pevt.set_tooltip_text(self.__option.get_help()) 989 note_button.set_tooltip_text(_('Select an existing note')) 990 991 def __get_note_clicked(self, obj): # IGNORE:W0613 - obj is unused 992 """ 993 Handle the button to choose a different note. 994 """ 995 select_class = SelectorFactory('Note') 996 sel = select_class(self.__dbstate, self.__uistate, self.__track) 997 note = sel.run() 998 self.__update_note(note) 999 1000 def __update_note(self, note): 1001 """ 1002 Update the currently selected note. 1003 """ 1004 if note: 1005 note_id = note.get_gramps_id() 1006 txt = " ".join(note.get().split()) 1007 if len(txt) > 35: 1008 txt = txt[:35] + "..." 1009 txt = "%s [%s]" % (txt, note_id) 1010 1011 self.__note_label.set_text(txt) 1012 self.__option.set_value(note_id) 1013 else: 1014 txt = "<i>%s</i>" % _('No note given, click button to select one') 1015 self.__note_label.set_text(txt) 1016 self.__note_label.set_use_markup(True) 1017 self.__option.set_value("") 1018 1019 def __update_avail(self): 1020 """ 1021 Update the availability (sensitivity) of this widget. 1022 """ 1023 avail = self.__option.get_available() 1024 self.set_sensitive(avail) 1025 1026 def __value_changed(self): 1027 """ 1028 Handle the change made programmatically 1029 """ 1030 nid = self.__option.get_value() 1031 note = self.__db.get_note_from_gramps_id(nid) 1032 1033 # Need to disable signals as __update_note() calls set_value() 1034 # which would launch the 'value-changed' signal which is what 1035 # we are reacting to here in the first place (don't need the 1036 # signal repeated) 1037 self.__option.disable_signals() 1038 self.__update_note(note) 1039 self.__option.enable_signals() 1040 1041 def clean_up(self): 1042 """ 1043 remove stuff that blocks garbage collection 1044 """ 1045 self.__option.disconnect(self.valuekey) 1046 self.__option = None 1047 1048#------------------------------------------------------------------------- 1049# 1050# GuiMediaOption class 1051# 1052#------------------------------------------------------------------------- 1053class GuiMediaOption(Gtk.Box): 1054 """ 1055 This class displays an option that allows a media object from the 1056 database to be selected. 1057 """ 1058 def __init__(self, option, dbstate, uistate, track, override): 1059 """ 1060 @param option: The option to display. 1061 @type option: gen.plug.menu.MediaOption 1062 @return: nothing 1063 """ 1064 Gtk.Box.__init__(self) 1065 self.__option = option 1066 self.__dbstate = dbstate 1067 self.__db = dbstate.get_database() 1068 self.__uistate = uistate 1069 self.__track = track 1070 self.__media_label = Gtk.Label() 1071 self.__media_label.set_halign(Gtk.Align.START) 1072 1073 pevt = Gtk.EventBox() 1074 pevt.add(self.__media_label) 1075 media_button = widgets.SimpleButton('gtk-index', 1076 self.__get_media_clicked) 1077 media_button.set_relief(Gtk.ReliefStyle.NORMAL) 1078 1079 self.pack_start(pevt, False, True, 0) 1080 self.pack_end(media_button, False, True, 0) 1081 1082 # Initialize to the current value 1083 mid = self.__option.get_value() 1084 media = self.__db.get_media_from_gramps_id(mid) 1085 self.__update_media(media) 1086 1087 self.valuekey = self.__option.connect('value-changed', 1088 self.__value_changed) 1089 1090 self.__option.connect('avail-changed', self.__update_avail) 1091 self.__update_avail() 1092 1093 pevt.set_tooltip_text(self.__option.get_help()) 1094 media_button.set_tooltip_text(_('Select an existing media object')) 1095 1096 def __get_media_clicked(self, obj): # IGNORE:W0613 - obj is unused 1097 """ 1098 Handle the button to choose a different note. 1099 """ 1100 select_class = SelectorFactory('Media') 1101 sel = select_class(self.__dbstate, self.__uistate, self.__track) 1102 media = sel.run() 1103 self.__update_media(media) 1104 1105 def __update_media(self, media): 1106 """ 1107 Update the currently selected media. 1108 """ 1109 if media: 1110 media_id = media.get_gramps_id() 1111 txt = "%s [%s]" % (media.get_description(), media_id) 1112 1113 self.__media_label.set_text(txt) 1114 self.__option.set_value(media_id) 1115 else: 1116 txt = "<i>%s</i>" % _('No image given, click button to select one') 1117 self.__media_label.set_text(txt) 1118 self.__media_label.set_use_markup(True) 1119 self.__option.set_value("") 1120 1121 def __update_avail(self): 1122 """ 1123 Update the availability (sensitivity) of this widget. 1124 """ 1125 avail = self.__option.get_available() 1126 self.set_sensitive(avail) 1127 1128 def __value_changed(self): 1129 """ 1130 Handle the change made programmatically 1131 """ 1132 mid = self.__option.get_value() 1133 media = self.__db.get_media_from_gramps_id(mid) 1134 1135 # Need to disable signals as __update_media() calls set_value() 1136 # which would launch the 'value-changed' signal which is what 1137 # we are reacting to here in the first place (don't need the 1138 # signal repeated) 1139 self.__option.disable_signals() 1140 self.__update_media(media) 1141 self.__option.enable_signals() 1142 1143 def clean_up(self): 1144 """ 1145 remove stuff that blocks garbage collection 1146 """ 1147 self.__option.disconnect(self.valuekey) 1148 self.__option = None 1149 1150#------------------------------------------------------------------------- 1151# 1152# GuiPersonListOption class 1153# 1154#------------------------------------------------------------------------- 1155class GuiPersonListOption(Gtk.Box): 1156 """ 1157 This class displays a widget that allows multiple people from the 1158 database to be selected. 1159 """ 1160 def __init__(self, option, dbstate, uistate, track, override): 1161 """ 1162 @param option: The option to display. 1163 @type option: gen.plug.menu.PersonListOption 1164 @return: nothing 1165 """ 1166 Gtk.Box.__init__(self) 1167 self.__option = option 1168 self.__dbstate = dbstate 1169 self.__db = dbstate.get_database() 1170 self.__uistate = uistate 1171 self.__track = track 1172 self.set_size_request(150, 100) 1173 1174 self.__model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) 1175 self.__tree_view = Gtk.TreeView(model=self.__model) 1176 col1 = Gtk.TreeViewColumn(_('Name'), Gtk.CellRendererText(), text=0) 1177 col2 = Gtk.TreeViewColumn(_('ID'), Gtk.CellRendererText(), text=1) 1178 col1.set_resizable(True) 1179 col2.set_resizable(True) 1180 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1181 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1182 col1.set_sort_column_id(0) 1183 col2.set_sort_column_id(1) 1184 self.__tree_view.append_column(col1) 1185 self.__tree_view.append_column(col2) 1186 self.__scrolled_window = Gtk.ScrolledWindow() 1187 self.__scrolled_window.add(self.__tree_view) 1188 self.__scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 1189 Gtk.PolicyType.AUTOMATIC) 1190 self.__scrolled_window.set_shadow_type(Gtk.ShadowType.OUT) 1191 1192 self.pack_start(self.__scrolled_window, True, True, 0) 1193 1194 self.__value_changed() 1195 1196 # now setup the '+' and '-' pushbutton for adding/removing people from 1197 # the container 1198 self.__add_person = widgets.SimpleButton('list-add', 1199 self.__add_person_clicked) 1200 self.__del_person = widgets.SimpleButton('list-remove', 1201 self.__del_person_clicked) 1202 self.__vbbox = Gtk.ButtonBox(orientation=Gtk.Orientation.VERTICAL) 1203 self.__vbbox.add(self.__add_person) 1204 self.__vbbox.add(self.__del_person) 1205 self.__vbbox.set_layout(Gtk.ButtonBoxStyle.SPREAD) 1206 self.pack_end(self.__vbbox, False, False, 0) 1207 1208 self.valuekey = self.__option.connect('value-changed', 1209 self.__value_changed) 1210 1211 self.__tree_view.set_tooltip_text(self.__option.get_help()) 1212 1213 def __add_person_clicked(self, obj): # IGNORE:W0613 - obj is unused 1214 """ 1215 Handle the add person button. 1216 """ 1217 # people we already have must be excluded 1218 # so we don't list them multiple times 1219 skip_list = set() 1220 tree_iter = self.__model.get_iter_first() 1221 while tree_iter: 1222 gid = self.__model.get_value(tree_iter, 1) # get the GID in col. #1 1223 person = self.__db.get_person_from_gramps_id(gid) 1224 skip_list.add(person.get_handle()) 1225 tree_iter = self.__model.iter_next(tree_iter) 1226 1227 select_class = SelectorFactory('Person') 1228 sel = select_class(self.__dbstate, self.__uistate, 1229 self.__track, skip=skip_list) 1230 person = sel.run() 1231 if person: 1232 name = _nd.display(person) 1233 gid = person.get_gramps_id() 1234 self.__model.append([name, gid]) 1235 1236 # if this person has a spouse, ask if we should include the spouse 1237 # in the list of "people of interest" 1238 # 1239 # NOTE: we may want to make this an optional thing, determined 1240 # by the use of a parameter at the time this class is instatiated 1241 family_list = person.get_family_handle_list() 1242 for family_handle in family_list: 1243 family = self.__db.get_family_from_handle(family_handle) 1244 1245 if person.get_handle() == family.get_father_handle(): 1246 spouse_handle = family.get_mother_handle() 1247 else: 1248 spouse_handle = family.get_father_handle() 1249 1250 if spouse_handle and (spouse_handle not in skip_list): 1251 spouse = self.__db.get_person_from_handle(spouse_handle) 1252 spouse_name = _nd.display(spouse) 1253 text = _('Also include %s?') % spouse_name 1254 1255 prompt = OptionDialog(_('Select Person'), 1256 text, 1257 _('No'), None, 1258 _('Yes'), None, 1259 parent=self.__uistate.window) 1260 if prompt.get_response() == Gtk.ResponseType.YES: 1261 gid = spouse.get_gramps_id() 1262 self.__model.append([spouse_name, gid]) 1263 1264 self.__update_value() 1265 1266 def __del_person_clicked(self, obj): # IGNORE:W0613 - obj is unused 1267 """ 1268 Handle the delete person button. 1269 """ 1270 (path, column) = self.__tree_view.get_cursor() 1271 if path: 1272 tree_iter = self.__model.get_iter(path) 1273 self.__model.remove(tree_iter) 1274 self.__update_value() 1275 1276 def __update_value(self): 1277 """ 1278 Parse the object and return. 1279 """ 1280 gidlist = '' 1281 tree_iter = self.__model.get_iter_first() 1282 while tree_iter: 1283 gid = self.__model.get_value(tree_iter, 1) 1284 gidlist = gidlist + gid + ' ' 1285 tree_iter = self.__model.iter_next(tree_iter) 1286 1287 # Supress signals so that the set_value() handler 1288 # (__value_changed()) doesn't get called 1289 self.__option.disable_signals() 1290 self.__option.set_value(gidlist) 1291 self.__option.enable_signals() 1292 1293 def __value_changed(self): 1294 """ 1295 Handle the change made programmatically 1296 """ 1297 value = self.__option.get_value() 1298 1299 if not isinstance(value, str): 1300 # Convert array into a string 1301 # (convienence so that programmers can 1302 # set value using a list) 1303 value = " ".join(value) 1304 1305 # Need to change __option value to be the string 1306 1307 self.__option.disable_signals() 1308 self.__option.set_value(value) 1309 self.__option.enable_signals() 1310 1311 # Remove all entries (the new values will REPLACE 1312 # rather than APPEND) 1313 self.__model.clear() 1314 1315 for gid in value.split(): 1316 person = self.__db.get_person_from_gramps_id(gid) 1317 if person: 1318 name = _nd.display(person) 1319 self.__model.append([name, gid]) 1320 1321 def clean_up(self): 1322 """ 1323 remove stuff that blocks garbage collection 1324 """ 1325 self.__option.disconnect(self.valuekey) 1326 self.__option = None 1327 1328#------------------------------------------------------------------------- 1329# 1330# GuiPlaceListOption class 1331# 1332#------------------------------------------------------------------------- 1333class GuiPlaceListOption(Gtk.Box): 1334 """ 1335 This class displays a widget that allows multiple places from the 1336 database to be selected. 1337 """ 1338 def __init__(self, option, dbstate, uistate, track, override): 1339 """ 1340 @param option: The option to display. 1341 @type option: gen.plug.menu.PlaceListOption 1342 @return: nothing 1343 """ 1344 Gtk.Box.__init__(self) 1345 self.__option = option 1346 self.__dbstate = dbstate 1347 self.__db = dbstate.get_database() 1348 self.__uistate = uistate 1349 self.__track = track 1350 self.set_size_request(150, 150) 1351 1352 self.__model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) 1353 self.__tree_view = Gtk.TreeView(self.__model) 1354 col1 = Gtk.TreeViewColumn(_('Place'), Gtk.CellRendererText(), text=0) 1355 col2 = Gtk.TreeViewColumn(_('ID'), Gtk.CellRendererText(), text=1) 1356 col1.set_resizable(True) 1357 col2.set_resizable(True) 1358 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1359 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1360 col1.set_sort_column_id(0) 1361 col2.set_sort_column_id(1) 1362 self.__tree_view.append_column(col1) 1363 self.__tree_view.append_column(col2) 1364 self.__scrolled_window = Gtk.ScrolledWindow() 1365 self.__scrolled_window.add(self.__tree_view) 1366 self.__scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 1367 Gtk.PolicyType.AUTOMATIC) 1368 self.__scrolled_window.set_shadow_type(Gtk.ShadowType.OUT) 1369 1370 self.pack_start(self.__scrolled_window, True, True, 0) 1371 1372 self.__value_changed() 1373 1374 # now setup the '+' and '-' pushbutton for adding/removing places from 1375 # the container 1376 self.__add_place = widgets.SimpleButton('list-add', 1377 self.__add_place_clicked) 1378 self.__del_place = widgets.SimpleButton('list-remove', 1379 self.__del_place_clicked) 1380 self.__vbbox = Gtk.ButtonBox(orientation=Gtk.Orientation.VERTICAL) 1381 self.__vbbox.add(self.__add_place) 1382 self.__vbbox.add(self.__del_place) 1383 self.__vbbox.set_layout(Gtk.ButtonBoxStyle.SPREAD) 1384 self.pack_end(self.__vbbox, False, False, 0) 1385 1386 self.valuekey = self.__option.connect('value-changed', 1387 self.__value_changed) 1388 1389 self.__tree_view.set_tooltip_text(self.__option.get_help()) 1390 1391 def __add_place_clicked(self, obj): # IGNORE:W0613 - obj is unused 1392 """ 1393 Handle the add place button. 1394 """ 1395 # places we already have must be excluded 1396 # so we don't list them multiple times 1397 skip_list = set() 1398 tree_iter = self.__model.get_iter_first() 1399 while tree_iter: 1400 gid = self.__model.get_value(tree_iter, 1) # get the GID in col. #1 1401 place = self.__db.get_place_from_gramps_id(gid) 1402 skip_list.add(place.get_handle()) 1403 tree_iter = self.__model.iter_next(tree_iter) 1404 1405 select_class = SelectorFactory('Place') 1406 sel = select_class(self.__dbstate, self.__uistate, 1407 self.__track, skip=skip_list) 1408 place = sel.run() 1409 if place: 1410 place_name = _pd.display(self.__db, place) 1411 gid = place.get_gramps_id() 1412 self.__model.append([place_name, gid]) 1413 self.__update_value() 1414 1415 def __del_place_clicked(self, obj): # IGNORE:W0613 - obj is unused 1416 """ 1417 Handle the delete place button. 1418 """ 1419 (path, column) = self.__tree_view.get_cursor() 1420 if path: 1421 tree_iter = self.__model.get_iter(path) 1422 self.__model.remove(tree_iter) 1423 self.__update_value() 1424 1425 def __update_value(self): 1426 """ 1427 Parse the object and return. 1428 """ 1429 gidlist = '' 1430 tree_iter = self.__model.get_iter_first() 1431 while tree_iter: 1432 gid = self.__model.get_value(tree_iter, 1) 1433 gidlist = gidlist + gid + ' ' 1434 tree_iter = self.__model.iter_next(tree_iter) 1435 self.__option.set_value(gidlist) 1436 1437 def __value_changed(self): 1438 """ 1439 Handle the change made programmatically 1440 """ 1441 value = self.__option.get_value() 1442 1443 if not isinstance(value, str): 1444 # Convert array into a string 1445 # (convienence so that programmers can 1446 # set value using a list) 1447 value = " ".join(value) 1448 1449 # Need to change __option value to be the string 1450 1451 self.__option.disable_signals() 1452 self.__option.set_value(value) 1453 self.__option.enable_signals() 1454 1455 # Remove all entries (the new values will REPLACE 1456 # rather than APPEND) 1457 self.__model.clear() 1458 1459 for gid in value.split(): 1460 place = self.__db.get_place_from_gramps_id(gid) 1461 if place: 1462 place_name = _pd.display(self.__db, place) 1463 self.__model.append([place_name, gid]) 1464 1465 def clean_up(self): 1466 """ 1467 remove stuff that blocks garbage collection 1468 """ 1469 self.__option.disconnect(self.valuekey) 1470 self.__option = None 1471 1472#------------------------------------------------------------------------- 1473# 1474# GuiSurnameColorOption class 1475# 1476#------------------------------------------------------------------------- 1477class GuiSurnameColorOption(Gtk.Box): 1478 """ 1479 This class displays a widget that allows multiple surnames to be 1480 selected from the database, and to assign a colour (not necessarily 1481 unique) to each one. 1482 """ 1483 def __init__(self, option, dbstate, uistate, track, override): 1484 """ 1485 @param option: The option to display. 1486 @type option: gen.plug.menu.SurnameColorOption 1487 @return: nothing 1488 """ 1489 Gtk.Box.__init__(self) 1490 self.__option = option 1491 self.__dbstate = dbstate 1492 self.__db = dbstate.get_database() 1493 self.__uistate = uistate 1494 self.__track = track 1495 item = uistate.gwm.get_item_from_track(track) 1496 self.__parent = item[0].window if isinstance(item, list) \ 1497 else item.window 1498 1499 self.set_size_request(150, 150) 1500 1501 # This will get populated the first time the dialog is run, 1502 # and used each time after. 1503 self.__surnames = {} # list of surnames and count 1504 1505 self.__model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) 1506 self.__tree_view = Gtk.TreeView(model=self.__model) 1507 self.__tree_view.connect('row-activated', self.__row_clicked) 1508 col1 = Gtk.TreeViewColumn(_('Surname'), Gtk.CellRendererText(), text=0) 1509 col2 = Gtk.TreeViewColumn(_('Color'), Gtk.CellRendererText(), text=1) 1510 col1.set_resizable(True) 1511 col2.set_resizable(True) 1512 col1.set_sort_column_id(0) 1513 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1514 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 1515 self.__tree_view.append_column(col1) 1516 self.__tree_view.append_column(col2) 1517 self.scrolled_window = Gtk.ScrolledWindow() 1518 self.scrolled_window.add(self.__tree_view) 1519 self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, 1520 Gtk.PolicyType.AUTOMATIC) 1521 self.scrolled_window.set_shadow_type(Gtk.ShadowType.OUT) 1522 self.pack_start(self.scrolled_window, True, True, 0) 1523 1524 self.add_surname = widgets.SimpleButton('list-add', 1525 self.__add_clicked) 1526 self.del_surname = widgets.SimpleButton('list-remove', 1527 self.__del_clicked) 1528 self.vbbox = Gtk.ButtonBox(orientation=Gtk.Orientation.VERTICAL) 1529 self.vbbox.add(self.add_surname) 1530 self.vbbox.add(self.del_surname) 1531 self.vbbox.set_layout(Gtk.ButtonBoxStyle.SPREAD) 1532 self.pack_end(self.vbbox, False, False, 0) 1533 1534 self.__value_changed() 1535 1536 self.valuekey = self.__option.connect('value-changed', 1537 self.__value_changed) 1538 1539 self.__tree_view.set_tooltip_text(self.__option.get_help()) 1540 1541 def __add_clicked(self, obj): # IGNORE:W0613 - obj is unused 1542 """ 1543 Handle the add surname button. 1544 """ 1545 skip_list = set() 1546 tree_iter = self.__model.get_iter_first() 1547 while tree_iter: 1548 surname = self.__model.get_value(tree_iter, 0) 1549 skip_list.add(surname.encode('iso-8859-1', 'xmlcharrefreplace')) 1550 tree_iter = self.__model.iter_next(tree_iter) 1551 1552 ln_dialog = LastNameDialog(self.__db, self.__uistate, 1553 self.__track, self.__surnames, skip_list) 1554 surname_set = ln_dialog.run() 1555 for surname in surname_set: 1556 self.__model.append([surname, '#ffffff']) 1557 self.__update_value() 1558 1559 def __del_clicked(self, obj): # IGNORE:W0613 - obj is unused 1560 """ 1561 Handle the delete surname button. 1562 """ 1563 (path, column) = self.__tree_view.get_cursor() 1564 if path: 1565 tree_iter = self.__model.get_iter(path) 1566 self.__model.remove(tree_iter) 1567 self.__update_value() 1568 1569 def __row_clicked(self, treeview, path, column): 1570 """ 1571 Handle the case of a row being clicked on. 1572 """ 1573 # get the surname and colour value for this family 1574 tree_iter = self.__model.get_iter(path) 1575 surname = self.__model.get_value(tree_iter, 0) 1576 rgba = Gdk.RGBA() 1577 rgba.parse(self.__model.get_value(tree_iter, 1)) 1578 1579 title = _('Select color for %s') % surname 1580 colour_dialog = Gtk.ColorChooserDialog(title=title, 1581 transient_for=self.__parent) 1582 colour_dialog.set_rgba(rgba) 1583 response = colour_dialog.run() 1584 1585 if response == Gtk.ResponseType.OK: 1586 rgba = colour_dialog.get_rgba() 1587 colour_name = '#%02x%02x%02x' % (int(rgba.red * 255), 1588 int(rgba.green * 255), 1589 int(rgba.blue * 255)) 1590 self.__model.set_value(tree_iter, 1, colour_name) 1591 1592 colour_dialog.destroy() 1593 self.__update_value() 1594 1595 def __update_value(self): 1596 """ 1597 Parse the object and return. 1598 """ 1599 surname_colours = '' 1600 tree_iter = self.__model.get_iter_first() 1601 while tree_iter: 1602 surname = self.__model.get_value(tree_iter, 0) 1603 #surname = surname.encode('iso-8859-1', 'xmlcharrefreplace') 1604 colour = self.__model.get_value(tree_iter, 1) 1605 # Tried to use a dictionary, and tried to save it as a tuple, 1606 # but couldn't get this to work right -- this is lame, but now 1607 # the surnames and colours are saved as a plain text string 1608 # 1609 # Hmmm...putting whitespace between the fields causes 1610 # problems when the surname has whitespace -- for example, 1611 # with surnames like "Del Monte". So now we insert a non- 1612 # whitespace character which is unlikely to appear in 1613 # a surname. (See bug report #2162.) 1614 surname_colours += surname + '\xb0' + colour + '\xb0' 1615 tree_iter = self.__model.iter_next(tree_iter) 1616 self.__option.set_value(surname_colours) 1617 1618 def __value_changed(self): 1619 """ 1620 Handle the change made programmatically 1621 """ 1622 value = self.__option.get_value() 1623 1624 if not isinstance(value, str): 1625 # Convert dictionary into a string 1626 # (convienence so that programmers can 1627 # set value using a dictionary) 1628 value_str = "" 1629 1630 for name in value: 1631 value_str += "%s\xb0%s\xb0" % (name, value[name]) 1632 1633 value = value_str 1634 1635 # Need to change __option value to be the string 1636 1637 self.__option.disable_signals() 1638 self.__option.set_value(value) 1639 self.__option.enable_signals() 1640 1641 # Remove all entries (the new values will REPLACE 1642 # rather than APPEND) 1643 self.__model.clear() 1644 1645 # populate the surname/colour treeview 1646 # 1647 # For versions prior to 3.0.2, the fields were delimited with 1648 # whitespace. However, this causes problems when the surname 1649 # also has a space within it. When populating the control, 1650 # support both the new and old format -- look for the \xb0 1651 # delimiter, and if it isn't there, assume this is the old- 1652 # style space-delimited format. (Bug #2162.) 1653 if value.find('\xb0') >= 0: 1654 tmp = value.split('\xb0') 1655 else: 1656 tmp = value.split(' ') 1657 while len(tmp) > 1: 1658 surname = tmp.pop(0) 1659 colour = tmp.pop(0) 1660 self.__model.append([surname, colour]) 1661 1662 def clean_up(self): 1663 """ 1664 remove stuff that blocks garbage collection 1665 """ 1666 self.__option.disconnect(self.valuekey) 1667 self.__option = None 1668 1669#------------------------------------------------------------------------- 1670# 1671# GuiDestinationOption class 1672# 1673#------------------------------------------------------------------------- 1674class GuiDestinationOption(Gtk.Box): 1675 """ 1676 This class displays an option that allows the user to select a 1677 DestinationOption. 1678 """ 1679 def __init__(self, option, dbstate, uistate, track, override): 1680 """ 1681 @param option: The option to display. 1682 @type option: gen.plug.menu.DestinationOption 1683 @return: nothing 1684 """ 1685 Gtk.Box.__init__(self) 1686 self.__option = option 1687 self.__uistate = uistate 1688 self.__entry = Gtk.Entry() 1689 self.__entry.set_text(self.__option.get_value()) 1690 1691 self.__button = Gtk.Button() 1692 img = Gtk.Image() 1693 img.set_from_icon_name('document-open', Gtk.IconSize.BUTTON) 1694 self.__button.add(img) 1695 self.__button.connect('clicked', self.__select_file) 1696 1697 self.pack_start(self.__entry, True, True, 0) 1698 self.pack_end(self.__button, False, False, 0) 1699 1700 # Set up signal handlers when the widget value is changed 1701 # from user interaction or programmatically. When handling 1702 # a specific signal, we need to temporarily block the signal 1703 # that would call the other signal handler. 1704 self.changekey = self.__entry.connect('changed', self.__text_changed) 1705 self.valuekey = self.__option.connect('value-changed', 1706 self.__value_changed) 1707 1708 self.conkey1 = self.__option.connect('options-changed', 1709 self.__option_changed) 1710 self.conkey2 = self.__option.connect('avail-changed', 1711 self.__update_avail) 1712 self.__update_avail() 1713 1714 self.set_tooltip_text(self.__option.get_help()) 1715 1716 def __option_changed(self): 1717 """ 1718 Handle a change of the option. 1719 """ 1720 extension = self.__option.get_extension() 1721 directory = self.__option.get_directory_entry() 1722 value = self.__option.get_value() 1723 1724 if not directory and not value.endswith(extension): 1725 value = value + extension 1726 self.__option.set_value(value) 1727 elif directory and value.endswith(extension): 1728 value = value[:-len(extension)] 1729 self.__option.set_value(value) 1730 1731 self.__entry.set_text(self.__option.get_value()) 1732 1733 def __select_file(self, obj): 1734 """ 1735 Handle the user's request to select a file (or directory). 1736 """ 1737 if self.__option.get_directory_entry(): 1738 my_action = Gtk.FileChooserAction.SELECT_FOLDER 1739 else: 1740 my_action = Gtk.FileChooserAction.SAVE 1741 1742 fcd = Gtk.FileChooserDialog(_("Save As"), action=my_action, 1743 parent=self.__uistate.window, 1744 buttons=(_('_Cancel'), 1745 Gtk.ResponseType.CANCEL, 1746 _('_Open'), 1747 Gtk.ResponseType.OK)) 1748 1749 name = os.path.abspath(self.__option.get_value()) 1750 if self.__option.get_directory_entry(): 1751 while not os.path.isdir(name): 1752 # Keep looking up levels to find a valid drive. 1753 name, tail = os.path.split(name) 1754 if not name: 1755 # Avoid infinite loops 1756 name = get_curr_dir 1757 fcd.set_current_folder(name) 1758 else: 1759 fcd.set_current_name(os.path.basename(name)) 1760 fcd.set_current_folder(os.path.dirname(name)) 1761 1762 status = fcd.run() 1763 if status == Gtk.ResponseType.OK: 1764 path = fcd.get_filename() 1765 if path: 1766 if not self.__option.get_directory_entry() and \ 1767 not path.endswith(self.__option.get_extension()): 1768 path = path + self.__option.get_extension() 1769 self.__entry.set_text(path) 1770 self.__option.set_value(path) 1771 fcd.destroy() 1772 1773 def __text_changed(self, obj): # IGNORE:W0613 - obj is unused 1774 """ 1775 Handle the change of the value made by the user. 1776 """ 1777 self.__option.disable_signals() 1778 self.__option.set_value(self.__entry.get_text()) 1779 self.__option.enable_signals() 1780 1781 def __update_avail(self): 1782 """ 1783 Update the availability (sensitivity) of this widget. 1784 """ 1785 avail = self.__option.get_available() 1786 self.set_sensitive(avail) 1787 1788 def __value_changed(self): 1789 """ 1790 Handle the change made programmatically 1791 """ 1792 self.__entry.handler_block(self.changekey) 1793 self.__entry.set_text(self.__option.get_value()) 1794 self.__entry.handler_unblock(self.changekey) 1795 1796 def clean_up(self): 1797 """ 1798 remove stuff that blocks garbage collection 1799 """ 1800 self.__option.disconnect(self.valuekey) 1801 self.__option.disconnect(self.conkey1) 1802 self.__option.disconnect(self.conkey2) 1803 self.__option = None 1804 1805#------------------------------------------------------------------------- 1806# 1807# GuiStyleOption class 1808# 1809#------------------------------------------------------------------------- 1810class GuiStyleOption(GuiEnumeratedListOption): # TODO this is likely dead code 1811 """ 1812 This class displays a StyleOption. 1813 """ 1814 def __init__(self, option, dbstate, uistate, track, override): 1815 """ 1816 @param option: The option to display. 1817 @type option: gen.plug.menu.StyleOption 1818 @return: nothing 1819 """ 1820 GuiEnumeratedListOption.__init__(self, option, dbstate, 1821 uistate, track) 1822 self.__option = option 1823 1824 self.__button = Gtk.Button("%s..." % _("Style Editor")) 1825 self.__button.connect('clicked', self.__on_style_edit_clicked) 1826 1827 self.pack_end(self.__button, False, False) 1828 self.uistate = uistate 1829 self.track = track 1830 1831 def __on_style_edit_clicked(self, *obj): 1832 """The user has clicked on the 'Edit Styles' button. Create a 1833 style sheet editor object and let them play. When they are 1834 done, update the displayed styles.""" 1835 from gramps.gen.plug.docgen import StyleSheetList 1836 from .report._styleeditor import StyleListDisplay 1837 style_list = StyleSheetList(self.__option.get_style_file(), 1838 self.__option.get_default_style()) 1839 StyleListDisplay(style_list, self.uistate, self.track) 1840 1841 new_items = [] 1842 for style_name in style_list.get_style_names(): 1843 new_items.append((style_name, style_name)) 1844 self.__option.set_items(new_items) 1845 1846#------------------------------------------------------------------------- 1847# 1848# GuiBooleanListOption class 1849# 1850#------------------------------------------------------------------------- 1851class GuiBooleanListOption(Gtk.Box): 1852 """ 1853 This class displays an option that provides a list of check boxes. 1854 Each possible value is assigned a value and a description. 1855 """ 1856 def __init__(self, option, dbstate, uistate, track, override): 1857 Gtk.Box.__init__(self) 1858 self.__option = option 1859 self.__cbutton = [] 1860 1861 default = option.get_value().split(',') 1862 if len(default) < 15: 1863 columns = 2 # number of checkbox columns 1864 else: 1865 columns = 3 1866 column = [] 1867 for dummy in range(columns): 1868 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 1869 self.pack_start(vbox, True, True, 0) 1870 column.append(vbox) 1871 vbox.show() 1872 1873 counter = 0 1874 this_column_counter = 0 1875 ncolumn = 0 1876 for description in option.get_descriptions(): 1877 button = Gtk.CheckButton(label=description) 1878 self.__cbutton.append(button) 1879 if counter < len(default): 1880 if default[counter] == 'True': 1881 button.set_active(True) 1882 button.connect("toggled", self.__list_changed) 1883 # show the items vertically, not alternating left and right 1884 # (if the number is uneven, the left column(s) will have one more) 1885 column[ncolumn].pack_start(button, True, True, 0) 1886 button.show() 1887 counter += 1 1888 this_column_counter += 1 1889 this_column_gets = (len(default)+(columns-(ncolumn+1))) // columns 1890 if this_column_counter + 1 > this_column_gets: 1891 ncolumn += 1 1892 this_column_counter = 0 1893 1894 self.valuekey = self.__option.connect('value-changed', 1895 self.__value_changed) 1896 1897 self.__option.connect('avail-changed', self.__update_avail) 1898 self.__update_avail() 1899 1900 self.set_tooltip_text(self.__option.get_help()) 1901 1902 def __list_changed(self, button): 1903 """ 1904 Handle the change of the value made by the user. 1905 """ 1906 value = '' 1907 for button in self.__cbutton: 1908 value = value + str(button.get_active()) + ',' 1909 value = value[:len(value)-1] 1910 1911 self.__option.disable_signals() 1912 self.__option.set_value(value) 1913 self.__option.enable_signals() 1914 1915 def __update_avail(self): 1916 """ 1917 Update the availability (sensitivity) of this widget. 1918 """ 1919 avail = self.__option.get_available() 1920 self.set_sensitive(avail) 1921 1922 def __value_changed(self): 1923 """ 1924 Handle the change made programmatically 1925 """ 1926 value = self.__option.get_value() 1927 1928 self.__option.disable_signals() 1929 1930 for button in self.__cbutton: 1931 for key in value: 1932 if key == button.get_label(): 1933 bool_value = (value[key] == "True" or value[key] == True) 1934 button.set_active(bool_value) 1935 1936 # Update __option value so that it's correct 1937 self.__list_changed(None) 1938 1939 self.__option.enable_signals() 1940 1941 def clean_up(self): 1942 """ 1943 remove stuff that blocks garbage collection 1944 """ 1945 self.__option.disconnect(self.valuekey) 1946 self.__option = None 1947 1948#-----------------------------------------------------------------------------# 1949# # 1950# Table mapping menu types to gui widgets used in make_gui_option function # 1951# # 1952#-----------------------------------------------------------------------------# 1953 1954from gramps.gen.plug import menu as menu 1955_OPTIONS = ( 1956 1957 (menu.BooleanListOption, True, GuiBooleanListOption), 1958 (menu.BooleanOption, False, GuiBooleanOption), 1959 (menu.ColorOption, True, GuiColorOption), 1960 (menu.DestinationOption, True, GuiDestinationOption), 1961 (menu.EnumeratedListOption, True, GuiEnumeratedListOption), 1962 (menu.FamilyOption, True, GuiFamilyOption), 1963 (menu.MediaOption, True, GuiMediaOption), 1964 (menu.NoteOption, True, GuiNoteOption), 1965 (menu.NumberOption, True, GuiNumberOption), 1966 (menu.PersonListOption, True, GuiPersonListOption), 1967 (menu.PersonOption, True, GuiPersonOption), 1968 (menu.PlaceListOption, True, GuiPlaceListOption), 1969 (menu.StringOption, True, GuiStringOption), 1970 (menu.StyleOption, True, GuiStyleOption), 1971 (menu.SurnameColorOption, True, GuiSurnameColorOption), 1972 (menu.TextOption, True, GuiTextOption), 1973 1974 # This entry must be last! 1975 1976 (menu.Option, None, None), 1977 1978 ) 1979del menu 1980 1981def make_gui_option(option, dbstate, uistate, track, override=False): 1982 """ 1983 Stand-alone function so that Options can be used in other 1984 ways, too. Takes an Option and returns a GuiOption. 1985 1986 override: if True will override the GuiOption's normal behavior 1987 (in a GuiOption-dependant fashion, for instance in a GuiPersonOption 1988 it will force the use of the options's value to set the GuiOption) 1989 """ 1990 1991 label, widget = True, None 1992 pmgr = GuiPluginManager.get_instance() 1993 external_options = pmgr.get_external_opt_dict() 1994 if option.__class__ in external_options: 1995 widget = external_options[option.__class__] 1996 else: 1997 for type_, label, widget in _OPTIONS: 1998 if isinstance(option, type_): 1999 break 2000 else: 2001 raise AttributeError( 2002 "can't make GuiOption: unknown option type: '%s'" % option) 2003 2004 if widget: 2005 widget = widget(option, dbstate, uistate, track, override) 2006 2007 return widget, label 2008 2009def add_gui_options(dialog): 2010 """ 2011 Stand-alone function to add user options to the GUI. 2012 """ 2013 if not hasattr(dialog.options, "menu"): 2014 return 2015 o_menu = dialog.options.menu 2016 options_dict = dialog.options.options_dict 2017 for category in o_menu.get_categories(): 2018 for name in o_menu.get_option_names(category): 2019 option = o_menu.get_option(category, name) 2020 2021 # override option default with xml-saved value: 2022 if name in options_dict: 2023 option.set_value(options_dict[name]) 2024 2025 widget, label = make_gui_option(option, dialog.dbstate, 2026 dialog.uistate, dialog.track) 2027 if widget is not None: 2028 if label: 2029 dialog.add_frame_option(category, 2030 option.get_label(), 2031 widget) 2032 else: 2033 dialog.add_frame_option(category, "", widget) 2034