In this decoration, we implement the buttons used in the decoration as pixmaps. To help us relate regions of the window to these, we define mappings between each DecorationRegion and its corresponding pixmap for two situations: when a window is shown normally and when it has been maximized. This is purely for cosmetic purposes.
We finish the constructor by defining the regions for buttons that we understand. This will be useful when we are asked to give regions for window decoration buttons.
Finding Regions
Each decoration needs to be able to describe the regions used for parts of the window furniture, such as the close button, window borders and title bar. We reimplement the region() function to do this for our decoration. This function returns a QRegion object that describes an arbitrarily-shaped region of the screen that can itself be made up of several distinct areas.
QRegion MyDecoration::region(const QWidget *widget, const QRect &insideRect,
int decorationRegion)
{
The function is called for a given widget, occupying a region specified by insideRect, and is expected to return a region for the collection of DecorationRegion enum values supplied in the decorationRegion parameter.
We begin by figuring out how much space in the decoration we will need to allocate for buttons, and where to place them:
QHash<DecorationRegion, int> buttons;
Qt::WindowFlags flags = widget->windowFlags();
int dx = -buttonMargin - buttonWidth;
foreach (Qt::WindowType button, buttonHints) {
if (flags & button) {
int x = (buttons.size() + 1) * dx;
buttons[buttonHintMap[button]] = x;
}
}
In a more sophisticated implementation, we might test the decorationRegion supplied for regions related to buttons and the title bar, and only perform this space allocation if asked for regions related to these.
We also use the information about the area occupied by buttons to determine how large an area we can use for the window title:
int titleRightMargin = buttons.size() * dx;
QRect outsideRect(insideRect.left() - border,
insideRect.top() - titleHeight - border,
insideRect.width() + 2 * border,
insideRect.height() + titleHeight + 2 * border);
With these basic calculations done, we can start to compose a region, first checking whether we have been asked for all of the window, and we return immediately if so.
QRegion region;
if (decorationRegion == All) {
region += QRegion(outsideRect) - QRegion(insideRect);
return region;
}
We examine each decoration region in turn, adding the corresponding region to the region object created earlier. We take care to avoid "off by one" errors in the coordinate calculations.
if (decorationRegion & Title) {
QRect rect = outsideRect.adjusted(border, border, -border, 0);
rect.setHeight(titleHeight);
rect.setWidth(qMax(0, rect.width() + titleRightMargin));
region += rect;
}
if (decorationRegion & Top) {
QRect rect = outsideRect.adjusted(border, 0, -border, 0);
rect.setHeight(border);
region += rect;
}
if (decorationRegion & Left) {
QRect rect = outsideRect.adjusted(0, border, 0, -border);
rect.setWidth(border);
region += rect;
}
if (decorationRegion & Right) {
QRect rect = outsideRect.adjusted(0, border, 0, -border);
rect.setLeft(rect.right() + 1 - border);
region += rect;
}
if (decorationRegion & Bottom) {
QRect rect = outsideRect.adjusted(border, 0, -border, 0);
rect.setTop(rect.bottom() + 1 - border);
region += rect;
}
if (decorationRegion & TopLeft) {
QRect rect = outsideRect;
rect.setWidth(border);
rect.setHeight(border);
region += rect;
}
if (decorationRegion & TopRight) {
QRect rect = outsideRect;
rect.setLeft(rect.right() + 1 - border);
rect.setHeight(border);
region += rect;
}
if (decorationRegion & BottomLeft) {
QRect rect = outsideRect;
rect.setWidth(border);
rect.setTop(rect.bottom() + 1 - border);
region += rect;
}
if (decorationRegion & BottomRight) {
QRect rect = outsideRect;
rect.setLeft(rect.right() + 1 - border);
rect.setTop(rect.bottom() + 1 - border);
region += rect;
}
Unlike the window borders and title bar, the regions occupied by buttons many of the window decorations do not occupy fixed places in the window. Instead, their locations depend on which other buttons are present. We only add regions for buttons we can handle (defined in the stateRegions) member variable, and only for those that are present (defined in the buttons hash).
foreach (QDecoration::DecorationRegion testRegion, stateRegions) {
if (decorationRegion & testRegion and buttons.contains(testRegion)) {
QRect rect = outsideRect.adjusted(border, border, -border, 0);
rect.setHeight(titleHeight);
dx = buttons[testRegion];
rect.setLeft(rect.right() + 1 + dx);
rect.setWidth(buttonWidth + buttonMargin);
region += rect;
}
}
The fully composed region can then be returned:
return region;
}
The information returned by this function is used when the decoration is painted. Ideally, this function should be implemented to perform all the calculations necessary to place elements of the decoration; this makes the implementation of the paint() function much easier.
Painting the Decoration
The paint() function is responsible for drawing each window element for a given widget. Information about the decoration region, its state and the widget itself is provided along with a QPainter object to use.
The first check we make is for a call with no regions:
bool MyDecoration::paint(QPainter *painter, const QWidget *widget,
int decorationRegion, DecorationState state)
{
if (decorationRegion == None)
return false;
We return false to indicate that we have not painted anything. If we paint something, we must return true so that the window can be composed, if necessary.
Just as with the region() function, we test the decoration region to determine which elements need to be drawn. If we paint anything, we set the handled variable to true so that we can return the correct value when we have finished.
bool handled = false;
QPalette palette = QApplication::palette();
QHash<DecorationRegion, QPixmap> buttonPixmaps;
if (widget->windowState() == Qt::WindowMaximized)
buttonPixmaps = maximizedButtonPixmaps;
else
buttonPixmaps = normalButtonPixmaps;
if (decorationRegion & Title) {
QRect rect = QDecoration::region(widget, Title).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Base));
painter->save();
painter->setPen(QPen(palette.color(QPalette::Text)));
painter->drawText(rect, Qt::AlignCenter, widget->windowTitle());
painter->restore();
handled = true;
}
if (decorationRegion & Top) {
QRect rect = QDecoration::region(widget, Top).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & Left) {
QRect rect = QDecoration::region(widget, Left).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & Right) {
QRect rect = QDecoration::region(widget, Right).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & Bottom) {
QRect rect = QDecoration::region(widget, Bottom).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & TopLeft) {
QRect rect = QDecoration::region(widget, TopLeft).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & TopRight) {
QRect rect = QDecoration::region(widget, TopRight).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & BottomLeft) {
QRect rect = QDecoration::region(widget, BottomLeft).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
if (decorationRegion & BottomRight) {
QRect rect = QDecoration::region(widget, BottomRight).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Dark));
handled = true;
}
Note that we use our own region() implementation to determine where to draw decorations.
Since the region() function performs calculations to place buttons, we can simply test the window flags against the buttons we support (using the buttonHintMap defined in the constructor), and draw each button in the relevant region:
int margin = (titleHeight - 16) / 2;
Qt::WindowFlags flags = widget->windowFlags();
foreach (DecorationRegion testRegion, stateRegions) {
if (decorationRegion & testRegion && flags & buttonHintMap.key(testRegion)) {
QRect rect = QDecoration::region(
widget, testRegion).boundingRect();
painter->fillRect(rect, palette.brush(QPalette::Base));
QRect buttonRect = rect.adjusted(0, margin, -buttonMargin - margin,
-buttonMargin);
painter->drawPixmap(buttonRect.topLeft(), buttonPixmaps[testRegion]);
handled = true;
}
}
Finally, we return the value of handled to indicate whether any painting was performed:
return handled;
}
We now have a decoration class that we can use in an application.
Using the Decoration
In the main.cpp file, we set up the application as usual, but we also create an instance of our decoration and set it as the standard decoration for the application:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyDecoration *decoration = new MyDecoration();
app.qwsSetDecoration(decoration);
This causes all windows opened by this application to use our decoration. To demonstrate this, we show the analog clock widget from the Analog Clock Example, which we build into the application:
AnalogClock clock;
clock.show();
return app.exec();
}
The application can be run either as a server or a client application. In both cases, it will use our decoration rather than the default one provided with Qt.
Notes
This example does not cache any information about the state or buttons used for each window. This means that the region() function calculates the locations and regions of buttons in cases where it could re-use existing information.
If you run the application as a window server, you may expect client applications to use our decoration in preference to the default Qt decoration. However, it is up to each application to draw its own decoration, so this will not happen automatically. One way to achieve this is to compile the decoration with each application that needs it; another way is to build the decoration as a plugin, using the QDecorationPlugin class, and load it into the server and client applications.