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