diff --git a/ChangeLog b/ChangeLog index 3a41d8cbc..22591b00c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,12 @@ * src/plugins/NarrativeWeb.py: error reporting 2007-01-30 Alex Roitman + * src/plugins/RemoveUnused.py: Rewrite to offer object list + proposed for removal. + * src/DisplayTabs/_BackRefList.py (edit_button_clicked): Change + argument order in EditEvent call. + * src/DataViews/_EventView.py: Change argument order in EditEvent call. + * src/Editors/_EditEvent.py: Change argument order. * configure.in: Bump up the version. 2007-01-30 Martin Hawlisch diff --git a/src/DataViews/_EventView.py b/src/DataViews/_EventView.py index 4750b770f..18c381159 100644 --- a/src/DataViews/_EventView.py +++ b/src/DataViews/_EventView.py @@ -1,6 +1,6 @@ # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2001-2006 Donald N. Allingham +# Copyright (C) 2001-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 @@ -190,13 +190,13 @@ class EventView(PageView.ListView): handle = self.first_selected() the_event = self.dbstate.db.get_event_from_handle(handle) try: - EditEvent(the_event, self.dbstate, self.uistate, []) + EditEvent(self.dbstate, self.uistate, [], the_event) except Errors.WindowActiveError: pass def add(self, obj): try: - EditEvent(RelLib.Event(), self.dbstate, self.uistate, []) + EditEvent(self.dbstate, self.uistate, [], RelLib.Event()) except Errors.WindowActiveError: pass @@ -240,6 +240,6 @@ class EventView(PageView.ListView): for handle in mlist: event = self.dbstate.db.get_event_from_handle(handle) try: - EditEvent(event, self.dbstate, self.uistate) + EditEvent(self.dbstate, self.uistate, [], event) except Errors.WindowActiveError: pass diff --git a/src/DisplayTabs/_BackRefList.py b/src/DisplayTabs/_BackRefList.py index af216ad14..f4cb46f87 100644 --- a/src/DisplayTabs/_BackRefList.py +++ b/src/DisplayTabs/_BackRefList.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2006 Donald N. Allingham +# 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 @@ -156,7 +156,7 @@ class BackRefList(EmbeddedList): try: from Editors import EditEvent - obj = self.dbstate.db.get_event_from_handle(ref) - EditEvent(obj, self.dbstate, self.uistate, []) + event = self.dbstate.db.get_event_from_handle(ref) + EditEvent(self.dbstate, self.uistate, [], event) except Errors.WindowActiveError: pass diff --git a/src/Editors/_EditEvent.py b/src/Editors/_EditEvent.py index bd86d0fa0..53299ab0e 100644 --- a/src/Editors/_EditEvent.py +++ b/src/Editors/_EditEvent.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2006 Donald N. Allingham +# 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 @@ -62,7 +62,7 @@ from GrampsWidgets import * #------------------------------------------------------------------------- class EditEvent(EditPrimary): - def __init__(self,event,dbstate,uistate,track=[],callback=None): + def __init__(self,dbstate,uistate,track,event,callback=None): EditPrimary.__init__(self, dbstate, uistate, track, event, dbstate.db.get_event_from_handle) @@ -268,9 +268,8 @@ class EditEvent(EditPrimary): class EditPersonEvent(EditEvent): - def __init__(self, event, dbstate, uistate, track=[], callback=None): - EditEvent.__init__(self, event, dbstate, uistate, track, - callback) + def __init__(self, dbstate, uistate, track, event, callback=None): + EditEvent.__init__(self, dbstate, uistate, track, event, callback) def _init_event(self): self.commit_event = self.db.commit_personal_event @@ -280,9 +279,8 @@ class EditPersonEvent(EditEvent): class EditFamilyEvent(EditEvent): - def __init__(self, event, dbstate, uistate, track=[], callback=None): - EditEvent.__init__(self, event, dbstate, uistate, track, - callback) + def __init__(self, dbstate, uistate, track, event, callback=None): + EditEvent.__init__(self, dbstate, uistate, track, event, callback) def _init_event(self): self.commit_event = self.db.commit_family_event diff --git a/src/plugins/RemoveUnused.py b/src/plugins/RemoveUnused.py index 16a45c4a0..680b09ed9 100644 --- a/src/plugins/RemoveUnused.py +++ b/src/plugins/RemoveUnused.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2006 Donald N. Allingham +# 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 @@ -20,7 +20,7 @@ # $Id: Check.py 7321 2006-09-13 02:57:45Z dallingham $ -"Database Processing/Check and repair database" +"Find unused objects and remove with the user's permission" #------------------------------------------------------------------------- # @@ -28,8 +28,6 @@ # #------------------------------------------------------------------------- import os -import cStringIO -import sets from gettext import gettext as _ #------------------------------------------------------------------------ @@ -53,191 +51,354 @@ import gtk.glade # GRAMPS modules # #------------------------------------------------------------------------- -import RelLib -import Utils -import const +import Errors import ManagedWindow - +from BasicUtils import UpdateCallback from PluginUtils import Tool, register_tool -from QuestionDialog import OkDialog, MissingMediaDialog #------------------------------------------------------------------------- # # runTool # #------------------------------------------------------------------------- -class RemoveUnused: +class RemoveUnused(Tool.Tool,ManagedWindow.ManagedWindow,UpdateCallback): def __init__(self, dbstate, uistate, options_class, name, callback=None): + self.label = _('Remove Unused Objects tool') + + Tool.Tool.__init__(self, dbstate, options_class, name) + ManagedWindow.ManagedWindow.__init__(self, uistate,[],self.__class__) + UpdateCallback.__init__(self,self.uistate.pulse_progressbar) - self.db = dbstate.db self.dbstate = dbstate self.uistate = uistate if self.db.readonly: return + self.init_gui() def init_gui(self): - a = gtk.Dialog("%s - GRAMPS" % _('Remove unused objects'), - flags=gtk.DIALOG_MODAL, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, - gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + window = gtk.Dialog("%s - GRAMPS" % self.label, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) - a.set_size_request(400, 200) - a.set_border_width(12) - a.set_has_separator(False) + window.set_border_width(12) + window.set_has_separator(False) - self.event = gtk.CheckButton(_('Remove unused events')) - self.source = gtk.CheckButton(_('Remove unused sources')) - self.place = gtk.CheckButton(_('Remove unused places')) + self.events_box = gtk.CheckButton(_('Remove unused events')) + self.sources_box = gtk.CheckButton(_('Remove unused sources')) + self.places_box = gtk.CheckButton(_('Remove unused places')) + self.media_box = gtk.CheckButton(_('Remove unused media')) + self.repos_box = gtk.CheckButton(_('Remove unused repositories')) - self.event.set_active(True) - self.source.set_active(True) - self.place.set_active(True) + self.events_box.set_active(self.options.handler.options_dict['events']) + self.sources_box.set_active( + self.options.handler.options_dict['sources']) + self.places_box.set_active( + self.options.handler.options_dict['places']) + self.media_box.set_active(self.options.handler.options_dict['media']) + self.repos_box.set_active(self.options.handler.options_dict['repos']) - label = gtk.Label('%s' % _('Remove unused objects')) - label.set_use_markup(True) + label = gtk.Label() + window.vbox.add(label) + label.set_padding(12,12) + window.vbox.add(self.events_box) + window.vbox.add(self.sources_box) + window.vbox.add(self.places_box) + window.vbox.add(self.media_box) + window.vbox.add(self.repos_box) + window.vbox.show_all() - a.vbox.add(label) - a.vbox.add(self.event) - a.vbox.add(self.source) - a.vbox.add(self.place) - a.vbox.show_all() - result = a.run() - a.destroy() + self.set_window(window,label,self.label) - if result == gtk.RESPONSE_ACCEPT: - self.run_tool(self.event.get_active(), - self.source.get_active(), - self.place.get_active()) + self.window.connect('response',self.response_handler) + self.show() - def run_tool(self, clean_events, clean_sources, clean_places): + def response_handler(self,window,response): + if response == gtk.RESPONSE_ACCEPT: + self.run_tool() + else: + self.close() + + def build_menu_names(self,obj): + return (_("Tool settings"),self.label) + + def run_tool(self): + self.options.handler.options_dict['events'] = \ + int(self.events_box.get_active()) + self.options.handler.options_dict['sources'] = \ + int(self.sources_box.get_active()) + self.options.handler.options_dict['places'] = \ + int(self.places_box.get_active()) + self.options.handler.options_dict['media'] = \ + int(self.media_box.get_active()) + self.options.handler.options_dict['repos'] = \ + int(self.repos_box.get_active()) + + sr = ShowResults(self.dbstate,self.uistate,self.track) + self.add_results = sr.add_results + + self.uistate.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self.uistate.progress.show() + self.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + sr.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + self.collect_unused() + + self.uistate.progress.hide() + self.uistate.window.window.set_cursor(None) + self.window.window.set_cursor(None) + sr.window.window.set_cursor(None) + self.reset() + + # Save options + self.options.handler.save_options() + + def collect_unused(self): + # Run through all requested tables and check all objects + # for being referenced some place. If not, add_results on them. + + tables = { + 'events' : {'cursor_func': self.db.get_event_cursor, + 'total_func' : self.db.get_number_of_events}, + 'sources' : {'cursor_func': self.db.get_source_cursor, + 'total_func' : self.db.get_number_of_sources}, + 'places' : {'cursor_func': self.db.get_place_cursor, + 'total_func' : self.db.get_number_of_places}, + 'media' : {'cursor_func': self.db.get_media_cursor, + 'total_func' : self.db.get_number_of_media_objects}, + 'repos' : {'cursor_func': self.db.get_repository_cursor, + 'total_func' : self.db.get_number_of_repositories}, + } + + for the_type in tables.keys(): + if not self.options.handler.options_dict[the_type]: + # This table was not requested. Skip it. + continue + + cursor = tables[the_type]['cursor_func']() + total = tables[the_type]['total_func']() + self.set_total(total) + item = cursor.first() + while item: + (handle,data) = item + + hlist = [x for x in self.db.find_backlink_handles(handle)] + if len(hlist) == 0: + self.add_results((the_type,handle,data)) + item = cursor.next() + self.update() + cursor.close() + self.reset() + +#------------------------------------------------------------------------- +# +# Show the results +# +#------------------------------------------------------------------------- +class ShowResults(ManagedWindow.ManagedWindow): + MARK_COL = 0 + OBJ_ID_COL = 1 + OBJ_NAME_COL = 2 + OBJ_TYPE_COL = 3 + OBJ_HANDLE_COL = 4 + + def __init__(self,dbstate,uistate,track): + self.title = _('Unused Objects') + + ManagedWindow.ManagedWindow.__init__(self,uistate,track,self.__class__) + + self.dbstate = dbstate + self.db = dbstate.db + + self.tables = { + 'events' : {'get_func': self.db.get_event_from_handle, + 'remove' : self.db.remove_event, + 'editor' : 'EditEvent', + 'stock' : 'gramps-event', + 'name_ix' : 4}, + 'sources' : {'get_func': self.db.get_source_from_handle, + 'remove' : self.db.remove_source, + 'editor' : 'EditSource', + 'stock' : 'gramps-source', + 'name_ix' : 2}, + 'places' : {'get_func': self.db.get_place_from_handle, + 'remove' : self.db.remove_place, + 'editor' : 'EditPlace', + 'stock' : 'gramps-place', + 'name_ix' : 2}, + 'media' : {'get_func': self.db.get_object_from_handle, + 'remove' : self.db.remove_object, + 'editor' : 'EditMedia', + 'stock' : 'gramps-media', + 'name_ix' : 4}, + 'repos' : {'get_func': self.db.get_repository_from_handle, + 'remove' : self.db.remove_repository, + 'editor' : 'EditRepository', + 'stock' : 'gramps-repository', + 'name_ix' : 3}, + } + + base = os.path.dirname(__file__) + self.glade_file = base + os.sep + "verify.glade" + + self.top = gtk.glade.XML(self.glade_file,"verify_result","gramps") + window = self.top.get_widget("verify_result") + self.set_window(window,self.top.get_widget('title'),self.title) + + self.top.signal_autoconnect({ + "destroy_passed_object" : self.close, + }) + + self.warn_tree = self.top.get_widget('warn_tree') + self.warn_tree.connect('button_press_event', self.double_click) + + self.selection = self.warn_tree.get_selection() + + self.hide_button = self.top.get_widget('hide_button') + self.hide_button.destroy() + + self.mark_button = self.top.get_widget('mark_all') + self.mark_button.connect('clicked',self.mark_clicked) + + self.unmark_button = self.top.get_widget('unmark_all') + self.unmark_button.connect('clicked',self.unmark_clicked) + + self.invert_button = self.top.get_widget('invert_all') + self.invert_button.connect('clicked',self.invert_clicked) + + self.real_model = gtk.ListStore(bool,str,str,str,str) + self.sort_model = gtk.TreeModelSort(self.real_model) + self.warn_tree.set_model(self.sort_model) + + self.renderer = gtk.CellRendererText() + self.img_renderer = gtk.CellRendererPixbuf() + self.bool_renderer = gtk.CellRendererToggle() + self.bool_renderer.connect('toggled',self.selection_toggled) + + # Add mark column + mark_column = gtk.TreeViewColumn(_('Mark'),self.bool_renderer, + active=ShowResults.MARK_COL) + mark_column.set_sort_column_id(ShowResults.MARK_COL) + self.warn_tree.append_column(mark_column) + + # Add image column + img_column = gtk.TreeViewColumn(None, self.img_renderer ) + img_column.set_cell_data_func(self.img_renderer,self.get_image) + self.warn_tree.append_column(img_column) + + # Add column with object gramps_id + id_column = gtk.TreeViewColumn(_('ID'), self.renderer, + text=ShowResults.OBJ_ID_COL) + id_column.set_sort_column_id(ShowResults.OBJ_ID_COL) + self.warn_tree.append_column(id_column) + + # Add column with object name + name_column = gtk.TreeViewColumn(_('Name'), self.renderer, + text=ShowResults.OBJ_NAME_COL) + name_column.set_sort_column_id(ShowResults.OBJ_NAME_COL) + self.warn_tree.append_column(name_column) + + # Add a button to remove selected objects + remove_button = self.window.add_button(gtk.STOCK_REMOVE, + gtk.RESPONSE_ACCEPT) + self.window.connect('response',self.response_handler) + + self.window.show_all() + self.window_shown = False + + def response_handler(self,window,response): + if response == gtk.RESPONSE_ACCEPT: + self.do_remove() + + def do_remove(self): trans = self.db.transaction_begin("",batch=False) self.db.disable_signals() - checker = CheckIntegrity(self.dbstate, self.uistate, trans) - if clean_events: - checker.cleanup_events() - if clean_sources: - checker.cleanup_sources() - if clean_places: - checker.cleanup_places() + + for row_num in range(len(self.real_model)-1,-1,-1): + path = (row_num,) + row = self.real_model[path] + if not row[ShowResults.MARK_COL]: + continue + + the_type = row[ShowResults.OBJ_TYPE_COL] + handle = row[ShowResults.OBJ_HANDLE_COL] + remove_func = self.tables[the_type]['remove'] + remove_func(handle, trans) + + self.real_model.remove(row.iter) self.db.transaction_commit(trans, _("Remove unused objects")) self.db.enable_signals() - self.db.request_rebuild() + self.db.request_rebuild() - errs = checker.build_report() - if errs: - Report(self.uistate, checker.text.getvalue()) + def selection_toggled(self,cell,path_string): + sort_path = tuple([int (i) for i in path_string.split(':')]) + real_path = self.sort_model.convert_path_to_child_path(sort_path) + row = self.real_model[real_path] + row[ShowResults.MARK_COL] = not row[ShowResults.MARK_COL] + self.real_model.row_changed(real_path,row.iter) -#------------------------------------------------------------------------- -# -# -# -#------------------------------------------------------------------------- -class CheckIntegrity: - - def __init__(self, dbstate, uistate, trans): - self.db = dbstate.db - self.uistate = uistate - self.trans = trans - self.place_cnt = 0 - self.source_cnt = 0 - self.event_cnt = 0 - self.progress = Utils.ProgressMeter(_('Checking database'),'') + def mark_clicked(self,mark_button): + for row_num in range(len(self.real_model)): + path = (row_num,) + row = self.real_model[path] + row[ShowResults.MARK_COL] = True - def _cleanup_map(self, title, no_events, db_map, remove_func): - self.progress.set_pass(title, no_events) + def unmark_clicked(self,unmark_button): + for row_num in range(len(self.real_model)): + path = (row_num,) + row = self.real_model[path] + row[ShowResults.MARK_COL] = False - cnt = 0 - for handle in db_map.keys(): - hlist = [ x for x in self.db.find_backlink_handles(handle)] - if len(hlist) == 0: - remove_func(handle, self.trans) - cnt += 1 - return cnt + def invert_clicked(self,invert_button): + for row_num in range(len(self.real_model)): + path = (row_num,) + row = self.real_model[path] + row[ShowResults.MARK_COL] = not row[ShowResults.MARK_COL] - def cleanup_events(self): - self.event_cnt = self._cleanup_map( - _('Removing unused events'), - self.db.get_number_of_events(), - self.db.event_map, - self.db.remove_event) + def double_click(self,obj,event): + if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1: + (model,node) = self.selection.get_selected() + if not node: + return + sort_path = self.sort_model.get_path(node) + real_path = self.sort_model.convert_path_to_child_path(sort_path) + row = self.real_model[real_path] + the_type = row[ShowResults.OBJ_TYPE_COL] + handle = row[ShowResults.OBJ_HANDLE_COL] + self.call_editor(the_type,handle) - def cleanup_sources(self): - self.source_cnt = self._cleanup_map( - _('Removing unused sources'), - self.db.get_number_of_sources(), - self.db.source_map, - self.db.remove_source) + def call_editor(self,the_type,handle): + try: + obj = self.tables[the_type]['get_func'](handle) + editor_str = 'from Editors import %s as editor' \ + % self.tables[the_type]['editor'] + exec(editor_str) + editor(self.dbstate, self.uistate, [], obj) + except Errors.WindowActiveError: + pass - def cleanup_places(self): - self.place_cnt = self._cleanup_map( - _('Removing unused places'), - self.db.get_number_of_places(), - self.db.place_map, - self.db.remove_place) + def get_image(self, column, cell, model, iter, user_data=None): + the_type = model.get_value(iter, ShowResults.OBJ_TYPE_COL) + the_stock = self.tables[the_type]['stock'] + cell.set_property('stock-id', the_stock) - def build_report(self): - self.progress.close() + def add_results(self,results): + (the_type,handle,data) = results - errors = self.event_cnt + self.source_cnt + self.place_cnt + gramps_id = data[1] + name_ix = self.tables[the_type]['name_ix'] + name = data[name_ix] - if errors == 0: - OkDialog(_("No unreferenced objects were found."), - _('The database has passed internal checks')) - return 0 - - self.text = cStringIO.StringIO() - - if self.event_cnt == 1: - self.text.write(_("1 non-referenced event removed\n")) - elif self.event_cnt > 1: - self.text.write(_("%d non-referenced events removed\n") % self.event_cnt) - - if self.source_cnt == 1: - self.text.write(_("1 non-referenced source removed\n")) - elif self.source_cnt > 1: - self.text.write(_("%d non-referenced sources removed\n") % self.source_cnt) - - if self.place_cnt == 1: - self.text.write(_("1 non-referenced place removed\n")) - elif self.place_cnt > 1: - self.text.write(_("%d non-referenced places removed\n") % self.place_cnt) - - return errors - -#------------------------------------------------------------------------- -# -# Display the results -# -#------------------------------------------------------------------------- -class Report(ManagedWindow.ManagedWindow): - - def __init__(self, uistate, text, cl=0): - if cl: - print text - return - - ManagedWindow.ManagedWindow.__init__(self, uistate, [], self) + self.real_model.append(row=[False,gramps_id,name,the_type,handle]) - base = os.path.dirname(__file__) - glade_file = base + os.sep + "summary.glade" - topDialog = gtk.glade.XML(glade_file,"summary","gramps") - topDialog.get_widget("close").connect('clicked',self.close) + if not self.window_shown: + self.show() + self.window_shown = True - window = topDialog.get_widget("summary") - textwindow = topDialog.get_widget("textwindow") - textwindow.get_buffer().set_text(text) - - self.set_window(window, - topDialog.get_widget("title"), - _("Integrity Check Results")) - - self.show() - - def build_menu_names(self, obj): - return (_('Remove unused objects'), None) + def build_menu_names(self,obj): + return (self.title,None) #------------------------------------------------------------------------ # @@ -252,6 +413,33 @@ class CheckOptions(Tool.ToolOptions): def __init__(self,name,person_id=None): Tool.ToolOptions.__init__(self,name,person_id) + def set_new_options(self): + # Options specific for this report + self.options_dict = { + 'events' : 1, + 'sources' : 1, + 'places' : 1, + 'media' : 1, + 'repos' : 1, + } + self.options_help = { + 'events' : ("=0/1","Whether to use check for unused events", + ["Do not check events","Check events"], + True), + 'sources' : ("=0/1","Whether to use check for unused sources", + ["Do not check sources","Check sources"], + True), + 'places' : ("=0/1","Whether to use check for unused places", + ["Do not check places","Check places"], + True), + 'media' : ("=0/1","Whether to use check for unused media", + ["Do not check media","Check media"], + True), + 'repos' : ("=0/1","Whether to use check for unused repositories", + ["Do not check repositories","Check repositories"], + True), + } + #------------------------------------------------------------------------ # #