In this chapter, we will explore the actual
implementation of some of those paradigms, starting with one of
the most useful and modern paradigms: the tabbed document
model.
Tabbed documents
Like most user interface paradigms, the
tabbed document paradigm has been popularized by current
integrated development environments. A tabbed document collects
all open documents in one window, with a row of tabs to facilitate easy navigation of
documents. This paradigm has become so prevalent that even the
old stalwart of user interface conservatism, XEmacs, supports
it.
It turns out to be remarkably easy to
implement a tabbed document interface. First, let's determine
what we want to get out of this component. It is the first of
several generic components that can take views — i.e.
QWidget's— and show them in an
application workspace. All view managers should have the same
API. That allows the user to choose his favorite way of working
without giving us lots of work — because, from the point
of view of the application, all view managers are exactly the
same.
We will provisionally call the component
that manages tabbed views TabManager. The
TabManager is meant to be almost a
drop-in replacement for the QWorkspace we
used in the Chapter 15. Therefore, it should support
most of the same functionality: adding, removing and listing
actual views. Other capabilities of
QWorkspace don't make sense: you cannot
tile or cascade tabbed windows. There must be some way to
indicate to the wrapping application whether the view manager
supports these capabilities.
PyQt offers a
QTabWidget, which fits the basics of our
needs perfectly. However, in contrast with the
QWorkspace, where merely creating a
widget with the workspace as parent widget was enough to let it
be managed, QTabWidget wants us to
explicitly add pages, and thus widgets, to its list of tabs.
Finally, it also allows the addition and removal of pages. We
can also request a reference to the active view, and ask to be
notified of page changes.
QTabWidget is used
in the QTabDialog dialog window class,
and makes use of QWidgetStack and
QTabBar.
QWidgetStack keeps a stack of widgets of
which only one is shown at a time.
QTabBar, which keeps a row of tabs.
Tabs can be square or triangular (the
latter is seldom seen nowadays, for it is very ugly), and shown
on the top or bottom of the window.
Applications that handle documents that
consist of several (but not many) pages often show a row of
triangular tabs at the bottom of the window. You cannot set the
tabs to appear at the side of the window. That's a pity, since
it is a position that is quite often preferred by users.
Let us take a look at the implementation of
a tabbed document manager:
"""
tabmanager.py - tabbed document manager for the mdi framework
copyright: (C) 2001, Boudewijn Rempt
email: boud@rempt.xs4all.nl
"""
from qt import *
from resources import TRUE, FALSE
class TabManager(QTabWidget):
def __init__(self, *args):
apply(QTabWidget.__init__,(self, ) + args)
self.views=[]
self.setMargin(10)
The TabManager is
derived from QTabWidget. A simple python
list of views is kept, otherwise we would not be able to
retrieve a list of all open views for the ‘windows' menu. The
margin between tab and document should really be a user-settable
property, but we won't develop a user preferences framework
until chapter
Chapter 18.
def addView(self, view):
if view not in self.views:
self.views.append(view)
self.addTab(view, view.caption())
self.showPage(view)
Adding a new view is a simple exercise.
However, note that until you actually call
showPage() on your view, the
QTabWidget appears to be innocent of your
addition, and won't manage the layout of the page. This means
that when you create a new window and resize the application
window, the contents won't resize with it. Simply drawing the
tab widget's attention to the page will suffice, however.
With PyQt's
QWorkspace it was enough to create a
widget with the workspace as its parent—the widget was
automatically managed shown. This is no longer enough when we
use QTabWidget. This means that we will have to adapt the
DocManager class to work with
addView. This is done in the private
_createView() function:
def _createView(self, document, viewClass):
view = viewClass(self._viewManager,
document,
None,
QWidget.WDestructiveClose)
if self._docToViewMap.has_key(document):
index = len(self._docToViewMap[document]) + 1
else:
index = 1
view.setCaption(document.title() + " %s" % index)
self._viewManager.addView(view)
view.installEventFilter(self._parent)
if self._viewToDocMap == {}:
view.showMaximized()
else:
view.show()
return view
To return to the
TabManager class:
def removeView(self, view):
if view in self.views:
self.views.remove(view)
self.removePage(view)
def activeWindow(self):
return self.currentPage()
def windowList(self):
return self.views
The first of these three functions is new.
Simply closing a widget was enough to remove it when it was
managed by the QWorkspace object; now we
must explicitly remove it. This, too, demands a change in the
DocManager class, but fortunately, it's a
simple change:
def _removeView(self, view, document):
try:
self._docToViewMap[document].remove(view)
self._viewManager.removeView(view)
del self._viewToDocMap[view]
except ValueError, e:
pass # apparently already deleted
Both activeWindow()
and windowList have been included to make
the interface of the tabmanager more similar to that of
QWorkspace. If you want to have
transparently interchangeable components, they must have the
same functions.
def cascade(self): pass
def tile(self): pass
def canCascade(self):
return FALSE
def canTile(self):
return FALSE
You cannot cascade nor tile a set of tab
pages. The functions are included, but merely to avoid runtime
exceptions when the application inadvertently does try to call
them. The functions canCascade() and
canTile() can be used to determine whether
this component supports this functionality.