Skip to content

Calligra Words: undo/redo framework

Thursday, 23 February 2012  |  pstirnweiss

As C.Boemann already said, we met at my place for two days in order to fix some serious issues we had with the undo/redo framework in Calligra Words (and Stage for that matter).

The undo/redo framework is something I wrote when I first started contributing to KOffice about 3 years ago. I have to say that I was not really looking forward having to jump into this stuff again. I am not such a masochist and the memories I have of writing this are not ones of an easy glide. It actually turned out to be really fun and gratifying. There were some headaches involved to be sure but overall I really enjoyed it.

To summarise a bit (more detailed description bellow for the hard hearted): we use Qt Scribe framework for our document. When an edition is done on a QTextDocument, it emits a signal telling that an undo/redo action was added on the QTextDocument internal undoStack. The application listens to this signal and can create an undo action on its stack to match QTextDocument's internal one. The initial framework I created basically followed that behaviour. There was one thing that it was never meant to handle: nested commands. This means that when nesting commands, like for example the delete command now contains a deleteInlineObject command, the framework would create 2 commands on the application's stack.

So we sat with Boemann thinking how to solve that problem. In the end we only needed to add, instead of a single head command member in the framework, a stack of head commands. Now we have a framework which is way more complete and solid, plus it is now documented in the code. There are some improvements we could already think of to make the API a bit more flexible, but we can be confident now that we have solid foundations for the upcoming Calligra Words releases.

Overall, I had a really good time coding with C.Boemann, who is not only a very talented coder but also somebody I really appreciate. It is amazing to see what we achieved in those just two days.

A bit more details:

As I said, we use QTextDocuments to hold the data of one text frame (text shapes). This document is edited through a specific handler: a KoTextEditor. This editor is not only responsible for editing the QTextDocument but also to listen to the QTextDocument's undoCommandAdded signal and keep our application's stack in sync. There are 3 use cases of our undo/redo framework:

  • editing done within the KoTextEditor
  • complete commands pushed on the KoTextEditor by an external tool
  • on-the-fly macro commands

In addition to this, there are two special cases in editing QTextDocument: inserting text and deleting text. These two actions only trigger a signal on the first edit. Any subsequent compatible edit is "merged" into the original edit command and will not trigger a signal. Inserting text and deleting are therefore open ended actions, as far as our framework is concerned.

In order to handle this, a sort of state machine is used. The KoTextEditor can be in a NoOp, KeyPress, Delete, Format or Custom state. Furthermore, for each signal received from the QTextDocument, we create a "dummy" UndoTextCommand, whose sole purpose is to call QTextDocument::undo or QTextDocument::redo. These commands need to be parented to a command which we push on our application's stack. This head command will call undo or redo on all its children when the user press undo or redo in the application. In order to allow for nested head commands, we maintain a stack. Its top-most command will be the parent of the signal induced UndoTextCommands. Depending on the KoTextEditor state and commandStack, new head commands are pushed on the commandStack, or the current top command is popped.

I will not enter into more details here, if you are interested in the whole gory logic of this, you can look at the code in calligra/libs/kotext/KoTextEditor_undo.cpp (which for now lies in the text_undo_munichsprint git branch). The code has now been pretty well documented, something I had not done before.

That's it for today. Once again, I ask from as much of you to try our next test release, specifically the undo/redo framework, so that we can ensure that we release a really good stable Calligra Words.