1# Gramps - a GTK+/GNOME based genealogy program
2#
3# Copyright (C) 2001-2006  Donald N. Allingham
4# Copyright (C) 2010       Nick Hall
5# Copyright (C) 2011       Tim G L Lyons
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20#
21
22"""
23Family View.
24"""
25
26#-------------------------------------------------------------------------
27#
28# Standard python modules
29#
30#-------------------------------------------------------------------------
31from gramps.gen.const import GRAMPS_LOCALE as glocale
32_ = glocale.translation.sgettext
33import logging
34_LOG = logging.getLogger(".plugins.eventview")
35#-------------------------------------------------------------------------
36#
37# GNOME/GTK+ modules
38#
39#-------------------------------------------------------------------------
40from gi.repository import Gtk
41
42#-------------------------------------------------------------------------
43#
44# gramps modules
45#
46#-------------------------------------------------------------------------
47from gramps.gen.lib import Family
48from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON
49from gramps.gui.views.treemodels import FamilyModel
50from gramps.gui.editors import EditFamily
51from gramps.gui.views.bookmarks import FamilyBookmarks
52from gramps.gen.errors import WindowActiveError
53from gramps.gen.config import config
54from gramps.gui.dialog import ErrorDialog
55from gramps.gui.filters.sidebar import FamilySidebarFilter
56from gramps.gui.merge import MergeFamily
57from gramps.gen.plug import CATEGORY_QR_FAMILY
58from gramps.gui.ddtargets import DdTargets
59
60#-------------------------------------------------------------------------
61#
62# FamilyView
63#
64#-------------------------------------------------------------------------
65class FamilyView(ListView):
66    """ FamilyView class, derived from the ListView
67    """
68    # columns in the model used in view
69    COL_ID = 0
70    COL_FATHER = 1
71    COL_MOTHER = 2
72    COL_REL = 3
73    COL_MARDATE = 4
74    COL_PRIV = 5
75    COL_TAGS = 6
76    COL_CHAN = 7
77    # column definitions
78    COLUMNS = [
79        (_('ID'), TEXT, None),
80        (_('Father'), TEXT, None),
81        (_('Mother'), TEXT, None),
82        (_('Relationship'), TEXT, None),
83        (_('Marriage Date'), MARKUP, None),
84        (_('Private'), ICON, 'gramps-lock'),
85        (_('Tags'), TEXT, None),
86        (_('Last Changed'), TEXT, None),
87        ]
88    #default setting with visible columns, order of the col, and their size
89    CONFIGSETTINGS = (
90        ('columns.visible', [COL_ID, COL_FATHER, COL_MOTHER, COL_REL,
91                             COL_MARDATE]),
92        ('columns.rank', [COL_ID, COL_FATHER, COL_MOTHER, COL_REL,
93                           COL_MARDATE, COL_PRIV, COL_TAGS, COL_CHAN]),
94        ('columns.size', [75, 200, 200, 100, 100, 40, 100, 100])
95        )
96
97    ADD_MSG = _("Add a new family")
98    EDIT_MSG = _("Edit the selected family")
99    DEL_MSG = _("Delete the selected family")
100    MERGE_MSG = _("Merge the selected families")
101    FILTER_TYPE = "Family"
102    QR_CATEGORY = CATEGORY_QR_FAMILY
103
104    def __init__(self, pdata, dbstate, uistate, nav_group=0):
105
106        signal_map = {
107            'family-add'     : self.row_add,
108            'family-update'  : self.row_update,
109            'family-delete'  : self.row_delete,
110            'family-rebuild' : self.object_build,
111            'event-update'   : self.related_update,
112            'person-update'  : self.related_update,
113            }
114
115        ListView.__init__(
116            self, _('Families'), pdata, dbstate, uistate,
117            FamilyModel,
118            signal_map,
119            FamilyBookmarks, nav_group,
120            multiple=True,
121            filter_class=FamilySidebarFilter)
122
123        uistate.connect('nameformat-changed', self.build_tree)
124
125        self.additional_uis.append(self.additional_ui)
126
127    def navigation_type(self):
128        return 'Family'
129
130    def get_stock(self):
131        return 'gramps-family'
132
133    additional_ui = [  # Defines the UI string for UIManager
134        '''
135      <placeholder id="LocalExport">
136        <item>
137          <attribute name="action">win.ExportTab</attribute>
138          <attribute name="label" translatable="yes">Export View...</attribute>
139        </item>
140      </placeholder>
141''',
142        '''
143      <section id="AddEditBook">
144        <item>
145          <attribute name="action">win.AddBook</attribute>
146          <attribute name="label" translatable="yes">_Add Bookmark</attribute>
147        </item>
148        <item>
149          <attribute name="action">win.EditBook</attribute>
150          <attribute name="label" translatable="no">%s...</attribute>
151        </item>
152      </section>
153''' % _('Organize Bookmarks'),
154        '''
155      <placeholder id="CommonGo">
156      <section>
157        <item>
158          <attribute name="action">win.Back</attribute>
159          <attribute name="label" translatable="yes">_Back</attribute>
160        </item>
161        <item>
162          <attribute name="action">win.Forward</attribute>
163          <attribute name="label" translatable="yes">_Forward</attribute>
164        </item>
165      </section>
166      </placeholder>
167''',
168        '''
169      <section id='CommonEdit' groups='RW'>
170        <item>
171          <attribute name="action">win.Add</attribute>
172          <attribute name="label" translatable="yes">_Add...</attribute>
173        </item>
174        <item>
175          <attribute name="action">win.Edit</attribute>
176          <attribute name="label">%s</attribute>
177        </item>
178        <item>
179          <attribute name="action">win.Remove</attribute>
180          <attribute name="label" translatable="yes">_Delete</attribute>
181        </item>
182        <item>
183          <attribute name="action">win.Merge</attribute>
184          <attribute name="label" translatable="yes">_Merge...</attribute>
185        </item>
186      </section>
187''' % _("action|_Edit..."),  # to use sgettext()
188        '''
189        <placeholder id='otheredit'>
190        <item>
191          <attribute name="action">win.FilterEdit</attribute>
192          <attribute name="label" translatable="yes">'''
193        '''Family Filter Editor</attribute>
194        </item>
195        </placeholder>
196''',  # Following are the Toolbar items
197        '''
198    <placeholder id='CommonNavigation'>
199    <child groups='RO'>
200      <object class="GtkToolButton">
201        <property name="icon-name">go-previous</property>
202        <property name="action-name">win.Back</property>
203        <property name="tooltip_text" translatable="yes">'''
204        '''Go to the previous object in the history</property>
205        <property name="label" translatable="yes">_Back</property>
206        <property name="use-underline">True</property>
207      </object>
208      <packing>
209        <property name="homogeneous">False</property>
210      </packing>
211    </child>
212    <child groups='RO'>
213      <object class="GtkToolButton">
214        <property name="icon-name">go-next</property>
215        <property name="action-name">win.Forward</property>
216        <property name="tooltip_text" translatable="yes">'''
217        '''Go to the next object in the history</property>
218        <property name="label" translatable="yes">_Forward</property>
219        <property name="use-underline">True</property>
220      </object>
221      <packing>
222        <property name="homogeneous">False</property>
223      </packing>
224    </child>
225    </placeholder>
226''',
227        '''
228    <placeholder id='BarCommonEdit'>
229    <child groups='RW'>
230      <object class="GtkToolButton">
231        <property name="icon-name">list-add</property>
232        <property name="action-name">win.Add</property>
233        <property name="tooltip_text">%s</property>
234        <property name="label" translatable="yes">_Add...</property>
235        <property name="use-underline">True</property>
236      </object>
237      <packing>
238        <property name="homogeneous">False</property>
239      </packing>
240    </child>
241    <child groups='RW'>
242      <object class="GtkToolButton">
243        <property name="icon-name">gtk-edit</property>
244        <property name="action-name">win.Edit</property>
245        <property name="tooltip_text">%s</property>
246        <property name="label" translatable="yes">Edit...</property>
247        <property name="use-underline">True</property>
248      </object>
249      <packing>
250        <property name="homogeneous">False</property>
251      </packing>
252    </child>
253    <child groups='RW'>
254      <object class="GtkToolButton">
255        <property name="icon-name">list-remove</property>
256        <property name="action-name">win.Remove</property>
257        <property name="tooltip_text">%s</property>
258        <property name="label" translatable="yes">_Delete</property>
259        <property name="use-underline">True</property>
260      </object>
261      <packing>
262        <property name="homogeneous">False</property>
263      </packing>
264    </child>
265    <child groups='RW'>
266      <object class="GtkToolButton">
267        <property name="icon-name">gramps-merge</property>
268        <property name="action-name">win.Merge</property>
269        <property name="tooltip_text">%s</property>
270        <property name="label" translatable="yes">_Merge...</property>
271        <property name="use-underline">True</property>
272      </object>
273      <packing>
274        <property name="homogeneous">False</property>
275      </packing>
276    </child>
277    </placeholder>
278''' % (ADD_MSG, EDIT_MSG, DEL_MSG, MERGE_MSG),
279        '''
280    <menu id="Popup">
281      <section>
282        <item>
283          <attribute name="action">win.Back</attribute>
284          <attribute name="label" translatable="yes">_Back</attribute>
285        </item>
286        <item>
287          <attribute name="action">win.Forward</attribute>
288          <attribute name="label" translatable="yes">Forward</attribute>
289        </item>
290      </section>
291      <section id="PopUpTree">
292      </section>
293      <section>
294        <item>
295          <attribute name="action">win.Add</attribute>
296          <attribute name="label" translatable="yes">_Add...</attribute>
297        </item>
298        <item>
299          <attribute name="action">win.Edit</attribute>
300          <attribute name="label">%s</attribute>
301        </item>
302        <item>
303          <attribute name="action">win.Remove</attribute>
304          <attribute name="label" translatable="yes">_Delete</attribute>
305        </item>
306        <item>
307          <attribute name="action">win.Merge</attribute>
308          <attribute name="label" translatable="yes">_Merge...</attribute>
309        </item>
310      </section>
311      <section>
312        <item>
313          <attribute name="action">win.MakeFatherActive</attribute>
314          <attribute name="label" translatable="yes">'''
315        '''Make Father Active Person</attribute>
316        </item>
317        <item>
318          <attribute name="action">win.MakeMotherActive</attribute>
319          <attribute name="label" translatable="yes">'''
320        '''Make Mother Active Person</attribute>
321        </item>
322      </section>
323      <section>
324        <placeholder id='QuickReport'>
325        </placeholder>
326      </section>
327    </menu>
328''' % _('action|_Edit...')  # to use sgettext()
329    ]
330
331    def define_actions(self):
332        """Add the Forward action group to handle the Forward button."""
333
334        ListView.define_actions(self)
335
336        self.action_list.extend([
337            ('MakeFatherActive', self._make_father_active),
338            ('MakeMotherActive', self._make_mother_active), ])
339
340    def add_bookmark(self, *obj):
341        mlist = self.selected_handles()
342        if mlist:
343            self.bookmarks.add(mlist[0])
344        else:
345            from gramps.gui.dialog import WarningDialog
346            WarningDialog(
347                _("Could Not Set a Bookmark"),
348                _("A bookmark could not be set because "
349                  "no one was selected."), parent=self.uistate.window)
350
351    def add(self, *obj):
352        family = Family()
353        try:
354            EditFamily(self.dbstate, self.uistate, [], family)
355        except WindowActiveError:
356            pass
357
358    def remove(self, *obj):
359        """
360        Method called when deleting a family from a family view.
361        """
362        from gramps.gui.dialog import QuestionDialog, MultiSelectDialog
363        from gramps.gen.utils.string import data_recover_msg
364        handles = self.selected_handles()
365        if len(handles) == 1:
366            family = self.dbstate.db.get_family_from_handle(handles[0])
367            msg1 = self._message1_format(family)
368            msg2 = self._message2_format(family)
369            msg2 = "%s %s" % (msg2, data_recover_msg)
370            QuestionDialog(msg1,
371                           msg2,
372                           _('_Delete Family'),
373                           lambda: self.delete_family_response(family),
374                           parent=self.uistate.window)
375        else:
376            MultiSelectDialog(self._message1_format,
377                              self._message2_format,
378                              handles,
379                              self.dbstate.db.get_family_from_handle,
380                              yes_func=self.delete_family_response,
381                              parent=self.uistate.window)
382
383    def _message1_format(self, family):
384        """
385        Header format for remove dialogs.
386        """
387        return _('Delete %s?') % (_('family') +
388                                  (" [%s]" % family.gramps_id))
389
390    def _message2_format(self, family):
391        """
392        Detailed message format for the remove dialogs.
393        """
394        return _('Deleting item will remove it from the database.')
395
396    def delete_family_response(self, family):
397        """
398        Deletes the family from the database. Callback to remove
399        dialogs.
400        """
401        from gramps.gen.db import DbTxn
402        # set the busy cursor, so the user knows that we are working
403        self.uistate.set_busy_cursor(True)
404        # create the transaction
405        with DbTxn('', self.dbstate.db) as trans:
406            gramps_id = family.gramps_id
407            self.dbstate.db.remove_family_relationships(family.handle, trans)
408            trans.set_description(_("Family [%s]") % gramps_id)
409        self.uistate.set_busy_cursor(False)
410
411    def remove_object_from_handle(self, handle):
412        """
413        The remove_selected_objects method is not called in this view.
414        """
415        pass
416
417    def edit(self, *obj):
418        for handle in self.selected_handles():
419            family = self.dbstate.db.get_family_from_handle(handle)
420            try:
421                EditFamily(self.dbstate, self.uistate, [], family)
422            except WindowActiveError:
423                pass
424
425    def merge(self, *obj):
426        """
427        Merge the selected families.
428        """
429        mlist = self.selected_handles()
430
431        if len(mlist) != 2:
432            msg = _("Cannot merge families.")
433            msg2 = _("Exactly two families must be selected to perform a merge."
434                     " A second family can be selected by holding down the "
435                     "control key while clicking on the desired family.")
436            ErrorDialog(msg, msg2, parent=self.uistate.window)
437        else:
438            MergeFamily(self.dbstate, self.uistate, [], mlist[0], mlist[1])
439
440    def _make_father_active(self, *obj):
441        """
442        Make the father of the family the active person.
443        """
444        fhandle = self.first_selected()
445        if fhandle:
446            family = self.dbstate.db.get_family_from_handle(fhandle)
447            if family:
448                self.uistate.set_active(family.father_handle, 'Person')
449
450    def _make_mother_active(self, *obj):
451        """
452        Make the mother of the family the active person.
453        """
454        fhandle = self.first_selected()
455        if fhandle:
456            family = self.dbstate.db.get_family_from_handle(fhandle)
457            if family:
458                self.uistate.set_active(family.mother_handle, 'Person')
459
460    def drag_info(self):
461        """
462        Indicate that the drag type is a FAMILY_LINK
463        """
464        return DdTargets.FAMILY_LINK
465
466    def tag_updated(self, handle_list):
467        """
468        Update tagged rows when a tag color changes.
469        """
470        all_links = set([])
471        for tag_handle in handle_list:
472            links = set([link[1] for link in
473                         self.dbstate.db.find_backlink_handles(tag_handle,
474                                                    include_classes='Family')])
475            all_links = all_links.union(links)
476        self.row_update(list(all_links))
477
478    def add_tag(self, transaction, family_handle, tag_handle):
479        """
480        Add the given tag to the given family.
481        """
482        family = self.dbstate.db.get_family_from_handle(family_handle)
483        family.add_tag(tag_handle)
484        self.dbstate.db.commit_family(family, transaction)
485
486    def get_default_gramplets(self):
487        """
488        Define the default gramplets for the sidebar and bottombar.
489        """
490        return (("Family Filter",),
491                ("Family Gallery",
492                 "Family Events",
493                 "Family Children",
494                 "Family Citations",
495                 "Family Notes",
496                 "Family Attributes",
497                 "Family Backlinks"))
498