Help - Undo system

From LibrAlign Documentation

To undo and redo changes in applications based on LibrAlign, several components were created which together make it possible to record changes. Developers can decide which changes should be undoable and which not.


Overview

The undo system in LibrAlign is comprised of three core components:


All three have an important role in the undo system. Edit objects save relevant information for undoing or redoing a change of a model. Different undo listeners are responsible for creating such edit objects. A respective type of undo listener is registered with the alignment model and each associated data model. Each time a change is registered, a respective edit object is created. The EditRecorder receives these edits from the undo listeners and stores them to allow undoing and redoing of all model changes.

Flow diagram of an overview of how the undo system works in LibrAlign. Once a change was made all registered listeners of the changed model are informed. One of them is an undo listener which uses the information about the change to make a fitting edit. This edit is then given to the EditRecorder, which stores it and adds it to the UndoManager. The change can then be undone or redone.

Usage

For users to utilize this undo system in their application they only further need to do the following steps:

// Example for setting up the EditRecorder and UndoListener

PackedAlignmentModel alignmentModel = new PackedAlignmentModel<>(CharacterTokenSet.newNucleotideInstance()), false);
EditRecorder editRecorder = new EditRecorder<>(alignmentModel); // Initiate an EditRecorder object with an alignment model
AlignmentModelUndoListener undoListener = new AlignmentModelUndoListener(editRecorder);
alignmentModel.addModelListener(undoListener); // Add the undoListener to the list of model listeners of the alignment model
  • For changes that should be undoable use the startEdit() method of this EditRecorder before causing the change
  • After the change was made use the endEdit() method of this EditRecorder to stop recording changes
// Example for starting and ending the recording of the EditRecorder
getEditRecorder().startEdit(); // Starting to record changes/edits
alignmentModel.addSequence("sequence name"); // This is the change
getEditRecorder().endEdit("Added a sequence."); // Recording is stopped and the change is added to the UndoManager
  • When undoing or redoing a change, get the UndoManager of the EditRecorder and use the undo or redo method
// Example for undoing a change
if (getEditRecorder().getUndoManager().canUndo()) { // Check if there is a change that can be undone
getEditRecorder().getUndoManager().undo(); // Undo the change
}

// Example for redoing a change
if (getEditRecorder().getUndoManager().canRedo()) { // Check if there is a change that can be redone
getEditRecorder().getUndoManager().redo(); // Redo the change
}

Edits

Edits are important objects which save important information about the change that was made. This information is necessary in order to redo or undo changes. For example, if the name of a sequence was changed, the edit object would save the old name of the sequence. This way the old name is not lost and can be restored. There are different edits in LibrAlign for each kind of change that was made to different models. Each model has its own assortment of edits. For example, the character set data model has a method to change the color of character sets. Therefore, an edit object for character set data models exists that saves the old and new color and which character set was changed.

All edits have two very important methods called undo() and redo(). These methods are later used by the UndoManager in order to undo or redo a change. undo() and redo() call respective model method to either be undo or redo the change.

The inheritance of edits is as follows:

UML class diagram of the edits available in LibrAlign. All classes with undo() or redo() methods are the final edits that are used to undo or redo changes. This is a short version as there are more edits than there is space here.

EditRecorder

The EditRecorder' is used to record and combine edit objects. Recording can be turned on and off with the use of the two methods EditRecorder.startEdit() and EditRecorder.endEdit(). This is necessary to combine several related edits to a single operation (e.g., combining the token edits that are made to reverse complement a sequence). Turing recording off is also relevant to avoid recording edits another time during the execution of the undo() or redo() methods.

In order to record changes using edit objects the method EditRecorder.startEdit() needs to be called. EditRecorder.startEdit() makes it possible to record all coming changes/edits, which are saved in a list. With EditRecorder.addSubedit() new edit objects of changes are automatically added to the list. Once all the changes were executed the method EditRecorder.endEdit() has to be used to end the recording. EditRecorder.endEdit() combines all saved edits and adds them to the UndoManager's undo history of the EditRecorder.

UndoListeners

Undo listeners automatically create fitting edit objects when a change was made to the model they listen to. When a change was made to a model with an UndoListener, the Undolistener creates an edit object which fits the made change. This edit object is then added to the EditRecorder with the method EditRecorder.addSubedit(). In LibrAlign exists one undo listener for the alignment model and each one specific undo listener for every supported type of data model. Two types already exist in LibrAlign and more can be added by the user. All types of UndoListener need an EditRecorder reference to be constructed.

Currently, LibrAlign has these undo listeners:

Each UndoListener has relevant methods for their respective model which create the edit objects. For applications of LibrAlign it is only necessary for the user to initiate an AlignmentModelUndoListener object and to add it to the other model listeners of the alignment model (see Usage above). The other UndoListeners do not need to be initiated as well because the AlignmentModelUndoListener automatically registers a respective undo listener to newly added DataModel objects (For a character set data model a CharSetDataModelUndoListener etc.). This done with the method DataModel.ensureUndoListener() which uses the implemented methods hasUndoListener() and createUndoListener() from either PherogramAreaModel or CharSetDataModel, depending on which type of DataModel was added, to create a fitting undo listener. hasUndoListener() checks that no undo listener for this model was created yet and createUndoListener() then initiates the fitting undo listener and returns it.

Flow diagram showing simplified how the AlignmentModelUndoListener registers undo listener to new data models. It uses the implemented methods from DataModel to create a fitting undo listener and to register it to the new data model.

Should a user want to add a new type of model that can use these undo functions, then the following steps are needed:

  • Implement DataModel in the new model
  • Implement the methods hasUndoListener() and createUndoListener() in the new model
  • Make a new undo listener class with the necessary methods and initiate it with createUndoListener() in the new model
  • If existing edits do not fit to the new model then create fitting new edit classes

After the implementation of the methods hasUndoListener() and createUndoListener(), a new undo listener should be automatically registered every time the new model is added to the alignment model.