From 5903853640d770765e7f49a05e50d13cdca7775c Mon Sep 17 00:00:00 2001 From: Tim G L Lyons Date: Mon, 16 Sep 2013 21:42:28 +0000 Subject: [PATCH] Fix citation filters so they no longer try to filter citations with a sources filter. Citation filter now allows specification of both source and citation data. When a citation is shown in a search or a filtered citation tree view, the corresponding source is also shown. svn: r23144 --- gramps/gen/filters/rules/citation/__init__.py | 4 + .../filters/rules/citation/_hassourceidof.py | 58 +++++++++ .../rules/citation/_regexpsourceidof.py | 60 ++++++++++ .../filters/sidebar/_citationsidebarfilter.py | 54 +++++++-- gramps/gui/selectors/selectcitation.py | 2 +- .../gui/views/treemodels/citationtreemodel.py | 13 ++ gramps/gui/views/treemodels/treebasemodel.py | 111 +++++++++++++----- gramps/plugins/view/citationtreeview.py | 13 +- 8 files changed, 267 insertions(+), 48 deletions(-) create mode 100644 gramps/gen/filters/rules/citation/_hassourceidof.py create mode 100644 gramps/gen/filters/rules/citation/_regexpsourceidof.py diff --git a/gramps/gen/filters/rules/citation/__init__.py b/gramps/gen/filters/rules/citation/__init__.py index d29446045..dfbcb25b6 100644 --- a/gramps/gen/filters/rules/citation/__init__.py +++ b/gramps/gen/filters/rules/citation/__init__.py @@ -37,11 +37,13 @@ from ._hasnotematchingsubstringof import HasNoteMatchingSubstringOf from ._hasnoteregexp import HasNoteRegexp from ._hasreferencecountof import HasReferenceCountOf from ._hassource import HasSource +from ._hassourceidof import HasSourceIdOf from ._matchesfilter import MatchesFilter from ._matchespagesubstringof import MatchesPageSubstringOf from ._matchesrepositoryfilter import MatchesRepositoryFilter from ._matchessourcefilter import MatchesSourceFilter from ._regexpidof import RegExpIdOf +from ._regexpsourceidof import RegExpSourceIdOf from ._hastag import HasTag editor_rule_list = [ @@ -56,10 +58,12 @@ editor_rule_list = [ HasNoteRegexp, HasReferenceCountOf, HasSource, + HasSourceIdOf, MatchesFilter, MatchesPageSubstringOf, MatchesRepositoryFilter, MatchesSourceFilter, RegExpIdOf, + RegExpSourceIdOf, HasTag ] diff --git a/gramps/gen/filters/rules/citation/_hassourceidof.py b/gramps/gen/filters/rules/citation/_hassourceidof.py new file mode 100644 index 000000000..f6ff7236f --- /dev/null +++ b/gramps/gen/filters/rules/citation/_hassourceidof.py @@ -0,0 +1,58 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2002-2006 Donald N. Allingham +# Copyright (C) 2011-2013 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from ....const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .._hasgrampsid import HasGrampsId + +#------------------------------------------------------------------------- +# +# HasSourceIdOf +# +#------------------------------------------------------------------------- +class HasSourceIdOf(HasGrampsId): + """Rule that checks for a citation with a source which has a specific + GRAMPS ID""" + + name = _('Citation with Source ') + description = _("Matches a citation with a source with a specified Gramps " + "ID") + category = _('Source filters') + + def apply(self, dbase, citation): + source = dbase.get_source_from_handle( + citation.get_reference_handle()) + if HasGrampsId.apply(self, dbase, source): + return True + return False diff --git a/gramps/gen/filters/rules/citation/_regexpsourceidof.py b/gramps/gen/filters/rules/citation/_regexpsourceidof.py new file mode 100644 index 000000000..b4c972a37 --- /dev/null +++ b/gramps/gen/filters/rules/citation/_regexpsourceidof.py @@ -0,0 +1,60 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2002-2006 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from ....const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .._regexpidbase import RegExpIdBase + +#------------------------------------------------------------------------- +# +# HasIdOf +# +#------------------------------------------------------------------------- +class RegExpSourceIdOf(RegExpIdBase): + """ + Rule that checks for a citation whose GRAMPS ID + matches regular expression. + """ + + name = _('Citations with Source Id containing ') + description = _("Matches citations whose source has a Gramps ID that " + "matches the regular expression") + category = _('Source filters') + + def apply(self, dbase, citation): + source = dbase.get_source_from_handle( + citation.get_reference_handle()) + if RegExpIdBase.apply(self, dbase, source): + return True + return False diff --git a/gramps/gui/filters/sidebar/_citationsidebarfilter.py b/gramps/gui/filters/sidebar/_citationsidebarfilter.py index 2ef0be1ff..1905ec7af 100644 --- a/gramps/gui/filters/sidebar/_citationsidebarfilter.py +++ b/gramps/gui/filters/sidebar/_citationsidebarfilter.py @@ -41,7 +41,7 @@ from gi.repository import Gtk # GRAMPS modules # #------------------------------------------------------------------------- -from ...widgets import MonitoredMenu +from ...widgets import MonitoredMenu, DateEntry, BasicEntry from gramps.gen.lib import Citation from .. import build_filter_model from . import SidebarFilter @@ -49,7 +49,8 @@ from gramps.gen.constfunc import cuni from gramps.gen.filters import GenericFilterFactory, rules from gramps.gen.filters.rules.citation import (RegExpIdOf, HasIdOf, HasCitation, HasNoteMatchingSubstringOf, - HasNoteRegexp, MatchesFilter, HasTag) + HasNoteRegexp, MatchesFilter, HasTag, + HasSource, RegExpSourceIdOf) from gramps.gen.utils.string import confidence GenericCitationFilter = GenericFilterFactory('Citation') #------------------------------------------------------------------------- @@ -61,6 +62,12 @@ class CitationSidebarFilter(SidebarFilter): def __init__(self, dbstate, uistate, clicked): self.clicked_func = clicked + self.filter_src_id = BasicEntry() + self.filter_src_title = BasicEntry() + self.filter_src_author = BasicEntry() + self.filter_src_abbr = BasicEntry() + self.filter_src_pub = BasicEntry() + self.filter_src_note = BasicEntry() self.filter_id = Gtk.Entry() self.filter_page = Gtk.Entry() self.filter_date = Gtk.Entry() @@ -101,16 +108,28 @@ class CitationSidebarFilter(SidebarFilter): self.tag.pack_start(cell, True) self.tag.add_attribute(cell, 'text', 0) - self.add_text_entry(_('ID'), self.filter_id) - self.add_text_entry(_('Volume/Page'), self.filter_page) - self.add_text_entry(_('Date'), self.filter_date) - self.add_entry(_('Minimum Confidence|Min. Conf.'), self.filter_conf) - self.add_text_entry(_('Note'), self.filter_note) + self.add_text_entry(_('Source: ID'), self.filter_src_id) + self.add_text_entry(_('Source: Title'), self.filter_src_title) + self.add_text_entry(_('Source: Author'), self.filter_src_author) + self.add_text_entry(_('Source: Abbreviation'), self.filter_src_abbr) + self.add_text_entry(_('Source: Publication'), self.filter_src_pub) + self.add_text_entry(_('Source: Note'), self.filter_src_note) + self.add_text_entry(_('Citation: ID'), self.filter_id) + self.add_text_entry(_('Citation: Volume/Page'), self.filter_page) + self.add_text_entry(_('Citation: Date'), self.filter_date) + self.add_entry(_('Citation: Minimum Confidence|Min. Conf.'), self.filter_conf) + self.add_text_entry(_('Citation: Note'), self.filter_note) self.add_entry(_('Tag'), self.tag) self.add_filter_entry(_('Custom filter'), self.generic) self.add_entry(None, self.filter_regex) def clear(self, obj): + self.filter_src_id.set_text('') + self.filter_src_title.set_text('') + self.filter_src_author.set_text('') + self.filter_src_abbr.set_text('') + self.filter_src_pub.set_text('') + self.filter_src_note.set_text('') self.filter_id.set_text('') self.filter_page.set_text('') self.filter_date.set_text('') @@ -120,6 +139,15 @@ class CitationSidebarFilter(SidebarFilter): self.generic.set_active(0) def get_filter(self): + src_id = unicode(self.filter_src_id.get_text()).strip() + src_title = unicode(self.filter_src_title.get_text()).strip() + src_author = unicode(self.filter_src_author.get_text()).strip() + src_abbr = unicode(self.filter_src_abbr.get_text()).strip() + src_pub = unicode(self.filter_src_pub.get_text()).strip() + src_note = unicode(self.filter_src_note.get_text()).strip() + gid = unicode(self.filter_id.get_text()).strip() + page = unicode(self.filter_page.get_text()).strip() + date = unicode(self.filter_date.get_text()).strip() gid = cuni(self.filter_id.get_text()).strip() page = cuni(self.filter_page.get_text()).strip() date = cuni(self.filter_date.get_text()).strip() @@ -137,7 +165,9 @@ class CitationSidebarFilter(SidebarFilter): tag = self.tag.get_active() > 0 gen = self.generic.get_active() > 0 - empty = not (gid or page or date or conf or note or regex or tag or gen) + empty = not (src_id or src_title or src_author or src_abbr or src_pub or + src_note or + gid or page or date or conf or note or regex or gen) if empty: generic_filter = None else: @@ -152,6 +182,14 @@ class CitationSidebarFilter(SidebarFilter): rule = HasCitation([page, date, conf], use_regex=regex) generic_filter.add_rule(rule) + if src_id: + rule = RegExpSourceIdOf([src_id], use_regex=regex) + generic_filter.add_rule(rule) + + rule = HasSource([src_title, src_author, src_abbr, src_pub], + use_regex=regex) + generic_filter.add_rule(rule) + if note: if regex: rule = HasNoteRegexp([note]) diff --git a/gramps/gui/selectors/selectcitation.py b/gramps/gui/selectors/selectcitation.py index b80a98dfc..5f78cfa7d 100644 --- a/gramps/gui/selectors/selectcitation.py +++ b/gramps/gui/selectors/selectcitation.py @@ -64,7 +64,7 @@ class SelectCitation(BaseSelector): def get_column_titles(self): return [ - (_('Page'), 350, BaseSelector.TEXT, 0), + (_('Name'), 350, BaseSelector.TEXT, 0), (_('ID'), 75, BaseSelector.TEXT, 1) ] diff --git a/gramps/gui/views/treemodels/citationtreemodel.py b/gramps/gui/views/treemodels/citationtreemodel.py index 819fa7bc8..2a9aa04a0 100644 --- a/gramps/gui/views/treemodels/citationtreemodel.py +++ b/gramps/gui/views/treemodels/citationtreemodel.py @@ -192,7 +192,20 @@ class CitationTreeModel(CitationBaseModel, TreeBaseModel): data The object data. """ sort_key = self.sort_func2(data) + # If the source for this citation already exists (in the tree model) we + # add the citation as a child of the source. Otherwise we add the source + # first (because citations don't have any meaning without the associated + # source) if self._get_node(data[5]): + # parent child sortkey handle + self.add_node(data[5], handle, sort_key, handle, secondary=True) + else: + # add the source node first + source_sort_key = self.sort_func(self.map(data[5])) + # parent child sortkey handle + self.add_node(None, data[5], source_sort_key, data[5]) + + # parent child sortkey handle self.add_node(data[5], handle, sort_key, handle, secondary=True) def on_get_n_columns(self): diff --git a/gramps/gui/views/treemodels/treebasemodel.py b/gramps/gui/views/treemodels/treebasemodel.py index 343ef9807..6f1591abd 100644 --- a/gramps/gui/views/treemodels/treebasemodel.py +++ b/gramps/gui/views/treemodels/treebasemodel.py @@ -342,7 +342,10 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): self.__displayed = 0 self.set_search(search) - self.rebuild_data(self.current_filter, skip) + if self.has_secondary: + self.rebuild_data(self.current_filter, self.current_filter2, skip) + else: + self.rebuild_data(self.current_filter, skip=skip) _LOG.debug(self.__class__.__name__ + ' __init__ ' + str(time.clock() - cput) + ' sec') @@ -362,7 +365,9 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): self.rebuild_data = None self._build_data = None self.search = None + self.search2 = None self.current_filter = None + self.current_filter2 = None self.clear_cache() self.lru_data = None @@ -447,31 +452,56 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): if search[0] == 1: # Filter #following is None if no data given in filter sidebar self.search = search[1] + if self.has_secondary: + self.search2 = search[1] + _LOG.debug("search2 filter %s %s" % (search[0], search[1])) self._build_data = self._rebuild_filter elif search[0] == 0: # Search if search[1]: # we have search[1] = (index, text_unicode, inversion) col, text, inv = search[1] - func = lambda x: self._get_value(x, col, store_cache=False) or "" + func = lambda x: self._get_value(x, col, secondary=False) or u"" + if self.has_secondary: + func2 = lambda x: self._get_value(x, col, secondary=True) or u"" if search[2]: self.search = ExactSearchFilter(func, text, inv) + if self.has_secondary: + self.search2 = ExactSearchFilter(func2, text, inv) else: self.search = SearchFilter(func, text, inv) + if self.has_secondary: + self.search2 = SearchFilter(func2, text, inv) else: self.search = None + if self.has_secondary: + self.search2 = None + _LOG.debug("search2 search with no data") self._build_data = self._rebuild_search else: # Fast filter self.search = search[1] + if self.has_secondary: + self.search2 = search[2] + _LOG.debug("search2 fast filter") self._build_data = self._rebuild_search else: self.search = None + if self.has_secondary: + self.search2 = search[2] + _LOG.debug("search2 no search parameter") self._build_data = self._rebuild_search self.current_filter = self.search + if self.has_secondary: + self.current_filter2 = self.search2 - def rebuild_data(self, data_filter=None, skip=[]): + def rebuild_data(self, data_filter=None, data_filter2=None, skip=[]): """ Rebuild the data map. + + When called externally (from listview), data_filter and data_filter2 + should be None; set_search will already have been called to establish + the filter functions. When called internally (from __init__) both + data_filter and data_filter2 will have been set from set_search """ cput = time.clock() self.clear_cache() @@ -481,16 +511,21 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): return self.clear() - self._build_data(self.current_filter, skip) + if self.has_secondary: + self._build_data(self.current_filter, self.current_filter2, skip) + else: + self._build_data(self.current_filter, None, skip) self._in_build = False self.current_filter = data_filter + if self.has_secondary: + self.current_filter2 = data_filter2 _LOG.debug(self.__class__.__name__ + ' rebuild_data ' + str(time.clock() - cput) + ' sec') - def _rebuild_search(self, dfilter, skip): + def _rebuild_search(self, dfilter, dfilter2, skip): """ Rebuild the data map where a search condition is applied. """ @@ -498,12 +533,14 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): self.__displayed = 0 items = self.number_items() + _LOG.debug("rebuild search primary") self.__rebuild_search(dfilter, skip, items, self.gen_cursor, self.add_row) if self.has_secondary: + _LOG.debug("rebuild search secondary") items = self.number_items2() - self.__rebuild_search(dfilter, skip, items, + self.__rebuild_search(dfilter2, skip, items, self.gen_cursor2, self.add_row2) def __rebuild_search(self, dfilter, skip, items, gen_cursor, add_func): @@ -533,20 +570,27 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): if not status.was_cancelled(): status.end() - def _rebuild_filter(self, dfilter, skip): + def _rebuild_filter(self, dfilter, dfilter2, skip): """ Rebuild the data map where a filter is applied. """ self.__total = 0 self.__displayed = 0 - items = self.number_items() - self.__rebuild_filter(dfilter, skip, items, - self.gen_cursor, self.map, self.add_row) - if self.has_secondary: - items = self.number_items2() + if not self.has_secondary: + # The tree only has primary data + items = self.number_items() + _LOG.debug("rebuild filter primary") self.__rebuild_filter(dfilter, skip, items, - self.gen_cursor2, self.map2, self.add_row2) + self.gen_cursor, self.map, self.add_row) + else: + # The tree has both primary and secondary data. The navigation type + # (navtype) which governs the filters that are offered, is for the + # secondary data. + items = self.number_items2() + _LOG.debug("rebuild filter secondary") + self.__rebuild_filter(dfilter2, skip, items, + self.gen_cursor2, self.map2, self.add_row2) def __rebuild_filter(self, dfilter, skip, items, gen_cursor, data_map, add_func): @@ -578,6 +622,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): status.heartbeat() if dfilter: + _LOG.debug("rebuild filter %s" % dfilter) status_filter = progressdlg.LongOpStatus(msg=_("Applying filter"), total_steps=items, interval=items//10) pmon.add_op(status_filter) @@ -743,13 +788,14 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): if self._get_node(handle) is not None: return # row already exists cput = time.clock() - if not self.search or \ - (self.search and self.search.match(handle, self.db)): - #row needs to be added to the model - data = self.map(handle) - if data: + data = self.map(handle) + if data: + if not self.search or \ + (self.search and self.search.match(handle, self.db)): self.add_row(handle, data) - else: + else: + if not self.search2 or \ + (self.search2 and self.search2.match(handle, self.db)): self.add_row2(handle, self.map2(handle)) _LOG.debug(self.__class__.__name__ + ' add_row_by_handle ' + @@ -916,6 +962,9 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): """ Returns the contents of a given column of a gramps object """ + if secondary is None: + raise NotImplementedError + if handle in self.lru_data: data = self.lru_data[handle] else: @@ -923,16 +972,20 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel): data = self.map(handle) else: data = self.map2(handle) - if not self._in_build and store_cache: - self.lru_data[handle] = data - - try: - if not secondary: - return self.fmap[col](data) - else: - return self.fmap2[col](data) - except: - return '' + if not self._in_build: + self.lru_data[handle] = data + + if not secondary: + # None is used to indicate this column has no data + if self.fmap[col] is None: + return '' + value = self.fmap[col](data) + else: + if self.fmap2[col] is None: + return '' + value = self.fmap2[col](data) + + return value def do_get_iter(self, path): """ diff --git a/gramps/plugins/view/citationtreeview.py b/gramps/plugins/view/citationtreeview.py index 5c6afa207..484392573 100644 --- a/gramps/plugins/view/citationtreeview.py +++ b/gramps/plugins/view/citationtreeview.py @@ -175,17 +175,10 @@ class CitationTreeView(ListView): def setup_filter(self): """ Override the setup of the default Search Bar in listview, so that only - the searchable source fields are shown. This includes renaming the - 'Title or Page' search to 'Title' + the searchable source fields are shown. """ - def name(i): - if i == 0: - return _('Title') - else: - return self.COLUMNS[i][0] - self.search_bar.setup_filter( - [(name(pair[1]), pair[1], pair[1] in self.exact_search()) + [(self.COLUMNS[(pair[1])][0], pair[1], pair[1] in self.exact_search()) for pair in self.column_order() if pair[0] and pair[1] in self.COLUMN_FILTERABLE]) @@ -593,7 +586,7 @@ class CitationTreeView(ListView): """ Define the default gramplets for the sidebar and bottombar. """ - return (("Source Filter",), + return (("Citation Filter",), ("Citation Gallery", "Citation Notes", "Citation Backlinks"))