Boudewijn Rempt
|
Warning |
I mentioned earlier, in the Section called QColor in Chapter 10, that the nice people at Trolltech changed the name of the function that is used to set background colors from setBackgroundColor to setEraseColor. This means of course that you, if you want to run this example with PyQt 3, will have to adapt the relevant calls. |
class DocviewView(QWidget): def __init__(self, doc, *args): apply(QWidget.__init__, (self, ) + args) self.doc = doc self.connect(self.doc, PYSIGNAL("sigDocModified"), self.slotDocModified) self.slotDocModified(self.doc.isModified()) def slotDocModified(self, value): if value: self.setBackgroundColor(QColor("red")) else: self.setBackgroundColor(QColor("green"))
The document has to notify the view of changes. This means that the view has to have slots corresponding to all the document signals the view is interested in. A view can thus show changes to the document selectively, and you can create more than one view, each with a specialized function.
The DocviewApp is the controller component. It controls both view and document.
class DocviewApp(QMainWindow): def __init__(self, *args): apply(QMainWindow.__init__,(self, ) + args) self.initActions() self.initMenuBar() self.initToolBar() self.initStatusBar() self.initDoc() self.initView()
The controller keeps a dictionary of actions, making it easier to refer to those actions when populating the menu and toolbars. The dictionary can also be used to export functionality for a macro language, by calling the QAction.activated() slot, which is connected to the relevant slots in the controller. The pixmap is in the form of an inline XPM image, which is not shown here.
def initActions(self): fileQuitIcon=QIconSet(QPixmap(filequit)) self.actions = {} self.actions["fileQuit"] = QAction("Exit", fileQuitIcon, "E&xit", QAccel.stringToKey("CTRL+Q"), self) self.connect(self.actions["fileQuit"], SIGNAL("activated()"), self.slotFileQuit) self.actions["editDoc"] = QAction("Edit", fileQuitIcon, "&Edit", QAccel.stringToKey("CTRL+E"), self) self.connect(self.actions["editDoc"], SIGNAL("activated()"), self.slotEditDoc)
Populating toolbars, menubars and statusbars are always a bit tedious. When BlackAdder is integrated with Qt 3.0, it will be possible to design not only dialogs and widgets, but also menu's and toolbars using a very comfortable action editor. I will discuss the various aspects of creating toolbars and menubars later in Chapter 13.
def initMenuBar(self): self.fileMenu = QPopupMenu() self.actions["fileQuit"].addTo(self.fileMenu) self.menuBar().insertItem("&File", self.fileMenu) self.editMenu = QPopupMenu() self.actions["editDoc"].addTo(self.editMenu) self.menuBar().insertItem("&Edit", self.editMenu) def initToolBar(self): self.fileToolbar = QToolBar(self, "file operations") self.actions["fileQuit"].addTo(self.fileToolbar) QWhatsThis.whatsThisButton(self.fileToolbar) def initStatusBar(self): self.statusBar().message("Ready...")
Here the document, or application model, is initialized.
def initDoc(self): self.doc=DocviewDoc()
The view is created after the document, and then made into the central application widget.
def initView(self): self.view = DocviewView( self.doc, self) self.setCentralWidget(self.view)
This function is called in the slotFileQuit() slot when the document has been modified. Note that we're using a class function, information, from QMessageBox. By passing an empty string after the button labels for "Ok" and "Cancel", the messagebox is created with only two buttons, instead of three.
def queryExit(self): exit = QMessageBox.information(self, "Quit...", "Do you really want to quit?", "&Ok", "&Cancel", "", 0, 1) if exit==0: return TRUE else: return FALSE
The slot functions are called whenever one of the QActions is activated(). Note how the statusbar message is set, before calling the document functions directly.
# # Slot implementations # def slotFileQuit(self): self.statusBar().message("Exiting application...") if self.doc.isModified(): if self.queryExit(): qApp.quit() else: qApp.quit() self.statusBar().message("Ready...") def slotEditDoc(self): self.doc.slotModify() def main(args): app=QApplication(args) docview = DocviewApp() app.setMainWidget(docview) docview.show() app.exec_loop() if __name__=="__main__": main(sys.argv)
This is the stub that starts the application. In contrast with the examples from Part I, such as hello5.py, this framework doesn't check if all windows are closed with:
app.connect(app, SIGNAL("lastWindowClosed()") , app, SLOT("quit()"))
This is because the framework supports only one window, and quitting the app is integrated in the DocviewApp class.
Now the startup bit is done, we can see what docview.py produces when it is run:
A very simple document-view framework application
This framework only supports one window with one view and one document. Another omission is that there is no interaction between view and document. Usually, you will also allow the view component to receive user actions, like mouse clicks. These mostly arrive in the form of events. You can handle these in various ways. The first is to directly call the relevant slot functions in the document. Try adding the following method to the DocviewView class:
def mouseDoubleClickEvent(self, ev): self.doc.slotModify()
This bypasses the controlling application (DocviewApp) and leads to an uncomfortably tight coupling between view and document. Another way to notify the document of the double-click is to let the view emit a signal, which can be caught by the application object and connected to the document slot. Replace the previous function with the following function in the DocviewView class instead:
def mouseDoubleClickEvent(self, ev): self.emit(PYSIGNAL("sigViewDoubleClick"),())
And to the DocviewApp:
def initView(self): self.view = DocviewView( self.doc, self) self.setCentralWidget(self.view) self.connect(self.view, PYSIGNAL("sigViewDoubleClick"), self.slotEditDoc)
As you can see, you can either call the document directly from the view, or via the application controller. The approach you choose depends on the complexity of your application. In the rest of this part we will extend this simple framework to include MDI (multiple document interface) and MTI (multiple top-level windows interface) applications.