Brainstorm: Generic Undo Stack Management for GTK+

This is a brainstorming page of how a generic undo/redo stack manager might fit into GTK+.

Tracking bug 322194.

Motivation

  • Be as forgiveful to user mistakes as possible. The GNOME HIG mentions undo as a tool to achieve that goal in several places (e.g. section 1.8, section 4.4.2.1)

  • Aza Raskin wrote a nice article called Never Use a Warning When you Mean Undo highlighting how important undo capabilities are, and how undo can help to avoid nagging the user with warning popups

  • Having generic undo stack management deep in the platform would have several advantages
    • consistent behaviour (and maybe look, for stack-inspection widgets)
    • save people from having to write the boilerplate code in their applications
    • hopefully motivate people to offer undo capabilities in their applications
    • hopefully motivate further undo capabilities in GTK+ itself

Overview

A number of GNOME and GTK+ applications already ship their own undo/redo stacks (e.g. GEdit, Tomboy, Dia), others are currently in the process of implementing their own (e.g. Nautilus).

Some toolkits offer generic stack mangement (e.g. Qt). There are at least two implementations based on GObject:

Comparison

Implementations are compared with respect to the following criteria:

Please feel free to add other design criteria here.

  • no objects: Whether or not elements of the undo stack must be derived from a specified base object or interface

  • limitable: Stack may or may not have a size limit

  • can fail: Individual undo/redo operations may fail

  • cleanable: Whether elements of the undo stack can be selectively removed from the stack. (Use case: A plugin adds elements to an undo stack, and then gets unloaded.)

  • groups: Stack supports grouping of individual elements into one undo/redo unit

  • nested groups: These groups can be nested (so the stack is in fact a tree, not a flat list)

  • descriptions: Each undo unit may have a textual, human readable description

  • signals: Important events (can-undo, can-redo, ...) are broadcasted as signals

  • actions: Stack offers convenience actions for undo/redo

  • widget: There is a widget to display the undo (and possibly redo) stack

  • compression: Elements may be compressed as they are pushed onto the stack

  • clean state: Stack tracks whether the current document has been saved to disk

  • stack groups: Manager for multiple stacks that forwards actions to the right undo stack

Please feel free to add other toolkits here, or implementations in other programs.

Implementation

no objects

limitable

can fail

cleanable

groups

nested groups

descriptions

signals

actions

widget

compression

clean state

stack groups

gtk+ branch

(./)

(./)

(./)

possible

(./)

(./)

(./)

(./)

(./)

(./) (needs work)

{X}

{X}

{X}

GUndo

(./)

{X}

{X}

{X}

(./)

(./)

{X}

(./)

{X}

(./)

{X}

{X}

{X}

Qt

{X}

(./)

{X}

possible

(./)

(./)

(./)

(./)

(./)

(./)

(./)

(./)

(./)

Proposal

Well, since I wrote the undo stack in the GTK+ branch linked to above, the feature set I consider important can be read from above table, the API can be inspected here, and very basic example usage can be found here. --HolgerBerndt

TBD

Where would it reside?

As undo can also be useful to non-gui applications, it would be advantageous to have the undo stack in glib (the gio module, as it depends on gobject). On the other hand, letting the undo stack offer GtkActions for undo and redo is convenient, but pulls a GTK+ dependancy.

TBD

Undo support inside GTK+ itself

Ideally, having a generic undo stack available would motivate to support undo/redo inside GTK+ itself where it makes sense. Example classes where that might be useful include GtkTextBuffer and GtkEntryBuffer. They could store a GtkUndo* stack in their properties, and push modifications into that stack if it is non-NULL.

TBD

Discussion

I would suggest not building in a specific undo approach to GTK (or GLib however). Rather, provide an interface for which applications can provide their own undo implementation, and GTK providing its own private implementation by default.

Different applications will have vastly different use cases. For example, some may want to include GtkEntry undo in their app-wide undo stack, some may want to have a per-window stack or even per-domain-object stacks, others may want every GtkEntry to have its own independent undo stack, etc etc. Some applications may require support for async or can-fail undo, many may not.

So having a one-size-fits-all approach isn't going to work well in many cases, but having some default support will get many people 80% of the way there, and having an interface means the others can provide their own custom implementation.

-- MichaelGratton


See Also

Coreobject

http://coreobject.org/

Video Demos

browser.html Trails

Good ux example of how persistent graph based undo could be utilized as alternative to tabbed interface.

* medium post explaining trails

Geary Command and CommandStack

Geary has https://gitlab.gnome.org/GNOME/geary/blob/mainline/src/client/application/application-command.vala that already meets most of the criteria above, with more improvements to land for 3.36, and is not Geary-specific.

Aside from no objects (which is an explicit non-goal since Vala makes writing objects trivial), by 3.36 all of the above criteria will be supported or implementable. Further, the implementation is fully async-based, making it suitable for applications that need to work with I/O, not just widget updates.

Projects/GTK/Undo (last edited 2019-11-05 01:24:05 by MichaelGratton)