1
# Gramps - a GTK+/GNOME based genealogy program
3
# Copyright (C) 2009-2010 Nick Hall
4
# Copyright (C) 2011 Tim G L Lyons
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
# $Id: citationtreeview.py 18548 2011-12-04 17:09:17Z kulath $
24
Citation Tree View (or Source tree view).
25
A view showing all the Sources with child Citations
27
#-------------------------------------------------------------------------
31
#-------------------------------------------------------------------------
33
LOG = logging.getLogger(".citation")
34
_LOG = logging.getLogger('.gui.citationtreeview')
36
#-------------------------------------------------------------------------
40
#-------------------------------------------------------------------------
43
#-------------------------------------------------------------------------
47
#-------------------------------------------------------------------------
48
from gui.views.listview import LISTTREE
49
from gui.views.treemodels.citationtreemodel import CitationTreeModel
50
from gen.plug import CATEGORY_QR_SOURCE_OR_CITATION
52
from gui.views.listview import ListView
56
from DdTargets import DdTargets
57
from QuestionDialog import ErrorDialog
58
from gui.editors import EditCitation, DeleteCitationQuery, EditSource, \
60
from Filters.SideBar import SourceSidebarFilter
62
#-------------------------------------------------------------------------
64
# Internationalization
66
#-------------------------------------------------------------------------
67
from gen.ggettext import gettext as _
69
#-------------------------------------------------------------------------
73
#-------------------------------------------------------------------------
74
class CitationTreeView(ListView):
76
A hierarchical view of sources with citations below them.
78
# The data items here have to correspond, in order, to the items in
79
# src/giu.views/treemodels/citationtreemodel.py
96
_('Source: Abbreviation'),
97
_('Source: Publication Information'),
107
# columns that contain markup
108
MARKUP_COLS = [COL_DATE]
109
# default setting with visible columns, order of the col, and their size
111
('columns.visible', [COL_TITLE_PAGE, COL_ID, COL_SRC_AUTH,
113
('columns.rank', [COL_TITLE_PAGE, COL_ID, COL_DATE, COL_CONFIDENCE,
114
COL_CHAN, COL_SRC_AUTH,
115
COL_SRC_ABBR, COL_SRC_PINFO]),
116
('columns.size', [200, 75, 100, 75, 100, 150, 100, 150])
118
ADD_MSG = _("Add a new citation and a new source")
119
ADD_SOURCE_MSG = _("Add a new source")
120
ADD_CITATION_MSG = _("Add a new citation to an existing source")
121
EDIT_MSG = _("Edit the selected citation or source")
122
DEL_MSG = _("Delete the selected citation or source")
123
MERGE_MSG = _("Merge the selected citations or selected sources")
124
FILTER_TYPE = "Citation"
125
QR_CATEGORY = CATEGORY_QR_SOURCE_OR_CITATION
127
def __init__(self, pdata, dbstate, uistate, nav_group=0):
130
'citation-add' : self._citation_row_add,
131
'citation-update' : self._citation_row_update,
132
'citation-delete' : self._citation_row_delete,
133
'citation-rebuild' : self._citation_object_build,
134
'source-add' : self._source_row_add,
135
'source-update' : self._source_row_update,
136
'source-delete' : self._source_row_delete,
137
'source-rebuild' : self._source_object_build,
141
self, _('Citation Tree View'), pdata, dbstate, uistate,
142
self.COLUMN_NAMES, len(self.COLUMN_NAMES),
143
CitationTreeModel, signal_map,
144
dbstate.db.get_citation_bookmarks(),
145
Bookmarks.CitationBookmarks, nav_group,
147
filter_class=SourceSidebarFilter,
148
markup = CitationTreeView.MARKUP_COLS)
150
self.func_list.update({
151
'<CONTROL>J' : self.jump,
152
'<CONTROL>BackSpace' : self.key_delete,
155
self.additional_uis.append(self.additional_ui())
157
def setup_filter(self):
159
Override the setup of the default Search Bar in listview, so that only
160
the searchable source fields are shown. This includes renaming the
161
'Title or Page' search to 'Title'
167
return self.colinfo[i]
169
self.search_bar.setup_filter(
170
[(name(pair[1]), pair[1], pair[1] in self.exact_search())
171
for pair in self.column_order() if pair[0] and
172
pair[1] in self.COLUMN_FILTERABLE])
174
def _print_handles(self, text, handle_list):
175
for handle in handle_list:
176
source = self.dbstate.db.get_source_from_handle(handle)
177
citation = self.dbstate.db.get_citation_from_handle(handle)
180
_LOG.debug("---- %s -- source %s" %
181
(text, source.get_title()))
183
_LOG.debug("---- %s -- citation %s" %
184
(text, citation.get_page()))
186
_LOG.debug("---- %s -- handle %s" % (text, handle))
188
def _citation_row_add(self, handle_list):
189
self._print_handles("citation row add", handle_list)
190
self.row_add(handle_list)
192
def _citation_row_update(self, handle_list):
193
self._print_handles("citation row update", handle_list)
194
self.row_update(handle_list)
196
def _citation_row_delete(self, handle_list):
197
self._print_handles("citation row delete", handle_list)
198
self.row_delete(handle_list)
200
def _citation_object_build(self, *args):
201
_LOG.debug("citation object build")
202
self.object_build(*args)
204
def _source_row_add(self, handle_list):
205
self._print_handles("source row add", handle_list)
206
self.row_add(handle_list)
208
def _source_row_update(self, handle_list):
209
self._print_handles("source row update", handle_list)
210
self.row_update(handle_list)
212
def _source_row_delete(self, handle_list):
213
self._print_handles("source row delete", handle_list)
214
self.row_delete(handle_list)
216
def _source_object_build(self, *args):
217
_LOG.debug("source object build")
218
self.object_build(*args)
220
def navigation_type(self):
223
def get_bookmarks(self):
224
return self.dbstate.db.get_citation_bookmarks()
227
# Since drag only needs to work when just one row is selected, ideally,
228
# this should just return SOURCE_LINK if one source is selected and
229
# CITATION_LINK if one citation is selected, and probably None
230
# otherwise. However, this doesn't work. Drag and drop failed to work at
231
# all for citationtree view, and I think this was because None is
232
# returned during initialisation. There is also a problem where it seems
233
# at some point during a citation merge, neither a Source nor a Citation
234
# is selected. Hence the simplistic solution implemented below, where
235
# CITATION_LINK is always returned except when it is obviously correct
236
# to return SOURCE_LINK.
238
selection = self.selected_handles()
239
if len(selection) == 1 and \
240
self.dbstate.db.get_source_from_handle(selection[0]):
241
return DdTargets.SOURCE_LINK
243
return DdTargets.CITATION_LINK
247
set the listtype, this governs eg keybinding
252
return 'gramps-citation'
254
def get_viewtype_stock(self):
256
Override the default icon. Set for hierarchical view.
258
return 'gramps-tree-group'
260
def define_actions(self):
262
This defines the possible actions for the citation views.
263
Possible actions are:
264
add_source: Add a new source (this is also available from the
266
add: Add a new citation and a new source (this can also be done
267
by source view add a source, then citation view add a new
268
citation to an existing source)
269
share: Add a new citation to an existing source (when a source is
271
edit: Edit a source or a citation.
272
merge: Merge the selected sources or citations.
273
remove: Delete the selected sources or citations.
277
ListView.define_actions(self)
279
self._add_action('Add source', 'gramps-source', _("Add source..."),
281
tip=self.ADD_SOURCE_MSG,
282
callback=self.add_source)
283
self._add_action('Add citation', 'gramps-citation',
284
_("Add citation..."),
286
tip=self.ADD_CITATION_MSG,
289
self.all_action = gtk.ActionGroup(self.title + "/CitationAll")
290
self.edit_action = gtk.ActionGroup(self.title + "/CitationEdit")
292
self._add_action('FilterEdit', None, _('Citation Filter Editor'),
293
callback=self.filter_editor,)
294
self._add_action('QuickReport', None, _("Quick View"), None, None, None)
295
self._add_action('Dummy', None, ' ', None, None, self.dummy_report)
297
self._add_action_group(self.edit_action)
298
self._add_action_group(self.all_action)
300
self.all_action.add_actions([
301
('OpenAllNodes', None, _("Expand all Nodes"), None, None,
302
self.open_all_nodes),
303
('CloseAllNodes', None, _("Collapse all Nodes"), None, None,
304
self.close_all_nodes),
307
def additional_ui(self):
309
Defines the UI string for UIManager
312
<menubar name="MenuBar">
313
<menu action="FileMenu">
314
<placeholder name="LocalExport">
315
<menuitem action="ExportTab"/>
318
<menu action="BookMenu">
319
<placeholder name="AddEditBook">
320
<menuitem action="AddBook"/>
321
<menuitem action="EditBook"/>
324
<menu action="GoMenu">
325
<placeholder name="CommonGo">
326
<menuitem action="Back"/>
327
<menuitem action="Forward"/>
330
<menu action="EditMenu">
331
<placeholder name="CommonEdit">
332
<menuitem action="Add"/>
333
<menuitem action="Add source"/>
334
<menuitem action="Add citation"/>
335
<menuitem action="Edit"/>
336
<menuitem action="Remove"/>
337
<menuitem action="Merge"/>
339
<menuitem action="FilterEdit"/>
342
<toolbar name="ToolBar">
343
<placeholder name="CommonNavigation">
344
<toolitem action="Back"/>
345
<toolitem action="Forward"/>
347
<placeholder name="CommonEdit">
348
<toolitem action="Add"/>
349
<toolitem action="Add source"/>
350
<toolitem action="Add citation"/>
351
<toolitem action="Edit"/>
352
<toolitem action="Remove"/>
353
<toolitem action="Merge"/>
357
<menuitem action="Back"/>
358
<menuitem action="Forward"/>
360
<menuitem action="OpenAllNodes"/>
361
<menuitem action="CloseAllNodes"/>
363
<menuitem action="Add"/>
364
<menuitem action="Edit"/>
365
<menuitem action="Remove"/>
366
<menuitem action="Merge"/>
368
<menu name="QuickReport" action="QuickReport">
369
<menuitem action="Dummy"/>
374
def dummy_report(self, obj):
375
""" For the xml UI definition of popup to work, the submenu
376
Quick Report must have an entry in the xml
377
As this submenu will be dynamically built, we offer a dummy action
381
def add_source(self, obj):
383
add_source: Add a new source (this is also available from the
386
Create a new Source instance and call the EditSource editor with the
389
Called when the Add_source button is clicked.
390
If the window already exists (Errors.WindowActiveError), we ignore it.
391
This prevents the dialog from coming up twice on the same object.
393
However, since the window is identified by the Source object, and
394
we have just created a new one, it seems to be impossible for the
395
window to already exist, so this is just an extra safety measure.
398
EditSource(self.dbstate, self.uistate, [], gen.lib.Source())
399
except Errors.WindowActiveError:
404
add: Add a new citation and a new source (this can also be done
405
by source view add a source, then citation view add a new
406
citation to an existing source)
408
Create a new Source instance and Citation instance and call the
409
EditSource editor with the new source.
411
Called when the Add button is clicked.
412
If the window already exists (Errors.WindowActiveError), we ignore it.
413
This prevents the dialog from coming up twice on the same object.
415
However, since the window is identified by the Source object, and
416
we have just created a new one, it seems to be impossible for the
417
window to already exist, so this is just an extra safety measure.
420
EditCitation(self.dbstate, self.uistate, [], gen.lib.Citation(),
422
except Errors.WindowActiveError:
425
def share(self, obj):
427
share: Add a new citation to an existing source (when a source is
430
for handle in self.selected_handles():
431
# The handle will either be a Source handle or a Citation handle
432
source = self.dbstate.db.get_source_from_handle(handle)
433
citation = self.dbstate.db.get_citation_from_handle(handle)
434
if (not source and not citation) or (source and citation):
435
raise ValueError("selection must be either source or citation")
438
EditCitation(self.dbstate, self.uistate, [],
439
gen.lib.Citation(), source)
440
except Errors.WindowActiveError:
441
from QuestionDialog import WarningDialog
442
WarningDialog(_("Cannot share this reference"),
443
self.__blocked_text())
445
msg = _("Cannot add citation.")
446
msg2 = _("In order to add a citation to an existing source, "
447
" you must select a source.")
448
ErrorDialog(msg, msg2)
450
def remove(self, obj):
451
self.remove_selected_objects()
453
def remove_object_from_handle(self, handle):
454
# The handle will either be a Source handle or a Citation handle
455
source = self.dbstate.db.get_source_from_handle(handle)
456
citation = self.dbstate.db.get_citation_from_handle(handle)
457
if (not source and not citation) or (source and citation):
458
raise ValueError("selection must be either source or citation")
460
the_lists = Utils.get_citation_referents(handle, self.dbstate.db)
461
object = self.dbstate.db.get_citation_from_handle(handle)
462
query = DeleteCitationQuery(self.dbstate, self.uistate, object,
464
is_used = any(the_lists)
465
return (query, is_used, object)
467
the_lists = Utils.get_source_and_citation_referents(handle,
469
LOG.debug('the_lists %s' % [the_lists])
471
object = self.dbstate.db.get_source_from_handle(handle)
472
query = DeleteSrcQuery(self.dbstate, self.uistate, object,
474
is_used = any(the_lists)
475
return (query, is_used, object)
479
Edit either a Source or a Citation, depending on user selection
481
for handle in self.selected_handles():
482
# The handle will either be a Source handle or a Citation handle
483
source = self.dbstate.db.get_source_from_handle(handle)
484
citation = self.dbstate.db.get_citation_from_handle(handle)
485
if (not source and not citation) or (source and citation):
486
raise ValueError("selection must be either source or citation")
489
EditCitation(self.dbstate, self.uistate, [], citation)
490
except Errors.WindowActiveError:
492
else: # FIXME need try block here
494
EditSource(self.dbstate, self.uistate, [], source)
495
except Errors.WindowActiveError:
496
from QuestionDialog import WarningDialog
497
WarningDialog(_("Cannot share this reference"),
498
self.__blocked_text2())
500
def __blocked_text(self):
502
Return the common text used when citation cannot be edited
504
return _("This citation cannot be created at this time. "
505
"Either the associated Source object is already being "
506
"edited, or another citation associated with the same "
507
"source is being edited.\n\nTo edit this "
508
"citation, you need to close the object.")
510
def __blocked_text2(self):
512
Return the common text used when citation cannot be edited
514
return _("This source cannot be edited at this time. "
515
"Either the associated Source object is already being "
516
"edited, or another citation associated with the same "
517
"source is being edited.\n\nTo edit this "
518
"source, you need to close the object.")
520
def merge(self, obj):
522
Merge the selected citations.
524
mlist = self.selected_handles()
527
msg = _("Cannot merge citations.")
528
msg2 = _("Exactly two citations must be selected to perform a "
529
"merge. A second citation can be selected by holding "
530
"down the control key while clicking on the desired "
532
ErrorDialog(msg, msg2)
534
source1 = self.dbstate.db.get_source_from_handle(mlist[0])
535
citation1 = self.dbstate.db.get_citation_from_handle(mlist[0])
536
if (not source1 and not citation1) or (source1 and citation1):
537
raise ValueError("selection must be either source or citation")
539
source2 = self.dbstate.db.get_source_from_handle(mlist[1])
540
citation2 = self.dbstate.db.get_citation_from_handle(mlist[1])
541
if (not source2 and not citation2) or (source2 and citation2):
542
raise ValueError("selection must be either source or citation")
544
if citation1 and citation2:
545
if not citation1.get_reference_handle() == \
546
citation2.get_reference_handle():
547
msg = _("Cannot merge citations.")
548
msg2 = _("The two selected citations must have the same "
549
"source to perform a merge. If you want to merge "
550
"these two citations, then you must merge the "
552
ErrorDialog(msg, msg2)
555
Merge.MergeCitations(self.dbstate, self.uistate,
557
elif source1 and source2:
559
Merge.MergeSources(self.dbstate, self.uistate,
562
msg = _("Cannot perform merge.")
563
msg2 = _("Both objects must be of the same type, either "
564
"both must be sources, or both must be "
566
ErrorDialog(msg, msg2)
568
def get_handle_from_gramps_id(self, gid):
569
obj = self.dbstate.db.get_citation_from_gramps_id(gid)
571
return obj.get_handle()
575
def get_default_gramplets(self):
577
Define the default gramplets for the sidebar and bottombar.
579
return (("Source Filter",),
582
"Citation Backlinks"))