Undo, redo and other editing functions
The editing component we are using,
QMultiLineEdit, already supports undo and
redo using standard keys. Because undo and redo are defined as
slots in the C++ source for
QMultiLineEdit, they immediately affect
the document and all views. The only thing left for us is to add
these functions to the edit menu and the toolbar. The same
principle holds for cut, copy, paste and select all.
The right place for these additions is the
central application class, KalamApp.
However, we need to do more than simply connect the correct
QActions to the relevant slots in the
view's editors. If we do that, then undo, for instance, would
undo the last action in all views, simultaneously! We need to
write special functions in KalamApp that
work only on the active view, and we must wrap the
QMultiLineEdit slots in the view
component.
First the KalamView
wrappings:
def clear(self):
self.editor.clear()
def append(self, s):
self.editor.append(s)
def deselect(self):
self.editor.deselect()
def selectAll(self):
self.editor.selectAll()
def paste(self):
self.editor.paste()
def copy(self):
self.editor.copy()
def cut(self):
self.editor.cut()
def insert(self, s):
self.editor.insert(s)
def undo(self):
self.editor.undo()
def redo(self):
self.editor.redo()
Of course, this initially looks very
silly, and we could just as well directly call the
QMultiLineEdit editor
object variable — but by encapsulating the editor
component we are free to substitute another component without
having to hack the other components of the application.
The other changes are in the
KalamApp class. First, a set of
QActions is added to the dictionary of
actions.
Some of these actions have an associated
toolbar or menubar icon defined. The icon data is defined in the
resources.py file. I've used the GPL'ed
toolbar icons from the KDE project. It is always a good idea to
blend in as closely to the desktop environment you are
targetting, so you might also want to provide a set of Windows
standard icons and make it a configuration option which set
should be used.
I do not show the full code, just the bits
that are new compared to the previous chapter:
def initActions(self):
self.actions = {}
...
#
# Edit actions
#
self.actions["editClear"] = QAction("Clear",
"C&lear",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editClear"],
SIGNAL("activated()"),
self.slotEditClear)
self.actions["editSelectAll"] = QAction("SelectAll",
"&SelectAll",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editSelectAll"],
SIGNAL("activated()"),
self.slotEditSelectAll)
self.actions["editDeselect"] = QAction("Deselect",
"Clear selection",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editDeselect"],
SIGNAL("activated()"),
self.slotEditDeselect)
self.actions["editCut"] = QAction("Cut",
QIconSet(QPixmap(editcut)),
"C&ut",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editCut"],
SIGNAL("activated()"),
self.slotEditCut)
self.actions["editCopy"] = QAction("Copy",
QIconSet(QPixmap(editcopy)),
"&Copy",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editCopy"],
SIGNAL("activated()"),
self.slotEditCopy)
self.actions["editPaste"] = QAction("Paste",
QIconSet(QPixmap(editpaste)),
"&Paste",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editPaste"],
SIGNAL("activated()"),
self.slotEditPaste)
self.actions["editInsert"] = QAction("Insert",
"&Insert",
QAccel.stringToKey(""),
self)
self.connect(self.actions["editInsert"],
SIGNAL("activated()"),
self.slotEditInsert)
self.actions["editUndo"] = QAction("Undo",
QIconSet(QPixmap(editundo)),
"&Undo",
QAccel.stringToKey("CTRL+Z"),
self)
self.connect(self.actions["editUndo"],
SIGNAL("activated()"),
self.slotEditUndo)
self.actions["editRedo"] = QAction("Redo",
QIconSet(QPixmap(editredo)),
"&Redo",
QAccel.stringToKey("CTRL+R"),
self)
self.connect(self.actions["editRedo"],
SIGNAL("activated()"),
self.slotEditRedo)
As you can see, there is still a fair
amount of drudgery involved in creating a GUI interface. Qt 3.0
provides an extended GUI Designer that lets you design actions,
menubars and toolbars with a comfortable interface.
For now, we'll have to distribute the
actions by hand in the initMenu() and
initToolbar() functions. Again, omitted
code is elided with three dots (...).
def initMenuBar(self):
...
self.editMenu = QPopupMenu()
self.actions["editUndo"].addTo(self.editMenu)
self.actions["editRedo"].addTo(self.editMenu)
self.editMenu.insertSeparator()
self.actions["editCut"].addTo(self.editMenu)
self.actions["editCopy"].addTo(self.editMenu)
self.actions["editPaste"].addTo(self.editMenu)
self.actions["editSelectAll"].addTo(self.editMenu)
self.actions["editDeselect"].addTo(self.editMenu)
self.actions["editClear"].addTo(self.editMenu)
self.menuBar().insertItem("&Edit", self.editMenu)
...
def initToolBar(self):
...
self.editToolbar = QToolBar(self, "edit operations")
self.actions["editUndo"].addTo(self.editToolbar)
self.actions["editRedo"].addTo(self.editToolbar)
self.actions["editCut"].addTo(self.editToolbar)
self.actions["editCopy"].addTo(self.editToolbar)
self.actions["editPaste"].addTo(self.editToolbar)
...
Finally, we have to define the actual
slots called by the QAction objects. Note
that we are not working directly on the document — if we
did, then all actions (such as selecting text) would apply to
all views of the document. We would also have to code an
undo-redo stack ourselves. Instead, we retrieve the active view
from the workspace manager, and work on that. This view will
pass the command on to the QMultiLineEdit
object, and propagate all changes to the relevant
document.
# Edit slots
def slotEditClear(self):
self.workspace.activeWindow().clear()
def slotEditDeselect(self):
self.workspace.activeWindow().deselect()
def slotEditSelectAll(self):
self.workspace.activeWindow().selectAll()
def slotEditPaste(self):
self.workspace.activeWindow().paste()
def slotEditCopy(self):
self.workspace.activeWindow().copy()
def slotEditCut(self):
self.workspace.activeWindow().cut()
def slotEditInsert(self):
self.workspace.activeWindow().insert()
def slotEditUndo(self):
self.workspace.activeWindow().undo()
def slotEditRedo(self):
self.workspace.activeWindow().redo()