Undo - System vs Custom

Ok… as we are all aware, various controls such as TextField and TextArea are automatically hooked to the internal UNDO manager, and can be implemented with Cmd/Ctrl-Z or the appropriate menu item.

However, I have a need to go a bit beyond that.
The app I’m working on is for lack of a better description a drawing program. Object are placed on the screen, moved around, changed in size and shape, have colors added or changed as well as managing various other attributes. Each one of those changes is implemented via an SQL statement, and a corresponding “UNDO” version of the statement is stored in a table.

Here is the “problem”

  • How can I tell if the UNDO key/menu is going to UNDO a TextField/Area change or not
  • If it is not, then I need/want to execute the last series of UNDO statements that have been stored

At what point does Xojo/macOS clear the UNDO buffer for a control? LostFocus?

Probably the easiest way, if I remember correctly, is catching these events manually with a MenuHandler on the window for Copy, Paste, Undo, Select All, etc. Here’s how I’ve done it to test:

Function EditCopy() As Boolean if me.Focus isa Canvas then '// Custom undo code here MsgBox( "Canvas" ) Return True elseif me.Focus isa TextField then MsgBox( "TextField" ) Return True end if End Function

I would store the objects of the canvas in a JSON or XML file. Every time the canvas is changed, a new version of the JSON is generated. You can save the older versions in an array. After finishing the changes, the JSON can be saved to the DB (or also while editing).

All you need is a JSON-Reader. On Startup you load the JSON from the DB. If you want to undo an action, just load the last JSON version from the array.

So you don’t have to write undo SQL statements, which can fastly be crappy, if you update your code later.

[quote=367094:@Lars Lehmann]I would store the objects of the canvas in a JSON or XML file. Every time the canvas is changed, a new version of the JSON is generated. You can save the older versions in an array. After finishing the changes, the JSON can be saved to the DB (or also while editing).

All you need is a JSON-Reader. On Startup you load the JSON from the DB. If you want to undo an action, just load the last JSON version from the array.

So you don’t have to write undo SQL statements, which can fastly be crappy, if you update your code later.[/quote]
Thanks… but not an answer to the question… and by no means a viable alternative to my current method, as I had already used both those methods in previous versions of this app and both had major issues which so far the SQLite method does not…

As to an UNDO solution… not wanting to UNDO EVERYTHING… just last move, edit or delete (and a delete may consist of actually “deleting” dozens of objects)…

but thanks anyways

[quote=367075:@Anthony Cyphers]Probably the easiest way, if I remember correctly, is catching these events manually with a MenuHandler on the window for Copy, Paste, Undo, Select All, etc. Here’s how I’ve done it to test:

Function EditCopy() As Boolean if me.Focus isa Canvas then '// Custom undo code here MsgBox( "Canvas" ) Return True elseif me.Focus isa TextField then MsgBox( "TextField" ) Return True end if End Function [/quote]

I will experiment with this… .however should’nt it be “RETURN FALSE”
IF ISA TEXTFIELD OR TEXTAREA?

You’re right. I can never remember, and I tossed that together in just a few moments. It should be:

[code]Function EditCopy() As Boolean
if me.Focus isa Canvas then
'// Custom undo code here
MsgBox( “Canvas” )
Return False
elseif me.Focus isa TextField then
MsgBox( “TextField” )
Return False
end if

Return True
End Function[/code]

That seems to be the direction I need… thanks
now how to tell if a TextArea/Field actually has UNDO information?
So EnableMenuItems can be set properly

dim flag as boolean
if me.focus is textfield or me.focus isa textarea then 
// flag=???????
else
   flag=myundoStack.unbound>0
end if
editundo.enabled=flag

As far as implementing an undo stack, I normally use dictionaries or a custom class I call “UndoItem”, and an array of these inside a class I call “UndoStack”, which does most of the heavy lifting. It’s not an incredibly simple thing to implement, but can be done well.

My UndoItem usually looks something like this:
Property Target as Control
Property Content as Variant
Property Metadata as Dictionary

In Metadata I’ll track things like SelStart and SelLength for text fields, as an example.

Another direction you could go would be to subclass those classes you want to have a custom Undo stack implementation, and have them manage all of that individually. With my previously posted approach you need to iterate over all undo actions in the stack to determine if the target control is dirty.

In short, there’s a million different ways this could be implemented.

My undo stack is much different as it it not undoing “text” (I leave that to the internal Undo manager)

If my app changes the color of an object it might execute this SQL

UPDATE attributes SET color='RED' where objectID=14

at the same time it STORES this statement on the UNDO stack

UPDATE attributes SET color='BLUE' where object=14 // assuming CURRENT color is in fact 'BLUE'

each statement also has an UNDO_GROUP number so that if necessary a group of statments can be “undone” at once

The entire databacking system is a relational SQLite database

This is the third rewrite of the app, where previous versions did use XML, JSON and/or custom classes, and the scalablity suffered, as did the performance (an in-memory SQLite database is very fast :slight_smile: )

Sounds good.