1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 2008 Raphael Ackermann 6# Copyright (C) 2010 Benny Malengier 7# Copyright (C) 2010 Nick Hall 8# Copyright (C) 2012 Doug Blank <doug.blank@gmail.com> 9# Copyright (C) 2015- Serge Noiraud 10# 11# This program is free software; you can redistribute it and/or modify 12# it under the terms of the GNU General Public License as published by 13# the Free Software Foundation; either version 2 of the License, or 14# (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with this program; if not, write to the Free Software 23# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24# 25 26#------------------------------------------------------------------------- 27# 28# Standard python modules 29# 30#------------------------------------------------------------------------- 31import random 32import os 33from xml.sax.saxutils import escape 34from collections import abc 35 36#------------------------------------------------------------------------- 37# 38# GTK/Gnome modules 39# 40#------------------------------------------------------------------------- 41from gi.repository import GObject 42from gi.repository import Gdk 43from gi.repository import Gtk 44from gi.repository import Pango 45 46#------------------------------------------------------------------------- 47# 48# gramps modules 49# 50#------------------------------------------------------------------------- 51from gramps.gen.config import config 52from gramps.gen.const import GRAMPS_LOCALE as glocale 53from gramps.gen.const import HOME_DIR, URL_WIKISTRING, URL_MANUAL_PAGE 54from gramps.gen.datehandler import get_date_formats 55from gramps.gen.display.name import displayer as _nd 56from gramps.gen.display.name import NameDisplayError 57from gramps.gen.display.place import displayer as _pd 58from gramps.gen.utils.alive import update_constants 59from gramps.gen.utils.file import media_path 60from gramps.gen.utils.keyword import (get_keywords, get_translations, 61 get_translation_from_keyword, 62 get_keyword_from_translation) 63from gramps.gen.lib import Date, FamilyRelType 64from gramps.gen.lib import Name, Surname, NameOriginType 65from .managedwindow import ManagedWindow 66from .widgets import MarkupLabel, BasicLabel 67from .dialog import ErrorDialog, OkDialog 68from .editors.editplaceformat import EditPlaceFormat 69from .display import display_help 70from gramps.gen.plug.utils import available_updates 71from .plug import PluginWindows 72#from gramps.gen.errors import WindowActiveError 73from .spell import HAVE_GTKSPELL 74from gramps.gen.constfunc import win 75_ = glocale.translation.gettext 76from gramps.gen.utils.symbols import Symbols 77from gramps.gen.constfunc import get_env_var 78 79#------------------------------------------------------------------------- 80# 81# Constants 82# 83#------------------------------------------------------------------------- 84 85_surname_styles = [ 86 _("Father's surname"), 87 _("None"), 88 _("Combination of mother's and father's surname"), 89 _("Icelandic style"), 90 ] 91 92# column numbers for the 'name format' model 93COL_NUM = 0 94COL_NAME = 1 95COL_FMT = 2 96COL_EXPL = 3 97 98WIKI_HELP_PAGE = URL_MANUAL_PAGE + "_-_Settings" 99WIKI_HELP_SEC = _('Preferences') 100 101#------------------------------------------------------------------------- 102# 103# 104# 105#------------------------------------------------------------------------- 106class DisplayNameEditor(ManagedWindow): 107 def __init__(self, uistate, dbstate, track, dialog): 108 # Assumes that there are two methods: dialog.name_changed_check(), 109 # and dialog._build_custom_name_ui() 110 ManagedWindow.__init__(self, uistate, track, DisplayNameEditor) 111 self.dialog = dialog 112 self.dbstate = dbstate 113 self.set_window( 114 Gtk.Dialog(title=_('Display Name Editor')), 115 None, _('Display Name Editor'), None) 116 self.window.add_button(_('_Close'), Gtk.ResponseType.CLOSE) 117 self.setup_configs('interface.displaynameeditor', 820, 550) 118 grid = self.dialog._build_custom_name_ui() 119 label = Gtk.Label(label=_("""The following keywords are replaced with the appropriate name parts:<tt> 120 <b>Given</b> - given name (first name) <b>Surname</b> - surnames (with prefix and connectors) 121 <b>Title</b> - title (Dr., Mrs.) <b>Suffix</b> - suffix (Jr., Sr.) 122 <b>Call</b> - call name <b>Nickname</b> - nick name 123 <b>Initials</b>- first letters of given <b>Common</b> - nick name, call, or first of given 124 <b>Prefix</b> - all prefixes (von, de) 125Surnames: 126 <b>Rest</b> - non primary surnames <b>Notpatronymic</b>- all surnames, except pa/matronymic & primary 127 <b>Familynick</b>- family nick name <b>Rawsurnames</b> - surnames (no prefixes and connectors) 128 <b>Primary, Primary[pre] or [sur] or [con]</b>- full primary surname, prefix, surname only, connector 129 <b>Patronymic, or [pre] or [sur] or [con]</b> - full pa/matronymic surname, prefix, surname only, connector 130</tt> 131UPPERCASE keyword forces uppercase. Extra parentheses, commas are removed. Other text appears literally. 132 133<b>Example</b>: Dr. Edwin Jose von der Smith and Weston Wilson Sr ("Ed") - Underhills 134 <i>Edwin Jose</i>: Given, <i>von der</i>: Prefix, <i>Smith</i> and <i>Weston</i>: Primary, <i>and</i>: [con], <i>Wilson</i>: Patronymic, 135 <i>Dr.</i>: Title, <i>Sr</i>: Suffix, <i>Ed</i>: Nickname, <i>Underhills</i>: Familynick, <i>Jose</i>: Call. 136""")) 137 label.set_use_markup(True) 138 self.window.vbox.pack_start(label, False, True, 0) 139 self.window.vbox.pack_start(grid, True, True, 0) 140 self.window.connect('response', self.close) 141 self.show() 142 143 def close(self, *obj): 144 self.dialog.name_changed_check() 145 ManagedWindow.close(self, *obj) 146 147 def build_menu_names(self, obj): 148 return (_(" Name Editor"), None) 149 150 151#------------------------------------------------------------------------- 152# 153# ConfigureDialog 154# 155#------------------------------------------------------------------------- 156 157class ConfigureDialog(ManagedWindow): 158 """ 159 Base class for configuration dialogs. They provide a Notebook, to which 160 pages are added with configuration options, and a Cancel and Save button. 161 On save, a config file on which the dialog works, is saved to disk, and 162 a callback called. 163 """ 164 def __init__(self, uistate, dbstate, configure_page_funcs, configobj, 165 configmanager, 166 dialogtitle=_("Preferences"), on_close=None): 167 """ 168 Set up a configuration dialog 169 :param uistate: a DisplayState instance 170 :param dbstate: a DbState instance 171 :param configure_page_funcs: a list of function that return a tuple 172 (str, Gtk.Widget). The string is used as label for the 173 configuration page, and the widget as the content of the 174 configuration page 175 :param configobj: the unique object that is configured, it must be 176 identifiable (id(configobj)). If the configure dialog of the 177 configobj is already open, a WindowActiveError will be 178 raised. Grab this exception in the calling method 179 :param configmanager: a configmanager object. Several convenience 180 methods are present in ConfigureDialog to set up widgets that 181 write changes directly via this configmanager. 182 :param dialogtitle: the title of the configuration dialog 183 :param on_close: callback that is called on close 184 """ 185 self.dbstate = dbstate 186 self.__config = configmanager 187 ManagedWindow.__init__(self, uistate, [], configobj) 188 self.set_window(Gtk.Dialog(title=dialogtitle), None, dialogtitle, None) 189 self.window.add_button(_('_Close'), Gtk.ResponseType.CLOSE) 190 self.panel = Gtk.Notebook() 191 self.panel.set_scrollable(True) 192 self.window.vbox.pack_start(self.panel, True, True, 0) 193 self.__on_close = on_close 194 self.window.connect('response', self.done) 195 196 self.__setup_pages(configure_page_funcs) 197 198 self.show() 199 200 def __setup_pages(self, configure_page_funcs): 201 """ 202 This method builds the notebook pages in the panel 203 """ 204 if isinstance(configure_page_funcs, abc.Callable): 205 pages = configure_page_funcs() 206 else: 207 pages = configure_page_funcs 208 for func in pages: 209 labeltitle, widget = func(self) 210 self.panel.append_page(widget, MarkupLabel(labeltitle)) 211 212 def done(self, obj, value): 213 if value == Gtk.ResponseType.HELP: 214 return 215 if self.__on_close: 216 self.__on_close() 217 self.close() 218 219 def update_int_entry(self, obj, constant): 220 """ 221 :param obj: an object with get_text method that should contain an 222 integer 223 :param constant: the config setting to which the integer value must be 224 saved 225 """ 226 try: 227 self.__config.set(constant, int(obj.get_text())) 228 except: 229 print("WARNING: ignoring invalid value for '%s'" % constant) 230 231 def update_markup_entry(self, obj, constant): 232 """ 233 :param obj: an object with get_text method 234 :param constant: the config setting to which the text value must be 235 saved 236 """ 237 try: 238 obj.get_text() % 'test_markup' 239 except TypeError: 240 print("WARNING: ignoring invalid value for '%s'" % constant) 241 ErrorDialog( 242 _("Invalid or incomplete format definition."), 243 obj.get_text(), parent=self.window) 244 obj.set_text('<b>%s</b>') 245 except ValueError: 246 print("WARNING: ignoring invalid value for '%s'" % constant) 247 ErrorDialog( 248 _("Invalid or incomplete format definition."), 249 obj.get_text(), parent=self.window) 250 obj.set_text('<b>%s</b>') 251 252 self.__config.set(constant, obj.get_text()) 253 254 def update_entry(self, obj, constant): 255 """ 256 :param obj: an object with get_text method 257 :param constant: the config setting to which the text value must be 258 saved 259 """ 260 self.__config.set(constant, obj.get_text()) 261 262 def update_color(self, obj, pspec, constant, color_hex_label): 263 """ 264 Called on changing some color. 265 Either on programmatically color change. 266 """ 267 rgba = obj.get_rgba() 268 hexval = "#%02x%02x%02x" % (int(rgba.red * 255), 269 int(rgba.green * 255), 270 int(rgba.blue * 255)) 271 color_hex_label.set_text(hexval) 272 colors = self.__config.get(constant) 273 if isinstance(colors, list): 274 scheme = self.__config.get('colors.scheme') 275 colors[scheme] = hexval 276 self.__config.set(constant, colors) 277 else: 278 self.__config.set(constant, hexval) 279 280 def update_checkbox(self, obj, constant, config=None): 281 """ 282 :param obj: the CheckButton object 283 :param constant: the config setting to which the value must be saved 284 """ 285 if not config: 286 config = self.__config 287 config.set(constant, obj.get_active()) 288 289 def update_radiobox(self, obj, constant): 290 """ 291 :param obj: the RadioButton object 292 :param constant: the config setting to which the value must be saved 293 """ 294 self.__config.set(constant, obj.get_active()) 295 296 def update_combo(self, obj, constant): 297 """ 298 :param obj: the ComboBox object 299 :param constant: the config setting to which the value must be saved 300 """ 301 self.__config.set(constant, obj.get_active()) 302 303 def update_slider(self, obj, constant): 304 """ 305 :param obj: the HScale object 306 :param constant: the config setting to which the value must be saved 307 """ 308 self.__config.set(constant, int(obj.get_value())) 309 310 def update_spinner(self, obj, constant): 311 """ 312 :param obj: the SpinButton object 313 :param constant: the config setting to which the value must be saved 314 """ 315 self.__config.set(constant, int(obj.get_value())) 316 317 def add_checkbox(self, grid, label, index, constant, start=1, stop=9, 318 config=None, extra_callback=None, tooltip=''): 319 """ 320 Adds checkbox option with tooltip. 321 """ 322 if not config: 323 config = self.__config 324 checkbox = Gtk.CheckButton(label=label) 325 checkbox.set_active(config.get(constant)) 326 checkbox.connect('toggled', self.update_checkbox, constant, config) 327 if extra_callback: 328 checkbox.connect('toggled', extra_callback) 329 if tooltip: 330 checkbox.set_tooltip_text(tooltip) 331 grid.attach(checkbox, start, index, stop - start, 1) 332 return checkbox 333 334 def add_radiobox(self, grid, label, index, constant, group, column, 335 config=None): 336 """ 337 Adds radiobox option. 338 """ 339 if not config: 340 config = self.__config 341 radiobox = Gtk.RadioButton.new_with_mnemonic_from_widget(group, label) 342 if config.get(constant): 343 radiobox.set_active(True) 344 radiobox.connect('toggled', self.update_radiobox, constant) 345 grid.attach(radiobox, column, index, 1, 1) 346 return radiobox 347 348 def add_text(self, grid, label, index, config=None, line_wrap=True, 349 start=1, stop=9, justify=Gtk.Justification.LEFT, 350 align=Gtk.Align.START, bold=False): 351 """ 352 Adds text with specified parameters. 353 """ 354 if not config: 355 config = self.__config 356 text = Gtk.Label() 357 text.set_line_wrap(line_wrap) 358 text.set_halign(Gtk.Align.START) 359 if bold: 360 text.set_markup('<b>%s</b>' % label) 361 else: 362 text.set_text(label) 363 text.set_halign(align) 364 text.set_justify(justify) 365 text.set_hexpand(True) 366 grid.attach(text, start, index, stop - start, 1) 367 return text 368 369 def add_button(self, grid, label, index, constant, extra_callback=None, config=None): 370 if not config: 371 config = self.__config 372 button = Gtk.Button(label=label) 373 button.connect('clicked', extra_callback) 374 grid.attach(button, 1, index, 1, 1) 375 return button 376 377 def add_path_box(self, grid, label, index, entry, path, callback_label, 378 callback_sel, config=None): 379 """ 380 Add an entry to give in path and a select button to open a dialog. 381 Changing entry calls callback_label 382 Clicking open button call callback_sel 383 """ 384 if not config: 385 config = self.__config 386 lwidget = BasicLabel(_("%s: ") % label) # needed for French 387 hbox = Gtk.Box() 388 if path: 389 entry.set_text(path) 390 entry.connect('changed', callback_label) 391 btn = Gtk.Button() 392 btn.connect('clicked', callback_sel) 393 image = Gtk.Image() 394 image.set_from_icon_name('document-open', Gtk.IconSize.BUTTON) 395 image.show() 396 btn.add(image) 397 hbox.pack_start(entry, True, True, 0) 398 hbox.pack_start(btn, False, False, 0) 399 hbox.set_hexpand(True) 400 grid.attach(lwidget, 1, index, 1, 1) 401 grid.attach(hbox, 2, index, 1, 1) 402 403 def add_entry(self, grid, label, index, constant, callback=None, 404 config=None, col_attach=0, localized_config=True): 405 """ 406 Adds entry field. 407 """ 408 if not config: 409 config = self.__config 410 if not callback: 411 callback = self.update_entry 412 if label: 413 lwidget = BasicLabel(_("%s: ") % label) # translators: for French 414 entry = Gtk.Entry() 415 if localized_config: 416 entry.set_text(config.get(constant)) 417 else: # it needs localizing 418 entry.set_text(_(config.get(constant))) 419 entry.connect('changed', callback, constant) 420 entry.set_hexpand(True) 421 if label: 422 grid.attach(lwidget, col_attach, index, 1, 1) 423 grid.attach(entry, col_attach+1, index, 1, 1) 424 else: 425 grid.attach(entry, col_attach, index, 1, 1) 426 return entry 427 428 def add_pos_int_entry(self, grid, label, index, constant, callback=None, 429 config=None, col_attach=1, helptext=''): 430 """ 431 Adds entry field for positive integers. 432 """ 433 if not config: 434 config = self.__config 435 lwidget = BasicLabel(_("%s: ") % label) # needed for French 436 entry = Gtk.Entry() 437 entry.set_text(str(config.get(constant))) 438 entry.set_tooltip_markup(helptext) 439 entry.set_hexpand(True) 440 if callback: 441 entry.connect('changed', callback, constant) 442 grid.attach(lwidget, col_attach, index, 1, 1) 443 grid.attach(entry, col_attach+1, index, 1, 1) 444 445 def add_color(self, grid, label, index, constant, config=None, col=0): 446 """ 447 Add color chooser widget with label and hex value to the grid. 448 """ 449 if not config: 450 config = self.__config 451 lwidget = BasicLabel(_("%s: ") % label) # needed for French 452 colors = config.get(constant) 453 if isinstance(colors, list): 454 scheme = config.get('colors.scheme') 455 hexval = colors[scheme] 456 else: 457 hexval = colors 458 color = Gdk.color_parse(hexval) 459 entry = Gtk.ColorButton(color=color) 460 color_hex_label = BasicLabel(hexval) 461 color_hex_label.set_hexpand(True) 462 entry.connect('notify::color', self.update_color, constant, 463 color_hex_label) 464 grid.attach(lwidget, col, index, 1, 1) 465 grid.attach(entry, col+1, index, 1, 1) 466 grid.attach(color_hex_label, col+2, index, 1, 1) 467 return entry 468 469 def add_combo(self, grid, label, index, constant, opts, callback=None, 470 config=None, valueactive=False, setactive=None): 471 """ 472 A drop-down list allowing selection from a number of fixed options. 473 :param opts: A list of options. Each option is a tuple containing an 474 integer code and a textual description. 475 If valueactive = True, the constant stores the value, not the position 476 in the list 477 """ 478 if not config: 479 config = self.__config 480 if not callback: 481 callback = self.update_combo 482 lwidget = BasicLabel(_("%s: ") % label) # needed for French 483 store = Gtk.ListStore(int, str) 484 for item in opts: 485 store.append(item) 486 combo = Gtk.ComboBox(model=store) 487 cell = Gtk.CellRendererText() 488 combo.pack_start(cell, True) 489 combo.add_attribute(cell, 'text', 1) 490 if valueactive: 491 val = config.get(constant) 492 pos = 0 493 for nr, item in enumerate(opts): 494 if item[-1] == val: 495 pos = nr 496 break 497 combo.set_active(pos) 498 else: 499 if setactive is None: 500 combo.set_active(config.get(constant)) 501 else: 502 combo.set_active(setactive) 503 combo.connect('changed', callback, constant) 504 combo.set_hexpand(True) 505 grid.attach(lwidget, 1, index, 1, 1) 506 grid.attach(combo, 2, index, 1, 1) 507 return combo 508 509 def add_slider(self, grid, label, index, constant, range, callback=None, 510 config=None, width=1): 511 """ 512 Slider allowing the selection of an integer within a specified range. 513 :param range: Tuple containing the minimum and maximum allowed values. 514 """ 515 if not config: 516 config = self.__config 517 if not callback: 518 callback = self.update_slider 519 lwidget = BasicLabel(_("%s: ") % label) # needed for French 520 adj = Gtk.Adjustment(value=config.get(constant), lower=range[0], 521 upper=range[1], step_increment=1, 522 page_increment=0, page_size=0) 523 slider = Gtk.Scale(adjustment=adj) 524 slider.set_digits(0) 525 slider.set_value_pos(Gtk.PositionType.BOTTOM) 526 slider.connect('value-changed', callback, constant) 527 grid.attach(lwidget, 1, index, 1, 1) 528 grid.attach(slider, 2, index, width, 1) 529 return slider 530 531 def add_spinner(self, grid, label, index, constant, range, callback=None, 532 config=None): 533 """ 534 Spinner allowing the selection of an integer within a specified range. 535 :param range: Tuple containing the minimum and maximum allowed values. 536 """ 537 if not config: 538 config = self.__config 539 if not callback: 540 callback = self.update_spinner 541 lwidget = BasicLabel(_("%s: ") % label) # needed for French 542 adj = Gtk.Adjustment(value=config.get(constant), lower=range[0], 543 upper=range[1], step_increment=1, 544 page_increment=0, page_size=0) 545 spinner = Gtk.SpinButton(adjustment=adj, climb_rate=0.0, digits=0) 546 spinner.connect('value-changed', callback, constant) 547 spinner.set_hexpand(True) 548 grid.attach(lwidget, 1, index, 1, 1) 549 grid.attach(spinner, 2, index, 1, 1) 550 return spinner 551 552#------------------------------------------------------------------------- 553# 554# GrampsPreferences 555# 556#------------------------------------------------------------------------- 557class GrampsPreferences(ConfigureDialog): 558 559 def __init__(self, uistate, dbstate): 560 page_funcs = ( 561 self.add_behavior_panel, 562 self.add_famtree_panel, 563 self.add_formats_panel, 564 self.add_text_panel, 565 self.add_prefix_panel, 566 self.add_date_panel, 567 self.add_researcher_panel, 568 self.add_advanced_panel, 569 self.add_color_panel, 570 self.add_symbols_panel 571 ) 572 ConfigureDialog.__init__(self, uistate, dbstate, page_funcs, 573 GrampsPreferences, config, 574 on_close=update_constants) 575 help_btn = self.window.add_button(_('_Help'), Gtk.ResponseType.HELP) 576 help_btn.connect( 577 'clicked', lambda x: display_help(WIKI_HELP_PAGE, WIKI_HELP_SEC)) 578 self.setup_configs('interface.grampspreferences', 700, 450) 579 580 def create_grid(self): 581 """ 582 Gtk.Grid for config panels (tabs). 583 """ 584 grid = Gtk.Grid() 585 grid.set_border_width(12) 586 grid.set_column_spacing(6) 587 grid.set_row_spacing(6) 588 return grid 589 590 def add_researcher_panel(self, configdialog): 591 """ 592 Add the Researcher tab to the preferences. 593 """ 594 grid = self.create_grid() 595 row = 0 596 self.add_text( 597 grid, _('Researcher information'), row, 598 line_wrap=True, start=0, stop=2, justify=Gtk.Justification.CENTER, 599 align=Gtk.Align.CENTER, bold=True) 600 row += 1 601 self.add_text( 602 grid, _('Enter information about yourself so people can contact ' 603 'you when you distribute your Family Tree'), row, 604 line_wrap=True, start=0, stop=2, justify=Gtk.Justification.CENTER, 605 align=Gtk.Align.CENTER) 606 607 row += 1 608 self.add_entry(grid, _('Name'), row, 'researcher.researcher-name') 609 row += 1 610 self.add_entry(grid, _('Address'), row, 'researcher.researcher-addr') 611 row += 1 612 self.add_entry(grid, _('Locality'), row, 613 'researcher.researcher-locality') 614 row += 1 615 self.add_entry(grid, _('City'), row, 'researcher.researcher-city') 616 row += 1 617 self.add_entry(grid, _('State/County'), row, 618 'researcher.researcher-state') 619 row += 1 620 self.add_entry(grid, _('Country'), row, 621 'researcher.researcher-country') 622 row += 1 623 self.add_entry(grid, _('ZIP/Postal Code'), row, 624 'researcher.researcher-postal') 625 row += 1 626 self.add_entry(grid, _('Phone'), row, 'researcher.researcher-phone') 627 row += 1 628 self.add_entry(grid, _('Email'), row, 'researcher.researcher-email') 629 return _('Researcher'), grid 630 631 def add_prefix_panel(self, configdialog): 632 """ 633 Add the ID prefix tab to the preferences. 634 """ 635 grid = self.create_grid() 636 637 self.add_text( 638 grid, _('Gramps ID format settings'), 0, 639 line_wrap=True, start=0, stop=2, justify=Gtk.Justification.CENTER, 640 align=Gtk.Align.CENTER, bold=True) 641 642 row = 1 643 self.add_entry(grid, _('Person'), row, 'preferences.iprefix', 644 self.update_idformat_entry) 645 row += 1 646 self.add_entry(grid, _('Family'), row, 'preferences.fprefix', 647 self.update_idformat_entry) 648 row += 1 649 self.add_entry(grid, _('Place'), row, 'preferences.pprefix', 650 self.update_idformat_entry) 651 row += 1 652 self.add_entry(grid, _('Source'), row, 'preferences.sprefix', 653 self.update_idformat_entry) 654 row += 1 655 self.add_entry(grid, _('Citation'), row, 'preferences.cprefix', 656 self.update_idformat_entry) 657 row += 1 658 self.add_entry(grid, _('Media Object'), row, 'preferences.oprefix', 659 self.update_idformat_entry) 660 row += 1 661 self.add_entry(grid, _('Event'), row, 'preferences.eprefix', 662 self.update_idformat_entry) 663 row += 1 664 self.add_entry(grid, _('Repository'), row, 'preferences.rprefix', 665 self.update_idformat_entry) 666 row += 1 667 self.add_entry(grid, _('Note'), row, 'preferences.nprefix', 668 self.update_idformat_entry) 669 return _('ID Formats'), grid 670 671 def add_color_panel(self, configdialog): 672 """ 673 Add the tab to set defaults colors for graph boxes. 674 """ 675 grid = self.create_grid() 676 self.add_text( 677 grid, _('Colors used for boxes in the graphical views'), 678 0, line_wrap=True, start=0, stop=7, bold=True, 679 justify=Gtk.Justification.CENTER, align=Gtk.Align.CENTER) 680 681 hbox = Gtk.Box(spacing=12) 682 self.color_scheme_box = Gtk.ComboBoxText() 683 formats = [_("Light colors"), 684 _("Dark colors")] 685 list(map(self.color_scheme_box.append_text, formats)) 686 scheme = config.get('colors.scheme') 687 self.color_scheme_box.set_active(scheme) 688 self.color_scheme_box.connect('changed', self.color_scheme_changed) 689 lwidget = BasicLabel(_("%s: ") % _('Color scheme')) 690 hbox.pack_start(lwidget, False, False, 0) 691 hbox.pack_start(self.color_scheme_box, False, False, 0) 692 693 restore_btn = Gtk.Button(_('Restore to defaults')) 694 restore_btn.set_tooltip_text( 695 _('Restore colors for current theme to default.')) 696 restore_btn.connect('clicked', self.restore_colors) 697 hbox.pack_start(restore_btn, False, False, 0) 698 hbox.set_halign(Gtk.Align.CENTER) 699 grid.attach(hbox, 0, 1, 7, 1) 700 701 color_type = {'Male': _('Colors for Male persons'), 702 'Female': _('Colors for Female persons'), 703 'Unknown': _('Colors for Unknown persons'), 704 'Family': _('Colors for Family nodes'), 705 'Other': _('Other colors')} 706 707 bg_alive_text = _('Background for Alive') 708 bg_dead_text = _('Background for Dead') 709 brd_alive_text = _('Border for Alive') 710 brd_dead_text = _('Border for Dead') 711 712 # color label, config constant, group grid row, column, color type 713 color_list = [ 714 # for male 715 (bg_alive_text, 'male-alive', 1, 1, 'Male'), 716 (bg_dead_text, 'male-dead', 2, 1, 'Male'), 717 (brd_alive_text, 'border-male-alive', 1, 4, 'Male'), 718 (brd_dead_text, 'border-male-dead', 2, 4, 'Male'), 719 # for female 720 (bg_alive_text, 'female-alive', 1, 1, 'Female'), 721 (bg_dead_text, 'female-dead', 2, 1, 'Female'), 722 (brd_alive_text, 'border-female-alive', 1, 4, 'Female'), 723 (brd_dead_text, 'border-female-dead', 2, 4, 'Female'), 724 # for unknown 725 (bg_alive_text, 'unknown-alive', 1, 1, 'Unknown'), 726 (bg_dead_text, 'unknown-dead', 2, 1, 'Unknown'), 727 (brd_alive_text, 'border-unknown-alive', 1, 4, 'Unknown'), 728 (brd_dead_text, 'border-unknown-dead', 2, 4, 'Unknown'), 729 # for family 730 (_('Default background'), 'family', 1, 1, 'Family'), 731 (_('Background for Married'), 'family-married', 3, 1, 'Family'), 732 (_('Background for Unmarried'), 733 'family-unmarried', 4, 1, 'Family'), 734 (_('Background for Civil union'), 735 'family-civil-union', 5, 1, 'Family'), 736 (_('Background for Unknown'), 'family-unknown', 6, 1, 'Family'), 737 (_('Background for Divorced'), 'family-divorced', 7, 1, 'Family'), 738 (_('Default border'), 'border-family', 1, 4, 'Family'), 739 (_('Border for Divorced'), 740 'border-family-divorced', 7, 4, 'Family'), 741 # for other 742 (_('Background for Home Person'), 'home-person', 1, 1, 'Other'), 743 ] 744 745 # prepare scrolled window for colors settings 746 scroll_window = Gtk.ScrolledWindow() 747 colors_grid = self.create_grid() 748 scroll_window.add(colors_grid) 749 scroll_window.set_vexpand(True) 750 scroll_window.set_policy(Gtk.PolicyType.NEVER, 751 Gtk.PolicyType.AUTOMATIC) 752 grid.attach(scroll_window, 0, 3, 7, 1) 753 754 # add color settings to scrolled window by groups 755 row = 0 756 self.colors = {} 757 for key, frame_lbl in color_type.items(): 758 group_label = Gtk.Label() 759 group_label.set_halign(Gtk.Align.START) 760 group_label.set_margin_top(12) 761 group_label.set_markup(_('<b>%s</b>') % frame_lbl) 762 colors_grid.attach(group_label, 0, row, 3, 1) 763 764 row_added = 0 765 for color in color_list: 766 if color[4] == key: 767 pref_name = 'colors.' + color[1] 768 self.colors[pref_name] = self.add_color( 769 colors_grid, color[0], row + color[2], 770 pref_name, col=color[3]) 771 row_added += 1 772 row += row_added + 1 773 774 return _('Colors'), grid 775 776 def restore_colors(self, widget=None): 777 """ 778 Restore colors of selected scheme to default. 779 """ 780 scheme = config.get('colors.scheme') 781 for key, widget in self.colors.items(): 782 color = Gdk.RGBA() 783 hexval = config.get_default(key)[scheme] 784 Gdk.RGBA.parse(color, hexval) 785 widget.set_rgba(color) 786 787 def add_advanced_panel(self, configdialog): 788 """ 789 Config tab for Warnings and Error dialogs. 790 """ 791 grid = self.create_grid() 792 793 self.add_text( 794 grid, _('Warnings and Error dialogs'), 0, line_wrap=True, 795 justify=Gtk.Justification.CENTER, align=Gtk.Align.CENTER, 796 bold=True) 797 798 row = 1 799 self.add_checkbox( 800 grid, _('Suppress warning when adding parents to a child'), 801 row, 'preferences.family-warn') 802 row += 1 803 self.add_checkbox( 804 grid, _('Suppress warning when canceling with changed data'), 805 row, 'interface.dont-ask') 806 row += 1 807 self.add_checkbox( 808 grid, _('Suppress warning about missing researcher when' 809 ' exporting to GEDCOM'), 810 row, 'behavior.owner-warn') 811 row += 1 812 self.add_checkbox( 813 grid, _('Show plugin status dialog on plugin load error'), 814 row, 'behavior.pop-plugin-status') 815 816 return _('Warnings'), grid 817 818 def _build_name_format_model(self, active): 819 """ 820 Create a common model for ComboBox and TreeView 821 """ 822 name_format_model = Gtk.ListStore(GObject.TYPE_INT, 823 GObject.TYPE_STRING, 824 GObject.TYPE_STRING, 825 GObject.TYPE_STRING) 826 index = 0 827 the_index = 0 828 for num, name, fmt_str, act in _nd.get_name_format(): 829 translation = fmt_str 830 for key in get_keywords(): 831 if key in translation: 832 translation = translation.replace( 833 key, get_translation_from_keyword(key)) 834 self.examplename.set_display_as(num) 835 name_format_model.append(row=[num, translation, fmt_str, 836 _nd.display_name(self.examplename)]) 837 if num == active: 838 the_index = index 839 index += 1 840 return name_format_model, the_index 841 842 def __new_name(self, obj): 843 lyst = [ 844 "%s, %s %s (%s)" % (_("Surname"), _("Given"), _("Suffix"), 845 _("Common")), 846 "%s, %s %s (%s)" % (_("Surname"), _("Given"), _("Suffix"), 847 _("Nickname")), 848 "%s, %s %s (%s)" % (_("Surname"), _("Name|Common"), _("Suffix"), 849 _("Nickname")), 850 "%s, %s %s" % (_("Surname"), _("Name|Common"), _("Suffix")), 851 "%s, %s %s (%s)" % (_("SURNAME"), _("Given"), _("Suffix"), 852 _("Call")), 853 "%s, %s (%s)" % (_("Surname"), _("Given"), _("Name|Common")), 854 "%s, %s (%s)" % (_("Surname"), _("Name|Common"), _("Nickname")), 855 "%s %s" % (_("Given"), _("Surname")), 856 "%s %s, %s" % (_("Given"), _("Surname"), _("Suffix")), 857 "%s %s %s" % (_("Given"), _("NotPatronymic"), _("Patronymic")), 858 "%s, %s %s (%s)" % (_("SURNAME"), _("Given"), _("Suffix"), 859 _("Common")), 860 "%s, %s (%s)" % (_("SURNAME"), _("Given"), _("Name|Common")), 861 "%s, %s (%s)" % (_("SURNAME"), _("Given"), _("Nickname")), 862 "%s %s" % (_("Given"), _("SURNAME")), 863 "%s %s, %s" % (_("Given"), _("SURNAME"), _("Suffix")), 864 "%s /%s/" % (_("Given"), _("SURNAME")), 865 "%s %s, %s" % (_("Given"), _("Rawsurnames"), _("Suffix")), 866 ] 867 # repeat above list, but not translated. 868 fmtlyst = [ 869 "%s, %s %s (%s)" % (("Surname"), ("Given"), ("Suffix"), 870 ("Common")), 871 "%s, %s %s (%s)" % (("Surname"), ("Given"), ("Suffix"), 872 ("Nickname")), 873 "%s, %s %s (%s)" % (("Surname"), ("Name|Common"), ("Suffix"), 874 ("Nickname")), 875 "%s, %s %s" % (("Surname"), ("Name|Common"), ("Suffix")), 876 "%s, %s %s (%s)" % (("SURNAME"), ("Given"), ("Suffix"), 877 ("Call")), 878 "%s, %s (%s)" % (("Surname"), ("Given"), ("Name|Common")), 879 "%s, %s (%s)" % (("Surname"), ("Name|Common"), ("Nickname")), 880 "%s %s" % (("Given"), ("Surname")), 881 "%s %s, %s" % (("Given"), ("Surname"), ("Suffix")), 882 "%s %s %s" % (("Given"), ("NotPatronymic"), ("Patronymic")), 883 "%s, %s %s (%s)" % (("SURNAME"), ("Given"), ("Suffix"), 884 ("Common")), 885 "%s, %s (%s)" % (("SURNAME"), ("Given"), ("Name|Common")), 886 "%s, %s (%s)" % (("SURNAME"), ("Given"), ("Nickname")), 887 "%s %s" % (("Given"), ("SURNAME")), 888 "%s %s, %s" % (("Given"), ("SURNAME"), ("Suffix")), 889 "%s /%s/" % (("Given"), ("SURNAME")), 890 "%s %s, %s" % (("Given"), ("Rawsurnames"), ("Suffix")), 891 ] 892 rand = int(random.random() * len(lyst)) 893 f = lyst[rand] 894 fmt = fmtlyst[rand] 895 i = _nd.add_name_format(f, fmt) 896 fmt_str = _nd.format_str(self.examplename, fmt) 897 node = self.fmt_model.append(row=[i, f, fmt, fmt_str]) 898 path = self.fmt_model.get_path(node) 899 self.format_list.set_cursor(path, self.name_column, True) 900 self.edit_button.set_sensitive(False) 901 self.remove_button.set_sensitive(False) 902 self.insert_button.set_sensitive(False) 903 904 def __edit_name(self, obj): 905 store, node = self.format_list.get_selection().get_selected() 906 path = self.fmt_model.get_path(node) 907 self.edit_button.set_sensitive(False) 908 self.remove_button.set_sensitive(False) 909 self.insert_button.set_sensitive(False) 910 self.format_list.set_cursor(path, self.name_column, True) 911 912 def __check_for_name(self, name, oldnode): 913 """ 914 Check to see if there is another name the same as name 915 in the format list. Don't compare with self (oldnode). 916 """ 917 model = self.fmt_obox.get_model() 918 iter = model.get_iter_first() 919 while iter is not None: 920 othernum = model.get_value(iter, COL_NUM) 921 oldnum = model.get_value(oldnode, COL_NUM) 922 if othernum == oldnum: 923 pass # skip comparison with self 924 else: 925 othername = model.get_value(iter, COL_NAME) 926 if othername == name: 927 return True 928 iter = model.iter_next(iter) 929 return False 930 931 def __start_name_editing(self, dummy_renderer, dummy_editable, dummy_path): 932 """ 933 Method called at the start of editing a name format. 934 """ 935 self.format_list.set_tooltip_text(_("Enter to save, Esc to cancel " 936 "editing")) 937 938 def __cancel_change(self, dummy_renderer): 939 """ 940 Break off the editing of a name format. 941 """ 942 self.format_list.set_tooltip_text('') 943 num = self.selected_fmt[COL_NUM] 944 if any(fmt[COL_NUM] == num for fmt in self.dbstate.db.name_formats): 945 return 946 else: # editing a new format not yet in db, cleanup is needed 947 self.fmt_model.remove(self.iter) 948 _nd.del_name_format(num) 949 self.insert_button.set_sensitive(True) 950 951 def __change_name(self, text, path, new_text): 952 """ 953 Called when a name format changed and needs to be stored in the db. 954 """ 955 self.format_list.set_tooltip_text('') 956 if len(new_text) > 0 and text != new_text: 957 # build a pattern from translated pattern: 958 pattern = new_text 959 if (len(new_text) > 2 and 960 new_text[0] == '"' and 961 new_text[-1] == '"'): 962 pass 963 else: 964 for key in get_translations(): 965 if key in pattern: 966 pattern = pattern.replace( 967 key, get_keyword_from_translation(key)) 968 # now build up a proper translation: 969 translation = pattern 970 if (len(new_text) > 2 and 971 new_text[0] == '"' and 972 new_text[-1] == '"'): 973 pass 974 else: 975 for key in get_keywords(): 976 if key in translation: 977 translation = translation.replace( 978 key, get_translation_from_keyword(key)) 979 num, name, fmt = self.selected_fmt[COL_NUM:COL_EXPL] 980 node = self.fmt_model.get_iter(path) 981 oldname = self.fmt_model.get_value(node, COL_NAME) 982 # check to see if this pattern already exists 983 if self.__check_for_name(translation, node): 984 ErrorDialog(_("This format exists already."), 985 translation, parent=self.window) 986 self.edit_button.emit('clicked') 987 return 988 # else, change the name 989 self.edit_button.set_sensitive(True) 990 self.remove_button.set_sensitive(True) 991 self.insert_button.set_sensitive(True) 992 exmpl = _nd.format_str(self.examplename, pattern) 993 self.fmt_model.set(self.iter, COL_NAME, translation, 994 COL_FMT, pattern, 995 COL_EXPL, exmpl) 996 self.selected_fmt = (num, translation, pattern, exmpl) 997 _nd.edit_name_format(num, translation, pattern) 998 name_format = _nd.get_name_format(only_custom=True, 999 only_active=False) 1000 self.dbstate.db.name_formats = name_format 1001 1002 def __format_change(self, obj): 1003 try: 1004 t = (_nd.format_str(self.name, escape(obj.get_text()))) 1005 self.valid = True 1006 except NameDisplayError: 1007 t = _("Invalid or incomplete format definition.") 1008 self.valid = False 1009 self.fmt_model.set(self.iter, COL_EXPL, t) 1010 1011 def _build_custom_name_ui(self): 1012 """ 1013 UI to manage the custom name formats 1014 """ 1015 grid = Gtk.Grid() 1016 grid.set_border_width(6) 1017 grid.set_column_spacing(6) 1018 grid.set_row_spacing(6) 1019 1020 # make a treeview for listing all the name formats 1021 format_tree = Gtk.TreeView(model=self.fmt_model) 1022 name_renderer = Gtk.CellRendererText() 1023 name_column = Gtk.TreeViewColumn(_('Format'), 1024 name_renderer, 1025 text=COL_NAME) 1026 name_renderer.set_property('editable', False) 1027 name_renderer.connect('editing-started', self.__start_name_editing) 1028 name_renderer.connect('edited', self.__change_name) 1029 name_renderer.connect('editing-canceled', self.__cancel_change) 1030 self.name_renderer = name_renderer 1031 format_tree.append_column(name_column) 1032 example_renderer = Gtk.CellRendererText() 1033 example_column = Gtk.TreeViewColumn(_('Example'), 1034 example_renderer, 1035 text=COL_EXPL) 1036 format_tree.append_column(example_column) 1037 format_tree.get_selection().connect('changed', 1038 self.cb_format_tree_select) 1039 1040 # ... and put it into a scrolled win 1041 format_sw = Gtk.ScrolledWindow() 1042 format_sw.set_policy(Gtk.PolicyType.AUTOMATIC, 1043 Gtk.PolicyType.AUTOMATIC) 1044 format_sw.add(format_tree) 1045 format_sw.set_shadow_type(Gtk.ShadowType.IN) 1046 format_sw.set_hexpand(True) 1047 format_sw.set_vexpand(True) 1048 grid.attach(format_sw, 0, 0, 3, 1) 1049 1050 # to hold the values of the selected row of the tree and the iter 1051 self.selected_fmt = () 1052 self.iter = None 1053 1054 self.insert_button = Gtk.Button.new_with_mnemonic(_('_Add')) 1055 self.insert_button.connect('clicked', self.__new_name) 1056 1057 self.edit_button = Gtk.Button.new_with_mnemonic(_('_Edit')) 1058 self.edit_button.connect('clicked', self.__edit_name) 1059 self.edit_button.set_sensitive(False) 1060 1061 self.remove_button = Gtk.Button.new_with_mnemonic(_('_Remove')) 1062 self.remove_button.connect('clicked', self.cb_del_fmt_str) 1063 self.remove_button.set_sensitive(False) 1064 1065 grid.attach(self.insert_button, 0, 1, 1, 1) 1066 grid.attach(self.remove_button, 1, 1, 1, 1) 1067 grid.attach(self.edit_button, 2, 1, 1, 1) 1068 self.format_list = format_tree 1069 self.name_column = name_column 1070 return grid 1071 1072 def name_changed_check(self): 1073 """ 1074 Method to check for a name change. Called by Name Edit Dialog. 1075 """ 1076 obj = self.fmt_obox 1077 the_list = obj.get_model() 1078 the_iter = obj.get_active_iter() 1079 format = the_list.get_value(the_iter, COL_FMT) 1080 if format != self.old_format: 1081 # Yes a change; call the callback 1082 self.cb_name_changed(obj) 1083 1084 def cb_name_changed(self, obj): 1085 """ 1086 Preset name format ComboBox callback 1087 """ 1088 the_list = obj.get_model() 1089 the_iter = obj.get_active_iter() 1090 new_idx = the_list.get_value(the_iter, COL_NUM) 1091 config.set('preferences.name-format', new_idx) 1092 _nd.set_default_format(new_idx) 1093 self.uistate.emit('nameformat-changed') 1094 1095 def cb_place_fmt_changed(self, obj): 1096 """ 1097 Called when the place format is changed. 1098 """ 1099 config.set('preferences.place-format', obj.get_active()) 1100 self.uistate.emit('placeformat-changed') 1101 1102 def cb_pa_sur_changed(self, *args): 1103 """ 1104 Checkbox patronymic as surname changed, propagate to namedisplayer 1105 """ 1106 _nd.change_pa_sur() 1107 self.uistate.emit('nameformat-changed') 1108 1109 def cb_format_tree_select(self, tree_selection): 1110 """ 1111 Name format editor TreeView callback 1112 1113 Remember the values of the selected row (self.selected_fmt, self.iter) 1114 and set the Remove and Edit button sensitivity 1115 """ 1116 model, self.iter = tree_selection.get_selected() 1117 if self.iter is None: 1118 tree_selection.select_path(0) 1119 model, self.iter = tree_selection.get_selected() 1120 self.selected_fmt = model.get(self.iter, 0, 1, 2) 1121 idx = self.selected_fmt[COL_NUM] < 0 1122 self.remove_button.set_sensitive(idx) 1123 self.edit_button.set_sensitive(idx) 1124 self.name_renderer.set_property('editable', idx) 1125 1126 def cb_del_fmt_str(self, obj): 1127 """ 1128 Name format editor Remove button callback 1129 """ 1130 num = self.selected_fmt[COL_NUM] 1131 1132 if _nd.get_default_format() == num: 1133 self.fmt_obox.set_active(0) 1134 1135 self.fmt_model.remove(self.iter) 1136 _nd.set_format_inactive(num) 1137 self.dbstate.db.name_formats = _nd.get_name_format(only_custom=True, 1138 only_active=False) 1139 1140 def cb_grampletbar_close(self, obj): 1141 """ 1142 Gramplet bar close button preference callback 1143 """ 1144 self.uistate.emit('grampletbar-close-changed') 1145 1146 def add_formats_panel(self, configdialog): 1147 """ 1148 Config tab with Appearance and format settings. 1149 """ 1150 grid = self.create_grid() 1151 1152 self.add_text( 1153 grid, _('Appearance and format settings'), 0, 1154 line_wrap=True, start=0, stop=3, justify=Gtk.Justification.CENTER, 1155 align=Gtk.Align.CENTER, bold=True) 1156 1157 row = 1 1158 # Display name: 1159 self.examplename = Name() 1160 examplesurname = Surname() 1161 examplesurnamesecond = Surname() 1162 examplesurnamepat = Surname() 1163 self.examplename.set_title('Dr.') 1164 self.examplename.set_first_name('Edwin Jose') 1165 examplesurname.set_prefix('von der') 1166 examplesurname.set_surname('Smith') 1167 examplesurname.set_connector('and') 1168 self.examplename.add_surname(examplesurname) 1169 examplesurnamesecond.set_surname('Weston') 1170 self.examplename.add_surname(examplesurnamesecond) 1171 examplesurnamepat.set_surname('Wilson') 1172 examplesurnamepat.set_origintype( 1173 NameOriginType(NameOriginType.PATRONYMIC)) 1174 self.examplename.add_surname(examplesurnamepat) 1175 self.examplename.set_primary_surname(0) 1176 self.examplename.set_suffix('Sr') 1177 self.examplename.set_call_name('Jose') 1178 self.examplename.set_nick_name('Ed') 1179 self.examplename.set_family_nick_name('Underhills') 1180 # get the model for the combo and the treeview 1181 active = _nd.get_default_format() 1182 self.fmt_model, active = self._build_name_format_model(active) 1183 # set up the combo to choose the preset format 1184 self.fmt_obox = Gtk.ComboBox() 1185 cell = Gtk.CellRendererText() 1186 cell.set_property('ellipsize', Pango.EllipsizeMode.END) 1187 self.fmt_obox.pack_start(cell, True) 1188 self.fmt_obox.add_attribute(cell, 'text', 1) 1189 self.fmt_obox.set_model(self.fmt_model) 1190 # set the default value as active in the combo 1191 self.fmt_obox.set_active(active) 1192 self.fmt_obox.connect('changed', self.cb_name_changed) 1193 # label for the combo 1194 lwidget = BasicLabel(_("%s: ") % _('Name format')) 1195 lwidget.set_use_underline(True) 1196 lwidget.set_mnemonic_widget(self.fmt_obox) 1197 hbox = Gtk.Box() 1198 btn = Gtk.Button(label=("%s..." % _('Edit'))) 1199 btn.connect('clicked', self.cb_name_dialog) 1200 hbox.pack_start(self.fmt_obox, True, True, 0) 1201 hbox.pack_start(btn, False, False, 0) 1202 grid.attach(lwidget, 0, row, 1, 1) 1203 grid.attach(hbox, 1, row, 2, 1) 1204 1205 row += 1 1206 # Pa/Matronymic surname handling 1207 self.add_checkbox( 1208 grid, _("Consider single pa/matronymic as surname"), 1209 row, 'preferences.patronimic-surname', start=0, stop=2, 1210 extra_callback=self.cb_pa_sur_changed) 1211 1212 row += 1 1213 # Date format: 1214 obox = Gtk.ComboBoxText() 1215 formats = get_date_formats() 1216 list(map(obox.append_text, formats)) 1217 active = config.get('preferences.date-format') 1218 if active >= len(formats): 1219 active = 0 1220 obox.set_active(active) 1221 obox.connect('changed', self.date_format_changed) 1222 lwidget = BasicLabel(_("%s: ") % _('Date format')) 1223 grid.attach(lwidget, 0, row, 1, 1) 1224 grid.attach(obox, 1, row, 2, 1) 1225 1226 row += 1 1227 # Place format: 1228 self.pformat = Gtk.ComboBox() 1229 renderer = Gtk.CellRendererText() 1230 self.pformat.pack_start(renderer, True) 1231 self.pformat.add_attribute(renderer, "text", 0) 1232 self.cb_place_fmt_rebuild() 1233 active = config.get('preferences.place-format') 1234 self.pformat.set_active(active) 1235 self.pformat.connect('changed', self.cb_place_fmt_changed) 1236 hbox = Gtk.Box() 1237 self.fmt_btn = Gtk.Button(label=("%s..." % _('Edit'))) 1238 self.fmt_btn.connect('clicked', self.cb_place_fmt_dialog) 1239 cb_widget = self.add_checkbox( 1240 grid, _("%s: ") % _('Place format (auto place title)'), 1241 row, 'preferences.place-auto', start=0, stop=1, 1242 extra_callback=self.auto_title_changed, 1243 tooltip=_("Enables automatic place title generation " 1244 "using specified format.")) 1245 self.auto_title_changed(cb_widget) 1246 hbox.pack_start(self.pformat, True, True, 0) 1247 hbox.pack_start(self.fmt_btn, False, False, 0) 1248 grid.attach(hbox, 1, row, 2, 1) 1249 1250 row += 1 1251 # Age precision: 1252 # precision=1 for "year", 2: "year, month" or 3: "year, month, days" 1253 obox = Gtk.ComboBoxText() 1254 age_precision = [_("Years"), 1255 _("Years, Months"), 1256 _("Years, Months, Days")] 1257 list(map(obox.append_text, age_precision)) 1258 # Combo_box active index is from 0 to 2, we need values from 1 to 3 1259 active = config.get('preferences.age-display-precision') - 1 1260 if active >= 0 and active <= 2: 1261 obox.set_active(active) 1262 else: 1263 obox.set_active(0) 1264 obox.connect( 1265 'changed', 1266 lambda obj: config.set('preferences.age-display-precision', 1267 obj.get_active() + 1)) 1268 lwidget = BasicLabel(_("%s: ") 1269 % _('Age display precision (requires restart)')) 1270 grid.attach(lwidget, 0, row, 1, 1) 1271 grid.attach(obox, 1, row, 2, 1) 1272 1273 row += 1 1274 # Calendar format on report: 1275 obox = Gtk.ComboBoxText() 1276 list(map(obox.append_text, Date.ui_calendar_names)) 1277 active = config.get('preferences.calendar-format-report') 1278 if active >= len(formats): 1279 active = 0 1280 obox.set_active(active) 1281 obox.connect('changed', self.date_calendar_changed) 1282 lwidget = BasicLabel(_("%s: ") % _('Calendar on reports')) 1283 grid.attach(lwidget, 0, row, 1, 1) 1284 grid.attach(obox, 1, row, 2, 1) 1285 1286 row += 1 1287 # Surname guessing: 1288 obox = Gtk.ComboBoxText() 1289 formats = _surname_styles 1290 list(map(obox.append_text, formats)) 1291 obox.set_active(config.get('behavior.surname-guessing')) 1292 obox.connect('changed', 1293 lambda obj: config.set('behavior.surname-guessing', 1294 obj.get_active())) 1295 lwidget = BasicLabel(_("%s: ") % _('Surname guessing')) 1296 grid.attach(lwidget, 0, row, 1, 1) 1297 grid.attach(obox, 1, row, 2, 1) 1298 1299 row += 1 1300 # Default Family Relationship 1301 obox = Gtk.ComboBoxText() 1302 formats = FamilyRelType().get_standard_names() 1303 list(map(obox.append_text, formats)) 1304 obox.set_active(config.get('preferences.family-relation-type')) 1305 obox.connect('changed', 1306 lambda obj: config.set('preferences.family-relation-type', 1307 obj.get_active())) 1308 lwidget = BasicLabel(_("%s: ") % _('Default family relationship')) 1309 grid.attach(lwidget, 0, row, 1, 1) 1310 grid.attach(obox, 1, row, 2, 1) 1311 1312 row += 1 1313 # height multiple surname table 1314 self.add_pos_int_entry( 1315 grid, _('Height multiple surname box (pixels)'), 1316 row, 'interface.surname-box-height', self.update_surn_height, 1317 col_attach=0) 1318 1319 row += 1 1320 # Status bar: 1321 obox = Gtk.ComboBoxText() 1322 formats = [_("Active person's name and ID"), 1323 _("Relationship to home person")] 1324 list(map(obox.append_text, formats)) 1325 active = config.get('interface.statusbar') 1326 if active < 2: 1327 obox.set_active(0) 1328 else: 1329 obox.set_active(1) 1330 obox.connect('changed', 1331 lambda obj: config.set('interface.statusbar', 1332 2 * obj.get_active())) 1333 lwidget = BasicLabel(_("%s: ") % _('Status bar')) 1334 grid.attach(lwidget, 0, row, 1, 1) 1335 grid.attach(obox, 1, row, 2, 1) 1336 1337 row += 1 1338 # Text in sidebar: 1339 self.add_checkbox( 1340 grid, _("Show text label beside Navigator buttons " 1341 "(requires restart)"), 1342 row, 'interface.sidebar-text', start=0, stop=2, 1343 tooltip=_("Show or hide text beside Navigator buttons " 1344 "(People, Families, Events...).\n" 1345 "Requires Gramps restart to apply.")) 1346 row += 1 1347 # Gramplet bar close buttons: 1348 self.add_checkbox( 1349 grid, _("Show close button in gramplet bar tabs"), 1350 row, 'interface.grampletbar-close', start=0, stop=2, 1351 extra_callback=self.cb_grampletbar_close, 1352 tooltip=_("Show close button to simplify removing gramplets " 1353 "from bars.")) 1354 return _('Display'), grid 1355 1356 def auto_title_changed(self, obj): 1357 """ 1358 Update sensitivity of place format widget. 1359 """ 1360 state = obj.get_active() 1361 self.pformat.set_sensitive(state) 1362 self.fmt_btn.set_sensitive(state) 1363 1364 def add_text_panel(self, configdialog): 1365 """ 1366 Config tab for Text settings. 1367 """ 1368 grid = self.create_grid() 1369 1370 self.add_text( 1371 grid, _('Default text used for conditions'), 0, 1372 line_wrap=True, start=0, stop=2, justify=Gtk.Justification.CENTER, 1373 align=Gtk.Align.CENTER, bold=True) 1374 1375 row = 1 1376 self.add_entry(grid, _('Missing surname'), row, 1377 'preferences.no-surname-text') 1378 row += 1 1379 self.add_entry(grid, _('Missing given name'), row, 1380 'preferences.no-given-text') 1381 row += 1 1382 self.add_entry(grid, _('Missing record'), row, 1383 'preferences.no-record-text') 1384 row += 1 1385 self.add_entry(grid, _('Private surname'), row, 1386 'preferences.private-surname-text', 1387 localized_config=False) 1388 row += 1 1389 self.add_entry(grid, _('Private given name'), row, 1390 'preferences.private-given-text', 1391 localized_config=False) 1392 row += 1 1393 self.add_entry(grid, _('Private record'), row, 1394 'preferences.private-record-text') 1395 row += 1 1396 return _('Text'), grid 1397 1398 def cb_name_dialog(self, obj): 1399 the_list = self.fmt_obox.get_model() 1400 the_iter = self.fmt_obox.get_active_iter() 1401 self.old_format = the_list.get_value(the_iter, COL_FMT) 1402 win = DisplayNameEditor(self.uistate, self.dbstate, self.track, self) 1403 1404 def color_scheme_changed(self, obj): 1405 """ 1406 Called on swiching color scheme. 1407 """ 1408 scheme = obj.get_active() 1409 config.set('colors.scheme', scheme) 1410 for key, widget in self.colors.items(): 1411 color = Gdk.RGBA() 1412 hexval = config.get(key)[scheme] 1413 Gdk.RGBA.parse(color, hexval) 1414 widget.set_rgba(color) 1415 1416 def cb_place_fmt_dialog(self, button): 1417 """ 1418 Called to invoke the place format editor. 1419 """ 1420 EditPlaceFormat(self.uistate, self.dbstate, self.track, 1421 self.cb_place_fmt_rebuild) 1422 1423 def cb_place_fmt_rebuild(self): 1424 """ 1425 Called to rebuild the place format list. 1426 """ 1427 model = Gtk.ListStore(str) 1428 for fmt in _pd.get_formats(): 1429 model.append([fmt.name]) 1430 self.pformat.set_model(model) 1431 self.pformat.set_active(0) 1432 1433 def check_for_type_changed(self, obj): 1434 active = obj.get_active() 1435 if active == 0: # update 1436 config.set('behavior.check-for-addon-update-types', ["update"]) 1437 elif active == 1: # update 1438 config.set('behavior.check-for-addon-update-types', ["new"]) 1439 elif active == 2: # update 1440 config.set('behavior.check-for-addon-update-types', 1441 ["update", "new"]) 1442 1443 def toggle_tag_on_import(self, obj): 1444 """ 1445 Update Entry sensitive for tag on import. 1446 """ 1447 self.tag_format_entry.set_sensitive(obj.get_active()) 1448 1449 def check_for_updates_changed(self, obj): 1450 """ 1451 Save "Check for addon updates" option. 1452 """ 1453 active = obj.get_active() 1454 config.set('behavior.check-for-addon-updates', active) 1455 1456 def date_format_changed(self, obj): 1457 """ 1458 Save "Date format" option. 1459 And show notify message to restart Gramps. 1460 """ 1461 config.set('preferences.date-format', obj.get_active()) 1462 OkDialog(_('Change is not immediate'), 1463 _('Changing the date format will not take ' 1464 'effect until the next time Gramps is started.'), 1465 parent=self.window) 1466 1467 def date_calendar_changed(self, obj): 1468 """ 1469 Save "Date calendar" option. 1470 """ 1471 config.set('preferences.calendar-format-report', obj.get_active()) 1472 1473 def autobackup_changed(self, obj): 1474 """ 1475 Save "Autobackup" option on change. 1476 """ 1477 active = obj.get_active() 1478 config.set('database.autobackup', active) 1479 self.uistate.set_backup_timer() 1480 1481 def add_date_panel(self, configdialog): 1482 """ 1483 Config tab with Dates settings. 1484 """ 1485 grid = self.create_grid() 1486 1487 self.add_text( 1488 grid, _('Dates settings used for calculation operations'), 0, 1489 line_wrap=True, start=1, stop=3, justify=Gtk.Justification.CENTER, 1490 align=Gtk.Align.CENTER, bold=True) 1491 1492 row = 1 1493 self.add_pos_int_entry( 1494 grid, _('Markup for invalid date format'), 1495 row, 'preferences.invalid-date-format', 1496 self.update_markup_entry, 1497 helptext=_( 1498 'Convenience markups are:\n' 1499 '<b><b>Bold</b></b>\n' 1500 '<big><big>' 1501 'Makes font relatively larger</big></big>\n' 1502 '<i><i>Italic</i></i>\n' 1503 '<s><s>Strikethrough</s></s>\n' 1504 '<sub><sub>Subscript</sub></sub>\n' 1505 '<sup><sup>Superscript</sup></sup>\n' 1506 '<small><small>' 1507 'Makes font relatively smaller</small></small>\n' 1508 '<tt><tt>Monospace font</tt></tt>\n' 1509 '<u><u>Underline</u></u>\n\n' 1510 'For example: <u><b>%s</b></u>\n' 1511 'will display <u><b>Underlined bold date</b></u>.\n')) 1512 row += 1 1513 self.add_spinner( 1514 grid, _('Date about range'), 1515 row, 'behavior.date-about-range', (1, 9999)) 1516 row += 1 1517 self.add_spinner( 1518 grid, _('Date after range'), 1519 row, 'behavior.date-after-range', (1, 9999)) 1520 row += 1 1521 self.add_spinner( 1522 grid, _('Date before range'), 1523 row, 'behavior.date-before-range', (1, 9999)) 1524 row += 1 1525 self.add_spinner( 1526 grid, _('Maximum age probably alive'), 1527 row, 'behavior.max-age-prob-alive', (80, 140)) 1528 row += 1 1529 self.add_spinner( 1530 grid, _('Maximum sibling age difference'), 1531 row, 'behavior.max-sib-age-diff', (10, 30)) 1532 row += 1 1533 self.add_spinner( 1534 grid, _('Minimum years between generations'), 1535 row, 'behavior.min-generation-years', (5, 20)) 1536 row += 1 1537 self.add_spinner( 1538 grid, _('Average years between generations'), 1539 row, 'behavior.avg-generation-gap', (10, 30)) 1540 1541 return _('Dates'), grid 1542 1543 def add_behavior_panel(self, configdialog): 1544 """ 1545 Config tab with 'General' settings. 1546 """ 1547 grid = self.create_grid() 1548 1549 self.add_text(grid, _("General Gramps settings"), 0, 1550 line_wrap=True, start=1, stop=3, bold=True, 1551 justify=Gtk.Justification.CENTER, align=Gtk.Align.CENTER) 1552 current_line = 1 1553 1554 self.add_checkbox(grid, _('Add default source on GEDCOM import'), 1555 current_line, 'preferences.default-source', stop=3) 1556 1557 current_line += 1 1558 cb_const = 'preferences.tag-on-import' 1559 # tag Entry 1560 self.tag_format_entry = Gtk.Entry() 1561 tag_const = 'preferences.tag-on-import-format' 1562 tag_data = _(config.get(tag_const)) 1563 if not tag_data: 1564 # set default value if Entry is empty 1565 tag_data = config.get_default(tag_const) 1566 config.set(cb_const, False) 1567 config.set(tag_const, tag_data) 1568 self.tag_format_entry.set_text(tag_data) 1569 self.tag_format_entry.connect('changed', self.update_entry, tag_const) 1570 self.tag_format_entry.set_hexpand(True) 1571 self.tag_format_entry.set_sensitive(config.get(cb_const)) 1572 1573 self.add_checkbox(grid, _('Add tag on import'), current_line, 1574 cb_const, stop=2, 1575 extra_callback=self.toggle_tag_on_import, 1576 tooltip=_("Specified tag will be added on import.\n" 1577 "Clear to set default value.")) 1578 grid.attach(self.tag_format_entry, 2, current_line, 1, 1) 1579 1580 current_line += 1 1581 obj = self.add_checkbox(grid, _('Enable spelling checker'), 1582 current_line, 'behavior.spellcheck', stop=3) 1583 if not HAVE_GTKSPELL: 1584 obj.set_sensitive(False) 1585 spell_dict = {'gramps_wiki_build_spell_url': 1586 URL_WIKISTRING + 1587 "GEPS_029:_GTK3-GObject_introspection" 1588 "_Conversion#Spell_Check_Install"} 1589 obj.set_tooltip_text( 1590 _("GtkSpell not loaded. " 1591 "Spell checking will not be available.\n" 1592 "To build it for Gramps see " 1593 "%(gramps_wiki_build_spell_url)s") % spell_dict) 1594 1595 current_line += 1 1596 self.add_checkbox(grid, _('Display Tip of the Day'), 1597 current_line, 'behavior.use-tips', stop=3, 1598 tooltip=_("Show useful information about using " 1599 "Gramps on startup.")) 1600 current_line += 1 1601 self.add_checkbox(grid, _('Remember last view displayed'), 1602 current_line, 'preferences.use-last-view', stop=3, 1603 tooltip=_("Remember last view displayed " 1604 "and open it next time.")) 1605 current_line += 1 1606 self.add_spinner(grid, _('Max generations for relationships'), 1607 current_line, 'behavior.generation-depth', (5, 50), 1608 self.update_gendepth) 1609 current_line += 1 1610 self.path_entry = Gtk.Entry() 1611 self.add_path_box( 1612 grid, _('Base path for relative media paths'), 1613 current_line, self.path_entry, self.dbstate.db.get_mediapath(), 1614 self.set_mediapath, self.select_mediapath) 1615 1616 current_line += 1 1617 label = self.add_text( 1618 grid, _("Third party addons management"), current_line, 1619 line_wrap=True, start=1, stop=3, bold=True, 1620 justify=Gtk.Justification.CENTER, align=Gtk.Align.CENTER) 1621 label.set_margin_top(10) 1622 1623 current_line += 1 1624 # Check for addon updates: 1625 obox = Gtk.ComboBoxText() 1626 formats = [_("Never"), 1627 _("Once a month"), 1628 _("Once a week"), 1629 _("Once a day"), 1630 _("Always"), ] 1631 list(map(obox.append_text, formats)) 1632 active = config.get('behavior.check-for-addon-updates') 1633 obox.set_active(active) 1634 obox.connect('changed', self.check_for_updates_changed) 1635 lwidget = BasicLabel(_("%s: ") % _('Check for addon updates')) 1636 grid.attach(lwidget, 1, current_line, 1, 1) 1637 grid.attach(obox, 2, current_line, 1, 1) 1638 1639 current_line += 1 1640 self.whattype_box = Gtk.ComboBoxText() 1641 formats = [_("Updated addons only"), 1642 _("New addons only"), 1643 _("New and updated addons")] 1644 list(map(self.whattype_box.append_text, formats)) 1645 whattype = config.get('behavior.check-for-addon-update-types') 1646 if "new" in whattype and "update" in whattype: 1647 self.whattype_box.set_active(2) 1648 elif "new" in whattype: 1649 self.whattype_box.set_active(1) 1650 elif "update" in whattype: 1651 self.whattype_box.set_active(0) 1652 self.whattype_box.connect('changed', self.check_for_type_changed) 1653 lwidget = BasicLabel(_("%s: ") % _('What to check')) 1654 grid.attach(lwidget, 1, current_line, 1, 1) 1655 grid.attach(self.whattype_box, 2, current_line, 1, 1) 1656 1657 current_line += 1 1658 self.add_entry(grid, _('Where to check'), current_line, 1659 'behavior.addons-url', col_attach=1) 1660 1661 current_line += 1 1662 self.add_checkbox( 1663 grid, _('Do not ask about previously notified addons'), 1664 current_line, 'behavior.do-not-show-previously-seen-addon-updates', 1665 stop=3) 1666 1667 current_line += 1 1668 button = Gtk.Button(label=_("Check for updated addons now")) 1669 button.connect("clicked", self.check_for_updates) 1670 button.set_hexpand(False) 1671 button.set_halign(Gtk.Align.CENTER) 1672 grid.attach(button, 1, current_line, 3, 1) 1673 1674 return _('General'), grid 1675 1676 def check_for_updates(self, button): 1677 try: 1678 addon_update_list = available_updates() 1679 except: 1680 OkDialog(_("Checking Addons Failed"), 1681 _("The addon repository appears to be unavailable. " 1682 "Please try again later."), 1683 parent=self.window) 1684 return 1685 1686 if len(addon_update_list) > 0: 1687 rescan = PluginWindows.UpdateAddons(self.uistate, self.track, 1688 addon_update_list).rescan 1689 self.uistate.viewmanager.do_reg_plugins(self.dbstate, self.uistate, 1690 rescan=rescan) 1691 else: 1692 check_types = config.get('behavior.check-for-addon-update-types') 1693 OkDialog( 1694 _("There are no available addons of this type"), 1695 _("Checked for '%s'") % 1696 _("' and '").join([_(t) for t in check_types]), 1697 parent=self.window) 1698 1699 # List of translated strings used here 1700 # Dead code for l10n 1701 _('new'), _('update') 1702 1703 def database_backend_changed(self, obj): 1704 """ 1705 Update Database Backend. 1706 """ 1707 the_list = obj.get_model() 1708 the_iter = obj.get_active_iter() 1709 db_choice = the_list.get_value(the_iter, 2) 1710 config.set('database.backend', db_choice) 1711 self.set_connection_widgets(db_choice) 1712 1713 def set_connection_widgets(self, db_choice): 1714 """ 1715 Sets the connection widgets insensitive for embedded databases. 1716 """ 1717 for widget in self.connection_widgets: 1718 if db_choice in ('bsddb', 'sqlite'): 1719 widget.set_sensitive(False) 1720 else: 1721 widget.set_sensitive(True) 1722 1723 def add_famtree_panel(self, configdialog): 1724 """ 1725 Config tab for family tree and backup settings. 1726 """ 1727 grid = self.create_grid() 1728 1729 self.add_text( 1730 grid, _("Family tree database settings and Backup " 1731 "management"), 0, line_wrap=True, start=1, stop=3, 1732 justify=Gtk.Justification.CENTER, align=Gtk.Align.CENTER, 1733 bold=True) 1734 1735 current_line = 1 1736 lwidget = BasicLabel(_("%s: ") % _('Database backend')) 1737 grid.attach(lwidget, 1, current_line, 1, 1) 1738 obox = self.__create_backend_combo() 1739 grid.attach(obox, 2, current_line, 1, 1) 1740 1741 current_line += 1 1742 self.connection_widgets = [] 1743 entry = self.add_entry(grid, _('Host'), current_line, 1744 'database.host', col_attach=1) 1745 self.connection_widgets.append(entry) 1746 1747 current_line += 1 1748 entry = self.add_entry(grid, _('Port'), current_line, 1749 'database.port', col_attach=1) 1750 self.connection_widgets.append(entry) 1751 1752 current_line += 1 1753 self.set_connection_widgets(config.get('database.backend')) 1754 1755 self.dbpath_entry = Gtk.Entry() 1756 self.add_path_box(grid, _('Family Tree Database path'), current_line, 1757 self.dbpath_entry, config.get('database.path'), 1758 self.set_dbpath, self.select_dbpath) 1759 current_line += 1 1760 self.add_checkbox(grid, _('Automatically load last Family Tree'), 1761 current_line, 'behavior.autoload', stop=3, 1762 tooltip=_("Don't open dialog to choose family " 1763 "tree to load on startup, " 1764 "just load last used.")) 1765 current_line += 1 1766 self.backup_path_entry = Gtk.Entry() 1767 self.add_path_box(grid, _('Backup path'), 1768 current_line, self.backup_path_entry, 1769 config.get('database.backup-path'), 1770 self.set_backup_path, self.select_backup_path) 1771 current_line += 1 1772 self.add_checkbox(grid, _('Backup on exit'), current_line, 1773 'database.backup-on-exit', stop=3, 1774 tooltip=_("Backup Your family tree on exit " 1775 "to Backup path specified above.")) 1776 current_line += 1 1777 # Check for updates: 1778 obox = Gtk.ComboBoxText() 1779 formats = [_("Never"), 1780 _("Every 15 minutes"), 1781 _("Every 30 minutes"), 1782 _("Every hour")] 1783 list(map(obox.append_text, formats)) 1784 active = config.get('database.autobackup') 1785 obox.set_active(active) 1786 obox.connect('changed', self.autobackup_changed) 1787 lwidget = BasicLabel(_("%s: ") % _('Autobackup')) 1788 grid.attach(lwidget, 1, current_line, 1, 1) 1789 grid.attach(obox, 2, current_line, 1, 1) 1790 1791 return _('Family Tree'), grid 1792 1793 def __create_backend_combo(self): 1794 """ 1795 Create backend selection widget. 1796 """ 1797 backend_plugins = self.uistate.viewmanager._pmgr.get_reg_databases() 1798 obox = Gtk.ComboBox() 1799 cell = Gtk.CellRendererText() 1800 obox.pack_start(cell, True) 1801 obox.add_attribute(cell, 'text', 1) 1802 # Build model: 1803 model = Gtk.ListStore(GObject.TYPE_INT, 1804 GObject.TYPE_STRING, 1805 GObject.TYPE_STRING) 1806 count = 0 1807 active = 0 1808 default = config.get('database.backend') 1809 for plugin in sorted(backend_plugins, key=lambda plugin: plugin.name): 1810 if plugin.id == default: 1811 active = count 1812 model.append(row=[count, plugin.name, plugin.id]) 1813 count += 1 1814 obox.set_model(model) 1815 # set the default value as active in the combo 1816 obox.set_active(active) 1817 obox.connect('changed', self.database_backend_changed) 1818 return obox 1819 1820 def set_mediapath(self, *obj): 1821 if self.path_entry.get_text().strip(): 1822 self.dbstate.db.set_mediapath(self.path_entry.get_text()) 1823 else: 1824 self.dbstate.db.set_mediapath(None) 1825 1826 def select_mediapath(self, *obj): 1827 """ 1828 Show dialog to choose media directory. 1829 """ 1830 f = Gtk.FileChooserDialog(title=_("Select media directory"), 1831 parent=self.window, 1832 action=Gtk.FileChooserAction.SELECT_FOLDER, 1833 buttons=(_('_Cancel'), 1834 Gtk.ResponseType.CANCEL, 1835 _('_Apply'), 1836 Gtk.ResponseType.OK)) 1837 mpath = media_path(self.dbstate.db) 1838 f.set_current_folder(os.path.dirname(mpath)) 1839 1840 status = f.run() 1841 if status == Gtk.ResponseType.OK: 1842 val = f.get_filename() 1843 if val: 1844 self.path_entry.set_text(val) 1845 f.destroy() 1846 1847 def set_dbpath(self, *obj): 1848 path = self.dbpath_entry.get_text().strip() 1849 config.set('database.path', path) 1850 1851 def select_dbpath(self, *obj): 1852 """ 1853 Show dialog to choose database directory. 1854 """ 1855 f = Gtk.FileChooserDialog(title=_("Select database directory"), 1856 transient_for=self.window, 1857 action=Gtk.FileChooserAction.SELECT_FOLDER) 1858 f.add_buttons(_('_Cancel'), Gtk.ResponseType.CANCEL, 1859 _('_Apply'), Gtk.ResponseType.OK) 1860 dbpath = config.get('database.path') 1861 if not dbpath: 1862 dbpath = os.path.join(HOME_DIR, 'grampsdb') 1863 f.set_current_folder(os.path.dirname(dbpath)) 1864 1865 status = f.run() 1866 if status == Gtk.ResponseType.OK: 1867 val = f.get_filename() 1868 if val: 1869 self.dbpath_entry.set_text(val) 1870 f.destroy() 1871 1872 def set_backup_path(self, *obj): 1873 path = self.backup_path_entry.get_text().strip() 1874 config.set('database.backup-path', path) 1875 1876 def select_backup_path(self, *obj): 1877 """ 1878 Show dialog to choose backup directory. 1879 """ 1880 f = Gtk.FileChooserDialog(title=_("Select backup directory"), 1881 parent=self.window, 1882 action=Gtk.FileChooserAction.SELECT_FOLDER, 1883 buttons=(_('_Cancel'), 1884 Gtk.ResponseType.CANCEL, 1885 _('_Apply'), 1886 Gtk.ResponseType.OK)) 1887 backup_path = config.get('database.backup-path') 1888 if not backup_path: 1889 backup_path = config.get('database.path') 1890 f.set_current_folder(os.path.dirname(backup_path)) 1891 1892 status = f.run() 1893 if status == Gtk.ResponseType.OK: 1894 val = f.get_filename() 1895 if val: 1896 self.backup_path_entry.set_text(val) 1897 f.destroy() 1898 1899 def update_idformat_entry(self, obj, constant): 1900 config.set(constant, obj.get_text()) 1901 self.dbstate.db.set_prefixes( 1902 config.get('preferences.iprefix'), 1903 config.get('preferences.oprefix'), 1904 config.get('preferences.fprefix'), 1905 config.get('preferences.sprefix'), 1906 config.get('preferences.cprefix'), 1907 config.get('preferences.pprefix'), 1908 config.get('preferences.eprefix'), 1909 config.get('preferences.rprefix'), 1910 config.get('preferences.nprefix')) 1911 1912 def update_gendepth(self, obj, constant): 1913 """ 1914 Called when the generation depth setting is changed. 1915 """ 1916 intval = int(obj.get_value()) 1917 config.set(constant, intval) 1918 # immediately use this value in displaystate. 1919 self.uistate.set_gendepth(intval) 1920 1921 def update_surn_height(self, obj, constant): 1922 ok = True 1923 if not obj.get_text(): 1924 return 1925 try: 1926 intval = int(obj.get_text()) 1927 except: 1928 intval = config.get(constant) 1929 ok = False 1930 if intval < 0: 1931 intval = config.get(constant) 1932 ok = False 1933 if ok: 1934 config.set(constant, intval) 1935 else: 1936 obj.set_text(str(intval)) 1937 1938 def build_menu_names(self, obj): 1939 return (_('Preferences'), _('Preferences')) 1940 1941 def add_symbols_panel(self, configdialog): 1942 self.grid = Gtk.Grid() 1943 self.grid.set_border_width(12) 1944 self.grid.set_column_spacing(6) 1945 self.grid.set_row_spacing(6) 1946 1947 message = _('This tab gives you the possibility to use one font' 1948 ' which is able to show all genealogical symbols\n\n' 1949 'If you select the "use symbols" checkbox, ' 1950 'Gramps will use the selected font if it exists.' 1951 ) 1952 message += '\n' 1953 message += _('This can be useful if you want to add phonetic in ' 1954 'a note to show how to pronounce a name or if you mix' 1955 ' multiple languages like greek and russian.' 1956 ) 1957 self.add_text(self.grid, message, 1958 0, line_wrap=True) 1959 self.add_checkbox(self.grid, 1960 _('Use symbols'), 1961 1, 1962 'utf8.in-use', 1963 extra_callback=self.activate_change_font 1964 ) 1965 message = _('Be careful, if you click on the "Try to find" button, it can ' 1966 'take a while before you can continue (10 minutes or more). ' 1967 '\nIf you cancel the process, nothing will be changed.' 1968 ) 1969 self.add_text(self.grid, message, 1970 2, line_wrap=True) 1971 available_fonts = config.get('utf8.available-fonts') 1972 self.all_avail_fonts = list(enumerate(available_fonts)) 1973 if len(available_fonts) > 0: 1974 self.add_text(self.grid, 1975 _('You have already run the tool to search for genealogy fonts.' 1976 '\nRun it again only if you added fonts on your system.' 1977 ), 1978 3, line_wrap=True) 1979 self.add_button(self.grid, 1980 _('Try to find'), 1981 4, 1982 'utf8.in-use', 1983 extra_callback=self.can_we_use_genealogical_fonts) 1984 sel_font = config.get('utf8.selected-font') 1985 if len(available_fonts) > 0: 1986 try: 1987 active_val = available_fonts.index(sel_font) 1988 except: 1989 active_val = 0 1990 self.add_combo(self.grid, 1991 _('Choose font'), 1992 5, 'utf8.selected-font', 1993 self.all_avail_fonts, 1994 callback=self.utf8_update_font, 1995 valueactive=True, setactive=active_val) 1996 symbols = Symbols() 1997 all_sbls = symbols.get_death_symbols() 1998 all_symbols = [] 1999 for symbol in all_sbls: 2000 all_symbols.append(symbol[1] + " " + symbol[0]) 2001 self.all_death_symbols = list(enumerate(all_symbols)) 2002 pos = config.get('utf8.death-symbol') 2003 combo = self.add_combo(self.grid, 2004 _('Select default death symbol'), 2005 6, 'utf8.death-symbol', 2006 self.all_death_symbols, 2007 callback=self.utf8_update_death_symbol, 2008 valueactive=True, setactive='') 2009 combo.set_active(pos) 2010 if config.get('utf8.selected-font') != "": 2011 self.utf8_show_example() 2012 2013 return _('Genealogical Symbols'), self.grid 2014 2015 def can_we_use_genealogical_fonts(self, obj): 2016 try: 2017 import fontconfig 2018 from gramps.gui.utils import ProgressMeter 2019 from collections import defaultdict 2020 except: 2021 from gramps.gui.dialog import WarningDialog 2022 WarningDialog(_("Cannot look for genealogical fonts"), 2023 _("I am not able to select genealogical fonts. " 2024 "Please, install the module fontconfig for python 3."), 2025 parent=self.uistate.window) 2026 return False 2027 try: 2028 # remove the old messages with old font 2029 self.grid.remove_row(8) 2030 self.grid.remove_row(7) 2031 self.grid.remove_row(6) 2032 self.grid.remove_row(5) 2033 except: 2034 pass 2035 fonts = fontconfig.query() 2036 all_fonts = defaultdict(set) 2037 symbols = Symbols() 2038 nb_symbols = symbols.get_how_many_symbols() 2039 self.in_progress = True 2040 self.progress = ProgressMeter(_('Checking available genealogical fonts'), 2041 can_cancel=True, 2042 cancel_callback=self.stop_looking_for_font, 2043 parent=self.uistate.window) 2044 self.progress.set_pass(_('Looking for all fonts with genealogical symbols.'), nb_symbols*len(fonts)) 2045 for path in fonts: 2046 if not self.in_progress: 2047 return # We clicked on Cancel 2048 font = fontconfig.FcFont(path) 2049 local = get_env_var('LANGUAGE', 'en') 2050 if isinstance(font.family, list): 2051 fontname = None 2052 for lang,fam in font.family: 2053 if lang == local: 2054 fontname = fam 2055 if not fontname: 2056 fontname = font.family[0][1] 2057 else: 2058 if local in font.family: 2059 fontname = font.family[local] 2060 else: 2061 for lang, name in font.family: # version 0.6.0 use dict 2062 fontname = name 2063 break 2064 for rand in range(symbols.SYMBOL_MALE, symbols.SYMBOL_EXTINCT+1): 2065 string = symbols.get_symbol_for_html(rand) 2066 value = symbols.get_symbol_for_string(rand) 2067 if font.has_char(value): 2068 all_fonts[fontname].add(value) 2069 self.progress.step() 2070 for rand in range(symbols.DEATH_SYMBOL_SKULL, 2071 symbols.DEATH_SYMBOL_DEAD): 2072 value = symbols.get_death_symbol_for_char(rand) 2073 if font.has_char(value): 2074 all_fonts[fontname].add(value) 2075 self.progress.step() 2076 self.progress.close() 2077 available_fonts = [] 2078 for font, font_usage in all_fonts.items(): 2079 if not font_usage: 2080 continue 2081 if len(font_usage) == nb_symbols: # If the font use all symbols 2082 available_fonts.append(font) 2083 config.set('utf8.available-fonts', available_fonts) 2084 sel_font = config.get('utf8.selected-font') 2085 try: 2086 active_val = available_fonts.index(sel_font) 2087 except: 2088 active_val = 0 2089 if len(available_fonts) > 0: 2090 self.all_avail_fonts = list(enumerate(available_fonts)) 2091 choosefont = self.add_combo(self.grid, 2092 _('Choose font'), 2093 5, 'utf8.selected-font', 2094 self.all_avail_fonts, callback=self.utf8_update_font, 2095 valueactive=True, setactive=active_val) 2096 if len(available_fonts) == 1: 2097 single_font = self.all_avail_fonts[choosefont.get_active()][0] 2098 config.set('utf8.selected-font', 2099 self.all_avail_fonts[single_font][1]) 2100 self.utf8_show_example() 2101 symbols = Symbols() 2102 all_sbls = symbols.get_death_symbols() 2103 all_symbols = [] 2104 for symbol in all_sbls: 2105 all_symbols.append(symbol[1] + " " + symbol[0]) 2106 self.all_death_symbols = list(enumerate(all_symbols)) 2107 pos = config.get('utf8.death-symbol') 2108 combo = self.add_combo(self.grid, 2109 _('Select default death symbol'), 2110 6, 'utf8.death-symbol', 2111 self.all_death_symbols, 2112 callback=self.utf8_update_death_symbol, 2113 valueactive=True, setactive='') 2114 combo.set_active(pos) 2115 else: 2116 self.add_text(self.grid, 2117 _('You have no font with genealogical symbols on your ' 2118 'system. Gramps will not be able to use symbols.' 2119 ), 2120 6, line_wrap=True) 2121 config.set('utf8.selected-font',"") 2122 self.grid.show_all() 2123 self.in_progress = False 2124 2125 def utf8_update_font(self, obj, constant): 2126 entry = obj.get_active() 2127 config.set(constant, self.all_avail_fonts[entry][1]) 2128 self.utf8_show_example() 2129 2130 def activate_change_font(self, obj=None): 2131 if obj: 2132 if not obj.get_active(): 2133 # reset to the system default 2134 self.uistate.viewmanager.reset_font() 2135 font = config.get('utf8.selected-font') 2136 if not self.uistate.viewmanager.change_font(font): 2137 # We can't change the font, so reset the checkbox. 2138 if obj: 2139 obj.set_active(False) 2140 self.uistate.reload_symbols() 2141 self.uistate.emit('font-changed') 2142 2143 def utf8_show_example(self): 2144 from gi.repository import Pango 2145 from gramps.gen.utils.grampslocale import _LOCALE_NAMES as X 2146 from string import ascii_letters 2147 try: 2148 # remove the old messages with old font 2149 self.grid.remove_row(8) 2150 self.grid.remove_row(7) 2151 except: 2152 pass 2153 font = config.get('utf8.selected-font') 2154 symbols = Symbols() 2155 my_characters = _("What you will see") + " :\n" 2156 my_characters += ascii_letters 2157 my_characters += " àäâçùéèiïîêëiÉÀÈïÏËÄœŒÅåØøìòô ...\n" 2158 for k,v in sorted(X.items()): 2159 lang = Pango.Language.from_string(k) 2160 my_characters += v[2] + ":\t" + lang.get_sample_string() + "\n" 2161 2162 scrollw = Gtk.ScrolledWindow() 2163 scrollw.set_size_request(600, 100) 2164 text = Gtk.Label() 2165 text.set_line_wrap(True) 2166 font_description = Pango.font_description_from_string(font) 2167 text.modify_font(font_description) 2168 self.activate_change_font() 2169 text.set_halign(Gtk.Align.START) 2170 text.set_text(my_characters) 2171 scrollw.add(text) 2172 scrollw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 2173 self.grid.attach(scrollw, 1, 7, 8, 1) 2174 2175 my_characters = "" 2176 for idx in range(symbols.SYMBOL_FEMALE, symbols.SYMBOL_EXTINCT+1): 2177 my_characters += symbols.get_symbol_for_string(idx) + " " 2178 2179 death_symbl = config.get('utf8.death-symbol') 2180 my_characters += symbols.get_death_symbol_for_char(death_symbl) 2181 text = Gtk.Label() 2182 text.set_line_wrap(True) 2183 font_description = Pango.font_description_from_string(font) 2184 text.modify_font(font_description) 2185 text.set_halign(Gtk.Align.START) 2186 text.set_markup("<big><big><big><big>" + 2187 my_characters + 2188 "</big></big></big></big>") 2189 self.grid.attach(text, 1, 8, 8, 1) 2190 scrollw.show_all() 2191 text.show_all() 2192 2193 def stop_looking_for_font(self, *args, **kwargs): 2194 self.progress.close() 2195 self.in_progress = False 2196 2197 def utf8_update_death_symbol(self, obj, constant): 2198 entry = obj.get_active() 2199 config.set(constant, entry) 2200 self.utf8_show_example() 2201 2202