UndoEngine

The UndoEngine allows you to install Multiple Undo in your Application. It is RAM-effective, because it saves only the items that really have changed. It is objectoriented, as it communicates directly with the objects of your application. And it is also an example on how to use Class Interfaces. As a plus, provides you a method to store your document in a file.

Download class and sample project
http://www.belle-nuit.com/download/undoengine.sit.hqx

UndoEngine works with REALBasic 2.1 or later. In conjunction with the Undoable Object Interface Class it is self-contained and works with both 68k and PowerPC. It should work on Windows, but it hasn't been tested for.

How it works

Install the UndoEngine class and the UndoableObject Class interface in your project.

Define every class you want to be able to undo as subclass of the UndoableObject Class Interface. (If you are not already working with subclasses of the windows controls, subclass them, so that you can make them subclasses of the Undoable Object Class Interface.)

Every class has to provide two methods:

Backup() as RAMstream

Write a function which collects the values of your object before they have changed. You may have to define and use oldvalue-properties on every changement because the user may have changed the value before you can call the undo-backup.

Look at the RAMstream-page to know more about RAMstream.

Restore(bak as RAMstream)

Write a method which reassigns the values from the RAMstream to the object.

Install an instance of the UndoEngine on the document window or in the application subclass.

Methods

UndoEngine(max as integer)

Defines the number of multiple undos. Max must not be 0 or negative.

Backup(label as string, obj as UndoableObject)

Undo

Redo

UndoLabel() as string

Returns the label which was provided when the Backup method was called. Is emty, if Undo is not available.

RedoLabel() as string

Returns the label which was provided when the Backup method was called. Is emty, if Redo is not available.

Reset

Resets the undo. You may choose this method when saving your document, if you want to prevent the user to undo.

Function About() as string

Returns an about-string of the class.

Function Methods() as string

Returns a string with the definitions of the methods.

Function Properties() as string

Returns a string with the definitions of the properties.

Function Version() as string

Returns the current version of the class.

Properties

This class does not have any property. Providing it as a class however frees you of problems of namespace in your application. If you need to access to it from the whole program, make it a property of your app-subclass. In a multiple-document Application, however, you may want to install the class on the document level.

Technical comments

This class uses RAMstreams for different reasons:

  • I wanted to have the same datastructure to be able to use the undo and to save my document.
  • I needed fexible access to strings of different length, so memoryblocks were not flexible enough for me.
  • I wanted to keep it fast so strings weren't the solution either. Also, it should be able to store integers.
  • I didn't want to write to a file because this creates problems with object reference and deleting of obsolete undo steps.

On demand, I could provide a class which works with strings or with memoryblocks.

The sample project shows also an example for undoing an editfield. If you are working on bigger texts, backup and restore the whole text of the editfield may not be the fastest solution. You may want to design a backup function which only saves the changes and a restore method which is able to reconstruct the editfield.

If you create and destroy instances dynamically, you may want to backup at the creation and destruction not the created object but the holder of the object. For example, if you have a drawing application with circles and rectangles, you may backup the circle when it changes its size and its color, but the window, when the circle is created or destroyed. Also, when the user changes multiple circles (a selection) at once, you may back up more the window than one circle at a time. Be aware also, that destroyed objects will not be destroyed immediately, because they have a reference, as long as they are undoable.

The sample application

For every class we use (or subclass of a generic rectcontrol) we implement the backup and restore methods. Also, we need a UndoEngine instance in the Window

Now the real work begins. Everytime, when an object is about to change, we are calling the UndoEngine for a backup. The problem is that we have to intercept before the parameters of the object have been changed. There are different cases

  • -In the canvas color select example, the programmer has full control about the values. It opens the colorselect dialog, and when the user did not cancel, we backup the old color and set the new color.
  • In the slider example, the value has already changed, when the valuechanged event is called. So we have to create an oldvalue property, back up this one and set the oldvalue to the new value. Same goes for the checkbox, and would be for a popup-menu, a radiobutton etc.
  • In the window-example (resize and move) we cannot create a subclass, but we can create an abstract windowundo class which has one instance in the window.
  • The editfield is most tricky. We could do it like the REALbasic IDE and backup at every text change. Most text editors however do not behave like this. aAl keyboard writing in one run is considered as one undo. So we have to backup in TextChange, SelectChange, but also in LostFocus. The presenting example always stores the whole text, the SelStart and the SelLength. This works good for short text. For longer text it may be more effective to backup only the changements. (Here again, the UndoEngine is transparent. It does not interpret the provided backup. So if the editfield will implement a better Backup/Restore, this will not change the whole undo process.)

The Backup/Restore methods can be used for the document structure. On saving, a method collects all RAMstreams with backup and writes them to a binarystream. and on opening, a method distributes the RAMstreams and distributes them to the objects. The RAMstreams are saved as tagged records with the following structure

  1. A unique four caracter identifier of the object
  2. A long integer defining the length of the following RAMstream
  3. The RAMstream itself

This method gives you flexibility in further extensions of the document structure. File readers won't expect a particular order of the object being saved. Also, file readers can easily ignore the record if they can't handle the object.

Terms of use / Disclaimer

© Belle Nuit Montage / Matthias Bürcher April 2000. All rights reserved. Written in Switzerland.

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Comments please to matti@belle-nuit.com

The latest version is available at http//www.belle-nuit.com/

History

8.6.00 Released as open source

6.5.00 Sample project updated with version 1.1 of RAMstream

22.4.00 Release UndoEngine 1.0