Setting up the History
This chapter the basic principle to setup an history for a document.
Overview
History provides undo/redo features and more advanced ways to manipulate history. An history is a record of transactions to modify a document. Each transaction added to the history defines an undo step.
The History
class provides this feature. It is linked to a document on which it will operate, but marking undo steps and manipulating history is done programatically.
A document might be managed by multiple History
. An History
will store each undo step using an history store. Some stores are volatile while some stores are persistent.
Contructing an History
instance
History
are constructed by providing the document to which they are attached to. The History
itself is a template
and takes a template parameter which is the actual implementation of history store to use.
The following piece of code defines a volatile history for the document.
#include "flip/History.h" |
#include "flip/HistoryStoreMemory.h" |
Document document (Model::use (), 123456789UL, 'appl', 'gui '); |
History <HistoryStoreMemory> history (document); |
The following piece of code defines a persistent history for the document which survives the application life-span. The supplementary argument of the constructor is the path to the file which will store history.
#include "flip/History.h" |
#include "flip/contrib/HistoryStoreFile.h" |
Document document (Model::use (), 123456789UL, 'appl', 'gui '); |
History <HistoryStoreFile> history (document, "path/to/history"); |
Basic Manipulation
When you change the document, Flip will know that it was modified. Transactions represent the difference between two document states. When the document is modified, Flip internally keeps the old and new state of the document.
The history itself does not contain the whole state of the document, but rather the difference between two document states. In this way flip allows for a memory-performant managment of history.
The following piece of code shows how to add an undo step.
Document document (Model::use (), 123456789UL, 'appl', 'gui '); |
History <HistoryStoreMemory> history (document); |
Song & song = document.root <Root> (); |
song.tracks.emplace (song.tracks.end ()); |
auto tx = document.commit (); (1) |
history.add_undo_step (tx); (2) |
history.execute_undo (); (3) |
document.commit (); (4) |
history.execute_redo (); (5) |
document.commit (); |
- Commit the document and get the transaction representing the difference of states
- Add this transaction as an undo step for the history
- Execute undo, this puts the document back to the state before the first commit
- The document is modified but not commited. This allows for more changes before commit
- Execute redo, this puts the document back to the state after the first commit
Disabling Part of the Document in Undo
One might want to keep some actions as not part of the undo system. To do so one can mark objects to be not part of the undo system, so that Flip won't try to rollback their state during an undo.
Additionally, transactions which only contains operations on objects that are marked as not being part of the undo system will be ignored by the history system.
The following piece of code shows how to disable an object from undo.
class UserData : public Object { |
public: |
Float scroll_position_x; |
Float scroll_position_y; |
}; |
Document document (Model::use (), 123456789UL, 'appl', 'gui '); |
History <HistoryStoreMemory> history (document); |
Song & song = document.root <Root> (); |
song.user_data.disable_in_undo (); (1) |
song.user_data.scroll_position_x = 120; |
song.user_data.scroll_position_y = 140; |
auto tx = document.commit (); |
history.add_undo_step (tx); (2) |
song.tracks.emplace (song.tracks.end ()); |
song.user_data.scroll_position_x = 100; |
song.user_data.scroll_position_y = 100; |
tx = document.commit (); |
history.add_undo_step (tx); (3) |
history.execute_redo (); (4) |
document.commit (); |
- Disabling propagates to the object subtree, including
scroll_position_x
andscroll_position_y
- The transaction is therefore empty as seen from the History point of view. No undo point is created.
- The transaction is seen as only a track add from the History point of view.
- Therefore at this stage, the track is removed but the
scroll_position_*
value is100
Disabling is a property evaluated at commit
time. It is possible to change the undo recording state of an object and its subtree dynamically, using the respective disable_in_undo
and inherit_in_undo
appropriately.
Visiting the History
The History
can be seen as a container of transactions. As such, History
provides the functions begin
and end
which allows to iterate through the history to discover transactions.
History
also provides the function member last_undo
which points to the last undo step, that is the transaction executed when execute_undo
is called.
Similarly, History
also provides the function member first_redo
which points to the first redo step, that is the transaction executed when execute_redo
is called.
As such, iterating from begin
to end
will cover the all history, and the iterators last_undo
and first_redo
are in the history as long as an undo step exists or a redo step exists.
This architecture allows to provide an history browser, but also to modify the transactions in the history. This technique can be useful in a variety of situations which will be exposed in a later chapter of this book.
Rewriting History
History
allows to be rewritten which can be useful in some situations. History
provides the function member erase
to remove a specific transaction from the history.
The next chapter, Handling a Simple Modification will show a strategy to use for undo/redo in the immediate case.