1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# 2008-2009 Benny Malengier 6# 2009 Gary Burton 7# 2010 Michiel D. Nauta 8# Copyright (C) 2011 Tim G L Lyons 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program; if not, write to the Free Software 22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23# 24 25#------------------------------------------------------------------------- 26# 27# Standard python modules 28# 29#------------------------------------------------------------------------- 30from gi.repository import GObject 31from copy import copy 32 33#------------------------------------------------------------------------- 34# 35# GTK/Gnome modules 36# 37#------------------------------------------------------------------------- 38from gi.repository import Gtk 39 40#------------------------------------------------------------------------- 41# 42# gramps modules 43# 44#------------------------------------------------------------------------- 45from gramps.gen.const import GRAMPS_LOCALE as glocale 46_ = glocale.translation.sgettext 47from gramps.gen.const import URL_MANUAL_SECT3 48from gramps.gen.config import config 49from gramps.gen.display.name import displayer as name_displayer 50from .editsecondary import EditSecondary 51from gramps.gen.lib import NoteType 52from .displaytabs import GrampsTab, CitationEmbedList, NoteTab, SurnameTab 53from ..widgets import (MonitoredEntry, MonitoredMenu, MonitoredDate, 54 MonitoredDataType, PrivacyButton) 55from ..glade import Glade 56from gramps.gen.errors import ValidationError 57 58#------------------------------------------------------------------------- 59# 60# Classes 61# 62#------------------------------------------------------------------------- 63 64WIKI_HELP_PAGE = URL_MANUAL_SECT3 65 66 67class GeneralNameTab(GrampsTab): 68 """ 69 This class provides the tabpage of the general name tab 70 """ 71 72 def __init__(self, dbstate, uistate, track, name, widget): 73 """ 74 @param dbstate: The database state. Contains a reference to 75 the database, along with other state information. The GrampsTab 76 uses this to access the database and to pass to and created 77 child windows (such as edit dialogs). 78 @type dbstate: DbState 79 @param uistate: The UI state. Used primarily to pass to any created 80 subwindows. 81 @type uistate: DisplayState 82 @param track: The window tracking mechanism used to manage windows. 83 This is only used to pass to generted child windows. 84 @type track: list 85 @param name: Notebook label name 86 @type name: str/unicode 87 @param widget: widget to be shown in the tab 88 @type widge: gtk widget 89 """ 90 GrampsTab.__init__(self, dbstate, uistate, track, name) 91 eventbox = Gtk.EventBox() 92 eventbox.add(widget) 93 self.pack_start(eventbox, True, True, 0) 94 self._set_label(show_image=False) 95 widget.connect('key_press_event', self.key_pressed) 96 self.show_all() 97 98 def is_empty(self): 99 """ 100 Override base class 101 """ 102 return False 103 104#------------------------------------------------------------------------- 105# 106# EditName class 107# 108#------------------------------------------------------------------------- 109class EditName(EditSecondary): 110 111 def __init__(self, dbstate, uistate, track, name, callback): 112 113 EditSecondary.__init__(self, dbstate, uistate, 114 track, name, callback) 115 116 def _local_init(self): 117 118 self.top = Glade() 119 120 self.set_window(self.top.toplevel, None, _("Name Editor")) 121 self.setup_configs('interface.name', 600, 350) 122 123 tblgnam = self.top.get_object('table23') 124 notebook = self.top.get_object('notebook') 125 hbox_surn = self.top.get_object('hboxmultsurnames') 126 hbox_surn.set_size_request(-1, 127 int(config.get('interface.surname-box-height'))) 128 hbox_surn.pack_start(SurnameTab(self.dbstate, self.uistate, self.track, 129 self.obj, top_label=None), 130 True, True, 0) 131 #recreate start page as GrampsTab 132 notebook.remove_page(0) 133 self.gennam = GeneralNameTab(self.dbstate, self.uistate, self.track, 134 _('_General'), tblgnam) 135 136 self.original_group_as = self.obj.get_group_as() 137 self.original_group_set = not (self.original_group_as == '') 138 srn = self.obj.get_primary_surname().get_surname() 139 self._get_global_grouping(srn) 140 141 self.group_over = self.top.get_object('group_over') 142 self.group_over.connect('toggled',self.on_group_over_toggled) 143 self.group_over.set_sensitive(not self.db.readonly) 144 145 self.toggle_dirty = False 146 147 def _post_init(self): 148 """if there is override, set the override toggle active 149 """ 150 if self.original_group_set: 151 self.group_over.set_active(True) 152 else: 153 # The glade file correctly sets group_as widget editable=False. 154 # At this stage of initialization self.group_as.obj.get_editable() 155 # is however still true, correct that. 156 self.group_as.enable(False) 157 158 def _connect_signals(self): 159 self.define_cancel_button(self.top.get_object('button119')) 160 self.define_help_button(self.top.get_object('button131'), 161 WIKI_HELP_PAGE, 162 _('manual|Name_Editor')) 163 self.define_ok_button(self.top.get_object('button118'), self.save) 164 165 def _validate_call(self, widget, text): 166 """ a callname must be a part of the given name, see if this is the 167 case """ 168 validcall = self.given_field.obj.get_text().split() 169 dummy = copy(validcall) 170 for item in dummy: 171 validcall += item.split('-') 172 if text in validcall: 173 return 174 return ValidationError(_("Call name must be the given name that " 175 "is normally used.")) 176 177 def _setup_fields(self): 178 self.group_as = MonitoredEntry( 179 self.top.get_object("group_as"), 180 self.obj.set_group_as, 181 self.obj.get_group_as, 182 self.db.readonly) 183 184 if not self.original_group_set: 185 if self.global_group_set : 186 self.group_as.force_value(self.global_group_as) 187 else : 188 self.group_as.force_value(self.obj.get_primary_surname().get_surname()) 189 190 format_list = [(name, number) for (number, name,fmt_str,act) 191 in name_displayer.get_name_format(also_default=True)] 192 193 self.sort_as = MonitoredMenu( 194 self.top.get_object('sort_as'), 195 self.obj.set_sort_as, 196 self.obj.get_sort_as, 197 format_list, 198 self.db.readonly) 199 200 self.display_as = MonitoredMenu( 201 self.top.get_object('display_as'), 202 self.obj.set_display_as, 203 self.obj.get_display_as, 204 format_list, 205 self.db.readonly) 206 207 self.given_field = MonitoredEntry( 208 self.top.get_object("given_name"), 209 self.obj.set_first_name, 210 self.obj.get_first_name, 211 self.db.readonly) 212 213 self.call_field = MonitoredEntry( 214 self.top.get_object("call"), 215 self.obj.set_call_name, 216 self.obj.get_call_name, 217 self.db.readonly) 218 self.call_field.connect("validate", self._validate_call) 219 #force validation now with initial entry 220 self.call_field.obj.validate(force=True) 221 222 self.title_field = MonitoredEntry( 223 self.top.get_object("title_field"), 224 self.obj.set_title, 225 self.obj.get_title, 226 self.db.readonly) 227 228 self.suffix_field = MonitoredEntry( 229 self.top.get_object("suffix"), 230 self.obj.set_suffix, 231 self.obj.get_suffix, 232 self.db.readonly) 233 234 self.nick = MonitoredEntry( 235 self.top.get_object("nickname"), 236 self.obj.set_nick_name, 237 self.obj.get_nick_name, 238 self.db.readonly) 239 240 self.famnick = MonitoredEntry( 241 self.top.get_object("familynickname"), 242 self.obj.set_family_nick_name, 243 self.obj.get_family_nick_name, 244 self.db.readonly) 245 246 #self.surname_field = MonitoredEntry( 247 # self.top.get_object("alt_surname"), 248 # self.obj.set_surname, 249 # self.obj.get_surname, 250 # self.db.readonly, 251 # autolist=self.db.get_surname_list() if not self.db.readonly else [], 252 # changed=self.update_group_as) 253 254 self.date = MonitoredDate( 255 self.top.get_object("date_entry"), 256 self.top.get_object("date_stat"), 257 self.obj.get_date_object(), 258 self.uistate, 259 self.track, 260 self.db.readonly) 261 262 self.obj_combo = MonitoredDataType( 263 self.top.get_object("ntype"), 264 self.obj.set_type, 265 self.obj.get_type, 266 self.db.readonly, 267 self.db.get_name_types(), 268 ) 269 270 self.privacy = PrivacyButton( 271 self.top.get_object("priv"), self.obj, 272 self.db.readonly) 273 274 def _create_tabbed_pages(self): 275 276 notebook = self.top.get_object("notebook") 277 278 self._add_tab(notebook, self.gennam) 279 self.track_ref_for_deletion("gennam") 280 281 self.srcref_list = CitationEmbedList(self.dbstate, self.uistate, 282 self.track, 283 self.obj.get_citation_list()) 284 self._add_tab(notebook, self.srcref_list) 285 self.track_ref_for_deletion("srcref_list") 286 287 self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, 288 self.obj.get_note_list(), 289 notetype=NoteType.PERSONNAME) 290 self._add_tab(notebook, self.note_tab) 291 self.track_ref_for_deletion("note_tab") 292 293 self._setup_notebook_tabs( notebook) 294 295 def _get_global_grouping(self, srn): 296 """ we need info on the global grouping of the surname on init, 297 and on change of surname 298 """ 299 self.global_group_as = self.db.get_name_group_mapping(srn) 300 if srn == self.global_group_as: 301 self.global_group_as = None 302 self.global_group_set = False 303 else: 304 self.global_group_set = True 305 306 307 def build_menu_names(self, name): 308 if name: 309 ntext = name_displayer.display_name(name) 310 submenu_label = _('%(str1)s: %(str2)s') % {'str1' : _('Name'), 311 'str2' : ntext} 312 else: 313 submenu_label = _('New Name') 314 menu_label = _('Name Editor') 315 return (menu_label,submenu_label) 316 317 def update_group_as(self, obj): 318 """Callback if surname changes on GUI 319 If overwrite is not set, we change the group name too 320 """ 321 name = self.obj.get_primary_surname().get_surname() 322 if not self.group_over.get_active(): 323 self.group_as.force_value(name) 324 #new surname, so perhaps now a different grouping? 325 self._get_global_grouping(name) 326 if not self.group_over.get_active() and self.global_group_set : 327 self.group_over.set_active(True) 328 self.group_as.enable(True) 329 self.toggle_dirty = True 330 self.group_as.force_value(self.global_group_as) 331 elif self.group_over.get_active() and self.toggle_dirty: 332 #changing surname caused active group_over in past, change 333 # group_over as we type 334 if self.global_group_set : 335 self.group_as.force_value(self.global_group_as) 336 else: 337 self.toggle_dirty = False 338 self.group_as.force_value(name) 339 self.group_over.set_active(False) 340 self.group_as.enable(False) 341 342 def on_group_over_toggled(self, obj): 343 """ group over changes, if activated, enable edit, 344 if unactivated, go back to surname/global_group_as. 345 """ 346 self.toggle_dirty = False 347 #enable group as box 348 self.group_as.enable(obj.get_active()) 349 350 if not obj.get_active(): 351 if self.global_group_set: 352 self.group_as.set_text(self.global_group_as) 353 else: 354 surname = self.obj.get_primary_surname().get_surname() 355 self.group_as.set_text(surname) 356 357 def save(self, *obj): 358 """Save the name setting. All is ok, except grouping. We need to 359 consider: 360 1/ global set, not local set --> unset (ask if global unset) 361 2/ global set, local set --> unset (only local unset!) 362 3/ not global set, local set 363 or not global set, not local set --> unset 364 4/ not local set, not global set 365 or not local set, global set --> set val (ask global or local) 366 5/ local set, not global set --> set (change local) 367 6/ local set, global set --> set (set to global if possible) 368 """ 369 closeit = True 370 surname = self.obj.get_primary_surname().get_surname() 371 group_as= self.obj.get_group_as() 372 grouping_active = self.group_over.get_active() 373 374 if not grouping_active : 375 #user wants to group with surname 376 if self.global_group_set and not self.original_group_set : 377 #warn that group will revert to surname 378 from ..dialog import QuestionDialog2 379 q = QuestionDialog2( 380 _("Break global name grouping?"), 381 _("All people with the name of %(surname)s will no longer " 382 "be grouped with the name of %(group_name)s." 383 ) % { 'surname' : surname, 384 'group_name':group_as}, 385 _("Continue"), 386 _("Return to Name Editor"), 387 parent=self.window) 388 val = q.run() 389 if val: 390 #delete the grouping link on database 391 self.db.set_name_group_mapping(surname, None) 392 self.obj.set_group_as("") 393 else : 394 closeit = False 395 elif self.global_group_set and self.original_group_set: 396 #we change it only back to surname locally, so store group_as 397 # Note: if all surnames are locally changed to surname, we 398 # should actually unsed the global group here .... 399 pass 400 else : 401 #global group not set, don't set local group too: 402 self.obj.set_group_as("") 403 else: 404 #user wants to override surname, see what he wants 405 if not self.original_group_set : 406 #if changed, ask if this has to happen for the entire group, 407 #this might be creation of group link, or change of group link 408 if self.global_group_as != group_as: 409 from ..dialog import QuestionDialog2 410 411 q = QuestionDialog2( 412 _("Group all people with the same name?"), 413 _("You have the choice of grouping all people with the " 414 "name of %(surname)s with the name of %(group_name)s, or " 415 "just mapping this particular name." 416 ) % { 'surname' : surname, 417 'group_name':group_as}, 418 _("Group all"), 419 _("Group this name only"), 420 parent=self.window) 421 val = q.run() 422 if val: 423 if group_as == surname : 424 self.db.set_name_group_mapping(surname, None) 425 else: 426 self.db.set_name_group_mapping(surname, group_as) 427 self.obj.set_group_as("") 428 else: 429 if self.global_group_set : 430 #allow smith to Dummy, but one person still Smith 431 self.obj.set_group_as(group_as) 432 elif group_as == surname : 433 self.obj.set_group_as("") 434 else: 435 self.obj.set_group_as(group_as) 436 else: 437 #keep original value, no original group 438 self.obj.set_group_as("") 439 elif not self.global_group_set : 440 #don't ask user, group was set locally before, 441 #change it locally only 442 if group_as == surname : 443 #remove grouping 444 self.obj.set_group_as("") 445 else: 446 pass 447 448 else: 449 #locally set group and global group set 450 if group_as == self.global_group_as : 451 #unset local group, go with global one 452 self.obj.set_group_as("") 453 else : 454 #local set is different from global, keep it like that 455 pass 456 457 if closeit: 458 if self.callback: 459 self.callback(self.obj) 460 self.callback = None 461 self.close() 462 463 def _cleanup_on_exit(self): 464 """ 465 Somehow it was decided that a database value of group="" is represented 466 in the GUI by a widget with a group="surname" which is disabled. So if 467 the group_as widget is disabled then remove the group from the name 468 otherwise gramps thinks the name has changed resulting in asking if 469 data must be saved, and also bug 1892 occurs on reopening of the editor. 470 """ 471 # can't use group_over, see Note in gen/lib/name/Name.set_group_as(). 472 if not self.group_as.obj.get_editable(): 473 self.obj.set_group_as("") 474 EditSecondary._cleanup_on_exit(self) 475