
Yesterday on IRC I was reminded of the nice abstractions Qt has with QPaintDevice. (There are many useful paint devices, but most important for this discourse are QWidget, QPixmap and QImage.) So there are several things Qt4 allows you to do:
- QPainter::setRedirected
- QPixmap::grabWidget
- QWidget::render
Inspired by the video from Mirco Müller I wanted to see what could be done with those functions. I started with setRedirected but couldn't make it work immediately. So I did what the docs said and tried QPixmap::grabWidget. And without much effort I was able to draw a widget someplace else. But then I wanted to transform the image which needs a conversion to QImage. Expecting that Qt might optimize a bit if it renders directly into a QImage instead of a QPixmap and then converts to QImage I used QWidget::render to render a widget directly onto a QImage. Piece of cake. Now I had a QImage, transformed and painted into a different widget. Last missing piece is to update the image if the original widget changes (e.g. mouse or keyboard events). QObject::installEventFilter makes it easy to listen for paint events on the other widget and update accordingly. And to make it a fully featured class I added the ability to look for child widget paint events and made it repaint only as little as needed.
The result is this code:
int main
(int argc,
char **argv
){ QApplication app
(argc, argv
);
QWidget topLevel;
QWidget w
(&topLevel
);
WidgetMirror leMirror
(&topLevel
);
leMirror.
setMirroredWidget(&w
);
QPushButton b
("Button", &w
);
QLineEdit le
(&w
);
QListWidget lw
(&w
);
lw.
setMaximumHeight(64);
lw.
addItems(QStringList() <<
"Item 1" <<
"Item 2" <<
"Item 3" <<
"Item 4" <<
"Item 5" <<
"Item 6" <<
"Item 7" <<
"Item 8");
QGridLayout layout
(&w
);
layout.
setMargin(0);
layout.
addWidget(&b,
0,
0, Qt::
AlignBottom);
layout.
addWidget(&le,
0,
1, Qt::
AlignBottom);
layout.
addWidget(&lw,
0,
2, Qt::
AlignBottom);
QVBoxLayout outerLayout
(&topLevel
);
outerLayout.
setSpacing(0);
outerLayout.
addWidget(&w
);
outerLayout.
addWidget(&leMirror
);
topLevel.
show();
return app.
exec();
}
which makes for this (All the artifacts and "unsmoothness" are results of the screen-capture).
Today I wanted to see whether this eye-candy can be put into KDE at some place and I have not succeeded. Perhaps somebody can make a mockup of how this could be used without making you feel "something is wrong".
Anyway more pictures:

Notice that all this is just fooling around with what Qt provides. This is neither useful nor going into KDE 4.0 
For the interested people, the implementation of WidgetMirror looks like this:
static const int MIRROR_HEIGHT =
38;
WidgetMirror::
WidgetMirror(QWidget *parent
) :
QWidget(parent
),
m_widget
(0),
m_inPaintEvent
(false){}void WidgetMirror::
setMirroredWidget(QWidget *w
){ if (m_widget
) { m_widget->removeEventFilter
(this);
QStack<QObject *> parentStack;
parentStack.
push(m_widget
);
while (!parentStack.
isEmpty()) { QObject *parent = parentStack.
pop();
foreach (QObject *child, parent->children
()) { child->removeEventFilter
(this);
parentStack.
push(child
);
} } } m_widget = w;
if (m_widget
) { m_widget->installEventFilter
(this);
setSizePolicy
(m_widget->sizePolicy
());
QStack<QObject *> parentStack;
parentStack.
push(m_widget
);
while (!parentStack.
isEmpty()) { QObject *parent = parentStack.
pop();
foreach (QObject *child, parent->children
()) { if (child->isWidgetType
()) { child->installEventFilter
(this);
parentStack.
push(child
);
} } } }}bool WidgetMirror::
eventFilter(QObject *obj,
QEvent *event
){ if (event->type
() ==
QEvent::
ChildPolished) { QChildEvent *e = static_cast<QChildEvent *>
(event
);
QObject *child = e->child
();
if (child->isWidgetType
()) { child->installEventFilter
(this);
QStack<QObject *> parentStack;
parentStack.
push(child
);
while (!parentStack.
isEmpty()) { QObject *parent = parentStack.
pop();
foreach (child, parent->children
()) { if (child->isWidgetType
()) { child->installEventFilter
(this);
parentStack.
push(child
);
} } } } } else if (!m_inPaintEvent && obj->isWidgetType
() && event->type
() ==
QEvent::
Paint) { QWidget *w = static_cast<QWidget *>
(obj
);
QPaintEvent *pe = static_cast<QPaintEvent *>
(event
);
QRect r
(pe->rect
());
const QPoint diff = w->mapTo
(m_widget,
QPoint());
r.
translate(diff
);
int tmp = r.
top();
r.
setTop(m_widget->height
() - r.
bottom() -
1);
r.
setBottom(m_widget->height
() - tmp -
1);
update
(r
);
} return QWidget::
eventFilter(obj, event
);
}void WidgetMirror::
paintEvent(QPaintEvent *e
){ if (!m_widget
) { return;
} QRect dirty
(e->rect
());
dirty.
setHeight(qMin
(dirty.
height(), MIRROR_HEIGHT
));
int tmp = dirty.
top();
dirty.
setTop(m_widget->height
() - dirty.
bottom() -
1);
dirty.
setBottom(m_widget->height
() - tmp -
1);
m_inPaintEvent =
true;
QImage im
(e->rect
().
size(),
QImage::
Format_ARGB32_Premultiplied);
m_widget->render
(&im, -dirty.
topLeft(), dirty,
QWidget::
DrawWindowBackground |
QWidget::
DrawChildren);
// | QWidget::IgnoreMask); im = im.
mirrored();
static QVector<QRgb> s_colorTable;
if (s_colorTable.
isEmpty()) { s_colorTable.
resize(256);
for (int i =
0; i <
256; ++i
) { s_colorTable
[i
] = qRgb
(i, i, i
);
} } QImage alphaChannel
(im.
size(),
QImage::
Format_Indexed8);
alphaChannel.
setColorTable(s_colorTable
);
alphaChannel.
fill(0);
const int maxAlpha =
100;
for (int line =
0; line < alphaChannel.
height() && line < MIRROR_HEIGHT - e->rect
().
top(); ++line
) { const int x = line + e->rect
().
top();
const uchar value =
4 *
(maxAlpha - x * maxAlpha / MIRROR_HEIGHT
) /
(x +
4);
// ; std::
memset(alphaChannel.
scanLine(line
), value, alphaChannel.
bytesPerLine());
} im.
setAlphaChannel(alphaChannel
);
QPainter p
(this);
p.
drawImage(e->rect
(), im
);
m_inPaintEvent =
false;
}QSize WidgetMirror::
sizeHint() const{ if (!m_widget
) { return QSize();
} QSize s
(m_widget->sizeHint
());
s.
setHeight(qMin
(s.
height(), MIRROR_HEIGHT
));
return s;
}