* 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.


svn: r8018
This commit is contained in:
Alex Roitman 2007-01-31 02:05:01 +00:00
parent 873e7bd697
commit 4d7d7e88ae
5 changed files with 354 additions and 162 deletions

View File

@ -4,6 +4,12 @@
* src/plugins/NarrativeWeb.py: error reporting * src/plugins/NarrativeWeb.py: error reporting
2007-01-30 Alex Roitman <shura@gramps-project.org> 2007-01-30 Alex Roitman <shura@gramps-project.org>
* 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. * configure.in: Bump up the version.
2007-01-30 Martin Hawlisch <Martin.Hawlisch@gmx.de> 2007-01-30 Martin Hawlisch <Martin.Hawlisch@gmx.de>

View File

@ -1,6 +1,6 @@
# Gramps - a GTK+/GNOME based genealogy program # 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 # 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 # 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() handle = self.first_selected()
the_event = self.dbstate.db.get_event_from_handle(handle) the_event = self.dbstate.db.get_event_from_handle(handle)
try: try:
EditEvent(the_event, self.dbstate, self.uistate, []) EditEvent(self.dbstate, self.uistate, [], the_event)
except Errors.WindowActiveError: except Errors.WindowActiveError:
pass pass
def add(self, obj): def add(self, obj):
try: try:
EditEvent(RelLib.Event(), self.dbstate, self.uistate, []) EditEvent(self.dbstate, self.uistate, [], RelLib.Event())
except Errors.WindowActiveError: except Errors.WindowActiveError:
pass pass
@ -240,6 +240,6 @@ class EventView(PageView.ListView):
for handle in mlist: for handle in mlist:
event = self.dbstate.db.get_event_from_handle(handle) event = self.dbstate.db.get_event_from_handle(handle)
try: try:
EditEvent(event, self.dbstate, self.uistate) EditEvent(self.dbstate, self.uistate, [], event)
except Errors.WindowActiveError: except Errors.WindowActiveError:
pass pass

View File

@ -1,7 +1,7 @@
# #
# Gramps - a GTK+/GNOME based genealogy program # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -156,7 +156,7 @@ class BackRefList(EmbeddedList):
try: try:
from Editors import EditEvent from Editors import EditEvent
obj = self.dbstate.db.get_event_from_handle(ref) event = self.dbstate.db.get_event_from_handle(ref)
EditEvent(obj, self.dbstate, self.uistate, []) EditEvent(self.dbstate, self.uistate, [], event)
except Errors.WindowActiveError: except Errors.WindowActiveError:
pass pass

View File

@ -1,7 +1,7 @@
# #
# Gramps - a GTK+/GNOME based genealogy program # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -62,7 +62,7 @@ from GrampsWidgets import *
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class EditEvent(EditPrimary): 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, EditPrimary.__init__(self, dbstate, uistate, track,
event, dbstate.db.get_event_from_handle) event, dbstate.db.get_event_from_handle)
@ -268,9 +268,8 @@ class EditEvent(EditPrimary):
class EditPersonEvent(EditEvent): class EditPersonEvent(EditEvent):
def __init__(self, event, dbstate, uistate, track=[], callback=None): def __init__(self, dbstate, uistate, track, event, callback=None):
EditEvent.__init__(self, event, dbstate, uistate, track, EditEvent.__init__(self, dbstate, uistate, track, event, callback)
callback)
def _init_event(self): def _init_event(self):
self.commit_event = self.db.commit_personal_event self.commit_event = self.db.commit_personal_event
@ -280,9 +279,8 @@ class EditPersonEvent(EditEvent):
class EditFamilyEvent(EditEvent): class EditFamilyEvent(EditEvent):
def __init__(self, event, dbstate, uistate, track=[], callback=None): def __init__(self, dbstate, uistate, track, event, callback=None):
EditEvent.__init__(self, event, dbstate, uistate, track, EditEvent.__init__(self, dbstate, uistate, track, event, callback)
callback)
def _init_event(self): def _init_event(self):
self.commit_event = self.db.commit_family_event self.commit_event = self.db.commit_family_event

View File

@ -1,7 +1,7 @@
# #
# Gramps - a GTK+/GNOME based genealogy program # 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 # 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 # 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 $ # $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 os
import cStringIO
import sets
from gettext import gettext as _ from gettext import gettext as _
#------------------------------------------------------------------------ #------------------------------------------------------------------------
@ -53,191 +51,354 @@ import gtk.glade
# GRAMPS modules # GRAMPS modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
import RelLib import Errors
import Utils
import const
import ManagedWindow import ManagedWindow
from BasicUtils import UpdateCallback
from PluginUtils import Tool, register_tool from PluginUtils import Tool, register_tool
from QuestionDialog import OkDialog, MissingMediaDialog
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# runTool # runTool
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class RemoveUnused: class RemoveUnused(Tool.Tool,ManagedWindow.ManagedWindow,UpdateCallback):
def __init__(self, dbstate, uistate, options_class, name, callback=None): 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.dbstate = dbstate
self.uistate = uistate self.uistate = uistate
if self.db.readonly: if self.db.readonly:
return return
self.init_gui() self.init_gui()
def init_gui(self): def init_gui(self):
a = gtk.Dialog("%s - GRAMPS" % _('Remove unused objects'), window = gtk.Dialog("%s - GRAMPS" % self.label,
flags=gtk.DIALOG_MODAL, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
a.set_size_request(400, 200) window.set_border_width(12)
a.set_border_width(12) window.set_has_separator(False)
a.set_has_separator(False)
self.event = gtk.CheckButton(_('Remove unused events')) self.events_box = gtk.CheckButton(_('Remove unused events'))
self.source = gtk.CheckButton(_('Remove unused sources')) self.sources_box = gtk.CheckButton(_('Remove unused sources'))
self.place = gtk.CheckButton(_('Remove unused places')) 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.events_box.set_active(self.options.handler.options_dict['events'])
self.source.set_active(True) self.sources_box.set_active(
self.place.set_active(True) 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('<span size="larger" weight="bold">%s</span>' % _('Remove unused objects')) label = gtk.Label()
label.set_use_markup(True) 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) self.set_window(window,label,self.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()
if result == gtk.RESPONSE_ACCEPT: self.window.connect('response',self.response_handler)
self.run_tool(self.event.get_active(), self.show()
self.source.get_active(),
self.place.get_active())
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) trans = self.db.transaction_begin("",batch=False)
self.db.disable_signals() self.db.disable_signals()
checker = CheckIntegrity(self.dbstate, self.uistate, trans)
if clean_events: for row_num in range(len(self.real_model)-1,-1,-1):
checker.cleanup_events() path = (row_num,)
if clean_sources: row = self.real_model[path]
checker.cleanup_sources() if not row[ShowResults.MARK_COL]:
if clean_places: continue
checker.cleanup_places()
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.transaction_commit(trans, _("Remove unused objects"))
self.db.enable_signals() self.db.enable_signals()
self.db.request_rebuild() self.db.request_rebuild()
errs = checker.build_report() def selection_toggled(self,cell,path_string):
if errs: sort_path = tuple([int (i) for i in path_string.split(':')])
Report(self.uistate, checker.text.getvalue()) 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)
#------------------------------------------------------------------------- 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
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 _cleanup_map(self, title, no_events, db_map, remove_func): def unmark_clicked(self,unmark_button):
self.progress.set_pass(title, no_events) for row_num in range(len(self.real_model)):
path = (row_num,)
row = self.real_model[path]
row[ShowResults.MARK_COL] = False
cnt = 0 def invert_clicked(self,invert_button):
for handle in db_map.keys(): for row_num in range(len(self.real_model)):
hlist = [ x for x in self.db.find_backlink_handles(handle)] path = (row_num,)
if len(hlist) == 0: row = self.real_model[path]
remove_func(handle, self.trans) row[ShowResults.MARK_COL] = not row[ShowResults.MARK_COL]
cnt += 1
return cnt
def cleanup_events(self): def double_click(self,obj,event):
self.event_cnt = self._cleanup_map( if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
_('Removing unused events'), (model,node) = self.selection.get_selected()
self.db.get_number_of_events(), if not node:
self.db.event_map, return
self.db.remove_event) 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): def call_editor(self,the_type,handle):
self.source_cnt = self._cleanup_map( try:
_('Removing unused sources'), obj = self.tables[the_type]['get_func'](handle)
self.db.get_number_of_sources(), editor_str = 'from Editors import %s as editor' \
self.db.source_map, % self.tables[the_type]['editor']
self.db.remove_source) exec(editor_str)
editor(self.dbstate, self.uistate, [], obj)
except Errors.WindowActiveError:
pass
def cleanup_places(self): def get_image(self, column, cell, model, iter, user_data=None):
self.place_cnt = self._cleanup_map( the_type = model.get_value(iter, ShowResults.OBJ_TYPE_COL)
_('Removing unused places'), the_stock = self.tables[the_type]['stock']
self.db.get_number_of_places(), cell.set_property('stock-id', the_stock)
self.db.place_map,
self.db.remove_place)
def build_report(self): def add_results(self,results):
self.progress.close() (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: self.real_model.append(row=[False,gramps_id,name,the_type,handle])
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)
base = os.path.dirname(__file__) if not self.window_shown:
glade_file = base + os.sep + "summary.glade" self.show()
topDialog = gtk.glade.XML(glade_file,"summary","gramps") self.window_shown = True
topDialog.get_widget("close").connect('clicked',self.close)
window = topDialog.get_widget("summary") def build_menu_names(self,obj):
textwindow = topDialog.get_widget("textwindow") return (self.title,None)
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)
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
@ -252,6 +413,33 @@ class CheckOptions(Tool.ToolOptions):
def __init__(self,name,person_id=None): def __init__(self,name,person_id=None):
Tool.ToolOptions.__init__(self,name,person_id) 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),
}
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
# #