from Delegator
import Delegator
#$ event <<dump-undo-state>>
#$ win <Control-backslash>
#$ unix <Control-backslash>
class UndoDelegator(Delegator
):
def setdelegate(self
, delegate
):
if self
.delegate
is not None:
self
.unbind("<<dump-undo-state>>")
Delegator
.setdelegate(self
, delegate
)
self
.bind("<<undo>>", self
.undo_event
)
self
.bind("<<redo>>", self
.redo_event
)
self
.bind("<<dump-undo-state>>", self
.dump_event
)
def dump_event(self
, event
):
from pprint
import pprint
pprint(self
.undolist
[:self
.pointer
])
print "pointer:", self
.pointer
,
print "saved:", self
.saved
,
print "can_merge:", self
.can_merge
,
print "get_saved():", self
.get_saved()
pprint(self
.undolist
[self
.pointer
:])
self
.undoblock
= 0 # or a CommandSequence instance
def set_saved(self
, flag
):
self
.saved
= self
.pointer
return self
.saved
== self
.pointer
def set_saved_change_hook(self
, hook
):
self
.saved_change_hook
= hook
is_saved
= self
.get_saved()
if is_saved
!= self
.was_saved
:
self
.was_saved
= is_saved
if self
.saved_change_hook
:
def insert(self
, index
, chars
, tags
=None):
self
.addcmd(InsertCommand(index
, chars
, tags
))
def delete(self
, index1
, index2
=None):
self
.addcmd(DeleteCommand(index1
, index2
))
# Clients should call undo_block_start() and undo_block_stop()
# around a sequence of editing cmds to be treated as a unit by
# undo & redo. Nested matching calls are OK, and the inner calls
# then act like nops. OK too if no editing cmds, or only one
# editing cmd, is issued in between: if no cmds, the whole
# sequence has no effect; and if only one cmd, that cmd is entered
# directly into the undo list, as if undo_block_xxx hadn't been
# called. The intent of all that is to make this scheme easy
# to use: all the client has to worry about is making sure each
# _start() call is matched by a _stop() call.
def undo_block_start(self
):
self
.undoblock
= CommandSequence()
self
.undoblock
.bump_depth()
def undo_block_stop(self
):
if self
.undoblock
.bump_depth(-1) == 0:
# no need to wrap a single cmd
# this blk of cmds, or single cmd, has already
# been done, so don't execute it again
def addcmd(self
, cmd
, execute
=True):
self
.undoblock
.append(cmd
)
if self
.can_merge
and self
.pointer
> 0:
lastcmd
= self
.undolist
[self
.pointer
-1]
self
.undolist
[self
.pointer
:] = [cmd
]
if self
.saved
> self
.pointer
:
self
.pointer
= self
.pointer
+ 1
if len(self
.undolist
) > self
.max_undo
:
##print "truncating undo list"
self
.pointer
= self
.pointer
- 1
self
.saved
= self
.saved
- 1
def undo_event(self
, event
):
cmd
= self
.undolist
[self
.pointer
- 1]
self
.pointer
= self
.pointer
- 1
def redo_event(self
, event
):
if self
.pointer
>= len(self
.undolist
):
cmd
= self
.undolist
[self
.pointer
]
self
.pointer
= self
.pointer
+ 1
# Base class for Undoable commands
def __init__(self
, index1
, index2
, chars
, tags
=None):
s
= self
.__class
__.__name
__
t
= (self
.index1
, self
.index2
, self
.chars
, self
.tags
)
def save_marks(self
, text
):
for name
in text
.mark_names():
if name
!= "insert" and name
!= "current":
marks
[name
] = text
.index(name
)
def set_marks(self
, text
, marks
):
for name
, index
in marks
.items():
text
.mark_set(name
, index
)
class InsertCommand(Command
):
# Undoable insert command
def __init__(self
, index1
, chars
, tags
=None):
Command
.__init
__(self
, index1
, None, chars
, tags
)
self
.marks_before
= self
.save_marks(text
)
self
.index1
= text
.index(self
.index1
)
if text
.compare(self
.index1
, ">", "end-1c"):
# Insert before the final newline
self
.index1
= text
.index("end-1c")
text
.insert(self
.index1
, self
.chars
, self
.tags
)
self
.index2
= text
.index("%s+%dc" % (self
.index1
, len(self
.chars
)))
self
.marks_after
= self
.save_marks(text
)
##sys.__stderr__.write("do: %s\n" % self)
text
.mark_set('insert', self
.index1
)
text
.insert(self
.index1
, self
.chars
, self
.tags
)
self
.set_marks(text
, self
.marks_after
)
##sys.__stderr__.write("redo: %s\n" % self)
text
.mark_set('insert', self
.index1
)
text
.delete(self
.index1
, self
.index2
)
self
.set_marks(text
, self
.marks_before
)
##sys.__stderr__.write("undo: %s\n" % self)
if self
.__class
__ is not cmd
.__class
__:
if self
.index2
!= cmd
.index1
:
if self
.tags
!= cmd
.tags
:
self
.classify(self
.chars
[-1]) != self
.classify(cmd
.chars
):
self
.chars
= self
.chars
+ cmd
.chars
alphanumeric
= string
.ascii_letters
+ string
.digits
+ "_"
if c
in self
.alphanumeric
:
class DeleteCommand(Command
):
# Undoable delete command
def __init__(self
, index1
, index2
=None):
Command
.__init
__(self
, index1
, index2
, None, None)
self
.marks_before
= self
.save_marks(text
)
self
.index1
= text
.index(self
.index1
)
self
.index2
= text
.index(self
.index2
)
self
.index2
= text
.index(self
.index1
+ " +1c")
if text
.compare(self
.index2
, ">", "end-1c"):
# Don't delete the final newline
self
.index2
= text
.index("end-1c")
self
.chars
= text
.get(self
.index1
, self
.index2
)
text
.delete(self
.index1
, self
.index2
)
self
.marks_after
= self
.save_marks(text
)
##sys.__stderr__.write("do: %s\n" % self)
text
.mark_set('insert', self
.index1
)
text
.delete(self
.index1
, self
.index2
)
self
.set_marks(text
, self
.marks_after
)
##sys.__stderr__.write("redo: %s\n" % self)
text
.mark_set('insert', self
.index1
)
text
.insert(self
.index1
, self
.chars
)
self
.set_marks(text
, self
.marks_before
)
##sys.__stderr__.write("undo: %s\n" % self)
class CommandSequence(Command
):
# Wrapper for a sequence of undoable cmds to be undone/redone
s
= self
.__class
__.__name
__
strs
.append(" %r" % (cmd
,))
return s
+ "(\n" + ",\n".join(strs
) + "\n)"
def bump_depth(self
, incr
=1):
self
.depth
= self
.depth
+ incr
from Percolator
import Percolator
root
.wm_protocol("WM_DELETE_WINDOW", root
.quit
)
if __name__
== "__main__":