A more complex view management solution

In the previous section I suggested that it might be nice to code up a view manager where the list of open views was shown in a listbox on the left side. I could have have left this for you to do, but I couldn't resist.

"""
listspace.py - stacked view manager with a list for the mdi framework

copyright: (C) 2001, Boudewijn Rempt
email:     boud@rempt.xs4all.nl
"""
from qt import *
from resources import TRUE, FALSE

class ListSpace(QSplitter):
    

The ListSpace is based on QSplitter — that way the user can decide how wide he wants to have his list of window titles.

    def __init__(self, *args):
        apply(QSplitter.__init__,(self, ) + args)
        self.viewlist=QListBox(self)
        self.setResizeMode(self.viewlist,
                           QSplitter.KeepSize)
        self.stack=QWidgetStack(self)
        self.views=[]
        self.connect(self.viewlist,
                     SIGNAL("highlighted(int)"),
                     self.__activateViewByIndex)
    

First the QListBox is added to the splitter, and then to the widget stack (which is used in the same way as in the previous section). Here, I chose to use a QListBox, because it offers a more comfortable interface for the adding, changing and removing of entries than a QListView. As soon as we need the treeview, column header or multi-column capabilities of QListView, the change to QListView will be simple enough.

Because the highlighted(int) signal of QListBox passed the index of the selected entry in the listbox, not the actual view object, we have to pass it through an internal function, __activateViewByIndex, which maps the index to the view object that should be activated.

    def addView(self, view):
        self.views.append(view)
        self.viewlist.insertItem(view.caption(), len(self.views) - 1)
        self.stack.addWidget(view, len(self.views) - 1)
        self.stack.raiseWidget(view)
        self.connect(view,
                     PYSIGNAL("sigCaptionChanged"),
                     self.setListText)

    def setListText(self, view, caption):
        i = self.views.index(view)
        self.viewlist.changeItem(caption, i)

    

Of course, adding a view is now slightly more complicated, because the caption of the view must also be inserted into the listbox. Note that we have changed the code of MDIView slightly: when its caption changes, it now emits a signal, which we use here to keep the title text in the listview synchronized with the title of the document. Synchronization is done using the setListText function, which uses the view to determine the right entry in the listbox. Of course, the mapping between the view object and the entry in the listbox should be encapsulated in a subclass of QListBox.

    def removeView(self, view):
        if view in self.views:
            self.viewlist.removeItem(self.views.index(view))
            self.stack.removeWidget(view)
            self.views.remove(view)
    

Removing an item from a QListView is rather difficult to do without clearing the entire listview and building the contents anew. [1] Fortunately, the QListBox class offers a handy remove() function.

    def activeWindow(self):
        return self.stack.visibleWidget()

    def cascade(self): pass

    def tile(self): pass

    def canCascade(self):
        return FALSE

    def canTile(self):
        return FALSE

    def windowList(self):
        return self.views

    def activateView(self, view):
        self.stack.raiseWidget(view)

    def __activateViewByIndex(self, index):
        self.activateView(self.views[index])
    

Apart from __activateViewByIndex(), which we discussed above, the rest of the code is a plain reflection of our view manager API — in other words, nothing spectacular.

Notes

[1]

This is one area where the cleverness of PyQt makes life a bit more difficult than you might like. In C++, you remove a QListViewItem by deleting it. The parent QListView or QListViewItem then forgets about the child item, too. However, sip keeps a reference to the QListViewItem; deleting the item from Python won't make any difference—as long as the parent keeps a reference to the child, sip will keep one, too. There is a function takeItem(), but its use is fraught with danger. You might want to try the item.parent().removeChild(item) trick if you want to remove items from a QListView.