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()