Better QWindowContainer by not relying on native widgets.

We change the behavior slightly from the initial implementation in
5.1. Forcing the use of native child widgets is causing massive
performance issues so instead, we attach the embedded QWindow directly
to the root window. The only exception is QScrollArea and QMdiArea
which still enforces native windows for the entire parent chain
to make clipping and stacking work.

Task-number: QTBUG-34138
Change-Id: If713637bd4dce630552ace2f8ad6b2e86c063721
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Gunnar Sletta 2013-10-18 12:36:47 +02:00 committed by The Qt Project
parent c9ad904af9
commit 72a7882cec
5 changed files with 230 additions and 27 deletions

View File

@ -1594,6 +1594,7 @@ void QWidgetPrivate::createExtra()
extra->autoFillBackground = 0;
extra->nativeChildrenForced = 0;
extra->inRenderWithPainter = 0;
extra->hasWindowContainer = false;
extra->hasMask = 0;
createSysExtra();
#ifdef QWIDGET_EXTRA_DEBUG
@ -6543,6 +6544,9 @@ void QWidget::move(const QPoint &p)
data->crect.moveTopLeft(p); // no frame yet
setAttribute(Qt::WA_PendingMoveEvent);
}
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasMoved(this);
}
/*! \fn void QWidget::resize(int w, int h)
@ -6581,6 +6585,9 @@ void QWidget::setGeometry(const QRect &r)
setAttribute(Qt::WA_PendingMoveEvent);
setAttribute(Qt::WA_PendingResizeEvent);
}
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasMoved(this);
}
/*!
@ -9715,6 +9722,9 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
ancestorProxy->d_func()->embedSubWindow(this);
}
#endif
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasChanged(this);
}
/*!
@ -10747,6 +10757,9 @@ void QWidget::raise()
if (testAttribute(Qt::WA_WState_Created))
d->raise_sys();
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasRaised(this);
QEvent e(QEvent::ZOrderChange);
QApplication::sendEvent(this, &e);
}
@ -10781,6 +10794,9 @@ void QWidget::lower()
if (testAttribute(Qt::WA_WState_Created))
d->lower_sys();
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasLowered(this);
QEvent e(QEvent::ZOrderChange);
QApplication::sendEvent(this, &e);
}

View File

@ -254,6 +254,7 @@ struct QWExtra {
uint nativeChildrenForced : 1;
uint inRenderWithPainter : 1;
uint hasMask : 1;
uint hasWindowContainer : 1;
// *************************** Platform specific values (bit fields first) **********
#if defined(Q_WS_WIN) // <----------------------------------------------------------- WIN

View File

@ -42,6 +42,10 @@
#include "qwindowcontainer_p.h"
#include "qwidget_p.h"
#include <QtGui/qwindow.h>
#include <QDebug>
#include <QMdiSubWindow>
#include <QAbstractScrollArea>
QT_BEGIN_NAMESPACE
@ -50,11 +54,67 @@ class QWindowContainerPrivate : public QWidgetPrivate
public:
Q_DECLARE_PUBLIC(QWindowContainer)
QWindowContainerPrivate() : window(0), oldFocusWindow(0) { }
QWindowContainerPrivate()
: window(0)
, oldFocusWindow(0)
, usesNativeWidgets(false)
{
}
~QWindowContainerPrivate() { }
static QWindowContainerPrivate *get(QWidget *w) {
QWindowContainer *wc = qobject_cast<QWindowContainer *>(w);
if (wc)
return wc->d_func();
return 0;
}
void updateGeometry() {
Q_Q(QWindowContainer);
if (usesNativeWidgets)
window->setGeometry(q->rect());
else
window->setGeometry(QRect(q->mapTo(q->window(), QPoint()), q->size()));
}
void updateUsesNativeWidgets()
{
if (usesNativeWidgets || window->parent() == 0)
return;
Q_Q(QWindowContainer);
QWidget *p = q->parentWidget();
while (p) {
if (qobject_cast<QMdiSubWindow *>(p) != 0
|| qobject_cast<QAbstractScrollArea *>(p) != 0) {
q->winId();
usesNativeWidgets = true;
break;
}
p = p->parentWidget();
}
}
void markParentChain() {
Q_Q(QWindowContainer);
QWidget *p = q;
while (p) {
QWidgetPrivate *d = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(p));
d->createExtra();
d->extra->hasWindowContainer = true;
p = p->parentWidget();
}
}
bool isStillAnOrphan() const {
return window->parent() == &fakeParent;
}
QPointer<QWindow> window;
QWindow *oldFocusWindow;
QWindow fakeParent;
uint usesNativeWidgets : 1;
};
@ -78,6 +138,14 @@ public:
be removed from the window container with a call to
QWindow::setParent().
The window container is attached as a native child window to the
toplevel window it is a child of. When a window container is used
as a child of a QAbstractScrollArea or QMdiArea, it will
create a \l {Native Widgets vs Alien Widgets} {native window} for
every widget in its parent chain to allow for proper stacking and
clipping in this use case. Applications with many native child
windows may suffer from performance issues.
The window container has a number of known limitations:
\list
@ -86,11 +154,6 @@ public:
widget hierarchy as an opaque box. The stacking order of multiple
overlapping window container instances is undefined.
\li Window Handles; The window container will explicitly invoke
winId() which will force the use of native window handles
inside the application. See \l {Native Widgets vs Alien Widgets}
{QWidget documentation} for more details.
\li Rendering Integration; The window container does not interoperate
with QGraphicsProxyWidget, QWidget::render() or similar functionality.
@ -132,13 +195,7 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt:
}
d->window = embeddedWindow;
// We force this window to become a native window and reparent the
// window directly to it. This is done so that the order in which
// the QWindowContainer is added to a QWidget tree and when it
// gets a window does not matter.
winId();
d->window->setParent(windowHandle());
d->window->setParent(&d->fakeParent);
connect(QGuiApplication::instance(), SIGNAL(focusWindowChanged(QWindow *)), this, SLOT(focusWindowChanged(QWindow *)));
}
@ -167,8 +224,6 @@ void QWindowContainer::focusWindowChanged(QWindow *focusWindow)
d->oldFocusWindow = focusWindow;
}
/*!
\internal
*/
@ -190,22 +245,38 @@ bool QWindowContainer::event(QEvent *e)
// The only thing we are interested in is making sure our sizes stay
// in sync, so do a catch-all case.
case QEvent::Resize:
d->updateGeometry();
break;
case QEvent::Move:
d->updateGeometry();
break;
case QEvent::PolishRequest:
d->window->setGeometry(0, 0, width(), height());
d->updateGeometry();
break;
case QEvent::Show:
d->window->show();
d->updateUsesNativeWidgets();
if (d->isStillAnOrphan()) {
d->window->setParent(d->usesNativeWidgets
? windowHandle()
: window()->windowHandle());
}
if (d->window->parent()) {
d->markParentChain();
d->window->show();
}
break;
case QEvent::Hide:
d->window->hide();
if (d->window->parent())
d->window->hide();
break;
case QEvent::FocusIn:
if (d->oldFocusWindow != d->window) {
d->window->requestActivate();
} else {
QWidget *next = nextInFocusChain();
next->setFocus();
if (d->window->parent()) {
if (d->oldFocusWindow != d->window) {
d->window->requestActivate();
} else {
QWidget *next = nextInFocusChain();
next->setFocus();
}
}
break;
default:
@ -215,4 +286,60 @@ bool QWindowContainer::event(QEvent *e)
return QWidget::event(e);
}
typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent);
static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback)
{
const QObjectList &children = parent->children();
for (int i=0; i<children.size(); ++i) {
QWidget *w = qobject_cast<QWidget *>(children.at(i));
if (w) {
QWidgetPrivate *wd = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w));
if (wd->extra && wd->extra->hasWindowContainer)
callback(w);
}
}
}
void QWindowContainer::parentWasChanged(QWidget *parent)
{
if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
if (d->window->parent()) {
d->updateUsesNativeWidgets();
d->markParentChain();
d->window->setParent(d->usesNativeWidgets
? parent->windowHandle()
: parent->window()->windowHandle());
d->updateGeometry();
}
}
qwindowcontainer_traverse(parent, parentWasChanged);
}
void QWindowContainer::parentWasMoved(QWidget *parent)
{
if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
if (d->window->parent())
d->updateGeometry();
}
qwindowcontainer_traverse(parent, parentWasMoved);
}
void QWindowContainer::parentWasRaised(QWidget *parent)
{
if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
if (d->window->parent())
d->window->raise();
}
qwindowcontainer_traverse(parent, parentWasRaised);
}
void QWindowContainer::parentWasLowered(QWidget *parent)
{
if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
if (d->window->parent())
d->window->lower();
}
qwindowcontainer_traverse(parent, parentWasLowered);
}
QT_END_NAMESPACE

View File

@ -57,6 +57,11 @@ public:
explicit QWindowContainer(QWindow *embeddedWindow, QWidget *parent = 0, Qt::WindowFlags f = 0);
~QWindowContainer();
static void parentWasChanged(QWidget *parent);
static void parentWasMoved(QWidget *parent);
static void parentWasRaised(QWidget *parent);
static void parentWasLowered(QWidget *parent);
protected:
bool event(QEvent *ev);

View File

@ -68,8 +68,6 @@ public:
int numberOfObscures;
};
class tst_QWindowContainer: public QObject
{
Q_OBJECT
@ -81,6 +79,7 @@ private slots:
void testBehindTheScenesDeletion();
void testUnparenting();
void testActivation();
void testAncestorChange();
};
@ -188,6 +187,7 @@ void tst_QWindowContainer::testActivation()
root.show();
root.activateWindow();
QVERIFY(QTest::qWaitForWindowExposed(&root));
QVERIFY(QTest::qWaitForWindowActive(root.windowHandle()));
QVERIFY(QGuiApplication::focusWindow() == root.windowHandle());
@ -204,8 +204,7 @@ void tst_QWindowContainer::testActivation()
QTest::qWait(100);
window->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(window));
QVERIFY(QGuiApplication::focusWindow() == window);
QTRY_VERIFY(QGuiApplication::focusWindow() == window);
// Verify that all states in the root widget still indicate it is active
QVERIFY(root.windowHandle()->isActive());
@ -231,6 +230,61 @@ void tst_QWindowContainer::testUnparenting()
QVERIFY(!window->isVisible());
}
void tst_QWindowContainer::testAncestorChange()
{
QWidget root;
QWidget *left = new QWidget(&root);
QWidget *right = new QWidget(&root);
root.setGeometry(0, 0, 200, 100);
left->setGeometry(0, 0, 100, 100);
right->setGeometry(100, 0, 100, 100);
QWindow *window = new QWindow();
QWidget *container = QWidget::createWindowContainer(window, left);
container->setGeometry(0, 0, 100, 100);
// Root
// + left
// | + container
// | + window
// + right
root.show();
QVERIFY(QTest::qWaitForWindowExposed(&root));
QCOMPARE(window->geometry(), QRect(0, 0, 100, 100));
container->setParent(right);
// Root
// + left
// + right
// + container
// + window
QCOMPARE(window->geometry(), QRect(100, 0, 100, 100));
QWidget *newRoot = new QWidget(&root);
newRoot->setGeometry(50, 50, 200, 200);
right->setParent(newRoot);
// Root
// + left
// + newRoot
// + right
// + container
// + window
QCOMPARE(window->geometry(), QRect(150, 50, 100, 100));
newRoot->move(0, 0);
QCOMPARE(window->geometry(), QRect(100, 0, 100, 100));
newRoot->setParent(0);
newRoot->setGeometry(100, 100, 200, 200);
newRoot->show();
QVERIFY(QTest::qWaitForWindowExposed(newRoot));
// newRoot
// + right
// + container
// + window
QCOMPARE(window->geometry(), QRect(100, 0, 100, 100));
}
QTEST_MAIN(tst_QWindowContainer)
#include "tst_qwindowcontainer.moc"