* src/GrampsDb/_GrampsDbBase.py: handle close/delete of active database
* src/DbManager.py: clean up * src/DbState.py: issue database-changed signal on db close * src/GrampsDbUtils/_GedcomParse.py: fix adding of notes * src/DbLoader.py: don't give undo warning if importing into empty db svn: r8347
This commit is contained in:
parent
30ce0c5291
commit
5580ad12d3
@ -1,4 +1,9 @@
|
||||
2007-04-01 Don Allingham <don@gramps-project.org>
|
||||
* src/GrampsDb/_GrampsDbBase.py: handle close/delete of active database
|
||||
* src/DbManager.py: clean up
|
||||
* src/DbState.py: issue database-changed signal on db close
|
||||
* src/GrampsDbUtils/_GedcomParse.py: fix adding of notes
|
||||
* src/DbLoader.py: don't give undo warning if importing into empty db
|
||||
* src/DataViews/_PedigreeView.py: display matches in statusbar
|
||||
* src/DataViews/_PersonView.py: display matches in statusbar
|
||||
* src/DataViews/_RelationView.py: display matches in statusbar
|
||||
|
@ -260,17 +260,19 @@ class DbLoader:
|
||||
def import_file(self):
|
||||
# First thing first: import is a batch transaction
|
||||
# so we will lose the undo history. Warn the user.
|
||||
warn_dialog = QuestionDialog.QuestionDialog2(
|
||||
_('Undo history warning'),
|
||||
_('Proceeding with import will erase the undo history '
|
||||
'for this session. In particular, you will not be able '
|
||||
'to revert the import or any changes made prior to it.\n\n'
|
||||
'If you think you may want to revert the import, '
|
||||
'please stop here and backup your database.'),
|
||||
_('_Proceed with import'), _('_Stop'),
|
||||
self.uistate.window)
|
||||
if not warn_dialog.run():
|
||||
return False
|
||||
|
||||
if self.dbstate.db.get_number_of_people() > 0:
|
||||
warn_dialog = QuestionDialog.QuestionDialog2(
|
||||
_('Undo history warning'),
|
||||
_('Proceeding with import will erase the undo history '
|
||||
'for this session. In particular, you will not be able '
|
||||
'to revert the import or any changes made prior to it.\n\n'
|
||||
'If you think you may want to revert the import, '
|
||||
'please stop here and backup your database.'),
|
||||
_('_Proceed with import'), _('_Stop'),
|
||||
self.uistate.window)
|
||||
if not warn_dialog.run():
|
||||
return False
|
||||
|
||||
choose = gtk.FileChooserDialog(
|
||||
_('GRAMPS: Import database'),
|
||||
@ -336,7 +338,7 @@ class DbLoader:
|
||||
const.app_gramps_xml,
|
||||
const.app_gedcom):
|
||||
importer = GrampsDbUtils.gramps_db_reader_factory(filetype)
|
||||
self.do_import(choose,importer,filename)
|
||||
self.do_import(choose, importer, filename)
|
||||
return True
|
||||
|
||||
# Then we try all the known plugins
|
||||
@ -345,7 +347,7 @@ class DbLoader:
|
||||
for (importData,mime_filter,mime_type,native_format,format_name) \
|
||||
in import_list:
|
||||
if filetype == mime_type or the_file == mime_type:
|
||||
self.do_import(choose,importData,filename)
|
||||
self.do_import(choose, importData, filename)
|
||||
return True
|
||||
|
||||
# Finally, we give up and declare this an unknown format
|
||||
|
304
src/DbManager.py
304
src/DbManager.py
@ -18,12 +18,13 @@
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id: Bookmarks.py 8197 2007-02-20 20:56:48Z hippy $
|
||||
"""
|
||||
Provides the management of databases. This includes opening, renaming,
|
||||
creating, and deleting of databases.
|
||||
"""
|
||||
|
||||
"Handle bookmarks for the gramps interface"
|
||||
|
||||
__author__ = "Donald N. Allingham"
|
||||
__version__ = "$Revision: 8197 $"
|
||||
__author__ = "Donald N. Allingham"
|
||||
__revision__ = "$Revision: 8197 $"
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -41,7 +42,7 @@ from gettext import gettext as _
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import logging
|
||||
log = logging.getLogger(".DbManager")
|
||||
LOG = logging.getLogger(".DbManager")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -58,7 +59,6 @@ import gtk.glade
|
||||
#-------------------------------------------------------------------------
|
||||
import QuestionDialog
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# constants
|
||||
@ -74,6 +74,7 @@ PATH_COL = 1
|
||||
FILE_COL = 2
|
||||
DATE_COL = 3
|
||||
OPEN_COL = 5
|
||||
STOCK_COL = 6
|
||||
|
||||
class DbManager:
|
||||
"""
|
||||
@ -98,6 +99,10 @@ class DbManager:
|
||||
self.dblist = self.glade.get_widget('dblist')
|
||||
self.rename = self.glade.get_widget('rename')
|
||||
self.model = None
|
||||
self.dbstate = dbstate
|
||||
self.column = None
|
||||
self.data_to_delete = None
|
||||
|
||||
if dbstate:
|
||||
self.active = dbstate.db.get_save_path()
|
||||
else:
|
||||
@ -122,47 +127,93 @@ class DbManager:
|
||||
self.dblist.connect('button-press-event', self.button_press)
|
||||
|
||||
def button_press(self, obj, event):
|
||||
"""
|
||||
Checks for a double click event. In the tree view, we want to
|
||||
treat a double click as if it was OK button press. However, we have
|
||||
to make sure that an item was selected first.
|
||||
"""
|
||||
if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
|
||||
self.top.response(gtk.RESPONSE_OK)
|
||||
data = self.selection.get_selected()
|
||||
if data[1]:
|
||||
self.top.response(gtk.RESPONSE_OK)
|
||||
return True
|
||||
return False
|
||||
|
||||
def selection_changed(self, selection):
|
||||
store, iter = selection.get_selected()
|
||||
if not iter or store.get_value(iter, OPEN_COL):
|
||||
self.remove.set_sensitive(False)
|
||||
"""
|
||||
Called with the selection is changed in the TreeView. What we
|
||||
are trying to detect is the selection or unselection of a row.
|
||||
When a row is unselected, the Open, Rename, and Remove buttons
|
||||
are set insensitive. If a row is selected, the rename and remove
|
||||
buttons are disabled, and the Open button is disabled if the
|
||||
row represents a open database.
|
||||
"""
|
||||
|
||||
# Get the current selection
|
||||
store, node = selection.get_selected()
|
||||
|
||||
if not node:
|
||||
self.connect.set_sensitive(False)
|
||||
self.rename.set_sensitive(False)
|
||||
self.remove.set_sensitive(False)
|
||||
else:
|
||||
self.remove.set_sensitive(True)
|
||||
self.connect.set_sensitive(True)
|
||||
if store.get_value(node, OPEN_COL):
|
||||
self.connect.set_sensitive(False)
|
||||
else:
|
||||
self.connect.set_sensitive(True)
|
||||
self.rename.set_sensitive(True)
|
||||
self.remove.set_sensitive(True)
|
||||
|
||||
def build_interface(self):
|
||||
render = gtk.CellRendererPixbuf()
|
||||
column = gtk.TreeViewColumn('', render, stock_id=6)
|
||||
self.dblist.append_column(column)
|
||||
"""
|
||||
Builds the columns for the TreeView. The columns are:
|
||||
|
||||
Icon, Database Name, Last Modified
|
||||
|
||||
The Icon column gets its data from column 6 of the database model.
|
||||
It is expecting either None, or a GTK stock icon name
|
||||
|
||||
The Database Name column is an editable column. We connect to the
|
||||
'edited' signal, so that we can change the name when the user changes
|
||||
the column.
|
||||
|
||||
The last modified column simply displays the last modification time.
|
||||
"""
|
||||
|
||||
# build the icon column
|
||||
render = gtk.CellRendererPixbuf()
|
||||
icon_column = gtk.TreeViewColumn('', render, stock_id=STOCK_COL)
|
||||
self.dblist.append_column(icon_column)
|
||||
|
||||
# build the database name column
|
||||
render = gtk.CellRendererText()
|
||||
render.set_property('editable',True)
|
||||
render.set_property('editable', True)
|
||||
render.connect('edited', self.change_name)
|
||||
self.column = gtk.TreeViewColumn(_('Family tree name'), render,
|
||||
text=NAME_COL)
|
||||
self.dblist.append_column(self.column)
|
||||
|
||||
# build the last modified cocolumn
|
||||
render = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn(_('Last modified'), render, text=DATE_COL)
|
||||
self.dblist.append_column(column)
|
||||
|
||||
# set the rules hit
|
||||
self.dblist.set_rules_hint(True)
|
||||
|
||||
def populate(self):
|
||||
"""
|
||||
Builds the display model.
|
||||
"""
|
||||
|
||||
self.model = gtk.ListStore(str, str, str, str, int, bool, str)
|
||||
|
||||
try:
|
||||
if not os.path.isdir(DEFAULT_DIR):
|
||||
os.mkdir(DEFAULT_DIR)
|
||||
except:
|
||||
print "did not make default dir"
|
||||
# make the default directory if it does not exist
|
||||
try:
|
||||
if not os.path.isdir(DEFAULT_DIR):
|
||||
os.mkdir(DEFAULT_DIR)
|
||||
except (IOError, OSError), msg:
|
||||
LOG.error(_("Could not make database directory: ") + str(msg))
|
||||
|
||||
self.current_names = []
|
||||
for dpath in os.listdir(DEFAULT_DIR):
|
||||
@ -171,115 +222,190 @@ class DbManager:
|
||||
if os.path.isfile(path_name):
|
||||
name = file(path_name).readline().strip()
|
||||
|
||||
meta = os.path.join(dirpath, META_NAME)
|
||||
if os.path.isfile(meta):
|
||||
tval = os.stat(meta)[9]
|
||||
last = time.asctime(time.localtime(tval))
|
||||
else:
|
||||
tval = 0
|
||||
last = _("Never")
|
||||
(tval, last) = time_val(dirpath)
|
||||
(enable, stock_id) = icon_values(dirpath, self.active)
|
||||
|
||||
if dirpath == self.active:
|
||||
enable = True
|
||||
stock_id = gtk.STOCK_OPEN
|
||||
else:
|
||||
enable = False
|
||||
stock_id = ""
|
||||
|
||||
self.current_names.append((name,
|
||||
os.path.join(DEFAULT_DIR, dpath),
|
||||
path_name,
|
||||
last,
|
||||
tval,
|
||||
enable,
|
||||
stock_id))
|
||||
self.current_names.append(
|
||||
(name, os.path.join(DEFAULT_DIR, dpath), path_name,
|
||||
last, tval, enable, stock_id))
|
||||
|
||||
self.current_names.sort()
|
||||
for items in self.current_names:
|
||||
data = [items[0], items[1], items[2], items[3], items[4], items[5], items[6]]
|
||||
data = [items[0], items[1], items[2], items[3],
|
||||
items[4], items[5], items[6]]
|
||||
self.model.append(data)
|
||||
self.dblist.set_model(self.model)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the dialog, returning None if nothing has been chosen,
|
||||
or the path and name if something has been selected
|
||||
"""
|
||||
value = self.top.run()
|
||||
if value == gtk.RESPONSE_OK:
|
||||
(model, node) = self.selection.get_selected()
|
||||
if node:
|
||||
self.top.destroy()
|
||||
return (self.model.get_value(node, PATH_COL),
|
||||
self.model.get_value(node, NAME_COL))
|
||||
else:
|
||||
self.top.destroy()
|
||||
return None
|
||||
else:
|
||||
self.top.destroy()
|
||||
return None
|
||||
return (model.get_value(node, PATH_COL),
|
||||
model.get_value(node, NAME_COL))
|
||||
self.top.destroy()
|
||||
return None
|
||||
|
||||
def change_name(self, text, path, new_text):
|
||||
"""
|
||||
Changes the name of the database. This is a callback from the
|
||||
column, which has been marked as editable.
|
||||
|
||||
If the new string is empty, do nothing. Otherwise, renaming the
|
||||
database is simply changing the contents of the name file.
|
||||
"""
|
||||
if len(new_text) > 0:
|
||||
iter = self.model.get_iter(path)
|
||||
filename = self.model.get_value(iter, FILE_COL)
|
||||
node = self.model.get_iter(path)
|
||||
filename = self.model.get_value(node, FILE_COL)
|
||||
try:
|
||||
f = open(filename, "w")
|
||||
f.write(new_text)
|
||||
f.close()
|
||||
self.model.set_value(iter, NAME_COL, new_text)
|
||||
except:
|
||||
pass
|
||||
name_file = open(filename, "w")
|
||||
name_file.write(new_text)
|
||||
name_file.close()
|
||||
self.model.set_value(node, NAME_COL, new_text)
|
||||
except (OSError, IOError), msg:
|
||||
QuestionDialog.ErrorDialog(
|
||||
_("Could not rename family tree"),
|
||||
str(msg))
|
||||
|
||||
def remove_db(self, obj):
|
||||
store, iter = self.selection.get_selected()
|
||||
path = store.get_path(iter)
|
||||
row = store[path]
|
||||
if row[OPEN_COL]:
|
||||
return
|
||||
self.data_to_delete = (row[0], row[1], row[2])
|
||||
"""
|
||||
Callback associated with the Remove button. Get the selected
|
||||
row and data, then call the verification dialog.
|
||||
"""
|
||||
store, node = self.selection.get_selected()
|
||||
self.data_to_delete = store[store.get_path(node)]
|
||||
|
||||
QuestionDialog.QuestionDialog(
|
||||
_("Remove the '%s' database?") % self.data_to_delete[0],
|
||||
_("Removing this database will permanently destroy "
|
||||
"the data."),
|
||||
_("Removing this database will permanently destroy the data."),
|
||||
_("Remove database"),
|
||||
self.really_delete_db)
|
||||
|
||||
# rebuild the display
|
||||
self.populate()
|
||||
|
||||
def really_delete_db(self):
|
||||
for (top, dirs, files) in os.walk(self.data_to_delete[1]):
|
||||
for f in files:
|
||||
os.unlink(os.path.join(top,f))
|
||||
os.rmdir(top)
|
||||
"""
|
||||
Delete the selected database. If the databse is open, close it first.
|
||||
Then scan the database directory, deleting the files, and finally
|
||||
removing the directory.
|
||||
"""
|
||||
|
||||
# close the database if the user has requested to delete the
|
||||
# active database
|
||||
if self.data_to_delete[OPEN_COL]:
|
||||
self.dbstate.no_database()
|
||||
|
||||
try:
|
||||
for (top, dirs, files) in os.walk(self.data_to_delete[1]):
|
||||
for filename in files:
|
||||
os.unlink(os.path.join(top, filename))
|
||||
os.rmdir(self.data_to_delete[1])
|
||||
except (IOError, OSError), msg:
|
||||
QuestionDialog.ErrorDialog(_("Could not delete family tree"),
|
||||
str(msg))
|
||||
|
||||
def rename_db(self, obj):
|
||||
"""
|
||||
Start the rename process by calling the start_editing option on
|
||||
the line with the cursor.
|
||||
"""
|
||||
store, node = self.selection.get_selected()
|
||||
path = self.model.get_path(node)
|
||||
self.dblist.set_cursor(path, focus_column=self.column, start_editing=True)
|
||||
self.dblist.set_cursor(path, focus_column=self.column,
|
||||
start_editing=True)
|
||||
|
||||
def new_db(self, obj):
|
||||
while True:
|
||||
base = "%x" % int(time.time())
|
||||
new_path = os.path.join(DEFAULT_DIR, base)
|
||||
if not os.path.isdir(new_path):
|
||||
break
|
||||
"""
|
||||
Callback wrapper around the actual routine that creates the
|
||||
new database. Catch OSError and IOError and display a warning
|
||||
message.
|
||||
"""
|
||||
try:
|
||||
self.mk_db()
|
||||
except (OSError, IOError), msg:
|
||||
QuestionDialog.ErrorDialog(_("Could not create family tree"),
|
||||
str(msg))
|
||||
|
||||
def mk_db(self):
|
||||
"""
|
||||
Create a new database.
|
||||
"""
|
||||
|
||||
new_path = find_next_db_dir()
|
||||
|
||||
os.mkdir(new_path)
|
||||
path_name = os.path.join(new_path, NAME_FILE)
|
||||
|
||||
name_list = [ name[0] for name in self.current_names ]
|
||||
|
||||
i = 1
|
||||
while True:
|
||||
title = "%s %d" % (DEFAULT_TITLE, i)
|
||||
if title not in name_list:
|
||||
break
|
||||
i += 1
|
||||
title = find_next_db_name(name_list)
|
||||
|
||||
f = open(path_name, "w")
|
||||
f.write(title)
|
||||
f.close()
|
||||
name_file = open(path_name, "w")
|
||||
name_file.write(title)
|
||||
name_file.close()
|
||||
|
||||
self.current_names.append(title)
|
||||
node = self.model.append([title, new_path, path_name, _("Never"), 0, False, ''])
|
||||
node = self.model.append([title, new_path, path_name,
|
||||
_("Never"), 0, False, ''])
|
||||
self.selection.select_iter(node)
|
||||
|
||||
path = self.model.get_path(node)
|
||||
self.dblist.set_cursor(path, focus_column=self.column, start_editing=True)
|
||||
self.dblist.set_cursor(path, focus_column=self.column,
|
||||
start_editing=True)
|
||||
|
||||
def find_next_db_name(name_list):
|
||||
"""
|
||||
Scan the name list, looking for names that do not yet exist.
|
||||
Use the DEFAULT_TITLE as the basis for the database name.
|
||||
"""
|
||||
i = 1
|
||||
while True:
|
||||
title = "%s %d" % (DEFAULT_TITLE, i)
|
||||
if title not in name_list:
|
||||
return title
|
||||
i += 1
|
||||
|
||||
def find_next_db_dir():
|
||||
"""
|
||||
Searches the default directory for the first available default
|
||||
database name. Base the name off the current time. In all actuality,
|
||||
the first should be valid.
|
||||
"""
|
||||
while True:
|
||||
base = "%x" % int(time.time())
|
||||
new_path = os.path.join(DEFAULT_DIR, base)
|
||||
if not os.path.isdir(new_path):
|
||||
break
|
||||
return new_path
|
||||
|
||||
def time_val(dirpath):
|
||||
"""
|
||||
Return the last modified time of the database. We do this by looking
|
||||
at the modification time of the meta db file. If this file does not
|
||||
exist, we indicate that database as never modified.
|
||||
"""
|
||||
meta = os.path.join(dirpath, META_NAME)
|
||||
if os.path.isfile(meta):
|
||||
tval = os.stat(meta)[9]
|
||||
last = time.asctime(time.localtime(tval))
|
||||
else:
|
||||
tval = 0
|
||||
last = _("Never")
|
||||
return (tval, last)
|
||||
|
||||
def icon_values(dirpath, active):
|
||||
"""
|
||||
If the directory path is the active path, then return values
|
||||
that indicate to use the icon, and which icon to use.
|
||||
"""
|
||||
if dirpath == active:
|
||||
return (True, gtk.STOCK_OPEN)
|
||||
else:
|
||||
return (False, "")
|
||||
|
||||
|
@ -86,4 +86,4 @@ class DbState(GrampsDBCallback):
|
||||
self.db = GrampsDbBase()
|
||||
self.active = None
|
||||
self.open = False
|
||||
self.emit('no-database')
|
||||
self.emit('database-changed', (self.db, ))
|
||||
|
@ -691,6 +691,9 @@ class GrampsDbBase(GrampsDBCallback):
|
||||
Commits the specified Note to the database, storing the changes
|
||||
as part of the transaction.
|
||||
"""
|
||||
if not note.gramps_id:
|
||||
import traceback
|
||||
traceback.print_stack()
|
||||
|
||||
self._commit_base(note, self.note_map, NOTE_KEY,
|
||||
transaction.note_update,
|
||||
@ -1487,7 +1490,7 @@ class GrampsDbBase(GrampsDBCallback):
|
||||
"""
|
||||
self.nprefix = self._validated_id_prefix(val, "N")
|
||||
|
||||
def transaction_begin(self, msg="",batch=False,no_magic=False):
|
||||
def transaction_begin(self, msg="", batch=False, no_magic=False):
|
||||
"""
|
||||
Creates a new Transaction tied to the current UNDO database. The
|
||||
transaction has no effect until it is committed using the
|
||||
|
@ -1072,7 +1072,7 @@ class GedcomParser(UpdateCallback):
|
||||
note.set_handle(intid)
|
||||
note.set_gramps_id(gramps_id)
|
||||
if need_commit:
|
||||
self.dbase.commit_note(note, self.trans)
|
||||
self.dbase.add_note(note, self.trans)
|
||||
return note
|
||||
|
||||
def __find_or_create_place(self, title):
|
||||
@ -4258,7 +4258,7 @@ class GedcomParser(UpdateCallback):
|
||||
else:
|
||||
new_note = RelLib.Note(line.data)
|
||||
new_note.set_handle(Utils.create_id())
|
||||
self.dbase.commit_note(new_note, self.trans)
|
||||
self.dbase.add_note(new_note, self.trans)
|
||||
self.__skip_subordinate_levels(level+1)
|
||||
|
||||
def __parse_inline_note(self, line, level):
|
||||
@ -4267,7 +4267,7 @@ class GedcomParser(UpdateCallback):
|
||||
handle = self.nid2id.get(gid)
|
||||
new_note.set_handle(handle)
|
||||
new_note.set_gramps_id(gid)
|
||||
self.dbase.commit_note(new_note,self.trans)
|
||||
self.dbase.add_note(new_note,self.trans)
|
||||
self.nid2id[new_note.gramps_id] = new_note.handle
|
||||
self.__skip_subordinate_levels(level+1)
|
||||
|
||||
|
@ -560,7 +560,7 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">GRAMPS - GEDCOM Encoding</property>
|
||||
<property name="type">GTK_WINDOW_TOPLEVEL</property>
|
||||
<property name="window_position">GTK_WIN_POS_NONE</property>
|
||||
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||
<property name="modal">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="destroy_with_parent">False</property>
|
||||
|
@ -735,11 +735,11 @@ class ListView(BookMarkView):
|
||||
self.tooltips = TreeTips.TreeTips(
|
||||
self.list, self.model.tooltip_column, True)
|
||||
self.dirty = False
|
||||
self.uistate.show_filter_results(self.dbstate,
|
||||
self.model.displayed,
|
||||
self.model.total)
|
||||
else:
|
||||
self.dirty = True
|
||||
self.uistate.show_filter_results(self.dbstate,
|
||||
self.model.displayed,
|
||||
self.model.total)
|
||||
|
||||
def filter_toggle_action(self,obj):
|
||||
if obj.get_active():
|
||||
@ -853,6 +853,10 @@ class ListView(BookMarkView):
|
||||
return False
|
||||
|
||||
def change_page(self):
|
||||
if self.model:
|
||||
self.uistate.show_filter_results(self.dbstate,
|
||||
self.model.displayed,
|
||||
self.model.total)
|
||||
self.edit_action.set_sensitive(not self.dbstate.db.readonly)
|
||||
|
||||
def key_delete(self):
|
||||
|
@ -244,9 +244,6 @@ class Gramps:
|
||||
ah.handle_args()
|
||||
self.vm.post_init_interface()
|
||||
|
||||
# state.db.request_rebuild()
|
||||
# state.change_active_person(state.db.get_default_person())
|
||||
|
||||
if Config.get(Config.USE_TIPS):
|
||||
TipOfDay.TipOfDay(self.vm.uistate)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user