diff --git a/po/POTFILES.in b/po/POTFILES.in index 8ce6f0b6c..997e9692b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -184,6 +184,7 @@ src/gui/__init__.py src/gui/dbguielement.py src/gui/dbloader.py src/gui/dbman.py +src/gui/filtereditor.py src/gui/grampsgui.py src/gui/pluginmanager.py src/gui/utils.py @@ -826,12 +827,6 @@ src/Filters/SideBar/_MediaSidebarFilter.py src/Filters/SideBar/_RepoSidebarFilter.py src/Filters/SideBar/_NoteSidebarFilter.py -# FilterEditor package -src/FilterEditor/_FilterEditor.py -src/FilterEditor/_EditFilter.py -src/FilterEditor/_EditRule.py -src/FilterEditor/_ShowResults.py - # # Glade files # diff --git a/src/FilterEditor/Makefile.am b/src/FilterEditor/Makefile.am deleted file mode 100644 index d5bcb5304..000000000 --- a/src/FilterEditor/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# This is the src/FilterEditor level Makefile for Gramps - -pkgdatadir = $(datadir)/@PACKAGE@/FilterEditor - -pkgdata_PYTHON = \ - __init__.py \ - _FilterEditor.py \ - _EditFilter.py \ - _EditRule.py \ - _ShowResults.py - -pkgpyexecdir = @pkgpyexecdir@/FilterEditor -pkgpythondir = @pkgpythondir@/FilterEditor - -# Clean up all the byte-compiled files -MOSTLYCLEANFILES = *pyc *pyo - -GRAMPS_PY_MODPATH = "../" - -pycheck: - (export PYTHONPATH=$(GRAMPS_PY_MODPATH); \ - pychecker $(pkgdata_PYTHON)); diff --git a/src/FilterEditor/_EditFilter.py b/src/FilterEditor/_EditFilter.py deleted file mode 100644 index a608fbd1c..000000000 --- a/src/FilterEditor/_EditFilter.py +++ /dev/null @@ -1,218 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2000-2007 Donald N. Allingham -# -# 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:_EditFilter.py 9912 2008-01-22 09:17:46Z acraphae $ - -""" -Custom Filter Editor tool. -""" - -#------------------------------------------------------------------------- -# -# Python modules -# -#------------------------------------------------------------------------- - -#------------------------------------------------------------------------ -# -# Set up logging -# -#------------------------------------------------------------------------ -import logging -log = logging.getLogger(".FilterEdit") - -#------------------------------------------------------------------------- -# -# GRAMPS modules -# -#------------------------------------------------------------------------- -import const -import ListModel -import ManagedWindow -import GrampsDisplay -import Errors -from TransUtils import sgettext as _ - -#------------------------------------------------------------------------- -# -# Constants -# -#------------------------------------------------------------------------- -WIKI_HELP_PAGE = WIKI_HELP_PAGE = '%s_-_Filters' % const.URL_MANUAL_PAGE - -#------------------------------------------------------------------------- -# -# -# -#------------------------------------------------------------------------- -class EditFilter(ManagedWindow.ManagedWindow): - - def __init__(self, namespace, dbstate, uistate, track, gfilter, - filterdb, update): - - ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) - - self.namespace = namespace - self.update = update - self.dbstate = dbstate - self.db = dbstate.db - self.filter = gfilter - self.filterdb = filterdb - - self.define_glade('define_filter', const.RULE_GLADE) - - self.set_window( - self.get_widget('define_filter'), - self.get_widget('definition_title'), - _('Define filter')) - - self.rlist = ListModel.ListModel( - self.get_widget('rule_list'), - [(_('Name'),-1,150),(_('Values'),-1,150)], - self.select_row, - self.on_edit_clicked) - - self.fname = self.get_widget('filter_name') - self.logical = self.get_widget('rule_apply') - self.logical_not = self.get_widget('logical_not') - self.comment = self.get_widget('comment') - self.ok_btn = self.get_widget('definition_ok') - self.edit_btn = self.get_widget('definition_edit') - self.del_btn = self.get_widget('definition_delete') - self.add_btn = self.get_widget('definition_add') - - self.ok_btn.connect('clicked', self.on_ok_clicked) - self.edit_btn.connect('clicked', self.on_edit_clicked) - self.del_btn.connect('clicked', self.on_delete_clicked) - self.add_btn.connect('clicked', self.on_add_clicked) - - self.get_widget('definition_help').connect('clicked', - self.on_help_clicked) - self.get_widget('definition_cancel').connect('clicked', - self.close_window) - self.fname.connect('changed', self.filter_name_changed) - - if self.filter.get_logical_op() == 'or': - self.logical.set_active(1) - elif self.filter.get_logical_op() == 'one': - self.logical.set_active(2) - else: - self.logical.set_active(0) - self.logical_not.set_active(self.filter.get_invert()) - if self.filter.get_name(): - self.fname.set_text(self.filter.get_name()) - self.comment.set_text(self.filter.get_comment()) - self.draw_rules() - - self.show() - - def on_help_clicked(self, obj): - """Display the relevant portion of GRAMPS manual""" - GrampsDisplay.help(webpage=WIKI_HELP_PAGE) - - def close_window(self, obj): - self.close() - - def filter_name_changed(self, obj): - name = unicode(self.fname.get_text()) - # Make sure that the name is not empty - # and not in the list of existing filters (excluding this one) - names = [filt.get_name() - for filt in self.filterdb.get_filters(self.namespace) - if filt != self.filter] - self.ok_btn.set_sensitive((len(name) != 0) and (name not in names)) - - def select_row(self, obj): - store, node = self.rlist.get_selected() - if node: - self.edit_btn.set_sensitive(True) - self.del_btn.set_sensitive(True) - else: - self.edit_btn.set_sensitive(False) - self.del_btn.set_sensitive(False) - - def draw_rules(self): - self.rlist.clear() - for r in self.filter.get_rules(): - self.rlist.add([r.name,r.display_values()],r) - - def on_ok_clicked(self, obj): - n = unicode(self.fname.get_text()).strip() - if n == '': - return - if n != self.filter.get_name(): - self.uistate.emit('filter-name-changed', - (self.namespace,unicode(self.filter.get_name()), n)) - self.filter.set_name(n) - self.filter.set_comment(unicode(self.comment.get_text()).strip()) - for f in self.filterdb.get_filters(self.namespace)[:]: - if n == f.get_name(): - self.filterdb.get_filters(self.namespace).remove(f) - break - val = self.logical.get_active() - if val == 1: - op = 'or' - elif val == 2: - op = 'one' - else: - op = 'and' - self.filter.set_logical_op(op) - self.filter.set_invert(self.logical_not.get_active()) - self.filterdb.add(self.namespace,self.filter) - self.update() - self.close() - - def on_add_clicked(self, obj): - from _EditRule import EditRule - - try: - EditRule(self.namespace, self.dbstate, self.uistate, self.track, - self.filterdb, None, _('Add Rule'), self.update_rule, - self.filter.get_name()) - except Errors.WindowActiveError: - pass - - def on_edit_clicked(self, obj): - store, node = self.rlist.get_selected() - if node: - from _EditRule import EditRule - - d = self.rlist.get_object(node) - - try: - EditRule(self.namespace, self.dbstate, self.uistate, self.track, - self.filterdb, d, _('Edit Rule'), self.update_rule, - self.filter.get_name()) - except Errors.WindowActiveError: - pass - - def update_rule(self, old_rule, new_rule): - if old_rule is not None: - self.filter.delete_rule(old_rule) - self.filter.add_rule(new_rule) - self.draw_rules() - - def on_delete_clicked(self, obj): - store, node = self.rlist.get_selected() - if node: - gfilter = self.rlist.get_object(node) - self.filter.delete_rule(gfilter) - self.draw_rules() - diff --git a/src/FilterEditor/_FilterEditor.py b/src/FilterEditor/_FilterEditor.py deleted file mode 100644 index f34085115..000000000 --- a/src/FilterEditor/_FilterEditor.py +++ /dev/null @@ -1,276 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2000-2007 Donald N. Allingham -# -# 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:_FilterEditor.py 9912 2008-01-22 09:17:46Z acraphae $ - -""" -Custom Filter Editor tool. -""" - -#------------------------------------------------------------------------- -# -# Python modules -# -#------------------------------------------------------------------------- -from gettext import gettext as _ - -#------------------------------------------------------------------------ -# -# Set up logging -# -#------------------------------------------------------------------------ -import logging -log = logging.getLogger(".FilterEdit") - -#------------------------------------------------------------------------- -# -# GTK/GNOME -# -#------------------------------------------------------------------------- -import GrampsDisplay - -#------------------------------------------------------------------------- -# -# GRAMPS modules -# -#------------------------------------------------------------------------- -import const -from Filters import (GenericFilterFactory, FilterList, reload_custom_filters) -from Filters.Rules._MatchesFilterBase import MatchesFilterBase -import ListModel -import ManagedWindow -from QuestionDialog import QuestionDialog -from FilterEditor._EditFilter import EditFilter - -# dictionary mapping FILTER_TYPE of views to Filter window name -_TITLES = { - 'Person' : _("Person Filters"), - 'Family' : _('Family Filters'), - 'Event' : _('Event Filters'), - 'Place' : _('Place Filters'), - 'Source' : _('Source Filters'), - 'MediaObject' : _('Media Object Filters'), - 'Repository' : _('Repository Filters'), - 'Note' : _('Note Filters'), -} - -class FilterEditor(ManagedWindow.ManagedWindow): - def __init__(self, namespace, filterdb, dbstate, uistate): - - ManagedWindow.ManagedWindow.__init__(self, uistate, [], FilterEditor) - self.dbstate = dbstate - self.db = dbstate.db - self.filterdb = FilterList(filterdb) - self.filterdb.load() - self.namespace = namespace - - self.define_glade('filter_list', const.RULE_GLADE) - self.filter_list = self.get_widget('filters') - self.edit = self.get_widget('filter_list_edit') - self.clone = self.get_widget('filter_list_clone') - self.delete = self.get_widget('filter_list_delete') - self.test = self.get_widget('filter_list_test') - - self.edit.set_sensitive(False) - self.clone.set_sensitive(False) - self.delete.set_sensitive(False) - self.test.set_sensitive(False) - - self.set_window(self.get_widget('filter_list'), - self.get_widget('filter_list_title'), - _TITLES[self.namespace]) - - self.edit.connect('clicked', self.edit_filter) - self.clone.connect('clicked', self.clone_filter) - self.test.connect('clicked', self.test_clicked) - self.delete.connect('clicked', self.delete_filter) - - self.connect_button('filter_list_help', self.help_clicked) - self.connect_button('filter_list_close', self.close) - self.connect_button('filter_list_add', self.add_new_filter) - - self.uistate.connect('filter-name-changed', self.clean_after_rename) - - self.clist = ListModel.ListModel( - self.filter_list, - [(_('Filter'), 0, 150), (_('Comment'), 1, 150)], - self.filter_select_row, - self.edit_filter) - self.draw_filters() - self.show() - - def build_menu_names(self, obj): - return (_("Custom Filter Editor"), _("Custom Filter Editor")) - - def help_clicked(self, obj): - """Display the relevant portion of GRAMPS manual""" - GrampsDisplay.help() - - def filter_select_row(self, obj): - store, node = self.clist.get_selected() - if node: - self.edit.set_sensitive(True) - self.clone.set_sensitive(True) - self.delete.set_sensitive(True) - self.test.set_sensitive(True) - else: - self.edit.set_sensitive(False) - self.clone.set_sensitive(False) - self.delete.set_sensitive(False) - self.test.set_sensitive(False) - - def close(self, *obj): - self.filterdb.save() - reload_custom_filters() - #reload_system_filters() - self.uistate.emit('filters-changed', (self.namespace,)) - ManagedWindow.ManagedWindow.close(self, *obj) - - def draw_filters(self): - self.clist.clear() - for f in self.filterdb.get_filters(self.namespace): - self.clist.add([f.get_name(), f.get_comment()], f) - - def add_new_filter(self, obj): - the_filter = GenericFilterFactory(self.namespace)() - EditFilter(self.namespace, self.dbstate, self.uistate, self.track, - the_filter, self.filterdb, self.draw_filters) - - def edit_filter(self, obj): - store, node = self.clist.get_selected() - if node: - gfilter = self.clist.get_object(node) - EditFilter(self.namespace, self.dbstate, self.uistate, self.track, - gfilter, self.filterdb, self.draw_filters) - - def clone_filter(self, obj): - store, node = self.clist.get_selected() - if node: - old_filter = self.clist.get_object(node) - the_filter = GenericFilterFactory(self.namespace)(old_filter) - the_filter.set_name('') - EditFilter(self.namespace, self.dbstate, self.uistate, self.track, - the_filter, self.filterdb, self.draw_filters) - - def test_clicked(self, obj): - store, node = self.clist.get_selected() - if node: - from FilterEditor._ShowResults import ShowResults - - filt = self.clist.get_object(node) - handle_list = filt.apply(self.db, self.get_all_handles()) - ShowResults(self.db, self.uistate, self.track, handle_list, - filt.get_name(),self.namespace) - - def delete_filter(self, obj): - store, node = self.clist.get_selected() - if node: - gfilter = self.clist.get_object(node) - name = gfilter.get_name() - if self.check_recursive_filters(self.namespace, name): - QuestionDialog( _('Delete Filter?'), - _('This filter is currently being used ' - 'as the base for other filters. Deleting' - 'this filter will result in removing all ' - 'other filters that depend on it.'), - _('Delete Filter'), - self._do_delete_selected_filter, - self.window) - else: - self._do_delete_selected_filter() - - def _do_delete_selected_filter(self): - store, node = self.clist.get_selected() - if node: - gfilter = self.clist.get_object(node) - self._do_delete_filter(self.namespace, gfilter) - self.draw_filters() - - def _do_delete_filter(self, space, gfilter): - # Find everything we need to remove - filter_set = set() - self._find_dependent_filters(space, gfilter, filter_set) - - # Remove what we found - filters = self.filterdb.get_filters(space) - for the_filter in filter_set: - filters.remove(the_filter) - - def _find_dependent_filters(self, space, gfilter, filter_set): - """ - This method recursively calls itself to find all filters that - depend on the given filter, either directly through one of the rules, - or through the chain of dependencies. - - The filter_set is amended with the found filters. - """ - filters = self.filterdb.get_filters(space) - name = gfilter.get_name() - for the_filter in filters: - for rule in the_filter.get_rules(): - values = rule.values() - if issubclass(rule.__class__, MatchesFilterBase) \ - and (name in values): - self._find_dependent_filters(space, the_filter, filter_set) - break - # Add itself to the filter_set - filter_set.add(gfilter) - - def get_all_handles(self): - if self.namespace == 'Person': - return self.db.iter_person_handles() - elif self.namespace == 'Family': - return self.db.iter_family_handles() - elif self.namespace == 'Event': - return self.db.get_event_handles() - elif self.namespace == 'Source': - return self.db.get_source_handles() - elif self.namespace == 'Place': - return self.db.iter_place_handles() - elif self.namespace == 'MediaObject': - return self.db.get_media_object_handles() - elif self.namespace == 'Repository': - return self.db.get_repository_handles() - elif self.namespace == 'Note': - return self.db.get_note_handles() - - def clean_after_rename(self, space, old_name, new_name): - if old_name == "": - return - - if old_name == new_name: - return - - for the_filter in self.filterdb.get_filters(space): - for rule in the_filter.get_rules(): - values = rule.values() - if issubclass(rule.__class__, MatchesFilterBase) \ - and (old_name in values): - ind = values.index(old_name) - values[ind] = new_name - - def check_recursive_filters(self, space, name): - for the_filter in self.filterdb.get_filters(space): - for rule in the_filter.get_rules(): - values = rule.values() - if issubclass(rule.__class__, MatchesFilterBase) \ - and (name in values): - return True - return False diff --git a/src/FilterEditor/_ShowResults.py b/src/FilterEditor/_ShowResults.py deleted file mode 100644 index 357c18f12..000000000 --- a/src/FilterEditor/_ShowResults.py +++ /dev/null @@ -1,164 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2000-2008 Donald N. Allingham -# -# 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$ - -""" -Custom Filter Editor tool. -""" - -#------------------------------------------------------------------------- -# -# Python modules -# -#------------------------------------------------------------------------- -import locale -from gettext import gettext as _ - -#------------------------------------------------------------------------ -# -# Set up logging -# -#------------------------------------------------------------------------ -import logging -log = logging.getLogger(".FilterEdit") - -#------------------------------------------------------------------------- -# -# GTK/GNOME -# -#------------------------------------------------------------------------- -import gtk -import gobject - -#------------------------------------------------------------------------- -# -# GRAMPS modules -# -#------------------------------------------------------------------------- -import const -import ManagedWindow -from gen.display.name import displayer as _nd -import Utils - -#------------------------------------------------------------------------- -# -# -# -#------------------------------------------------------------------------- -class ShowResults(ManagedWindow.ManagedWindow): - def __init__(self, db, uistate, track, handle_list, filtname, namespace): - - ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) - - self.db = db - self.filtname = filtname - self.namespace = namespace - self.define_glade('test', const.RULE_GLADE,) - self.set_window( - self.get_widget('test'), - self.get_widget('test_title'), - _('Filter Test')) - - render = gtk.CellRendererText() - - tree = self.get_widget('list') - model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - tree.set_model(model) - - column_n = gtk.TreeViewColumn(_('Name'), render, text=0) - tree.append_column(column_n) - - column_n = gtk.TreeViewColumn(_('ID'), render, text=1) - tree.append_column(column_n) - - self.get_widget('test_close').connect('clicked', self.close) - - new_list = sorted( - (self.sort_val_from_handle(h) for h in handle_list), - key=lambda x: locale.strxfrm(x[0]) - ) - - for s_, handle in new_list: - name, gid = self.get_name_id(handle) - model.append(row=[name, gid]) - - self.show() - - def get_name_id(self, handle): - if self.namespace == 'Person': - person = self.db.get_person_from_handle(handle) - name = _nd.sorted(person) - gid = person.get_gramps_id() - elif self.namespace == 'Family': - family = self.db.get_family_from_handle(handle) - name = Utils.family_name(family, self.db) - gid = family.get_gramps_id() - elif self.namespace == 'Event': - event = self.db.get_event_from_handle(handle) - name = event.get_description() - gid = event.get_gramps_id() - elif self.namespace == 'Source': - source = self.db.get_source_from_handle(handle) - name = source.get_title() - gid = source.get_gramps_id() - elif self.namespace == 'Place': - place = self.db.get_place_from_handle(handle) - name = place.get_title() - gid = place.get_gramps_id() - elif self.namespace == 'MediaObject': - obj = self.db.get_object_from_handle(handle) - name = obj.get_description() - gid = obj.get_gramps_id() - elif self.namespace == 'Repository': - repo = self.db.get_repository_from_handle(handle) - name = repo.get_name() - gid = repo.get_gramps_id() - elif self.namespace == 'Note': - note = self.db.get_note_from_handle(handle) - name = note.get().replace('\n', ' ') - #String must be unicode for truncation to work for non ascii characters - name = unicode(name) - if len(name) > 80: - name = name[:80]+"..." - gid = note.get_gramps_id() - return (name, gid) - - def sort_val_from_handle(self, handle): - if self.namespace == 'Person': - name = self.db.get_person_from_handle(handle).get_primary_name() - sortname = _nd.sort_string(name) - elif self.namespace == 'Family': - sortname = Utils.family_name( - self.db.get_family_from_handle(handle),self.db) - elif self.namespace == 'Event': - sortname = self.db.get_event_from_handle(handle).get_description() - elif self.namespace == 'Source': - sortname = self.db.get_source_from_handle(handle).get_title() - elif self.namespace == 'Place': - sortname = self.db.get_place_from_handle(handle).get_title() - elif self.namespace == 'MediaObject': - sortname = self.db.get_object_from_handle(handle).get_description() - elif self.namespace == 'Repository': - sortname = self.db.get_repository_from_handle(handle).get_name() - elif self.namespace == 'Note': - gid = self.db.get_note_from_handle(handle).get_gramps_id() - sortname = gid - return (sortname, handle) diff --git a/src/FilterEditor/__init__.py b/src/FilterEditor/__init__.py deleted file mode 100644 index 3d25bf480..000000000 --- a/src/FilterEditor/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2000-2006 Donald N. Allingham -# -# 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$ - -from _FilterEditor import FilterEditor diff --git a/src/Makefile.am b/src/Makefile.am index e9e888035..fc98d1028 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,13 +2,11 @@ # $Id$ SUBDIRS = \ - BasicUtils \ cli \ data \ DateHandler \ docgen \ Filters \ - FilterEditor \ gen \ glade \ GrampsLocale \ diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index c32b8deae..cc636deb3 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -16,6 +16,7 @@ pkgdata_PYTHON = \ dbguielement.py \ dbloader.py \ dbman.py \ + filtereditor.py \ grampsgui.py \ pluginmanager.py \ utils.py \ diff --git a/src/FilterEditor/_EditRule.py b/src/gui/filtereditor.py similarity index 53% rename from src/FilterEditor/_EditRule.py rename to src/gui/filtereditor.py index 2373c1737..ca2e60e22 100644 --- a/src/FilterEditor/_EditRule.py +++ b/src/gui/filtereditor.py @@ -2,8 +2,6 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham -# Copyright (C) 2007-2008 Brian G. Matherly -# Copyright (C) 2008 Benny Malengier # # 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 @@ -20,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# $Id$ +# $Id:_FilterEditor.py 9912 2008-01-22 09:17:46Z acraphae $ """ Custom Filter Editor tool. @@ -31,7 +29,7 @@ Custom Filter Editor tool. # Python modules # #------------------------------------------------------------------------- -from gettext import gettext as _ +import locale #------------------------------------------------------------------------ # @@ -39,7 +37,7 @@ from gettext import gettext as _ # #------------------------------------------------------------------------ import logging -log = logging.getLogger(".FilterEdit") +log = logging.getLogger(".filtereditor") #------------------------------------------------------------------------- # @@ -54,29 +52,41 @@ import gobject # GRAMPS modules # #------------------------------------------------------------------------- -import GrampsDisplay +from Filters import (GenericFilterFactory, FilterList, reload_custom_filters) +from Filters.Rules._MatchesFilterBase import MatchesFilterBase +import ListModel +import ManagedWindow +from QuestionDialog import QuestionDialog import const +import GrampsDisplay +import Errors +from TransUtils import sgettext as _ import gen.lib from Filters import Rules import AutoComp from gui.selectors import SelectorFactory from gen.display.name import displayer as _nd import Utils -import ManagedWindow - -#------------------------------------------------------------------------- -# -# Sorting function for the filter rules -# -#------------------------------------------------------------------------- -def by_rule_name(f, s): - return cmp(f.name, s.name) #------------------------------------------------------------------------- # # Constants # #------------------------------------------------------------------------- +WIKI_HELP_PAGE = WIKI_HELP_PAGE = '%s_-_Filters' % const.URL_MANUAL_PAGE + +# dictionary mapping FILTER_TYPE of views to Filter window name +_TITLES = { + 'Person' : _("Person Filters"), + 'Family' : _('Family Filters'), + 'Event' : _('Event Filters'), + 'Place' : _('Place Filters'), + 'Source' : _('Source Filters'), + 'MediaObject' : _('Media Object Filters'), + 'Repository' : _('Repository Filters'), + 'Note' : _('Note Filters'), +} + _name2typeclass = { _('Personal event:') : gen.lib.EventType, _('Family event:') : gen.lib.EventType, @@ -90,6 +100,14 @@ _name2typeclass = { _('Note type:') : gen.lib.NoteType, } +#------------------------------------------------------------------------- +# +# Sorting function for the filter rules +# +#------------------------------------------------------------------------- +def by_rule_name(f, s): + return cmp(f.name, s.name) + #------------------------------------------------------------------------- # # MyBoolean - check button with standard interface @@ -336,7 +354,7 @@ class MySource(MyID): #------------------------------------------------------------------------- # -# +# MySelect # #------------------------------------------------------------------------- class MySelect(gtk.ComboBoxEntry): @@ -359,7 +377,7 @@ class MySelect(gtk.ComboBoxEntry): #------------------------------------------------------------------------- # -# +# MyEntry # #------------------------------------------------------------------------- class MyEntry(gtk.Entry): @@ -370,7 +388,7 @@ class MyEntry(gtk.Entry): #------------------------------------------------------------------------- # -# +# EditRule # #------------------------------------------------------------------------- class EditRule(ManagedWindow.ManagedWindow): @@ -635,3 +653,470 @@ class EditRule(ManagedWindow.ManagedWindow): self.close() except KeyError: pass + +#------------------------------------------------------------------------- +# +# EditFilter +# +#------------------------------------------------------------------------- +class EditFilter(ManagedWindow.ManagedWindow): + + def __init__(self, namespace, dbstate, uistate, track, gfilter, + filterdb, update): + + ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) + + self.namespace = namespace + self.update = update + self.dbstate = dbstate + self.db = dbstate.db + self.filter = gfilter + self.filterdb = filterdb + + self.define_glade('define_filter', const.RULE_GLADE) + + self.set_window( + self.get_widget('define_filter'), + self.get_widget('definition_title'), + _('Define filter')) + + self.rlist = ListModel.ListModel( + self.get_widget('rule_list'), + [(_('Name'),-1,150),(_('Values'),-1,150)], + self.select_row, + self.on_edit_clicked) + + self.fname = self.get_widget('filter_name') + self.logical = self.get_widget('rule_apply') + self.logical_not = self.get_widget('logical_not') + self.comment = self.get_widget('comment') + self.ok_btn = self.get_widget('definition_ok') + self.edit_btn = self.get_widget('definition_edit') + self.del_btn = self.get_widget('definition_delete') + self.add_btn = self.get_widget('definition_add') + + self.ok_btn.connect('clicked', self.on_ok_clicked) + self.edit_btn.connect('clicked', self.on_edit_clicked) + self.del_btn.connect('clicked', self.on_delete_clicked) + self.add_btn.connect('clicked', self.on_add_clicked) + + self.get_widget('definition_help').connect('clicked', + self.on_help_clicked) + self.get_widget('definition_cancel').connect('clicked', + self.close_window) + self.fname.connect('changed', self.filter_name_changed) + + if self.filter.get_logical_op() == 'or': + self.logical.set_active(1) + elif self.filter.get_logical_op() == 'one': + self.logical.set_active(2) + else: + self.logical.set_active(0) + self.logical_not.set_active(self.filter.get_invert()) + if self.filter.get_name(): + self.fname.set_text(self.filter.get_name()) + self.comment.set_text(self.filter.get_comment()) + self.draw_rules() + + self.show() + + def on_help_clicked(self, obj): + """Display the relevant portion of GRAMPS manual""" + GrampsDisplay.help(webpage=WIKI_HELP_PAGE) + + def close_window(self, obj): + self.close() + + def filter_name_changed(self, obj): + name = unicode(self.fname.get_text()) + # Make sure that the name is not empty + # and not in the list of existing filters (excluding this one) + names = [filt.get_name() + for filt in self.filterdb.get_filters(self.namespace) + if filt != self.filter] + self.ok_btn.set_sensitive((len(name) != 0) and (name not in names)) + + def select_row(self, obj): + store, node = self.rlist.get_selected() + if node: + self.edit_btn.set_sensitive(True) + self.del_btn.set_sensitive(True) + else: + self.edit_btn.set_sensitive(False) + self.del_btn.set_sensitive(False) + + def draw_rules(self): + self.rlist.clear() + for r in self.filter.get_rules(): + self.rlist.add([r.name,r.display_values()],r) + + def on_ok_clicked(self, obj): + n = unicode(self.fname.get_text()).strip() + if n == '': + return + if n != self.filter.get_name(): + self.uistate.emit('filter-name-changed', + (self.namespace,unicode(self.filter.get_name()), n)) + self.filter.set_name(n) + self.filter.set_comment(unicode(self.comment.get_text()).strip()) + for f in self.filterdb.get_filters(self.namespace)[:]: + if n == f.get_name(): + self.filterdb.get_filters(self.namespace).remove(f) + break + val = self.logical.get_active() + if val == 1: + op = 'or' + elif val == 2: + op = 'one' + else: + op = 'and' + self.filter.set_logical_op(op) + self.filter.set_invert(self.logical_not.get_active()) + self.filterdb.add(self.namespace,self.filter) + self.update() + self.close() + + def on_add_clicked(self, obj): + try: + EditRule(self.namespace, self.dbstate, self.uistate, self.track, + self.filterdb, None, _('Add Rule'), self.update_rule, + self.filter.get_name()) + except Errors.WindowActiveError: + pass + + def on_edit_clicked(self, obj): + store, node = self.rlist.get_selected() + if node: + d = self.rlist.get_object(node) + + try: + EditRule(self.namespace, self.dbstate, self.uistate, self.track, + self.filterdb, d, _('Edit Rule'), self.update_rule, + self.filter.get_name()) + except Errors.WindowActiveError: + pass + + def update_rule(self, old_rule, new_rule): + if old_rule is not None: + self.filter.delete_rule(old_rule) + self.filter.add_rule(new_rule) + self.draw_rules() + + def on_delete_clicked(self, obj): + store, node = self.rlist.get_selected() + if node: + gfilter = self.rlist.get_object(node) + self.filter.delete_rule(gfilter) + self.draw_rules() + +#------------------------------------------------------------------------- +# +# ShowResults +# +#------------------------------------------------------------------------- +class ShowResults(ManagedWindow.ManagedWindow): + def __init__(self, db, uistate, track, handle_list, filtname, namespace): + + ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) + + self.db = db + self.filtname = filtname + self.namespace = namespace + self.define_glade('test', const.RULE_GLADE,) + self.set_window( + self.get_widget('test'), + self.get_widget('test_title'), + _('Filter Test')) + + render = gtk.CellRendererText() + + tree = self.get_widget('list') + model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + tree.set_model(model) + + column_n = gtk.TreeViewColumn(_('Name'), render, text=0) + tree.append_column(column_n) + + column_n = gtk.TreeViewColumn(_('ID'), render, text=1) + tree.append_column(column_n) + + self.get_widget('test_close').connect('clicked', self.close) + + new_list = sorted( + (self.sort_val_from_handle(h) for h in handle_list), + key=lambda x: locale.strxfrm(x[0]) + ) + + for s_, handle in new_list: + name, gid = self.get_name_id(handle) + model.append(row=[name, gid]) + + self.show() + + def get_name_id(self, handle): + if self.namespace == 'Person': + person = self.db.get_person_from_handle(handle) + name = _nd.sorted(person) + gid = person.get_gramps_id() + elif self.namespace == 'Family': + family = self.db.get_family_from_handle(handle) + name = Utils.family_name(family, self.db) + gid = family.get_gramps_id() + elif self.namespace == 'Event': + event = self.db.get_event_from_handle(handle) + name = event.get_description() + gid = event.get_gramps_id() + elif self.namespace == 'Source': + source = self.db.get_source_from_handle(handle) + name = source.get_title() + gid = source.get_gramps_id() + elif self.namespace == 'Place': + place = self.db.get_place_from_handle(handle) + name = place.get_title() + gid = place.get_gramps_id() + elif self.namespace == 'MediaObject': + obj = self.db.get_object_from_handle(handle) + name = obj.get_description() + gid = obj.get_gramps_id() + elif self.namespace == 'Repository': + repo = self.db.get_repository_from_handle(handle) + name = repo.get_name() + gid = repo.get_gramps_id() + elif self.namespace == 'Note': + note = self.db.get_note_from_handle(handle) + name = note.get().replace('\n', ' ') + #String must be unicode for truncation to work for non ascii characters + name = unicode(name) + if len(name) > 80: + name = name[:80]+"..." + gid = note.get_gramps_id() + return (name, gid) + + def sort_val_from_handle(self, handle): + if self.namespace == 'Person': + name = self.db.get_person_from_handle(handle).get_primary_name() + sortname = _nd.sort_string(name) + elif self.namespace == 'Family': + sortname = Utils.family_name( + self.db.get_family_from_handle(handle),self.db) + elif self.namespace == 'Event': + sortname = self.db.get_event_from_handle(handle).get_description() + elif self.namespace == 'Source': + sortname = self.db.get_source_from_handle(handle).get_title() + elif self.namespace == 'Place': + sortname = self.db.get_place_from_handle(handle).get_title() + elif self.namespace == 'MediaObject': + sortname = self.db.get_object_from_handle(handle).get_description() + elif self.namespace == 'Repository': + sortname = self.db.get_repository_from_handle(handle).get_name() + elif self.namespace == 'Note': + gid = self.db.get_note_from_handle(handle).get_gramps_id() + sortname = gid + return (sortname, handle) + +#------------------------------------------------------------------------- +# +# FilterEditor +# +#------------------------------------------------------------------------- +class FilterEditor(ManagedWindow.ManagedWindow): + def __init__(self, namespace, filterdb, dbstate, uistate): + + ManagedWindow.ManagedWindow.__init__(self, uistate, [], FilterEditor) + self.dbstate = dbstate + self.db = dbstate.db + self.filterdb = FilterList(filterdb) + self.filterdb.load() + self.namespace = namespace + + self.define_glade('filter_list', const.RULE_GLADE) + self.filter_list = self.get_widget('filters') + self.edit = self.get_widget('filter_list_edit') + self.clone = self.get_widget('filter_list_clone') + self.delete = self.get_widget('filter_list_delete') + self.test = self.get_widget('filter_list_test') + + self.edit.set_sensitive(False) + self.clone.set_sensitive(False) + self.delete.set_sensitive(False) + self.test.set_sensitive(False) + + self.set_window(self.get_widget('filter_list'), + self.get_widget('filter_list_title'), + _TITLES[self.namespace]) + + self.edit.connect('clicked', self.edit_filter) + self.clone.connect('clicked', self.clone_filter) + self.test.connect('clicked', self.test_clicked) + self.delete.connect('clicked', self.delete_filter) + + self.connect_button('filter_list_help', self.help_clicked) + self.connect_button('filter_list_close', self.close) + self.connect_button('filter_list_add', self.add_new_filter) + + self.uistate.connect('filter-name-changed', self.clean_after_rename) + + self.clist = ListModel.ListModel( + self.filter_list, + [(_('Filter'), 0, 150), (_('Comment'), 1, 150)], + self.filter_select_row, + self.edit_filter) + self.draw_filters() + self.show() + + def build_menu_names(self, obj): + return (_("Custom Filter Editor"), _("Custom Filter Editor")) + + def help_clicked(self, obj): + """Display the relevant portion of GRAMPS manual""" + GrampsDisplay.help() + + def filter_select_row(self, obj): + store, node = self.clist.get_selected() + if node: + self.edit.set_sensitive(True) + self.clone.set_sensitive(True) + self.delete.set_sensitive(True) + self.test.set_sensitive(True) + else: + self.edit.set_sensitive(False) + self.clone.set_sensitive(False) + self.delete.set_sensitive(False) + self.test.set_sensitive(False) + + def close(self, *obj): + self.filterdb.save() + reload_custom_filters() + #reload_system_filters() + self.uistate.emit('filters-changed', (self.namespace,)) + ManagedWindow.ManagedWindow.close(self, *obj) + + def draw_filters(self): + self.clist.clear() + for f in self.filterdb.get_filters(self.namespace): + self.clist.add([f.get_name(), f.get_comment()], f) + + def add_new_filter(self, obj): + the_filter = GenericFilterFactory(self.namespace)() + EditFilter(self.namespace, self.dbstate, self.uistate, self.track, + the_filter, self.filterdb, self.draw_filters) + + def edit_filter(self, obj): + store, node = self.clist.get_selected() + if node: + gfilter = self.clist.get_object(node) + EditFilter(self.namespace, self.dbstate, self.uistate, self.track, + gfilter, self.filterdb, self.draw_filters) + + def clone_filter(self, obj): + store, node = self.clist.get_selected() + if node: + old_filter = self.clist.get_object(node) + the_filter = GenericFilterFactory(self.namespace)(old_filter) + the_filter.set_name('') + EditFilter(self.namespace, self.dbstate, self.uistate, self.track, + the_filter, self.filterdb, self.draw_filters) + + def test_clicked(self, obj): + store, node = self.clist.get_selected() + if node: + filt = self.clist.get_object(node) + handle_list = filt.apply(self.db, self.get_all_handles()) + ShowResults(self.db, self.uistate, self.track, handle_list, + filt.get_name(),self.namespace) + + def delete_filter(self, obj): + store, node = self.clist.get_selected() + if node: + gfilter = self.clist.get_object(node) + name = gfilter.get_name() + if self.check_recursive_filters(self.namespace, name): + QuestionDialog( _('Delete Filter?'), + _('This filter is currently being used ' + 'as the base for other filters. Deleting' + 'this filter will result in removing all ' + 'other filters that depend on it.'), + _('Delete Filter'), + self._do_delete_selected_filter, + self.window) + else: + self._do_delete_selected_filter() + + def _do_delete_selected_filter(self): + store, node = self.clist.get_selected() + if node: + gfilter = self.clist.get_object(node) + self._do_delete_filter(self.namespace, gfilter) + self.draw_filters() + + def _do_delete_filter(self, space, gfilter): + # Find everything we need to remove + filter_set = set() + self._find_dependent_filters(space, gfilter, filter_set) + + # Remove what we found + filters = self.filterdb.get_filters(space) + for the_filter in filter_set: + filters.remove(the_filter) + + def _find_dependent_filters(self, space, gfilter, filter_set): + """ + This method recursively calls itself to find all filters that + depend on the given filter, either directly through one of the rules, + or through the chain of dependencies. + + The filter_set is amended with the found filters. + """ + filters = self.filterdb.get_filters(space) + name = gfilter.get_name() + for the_filter in filters: + for rule in the_filter.get_rules(): + values = rule.values() + if issubclass(rule.__class__, MatchesFilterBase) \ + and (name in values): + self._find_dependent_filters(space, the_filter, filter_set) + break + # Add itself to the filter_set + filter_set.add(gfilter) + + def get_all_handles(self): + if self.namespace == 'Person': + return self.db.iter_person_handles() + elif self.namespace == 'Family': + return self.db.iter_family_handles() + elif self.namespace == 'Event': + return self.db.get_event_handles() + elif self.namespace == 'Source': + return self.db.get_source_handles() + elif self.namespace == 'Place': + return self.db.iter_place_handles() + elif self.namespace == 'MediaObject': + return self.db.get_media_object_handles() + elif self.namespace == 'Repository': + return self.db.get_repository_handles() + elif self.namespace == 'Note': + return self.db.get_note_handles() + + def clean_after_rename(self, space, old_name, new_name): + if old_name == "": + return + + if old_name == new_name: + return + + for the_filter in self.filterdb.get_filters(space): + for rule in the_filter.get_rules(): + values = rule.values() + if issubclass(rule.__class__, MatchesFilterBase) \ + and (old_name in values): + ind = values.index(old_name) + values[ind] = new_name + + def check_recursive_filters(self, space, name): + for the_filter in self.filterdb.get_filters(space): + for rule in the_filter.get_rules(): + values = rule.values() + if issubclass(rule.__class__, MatchesFilterBase) \ + and (name in values): + return True + return False diff --git a/src/gui/views/listview.py b/src/gui/views/listview.py index 986c35212..a3c3cbdb4 100644 --- a/src/gui/views/listview.py +++ b/src/gui/views/listview.py @@ -59,6 +59,7 @@ from gui.utils import add_menuitem import const import Utils from QuestionDialog import QuestionDialog, QuestionDialog2 +from gui.filtereditor import FilterEditor from TransUtils import sgettext as _ #---------------------------------------------------------------- @@ -354,8 +355,6 @@ class ListView(NavigationView): self.build_tree() def filter_editor(self, obj): - from FilterEditor import FilterEditor - try: FilterEditor(self.FILTER_TYPE , const.CUSTOM_FILTERS, self.dbstate, self.uistate) diff --git a/src/plugins/tool/EventCmp.py b/src/plugins/tool/EventCmp.py index 971c6e764..5f4111248 100644 --- a/src/plugins/tool/EventCmp.py +++ b/src/plugins/tool/EventCmp.py @@ -58,7 +58,7 @@ import GrampsDisplay import ManagedWindow from TransUtils import sgettext as _ from glade import Glade - +from gui.filtereditor import FilterEditor #------------------------------------------------------------------------- # @@ -163,9 +163,8 @@ class EventComparison(Tool.Tool,ManagedWindow.ManagedWindow): return (_("Filter selection"),_("Event Comparison tool")) def filter_editor_clicked(self, obj): - import FilterEditor try: - FilterEditor.FilterEditor('Person',const.CUSTOM_FILTERS, + FilterEditor('Person',const.CUSTOM_FILTERS, self.dbstate,self.uistate) except Errors.WindowActiveError: pass diff --git a/src/plugins/view/pedigreeview.py b/src/plugins/view/pedigreeview.py index 04b48417b..68bc81489 100644 --- a/src/plugins/view/pedigreeview.py +++ b/src/plugins/view/pedigreeview.py @@ -65,6 +65,7 @@ import config from QuestionDialog import RunDatabaseRepair, ErrorDialog import Bookmarks import const +from gui.filtereditor import FilterEditor #------------------------------------------------------------------------- # @@ -505,8 +506,6 @@ class PedigreeView(NavigationView): callback=self.filter_editor) def filter_editor(self, obj): - from FilterEditor import FilterEditor - try: FilterEditor('Person', const.CUSTOM_FILTERS, self.dbstate, self.uistate) diff --git a/src/plugins/view/pedigreeviewext.py b/src/plugins/view/pedigreeviewext.py index 04756d142..ffc749278 100644 --- a/src/plugins/view/pedigreeviewext.py +++ b/src/plugins/view/pedigreeviewext.py @@ -53,6 +53,7 @@ except: #------------------------------------------------------------------------- import gen.lib from gui.views.navigationview import NavigationView +from gui.filtereditor import FilterEditor from gen.display.name import displayer as name_displayer from Utils import (media_path_full, probably_alive, find_children, find_parents, find_witnessed_people) @@ -632,8 +633,6 @@ class PedigreeViewExt(NavigationView): callback=self.filter_editor) def filter_editor(self, obj): - from FilterEditor import FilterEditor - try: FilterEditor('Person', const.CUSTOM_FILTERS, self.dbstate, self.uistate) diff --git a/src/plugins/view/relview.py b/src/plugins/view/relview.py index c01f344de..96f608174 100644 --- a/src/plugins/view/relview.py +++ b/src/plugins/view/relview.py @@ -49,6 +49,7 @@ import pango import gen.lib from gui.views.navigationview import NavigationView from gui.editors import EditPerson, EditFamily +from gui.filtereditor import FilterEditor from gen.display.name import displayer as name_displayer from Utils import media_path_full, probably_alive import DateHandler @@ -384,8 +385,6 @@ class RelationshipView(NavigationView): self.family_action.set_sensitive(False) def filter_editor(self, obj): - from FilterEditor import FilterEditor - try: FilterEditor('Person', const.CUSTOM_FILTERS, self.dbstate, self.uistate)