Undo

Interaction Techniques and Technologies (ITT), SS 2017
Session 20 (18.07.2017), Raphael Wimmer

Overview

These are slides/notes for the lecture, automatically generated from the slide set. Please extend this outline with your own notes.

  • Undo
    • History
    • User Interfaces
    • Implementations
  • partially based on slides by Brad Myers
    Undo: History and Models

Questions:

  • What is undo?
  • How does the user interface for undo look like?
  • How can undo functionality be implemented?

First documented use: Bravo text editor (Xerox Alto), 1974 (user manual)

  • NY Times (quoted by Wikipedia) says shortcut ^Z was selected „by programmers at the research center Xerox PARC“
  • Redo shortcut
    • ^Y in Macintosh
    • ^-SHIFT-Z in some other systems
  • Design Issue: how big a unit to undo?
    • Often typing coalesced into a single operation
    • Multiple backspaces may or may not be
    • Newer: “intelligent” single operations may be divided into multiple undoable operations
    • E.g., Auto-correct in Word

<small>(source: Brad Myers' slide set)</small>

  • All operations are in a history list
  • Can undo backwards
    • Undone operations are put into a redo list
  • Can then redo forwards
  • But once a new command is executed, anything in the redo list is discarded, so there is always only a linear history
  • May have a limited size of the history list
  • Almost all of today’s applications support restricted linear undo model

<small>(source: Brad Myers)</small>

  • Which commands are designed to be undoable, which not?
    • mostly undoable: modify text, delete objects
    • mostly not undoable: save file, select text, send e-mail
  • How are commands handled which are not undoable?
    • exclude from undo stack
  • What happens to clipboard contents?
    • leave clipboard unaffected from all undo operations
  • Problem: undo followed by other operations overwrites part of the linear undo stack
  • Emacs text editor: undo operations are appended to the undo stack similar to normal operations
    • very confusing for new users
  • Vim text editor: undo branches (move along main branch with u and Ctrl-R, move chronologically through all branches via g- and g+)
    • also offers 'persistent' undo by storing all operations in an undofile
  • Let the user select which operation(s) to undo while leaving later operations intact
  • „Script“ model – pretend the operation never happened
    • Can undo all operations to that point, remove the command, then redo all the subsequent commands
    • „Rewrite history“
    • But what if it was a “create” and later operations were “change color”?
      • Not allowed to selectively undo the create?
      • Or later operations are ignored?
      • Not always clear what the user would want
    • Can also support “insert” operation into history
      • If I create an object in the past, do future operations include it?
  • „Inverse“ Model: add inverse operation to end of history

<small>(source: Brad Myers)</small>

<small>(source: Brad Myers)</small>

Practical Implementations

  • remember each state
  • General idea:
    • an originator object has some internal state
    • it can produce a memento object that represents its internal state
    • a caretaker object applies an undo-able action to the originator by asking for a memento object of the current state and only then applying the action
    • to undo the action, the memento object is given to the originator

    * see also: (Wikipedia article)

  • remember each operation
  • General idea:
    • every action is encapsulated in a command object
    • the command object has specific do() and undo() methods
    • to apply an action, do() is called, to revert it, undo() is called
    • an undo stack organizes the sequence of commands
  • similar: diff and patch utilities for code development
  • see also: (Wikipedia article)
  • see also: undo branches, e.g. in vim
  • Qt's Undo framework implements the Command pattern.
  • QUndoCommand (undo-able actions implemented as subclasses)
    • undo() - undoes the action
    • redo() - executes / redoes the action
  • QUndoStack (maintains list of actions that can be undone)
    • push(command) - adds to stack and calls command.redo()
    • undo() - pops last command from stack and calls command.undo()
  • QUndoGroup (route undo()/redo() to QUndoStacks for multiple opened documents)
  • QUndoView (widget that shows a QUndoStack)
  • also: facilities for undoing multiple related actions at once
~~~~ undo.py
#!/usr/bin/env python3
from PyQt5.QtWidgets import QUndoCommand, QUndoStack, QUndoGroup 

class SimpleDocument(object):
    def __init__(self, text=None):
        if text is None:
            self.text = ""
        else:
            self.text = text

class InsertCharacter(QUndoCommand):
    def __init__(self, document, position, character):
        super().__init__()
        self.document = document
        self.character = character
        self.position = position
        self.setText("insert a character")
    def undo(self):
        self.document.text = self.document.text[:self.position] \
                           + self.document.text[self.position+1:]
    def redo(self):
        self.document.text = self.document.text[:self.position] \
                           + self.character \
                           + self.document.text[self.position:]
~~~~
~~~~ undo.py

if __name__ == "__main__":
    stack = QUndoStack()
    d = SimpleDocument("123456")
    stack.push(InsertCharacter(d, 1, "a"))
    # "1a23456"
    stack.push(InsertCharacter(d, 3, "b"))
    # "1a2b3456"
    stack.undo()
    stack.undo()
    # "123456" 
~~~~

ENDE