Updates to undo/redo logic:
1. Replace single transaction list with separate undo/redo deques 2. Update UndoHistory GUI to work with new queue setup 3. Add test to txn.py for empty transaction list svn: r15427
This commit is contained in:
parent
6da2ade5e3
commit
cd8ded4b37
@ -111,7 +111,6 @@ class UndoHistory(ManagedWindow.ManagedWindow):
|
|||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def _selection_changed(self, obj):
|
def _selection_changed(self, obj):
|
||||||
assert self.undodb.undo_count == self.undodb.undoindex + 1
|
|
||||||
(model, node) = self.selection.get_selected()
|
(model, node) = self.selection.get_selected()
|
||||||
if not node:
|
if not node:
|
||||||
return
|
return
|
||||||
@ -125,12 +124,12 @@ class UndoHistory(ManagedWindow.ManagedWindow):
|
|||||||
if path[0] < self.undodb.undo_count:
|
if path[0] < self.undodb.undo_count:
|
||||||
# This transaction is an undo candidate
|
# This transaction is an undo candidate
|
||||||
self.redo_button.set_sensitive(False)
|
self.redo_button.set_sensitive(False)
|
||||||
self.undo_button.set_sensitive(self.undodb.undo_available())
|
self.undo_button.set_sensitive(self.undodb.undo_count)
|
||||||
|
|
||||||
else: # path[0] >= self.undodb.undo_count:
|
else: # path[0] >= self.undodb.undo_count:
|
||||||
# This transaction is an redo candidate
|
# This transaction is an redo candidate
|
||||||
self.undo_button.set_sensitive(False)
|
self.undo_button.set_sensitive(False)
|
||||||
self.redo_button.set_sensitive(self.undodb.redo_available())
|
self.redo_button.set_sensitive(self.undodb.redo_count)
|
||||||
|
|
||||||
def _paint_rows(self, start, end, selected=False):
|
def _paint_rows(self, start, end, selected=False):
|
||||||
if selected:
|
if selected:
|
||||||
@ -144,7 +143,6 @@ class UndoHistory(ManagedWindow.ManagedWindow):
|
|||||||
self.model.set(the_iter, 3, bg)
|
self.model.set(the_iter, 3, bg)
|
||||||
|
|
||||||
def _response(self, obj, response_id):
|
def _response(self, obj, response_id):
|
||||||
assert self.undodb.undo_count == self.undodb.undoindex + 1
|
|
||||||
if response_id == gtk.RESPONSE_CLOSE:
|
if response_id == gtk.RESPONSE_CLOSE:
|
||||||
self.close(obj)
|
self.close(obj)
|
||||||
|
|
||||||
@ -201,14 +199,13 @@ class UndoHistory(ManagedWindow.ManagedWindow):
|
|||||||
|
|
||||||
def _update_ui(self):
|
def _update_ui(self):
|
||||||
self._paint_rows(0, len(self.model)-1, False)
|
self._paint_rows(0, len(self.model)-1, False)
|
||||||
self.undo_button.set_sensitive(self.undodb.undo_available())
|
self.undo_button.set_sensitive(self.undodb.undo_count)
|
||||||
self.redo_button.set_sensitive(self.undodb.redo_available())
|
self.redo_button.set_sensitive(self.undodb.redo_count)
|
||||||
self.clear_button.set_sensitive(
|
self.clear_button.set_sensitive(
|
||||||
self.undodb.undo_available() or self.undodb.redo_available()
|
self.undodb.undo_count or self.undodb.redo_count
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_model(self):
|
def _build_model(self):
|
||||||
assert self.undodb.undoindex+1 == len(self.undodb.undoq)
|
|
||||||
self.model.clear()
|
self.model.clear()
|
||||||
fg = bg = None
|
fg = bg = None
|
||||||
|
|
||||||
|
@ -169,6 +169,9 @@ class DbTxn(defaultdict):
|
|||||||
While the list is an arbitrary index of integers, it can be used
|
While the list is an arbitrary index of integers, it can be used
|
||||||
to indicate record numbers for a database.
|
to indicate record numbers for a database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.first is None or self.last is None:
|
||||||
|
return []
|
||||||
if not reverse:
|
if not reverse:
|
||||||
return xrange(self.first, self.last+1)
|
return xrange(self.first, self.last+1)
|
||||||
else:
|
else:
|
||||||
|
@ -54,8 +54,8 @@ import Errors
|
|||||||
DBERRS = (db.DBRunRecoveryError, db.DBAccessError,
|
DBERRS = (db.DBRunRecoveryError, db.DBAccessError,
|
||||||
db.DBPageNotFoundError, db.DBInvalidArgError)
|
db.DBPageNotFoundError, db.DBInvalidArgError)
|
||||||
|
|
||||||
_SIGBASE = ('person', 'family', 'source', 'event', 'media', 'place',
|
_SIGBASE = ('person', 'family', 'source', 'event', 'media',
|
||||||
'repository', 'reference', 'note', 'undoq', 'redoq')
|
'place', 'repository', 'reference', 'note')
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# DbUndo class
|
# DbUndo class
|
||||||
@ -67,15 +67,18 @@ class DbUndo(object):
|
|||||||
for use with a real backend.
|
for use with a real backend.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ['undodb', 'db', 'mapbase', 'translist', 'undoindex',
|
__slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp',
|
||||||
'undo_history_timestamp', 'txn']
|
'txn', 'undoq', 'redoq')
|
||||||
|
|
||||||
def __init__(self, grampsdb):
|
def __init__(self, grampsdb):
|
||||||
"""
|
"""
|
||||||
Class constructor. Set up main instance variables
|
Class constructor. Set up main instance variables
|
||||||
"""
|
"""
|
||||||
self.db = grampsdb
|
self.db = grampsdb
|
||||||
self.clear()
|
self.undoq = deque()
|
||||||
|
self.redoq = deque()
|
||||||
|
self.undo_history_timestamp = time.time()
|
||||||
|
self.txn = None
|
||||||
self.mapbase = (
|
self.mapbase = (
|
||||||
self.db.person_map,
|
self.db.person_map,
|
||||||
self.db.family_map,
|
self.db.family_map,
|
||||||
@ -92,10 +95,8 @@ class DbUndo(object):
|
|||||||
"""
|
"""
|
||||||
Clear the undo/redo list (but not the backing storage)
|
Clear the undo/redo list (but not the backing storage)
|
||||||
"""
|
"""
|
||||||
self.undoq = deque()
|
self.undoq.clear()
|
||||||
self.redoq = deque()
|
self.redoq.clear()
|
||||||
self.translist = []
|
|
||||||
self.undoindex = -1
|
|
||||||
self.undo_history_timestamp = time.time()
|
self.undo_history_timestamp = time.time()
|
||||||
self.txn = None
|
self.txn = None
|
||||||
|
|
||||||
@ -162,69 +163,35 @@ class DbUndo(object):
|
|||||||
"""
|
"""
|
||||||
txn.set_description(msg)
|
txn.set_description(msg)
|
||||||
txn.timestamp = time.time()
|
txn.timestamp = time.time()
|
||||||
|
|
||||||
# If we're within our undo limit, add this transaction
|
|
||||||
self.undoq.append(txn)
|
self.undoq.append(txn)
|
||||||
self.undoindex += 1
|
|
||||||
if self.undoindex < DBUNDO:
|
|
||||||
if self.undoindex >= len(self.translist):
|
|
||||||
self.translist.append(txn)
|
|
||||||
else:
|
|
||||||
self.translist[self.undoindex] = txn
|
|
||||||
del self.translist[self.undoindex+1:]
|
|
||||||
self.redoq.clear()
|
|
||||||
|
|
||||||
# Otherwise, we've exceeded our undo limit
|
|
||||||
else:
|
|
||||||
self.db.abort_possible = False
|
|
||||||
self.undo_history_timestamp = time.time()
|
|
||||||
self.translist[-1] = txn
|
|
||||||
self.redoq.clear()
|
|
||||||
|
|
||||||
def undo_available(self):
|
|
||||||
"""
|
|
||||||
Return boolean of whether or not there's a possibility of undo.
|
|
||||||
"""
|
|
||||||
#print "Undo available:", bool(self.undoq)
|
|
||||||
return len(self.undoq)
|
|
||||||
if 0 <= self.undoindex < len(self.translist):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def redo_available(self):
|
|
||||||
"""
|
|
||||||
Return boolean of whether or not there's a possibility of redo.
|
|
||||||
"""
|
|
||||||
#print "Redo available:", bool(self.redoq)
|
|
||||||
return len(self.redoq)
|
|
||||||
if 0 <= self.undoindex+1 < len(self.translist):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def undo(self, update_history=True):
|
def undo(self, update_history=True):
|
||||||
"""
|
"""
|
||||||
Undo a previously committed transaction
|
Undo a previously committed transaction
|
||||||
"""
|
"""
|
||||||
if self.db.readonly or not self.undo_available():
|
if self.db.readonly or self.undo_count == 0:
|
||||||
return False
|
return False
|
||||||
return self.__undoredo(update_history, self.__undo)
|
return self.__undo(update_history)
|
||||||
|
|
||||||
def redo(self, update_history=True):
|
def redo(self, update_history=True):
|
||||||
"""
|
"""
|
||||||
Redo a previously committed, then undone, transaction
|
Redo a previously committed, then undone, transaction
|
||||||
"""
|
"""
|
||||||
if self.db.readonly or not self.redo_available():
|
if self.db.readonly or self.redo_count == 0:
|
||||||
return False
|
return False
|
||||||
return self.__undoredo(update_history, self.__redo)
|
return self.__redo(update_history)
|
||||||
|
|
||||||
def __undoredo(self, update_history, func):
|
def undoredo(func):
|
||||||
"""
|
"""
|
||||||
Helper method used by both undo and redo methods.
|
Decorator function to wrap undo and redo operations within a bsddb
|
||||||
|
transaction. It also catches bsddb errors and raises an exception
|
||||||
|
as appropriate
|
||||||
"""
|
"""
|
||||||
|
def try_(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
with BSDDBTxn(self.db.env) as txn:
|
with BSDDBTxn(self.db.env) as txn:
|
||||||
self.txn = self.db.txn = txn.txn
|
self.txn = self.db.txn = txn.txn
|
||||||
status = func(update_history)
|
status = func(self, *args, **kwargs)
|
||||||
if not status:
|
if not status:
|
||||||
txn.abort()
|
txn.abort()
|
||||||
self.db.txn = None
|
self.db.txn = None
|
||||||
@ -234,6 +201,9 @@ class DbUndo(object):
|
|||||||
self.db._log_error()
|
self.db._log_error()
|
||||||
raise Errors.DbError(msg)
|
raise Errors.DbError(msg)
|
||||||
|
|
||||||
|
return try_
|
||||||
|
|
||||||
|
@undoredo
|
||||||
def __undo(self, update_history=True):
|
def __undo(self, update_history=True):
|
||||||
"""
|
"""
|
||||||
Access the last committed transaction, and revert the data to the
|
Access the last committed transaction, and revert the data to the
|
||||||
@ -241,11 +211,8 @@ class DbUndo(object):
|
|||||||
"""
|
"""
|
||||||
txn = self.undoq.pop()
|
txn = self.undoq.pop()
|
||||||
self.redoq.append(txn)
|
self.redoq.append(txn)
|
||||||
#transaction = self.translist[self.undoindex]
|
|
||||||
#assert transaction == txn
|
|
||||||
transaction = txn
|
transaction = txn
|
||||||
db = self.db
|
db = self.db
|
||||||
self.undoindex -= 1
|
|
||||||
subitems = transaction.get_recnos(reverse=True)
|
subitems = transaction.get_recnos(reverse=True)
|
||||||
|
|
||||||
# Process all records in the transaction
|
# Process all records in the transaction
|
||||||
@ -260,7 +227,7 @@ class DbUndo(object):
|
|||||||
db.emit, _SIGBASE[key])
|
db.emit, _SIGBASE[key])
|
||||||
# Notify listeners
|
# Notify listeners
|
||||||
if db.undo_callback:
|
if db.undo_callback:
|
||||||
if self.undo_available():
|
if self.undo_count > 0:
|
||||||
db.undo_callback(_("_Undo %s")
|
db.undo_callback(_("_Undo %s")
|
||||||
% transaction.get_description())
|
% transaction.get_description())
|
||||||
else:
|
else:
|
||||||
@ -274,6 +241,7 @@ class DbUndo(object):
|
|||||||
db.undo_history_callback()
|
db.undo_history_callback()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@undoredo
|
||||||
def __redo(self, db=None, update_history=True):
|
def __redo(self, db=None, update_history=True):
|
||||||
"""
|
"""
|
||||||
Access the last undone transaction, and revert the data to the state
|
Access the last undone transaction, and revert the data to the state
|
||||||
@ -281,9 +249,6 @@ class DbUndo(object):
|
|||||||
"""
|
"""
|
||||||
txn = self.redoq.pop()
|
txn = self.redoq.pop()
|
||||||
self.undoq.append(txn)
|
self.undoq.append(txn)
|
||||||
self.undoindex += 1
|
|
||||||
#transaction = self.translist[self.undoindex]
|
|
||||||
#assert transaction == txn
|
|
||||||
transaction = txn
|
transaction = txn
|
||||||
db = self.db
|
db = self.db
|
||||||
subitems = transaction.get_recnos()
|
subitems = transaction.get_recnos()
|
||||||
@ -304,8 +269,7 @@ class DbUndo(object):
|
|||||||
% transaction.get_description())
|
% transaction.get_description())
|
||||||
|
|
||||||
if db.redo_callback:
|
if db.redo_callback:
|
||||||
if len(self.redoq) > 1:
|
if self.redo_count > 1:
|
||||||
#new_transaction = self.translist[self.undoindex+1]
|
|
||||||
new_transaction = self.redoq[-2]
|
new_transaction = self.redoq[-2]
|
||||||
db.redo_callback(_("_Redo %s")
|
db.redo_callback(_("_Redo %s")
|
||||||
% new_transaction.get_description())
|
% new_transaction.get_description())
|
||||||
@ -351,10 +315,8 @@ class DbUndo(object):
|
|||||||
self.db._log_error()
|
self.db._log_error()
|
||||||
raise Errors.DbError(msg)
|
raise Errors.DbError(msg)
|
||||||
|
|
||||||
@property
|
undo_count = property(lambda self:len(self.undoq))
|
||||||
def undo_count(self):
|
redo_count = property(lambda self:len(self.redoq))
|
||||||
"""Number of undo requests in the queue"""
|
|
||||||
return len(self.undoq)
|
|
||||||
|
|
||||||
class DbUndoList(DbUndo):
|
class DbUndoList(DbUndo):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user