History
The mechanism underlying undo
and redo
. It is called History
, because I think of undo
as going back in time, and redo
as going forward in time.
You can think of History being a list of changes made to a document. Internally, History keeps an index
into this list. If the index
is 0, then we cannot undo. If the index
is at the end of the list, then we cannot redo.
To make a change to a document, we create a Change object, add it to the history, and then apply the Change.
Any Changes after the old index
are lost. For example : * Start with an empty document (index
is 0) * Add the word "Hello" (index
is 1) * Undo (so we are back to an empty document) (index
is 0 again) * Add the word "Goodbye" (index
is 1 again)
The Change which added the word "Hello" is lost forever.
Up until now, I've implied that the History is a list of changes, but that's not quite correct. History is a list of Batches, where a Batch contains one or more Change. This is because Changes are very simplistic, and complicated changes are implemented as a Batch of simple Changes. For example, to replace the word "Hi" with the word "Hello" is a deletion followed by an insertion. There is no Change for a replacement
.
Undo and redo works on Batches, not individual Changes.
Every Change instance must have enough information to undo
and redo
that change.
Merging Changes
Consider what happens when you type the word "Hello", and then press the Undo
button. Each individual key-press creates an insert
Change. But for efficiency (in both memory and speed), these changes can be merged together. So we end up with a single Batch containing a single insert
Change containing the word "Hello".
Merging is only allowed on the last Change of the current Batch.
Merging is only permitted within a short time-window. For example, if you type "Hello", then have some coffee, then type " World", the result is two Batches, and you can undo/redo each individually. mergeTimeThreshold defaults to 10,000 milliseconds (10 seconds). As this is an arbitrary value, TextDocument
s bind this to GlokSettings.historyMergeTimeThresholdProperty
.
Change Implementations
Each concrete implementation of HistoryDocument must be able to create Change objects, but the public API must NOT expose the ChangeImpl interface. Only History should have access to the change's undo()
and redo()
methods. Calling these from anywhere else will break the undo/redo mechanism.
Class Diagram
┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ ┏━━━━━━━━━━━━━━━━━━━━━┓
┆ HistoryDocument ┆ ┃ History ┃
┆ ┆ ┃ ┃
┆ history ├───────────┨ document ┃ ┏━━━━━━━━━━━━━━━━━━━━━┓
┌──▶┆ ┆ ┃ undoable : Boolean ┃ ┃ Batch ┃
│ ┆ ┆ ┃ redoable : Boolean ┃◆────────┨ ┃
│ └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ ┃ isSaved : Boolean ┃ ┃ ┃
│ ┃ ┃ ┃ ┃
│ ┃ undo() ┃ ┌───◆┃ ┃
│ ┃ redo() ┃ │ ┗━━━━━━━━━━━━━━━━━━━━━┛
│ ┃ saved() ┃ │
│ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ ┃ ┃ │
│ ┊ Change ┊ ┗━━━━━━━━━━━━━━━━━━━━━┛ │
│ ┊ ┊ │
└──┤ document ├──────────────────────────────────────┘
┊ ┊
┊ ┊
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
△
│
┌┄┄┄┄┄┄┄┄┄┴┄┄┄┄┄┄┄┄┄┄┐
┊ ChangeImpl ┊
┊ ┊
┊ ┊
┊ undo() ┊
┊ redo() ┊
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
Notes
History is used by Glok's TextArea
and StyledTextArea
, but it can also application developers for their own documents. It is intended to be general purpose, and includes features which glok-core
doesn't use (such as SkippableChange).
History is part of the glok-model
sub-project (not glok-core
), so that application developers can use this history mechanism within their model, with no dependency on glok-core
. For example, suppose I write a diagram editor, with classes Diagram
and DiagramView
. The Diagram
class has no GUI component, and therefore shouldn't depend on glok-core
.
Properties
The time between changes in milliseconds, where changes can be merged. The default is 10,000 milliseconds (10 seconds). i.e. if we type with less than 10 seconds between keystrokes, the changes will be merged to form a single batch, so undo/redo will not operate one character at a time.
Functions
Updates isSavedProperty.