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 |
|
|
|
possible |
|
|
|
|
|
(needs work) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
See Also
Coreobject
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.