1# Gramps - a GTK+/GNOME based genealogy program
2#
3# Copyright (C) 2001-2006  Donald N. Allingham
4# Copyright (C) 2008       Gary Burton
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"""
23Citation List View
24"""
25
26#-------------------------------------------------------------------------
27#
28# python modules
29#
30#-------------------------------------------------------------------------
31import logging
32LOG = logging.getLogger(".citation")
33
34#-------------------------------------------------------------------------
35#
36# GTK/Gnome modules
37#
38#-------------------------------------------------------------------------
39from gi.repository import Gtk
40
41#-------------------------------------------------------------------------
42#
43# gramps modules
44#
45#-------------------------------------------------------------------------
46from gramps.gui.views.treemodels.citationlistmodel import CitationListModel
47from gramps.gen.plug import CATEGORY_QR_CITATION
48from gramps.gen.lib import Citation, Source
49from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON
50from gramps.gen.utils.db import get_citation_referents
51from gramps.gui.views.bookmarks import CitationBookmarks
52from gramps.gen.errors import WindowActiveError
53from gramps.gui.ddtargets import DdTargets
54from gramps.gui.dialog import ErrorDialog
55from gramps.gui.editors import EditCitation, DeleteCitationQuery
56from gramps.gui.filters.sidebar import CitationSidebarFilter
57from gramps.gui.merge import MergeCitation
58
59#-------------------------------------------------------------------------
60#
61# internationalization
62#
63#-------------------------------------------------------------------------
64from gramps.gen.const import GRAMPS_LOCALE as glocale
65_ = glocale.translation.sgettext
66
67
68#-------------------------------------------------------------------------
69#
70# CitationView
71#
72#-------------------------------------------------------------------------
73class CitationListView(ListView):
74    """
75    A list view of citations.
76
77    The citation list view only shows the citations (it does not show
78    sources as separate list entries).
79    """
80    # The data items here have to correspond, in order, to the items in
81    # src/giu/views/treemodels/citationlismodel.py
82    COL_TITLE_PAGE = 0
83    COL_ID = 1
84    COL_DATE = 2
85    COL_CONFIDENCE = 3
86    COL_PRIV = 4
87    COL_TAGS = 5
88    COL_CHAN = 6
89    COL_SRC_TITLE = 7
90    COL_SRC_ID = 8
91    COL_SRC_AUTH = 9
92    COL_SRC_ABBR = 10
93    COL_SRC_PINFO = 11
94    COL_SRC_PRIV = 12
95    COL_SRC_CHAN = 13
96    # column definitions
97    COLUMNS = [
98        (_('Volume/Page'), TEXT, None),
99        (_('ID'), TEXT, None),
100        (_('Date'), MARKUP, None),
101        (_('Confidence'), TEXT, None),
102        (_('Private'), ICON, 'gramps-lock'),
103        (_('Tags'), TEXT, None),
104        (_('Last Changed'), TEXT, None),
105        (_('Source: Title'), TEXT, None),
106        (_('Source: ID'), TEXT, None),
107        (_('Source: Author'), TEXT, None),
108        (_('Source: Abbreviation'), TEXT, None),
109        (_('Source: Publication Information'), TEXT, None),
110        (_('Source: Private'), ICON, 'gramps-lock'),
111        (_('Source: Last Changed'), TEXT, None),
112        ]
113    # default setting with visible columns, order of the col, and their size
114    CONFIGSETTINGS = (
115        ('columns.visible', [COL_TITLE_PAGE, COL_ID, COL_DATE,
116                             COL_CONFIDENCE]),
117        ('columns.rank', [COL_TITLE_PAGE, COL_ID, COL_DATE, COL_CONFIDENCE,
118                          COL_PRIV, COL_TAGS, COL_CHAN, COL_SRC_TITLE,
119                          COL_SRC_ID, COL_SRC_AUTH, COL_SRC_ABBR, COL_SRC_PINFO,
120                          COL_SRC_PRIV, COL_SRC_CHAN]),
121        ('columns.size', [200, 75, 100, 100, 40, 100, 100, 200, 75, 75, 100,
122                          150, 40, 100])
123        )
124    ADD_MSG = _("Add a new citation and a new source")
125    ADD_SOURCE_MSG = _("Add a new source")
126    ADD_CITATION_MSG = _("Add a new citation to an existing source")
127    EDIT_MSG = _("Edit the selected citation")
128    DEL_MSG = _("Delete the selected citation")
129    MERGE_MSG = _("Merge the selected citations")
130    FILTER_TYPE = "Citation"
131    QR_CATEGORY = CATEGORY_QR_CITATION
132
133    def __init__(self, pdata, dbstate, uistate, nav_group=0):
134
135        signal_map = {
136            'citation-add'     : self.row_add,
137            'citation-update'  : self.row_update,
138            'citation-delete'  : self.row_delete,
139            'citation-rebuild' : self.object_build,
140            }
141
142        ListView.__init__(
143            self, _('Citation View'), pdata, dbstate, uistate,
144            CitationListModel, signal_map,
145            CitationBookmarks, nav_group,
146            multiple=True,
147            filter_class=CitationSidebarFilter)
148
149        self.additional_uis.append(self.additional_ui)
150
151    def navigation_type(self):
152        return 'Citation'
153
154    def drag_info(self):
155        return DdTargets.CITATION_LINK
156
157    def get_stock(self):
158        return 'gramps-citation'
159
160    additional_ui = [
161        '''
162      <placeholder id="LocalExport">
163        <item>
164          <attribute name="action">win.ExportTab</attribute>
165          <attribute name="label" translatable="yes">Export View...</attribute>
166        </item>
167      </placeholder>
168''',
169        '''
170      <section id="AddEditBook">
171        <item>
172          <attribute name="action">win.AddBook</attribute>
173          <attribute name="label" translatable="yes">_Add Bookmark</attribute>
174        </item>
175        <item>
176          <attribute name="action">win.EditBook</attribute>
177          <attribute name="label" translatable="no">%s...</attribute>
178        </item>
179      </section>
180''' % _('Organize Bookmarks'),
181        '''
182      <placeholder id="CommonGo">
183      <section>
184        <item>
185          <attribute name="action">win.Back</attribute>
186          <attribute name="label" translatable="yes">_Back</attribute>
187        </item>
188        <item>
189          <attribute name="action">win.Forward</attribute>
190          <attribute name="label" translatable="yes">_Forward</attribute>
191        </item>
192      </section>
193      </placeholder>
194''',
195        '''
196      <section id='CommonEdit' groups='RW'>
197        <item>
198          <attribute name="action">win.Add</attribute>
199          <attribute name="label" translatable="yes">_Add...</attribute>
200        </item>
201        <item>
202          <attribute name="action">win.Edit</attribute>
203          <attribute name="label">%s</attribute>
204        </item>
205        <item>
206          <attribute name="action">win.Remove</attribute>
207          <attribute name="label" translatable="yes">_Delete</attribute>
208        </item>
209        <item>
210          <attribute name="action">win.Merge</attribute>
211          <attribute name="label" translatable="yes">_Merge...</attribute>
212        </item>
213      </section>
214''' % _("action|_Edit..."),  # to use sgettext()
215        '''
216        <placeholder id='otheredit'>
217        <item>
218          <attribute name="action">win.FilterEdit</attribute>
219          <attribute name="label" translatable="yes">'''
220        '''Citation Filter Editor</attribute>
221        </item>
222        </placeholder>
223''',  # Following are the Toolbar items
224        '''
225    <placeholder id='CommonNavigation'>
226    <child groups='RO'>
227      <object class="GtkToolButton">
228        <property name="icon-name">go-previous</property>
229        <property name="action-name">win.Back</property>
230        <property name="tooltip_text" translatable="yes">'''
231        '''Go to the previous object in the history</property>
232        <property name="label" translatable="yes">_Back</property>
233        <property name="use-underline">True</property>
234      </object>
235      <packing>
236        <property name="homogeneous">False</property>
237      </packing>
238    </child>
239    <child groups='RO'>
240      <object class="GtkToolButton">
241        <property name="icon-name">go-next</property>
242        <property name="action-name">win.Forward</property>
243        <property name="tooltip_text" translatable="yes">'''
244        '''Go to the next object in the history</property>
245        <property name="label" translatable="yes">_Forward</property>
246        <property name="use-underline">True</property>
247      </object>
248      <packing>
249        <property name="homogeneous">False</property>
250      </packing>
251    </child>
252    </placeholder>
253''',
254        '''
255    <placeholder id='BarCommonEdit'>
256    <child groups='RW'>
257      <object class="GtkToolButton">
258        <property name="icon-name">list-add</property>
259        <property name="action-name">win.Add</property>
260        <property name="tooltip_text">%s</property>
261        <property name="label" translatable="yes">_Add...</property>
262        <property name="use-underline">True</property>
263      </object>
264      <packing>
265        <property name="homogeneous">False</property>
266      </packing>
267    </child>
268    <child groups='RW'>
269      <object class="GtkToolButton">
270        <property name="icon-name">gtk-edit</property>
271        <property name="action-name">win.Edit</property>
272        <property name="tooltip_text">%s</property>
273        <property name="label" translatable="yes">Edit...</property>
274        <property name="use-underline">True</property>
275      </object>
276      <packing>
277        <property name="homogeneous">False</property>
278      </packing>
279    </child>
280    <child groups='RW'>
281      <object class="GtkToolButton">
282        <property name="icon-name">list-remove</property>
283        <property name="action-name">win.Remove</property>
284        <property name="tooltip_text">%s</property>
285        <property name="label" translatable="yes">_Delete</property>
286        <property name="use-underline">True</property>
287      </object>
288      <packing>
289        <property name="homogeneous">False</property>
290      </packing>
291    </child>
292    <child groups='RW'>
293      <object class="GtkToolButton">
294        <property name="icon-name">gramps-merge</property>
295        <property name="action-name">win.Merge</property>
296        <property name="tooltip_text" >%s</property>
297        <property name="label" translatable="yes">_Merge...</property>
298        <property name="use-underline">True</property>
299      </object>
300      <packing>
301        <property name="homogeneous">False</property>
302      </packing>
303    </child>
304    </placeholder>
305''' % (ADD_MSG, EDIT_MSG, DEL_MSG, MERGE_MSG),
306        '''
307    <menu id="Popup">
308      <section>
309        <item>
310          <attribute name="action">win.Back</attribute>
311          <attribute name="label" translatable="yes">_Back</attribute>
312        </item>
313        <item>
314          <attribute name="action">win.Forward</attribute>
315          <attribute name="label" translatable="yes">Forward</attribute>
316        </item>
317      </section>
318      <section id="PopUpTree">
319      </section>
320      <section>
321        <item>
322          <attribute name="action">win.Add</attribute>
323          <attribute name="label" translatable="yes">_Add...</attribute>
324        </item>
325        <item>
326          <attribute name="action">win.Edit</attribute>
327          <attribute name="label">%s</attribute>
328        </item>
329        <item>
330          <attribute name="action">win.Remove</attribute>
331          <attribute name="label" translatable="yes">_Delete</attribute>
332        </item>
333        <item>
334          <attribute name="action">win.Merge</attribute>
335          <attribute name="label" translatable="yes">_Merge...</attribute>
336        </item>
337      </section>
338      <section>
339        <placeholder id='QuickReport'>
340        </placeholder>
341      </section>
342    </menu>
343''' % _('action|_Edit...')  # to use sgettext()
344]
345
346    def add(self, *obj):
347        """
348        add:        Add a new citation and a new source (this can also be done
349                      by source view add a source, then citation view add a new
350                      citation to an existing source)
351
352        Create a new Source instance and Citation instance and call the
353        EditCitation editor with the new source and new citation.
354
355        Called when the Add button is clicked.
356        If the window already exists (WindowActiveError), we ignore it.
357        This prevents the dialog from coming up twice on the same object.
358
359        However, since the window is identified by the Source object, and
360        we have just created a new one, it seems to be impossible for the
361        window to already exist, so this is just an extra safety measure.
362        """
363        try:
364            EditCitation(self.dbstate, self.uistate, [], Citation(),
365                         Source())
366        except WindowActiveError:
367            pass
368
369    def remove(self, *obj):
370        self.remove_selected_objects()
371
372    def remove_object_from_handle(self, handle):
373        the_lists = get_citation_referents(handle, self.dbstate.db)
374        object = self.dbstate.db.get_citation_from_handle(handle)
375        query = DeleteCitationQuery(self.dbstate, self.uistate, object,
376                                    the_lists)
377        is_used = any(the_lists)
378        return (query, is_used, object)
379
380    def edit(self, *obj):
381        """
382        Edit a Citation
383        """
384        for handle in self.selected_handles():
385            citation = self.dbstate.db.get_citation_from_handle(handle)
386            try:
387                EditCitation(self.dbstate, self.uistate, [], citation)
388            except WindowActiveError:
389                pass
390
391    def __blocked_text(self):
392        """
393        Return the common text used when citation cannot be edited
394        """
395        return _("This citation cannot be edited at this time. "
396                    "Either the associated citation is already being "
397                    "edited or another object that is associated with "
398                    "the same citation is being edited.\n\nTo edit this "
399                    "citation, you need to close the object.")
400
401    def merge(self, *obj):
402        """
403        Merge the selected citations.
404        """
405        mlist = self.selected_handles()
406
407        if len(mlist) != 2:
408            msg = _("Cannot merge citations.")
409            msg2 = _("Exactly two citations must be selected to perform a "
410                     "merge. A second citation can be selected by holding "
411                     "down the control key while clicking on the desired "
412                     "citation.")
413            ErrorDialog(msg, msg2, parent=self.uistate.window)
414        else:
415            citation1 = self.dbstate.db.get_citation_from_handle(mlist[0])
416            citation2 = self.dbstate.db.get_citation_from_handle(mlist[1])
417            if not citation1.get_reference_handle()  == \
418                            citation2.get_reference_handle():
419                msg = _("Cannot merge citations.")
420                msg2 = _("The two selected citations must have the same "
421                         "source to perform a merge. If you want to merge "
422                         "these two citations, then you must merge the "
423                         "sources first.")
424                ErrorDialog(msg, msg2, parent=self.uistate.window)
425            else:
426                MergeCitation(self.dbstate, self.uistate, [], mlist[0],
427                              mlist[1])
428
429    def get_handle_from_gramps_id(self, gid):
430        obj = self.dbstate.db.get_citation_from_gramps_id(gid)
431        if obj:
432            return obj.get_handle()
433        else:
434            return None
435
436    def tag_updated(self, handle_list):
437        """
438        Update tagged rows when a tag color changes.
439        """
440        all_links = set([])
441        for tag_handle in handle_list:
442            links = set([link[1] for link in
443                         self.dbstate.db.find_backlink_handles(tag_handle,
444                                                include_classes='Citation')])
445            all_links = all_links.union(links)
446        self.row_update(list(all_links))
447
448    def add_tag(self, transaction, citation_handle, tag_handle):
449        """
450        Add the given tag to the given citation.
451        """
452        citation = self.dbstate.db.get_citation_from_handle(citation_handle)
453        citation.add_tag(tag_handle)
454        self.dbstate.db.commit_citation(citation, transaction)
455
456    def get_default_gramplets(self):
457        """
458        Define the default gramplets for the sidebar and bottombar.
459        This is overridden for the tree view to give 'Source Filter'
460        """
461        return (("Citation Filter",),
462                ("Citation Gallery",
463                 "Citation Notes",
464                 "Citation Backlinks"))
465