1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qwindowcontainer_p.h"
41#include "qwidget_p.h"
42#include <QtGui/qwindow.h>
43#include <QtGui/private/qguiapplication_p.h>
44#include <qpa/qplatformintegration.h>
45#include <QDebug>
46
47#if QT_CONFIG(mdiarea)
48#include <QMdiSubWindow>
49#endif
50#include <QAbstractScrollArea>
51#include <QPainter>
52
53QT_BEGIN_NAMESPACE
54
55class QWindowContainerPrivate : public QWidgetPrivate
56{
57public:
58 Q_DECLARE_PUBLIC(QWindowContainer)
59
60 QWindowContainerPrivate()
61 : window(0)
62 , oldFocusWindow(0)
63 , usesNativeWidgets(false)
64 {
65 }
66
67 ~QWindowContainerPrivate() { }
68
69 static QWindowContainerPrivate *get(QWidget *w) {
70 QWindowContainer *wc = qobject_cast<QWindowContainer *>(w);
71 if (wc)
72 return wc->d_func();
73 return 0;
74 }
75
76 void updateGeometry() {
77 Q_Q(QWindowContainer);
78 if (!q->isWindow() && (q->geometry().bottom() <= 0 || q->geometry().right() <= 0))
79 /* Qt (e.g. QSplitter) sometimes prefer to hide a widget by *not* calling
80 setVisible(false). This is often done by setting its coordinates to a sufficiently
81 negative value so that its clipped outside the parent. Since a QWindow is not clipped
82 to widgets in general, it needs to be dealt with as a special case.
83 */
84 window->setGeometry(q->geometry());
85 else if (usesNativeWidgets)
86 window->setGeometry(q->rect());
87 else
88 window->setGeometry(QRect(q->mapTo(q->window(), QPoint()), q->size()));
89 }
90
91 void updateUsesNativeWidgets()
92 {
93 if (window->parent() == 0)
94 return;
95 Q_Q(QWindowContainer);
96 if (q->internalWinId()) {
97 // Allow use native widgets if the window container is already a native widget
98 usesNativeWidgets = true;
99 return;
100 }
101 bool nativeWidgetSet = false;
102 QWidget *p = q->parentWidget();
103 while (p) {
104 if (false
105#if QT_CONFIG(mdiarea)
106 || qobject_cast<QMdiSubWindow *>(p) != 0
107#endif
108#if QT_CONFIG(scrollarea)
109 || qobject_cast<QAbstractScrollArea *>(p) != 0
110#endif
111 ) {
112 q->winId();
113 nativeWidgetSet = true;
114 break;
115 }
116 p = p->parentWidget();
117 }
118 usesNativeWidgets = nativeWidgetSet;
119 }
120
121 void markParentChain() {
122 Q_Q(QWindowContainer);
123 QWidget *p = q;
124 while (p) {
125 QWidgetPrivate *d = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(p));
126 d->createExtra();
127 d->extra->hasWindowContainer = true;
128 p = p->parentWidget();
129 }
130 }
131
132 bool isStillAnOrphan() const {
133 return window->parent() == &fakeParent;
134 }
135
136 QPointer<QWindow> window;
137 QWindow *oldFocusWindow;
138 QWindow fakeParent;
139
140 uint usesNativeWidgets : 1;
141};
142
143
144
145/*!
146 \fn QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags);
147
148 Creates a QWidget that makes it possible to embed \a window into
149 a QWidget-based application.
150
151 The window container is created as a child of \a parent and with
152 window flags \a flags.
153
154 Once the window has been embedded into the container, the
155 container will control the window's geometry and
156 visibility. Explicit calls to QWindow::setGeometry(),
157 QWindow::show() or QWindow::hide() on an embedded window is not
158 recommended.
159
160 The container takes over ownership of \a window. The window can
161 be removed from the window container with a call to
162 QWindow::setParent().
163
164 The window container is attached as a native child window to the
165 toplevel window it is a child of. When a window container is used
166 as a child of a QAbstractScrollArea or QMdiArea, it will
167 create a \l {Native Widgets vs Alien Widgets} {native window} for
168 every widget in its parent chain to allow for proper stacking and
169 clipping in this use case. Creating a native window for the window
170 container also allows for proper stacking and clipping. This must
171 be done before showing the window container. Applications with
172 many native child windows may suffer from performance issues.
173
174 The window container has a number of known limitations:
175
176 \list
177
178 \li Stacking order; The embedded window will stack on top of the
179 widget hierarchy as an opaque box. The stacking order of multiple
180 overlapping window container instances is undefined.
181
182 \li Rendering Integration; The window container does not interoperate
183 with QGraphicsProxyWidget, QWidget::render() or similar functionality.
184
185 \li Focus Handling; It is possible to let the window container
186 instance have any focus policy and it will delegate focus to the
187 window via a call to QWindow::requestActivate(). However,
188 returning to the normal focus chain from the QWindow instance will
189 be up to the QWindow instance implementation itself. For instance,
190 when entering a Qt Quick based window with tab focus, it is quite
191 likely that further tab presses will only cycle inside the QML
192 application. Also, whether QWindow::requestActivate() actually
193 gives the window focus, is platform dependent.
194
195 \li Using many window container instances in a QWidget-based
196 application can greatly hurt the overall performance of the
197 application.
198
199 \endlist
200 */
201
202QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags)
203{
204 return new QWindowContainer(window, parent, flags);
205}
206
207
208
209/*!
210 \internal
211 */
212
213QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt::WindowFlags flags)
214 : QWidget(*new QWindowContainerPrivate, parent, flags)
215{
216 Q_D(QWindowContainer);
217 if (Q_UNLIKELY(!embeddedWindow)) {
218 qWarning("QWindowContainer: embedded window cannot be null");
219 return;
220 }
221
222 // The embedded QWindow must use the same logic as QWidget when it comes to the surface type.
223 // Otherwise we may end up with BadMatch failures on X11.
224 if (embeddedWindow->surfaceType() == QSurface::RasterSurface
225 && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)
226 && !QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets))
227 embeddedWindow->setSurfaceType(QSurface::RasterGLSurface);
228
229 d->window = embeddedWindow;
230
231 QString windowName = d->window->objectName();
232 if (windowName.isEmpty())
233 windowName = QString::fromUtf8(d->window->metaObject()->className());
234 d->fakeParent.setObjectName(windowName + QLatin1String("ContainerFakeParent"));
235
236 d->window->setParent(&d->fakeParent);
237 setAcceptDrops(true);
238
239 connect(QGuiApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)), this, SLOT(focusWindowChanged(QWindow*)));
240}
241
242QWindow *QWindowContainer::containedWindow() const
243{
244 Q_D(const QWindowContainer);
245 return d->window;
246}
247
248/*!
249 \internal
250 */
251
252QWindowContainer::~QWindowContainer()
253{
254 Q_D(QWindowContainer);
255
256 // Call destroy() explicitly first. The dtor would do this too, but
257 // QEvent::PlatformSurface delivery relies on virtuals. Getting
258 // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc.
259 // QWindow subclasses in particular. Keep these working.
260 if (d->window)
261 d->window->destroy();
262
263 delete d->window;
264}
265
266
267
268/*!
269 \internal
270 */
271
272void QWindowContainer::focusWindowChanged(QWindow *focusWindow)
273{
274 Q_D(QWindowContainer);
275 d->oldFocusWindow = focusWindow;
276 if (focusWindow == d->window) {
277 QWidget *widget = QApplication::focusWidget();
278 if (widget)
279 widget->clearFocus();
280 }
281}
282
283/*!
284 \internal
285 */
286
287bool QWindowContainer::event(QEvent *e)
288{
289 Q_D(QWindowContainer);
290 if (!d->window)
291 return QWidget::event(e);
292
293 QEvent::Type type = e->type();
294 switch (type) {
295 case QEvent::ChildRemoved: {
296 QChildEvent *ce = static_cast<QChildEvent *>(e);
297 if (ce->child() == d->window)
298 d->window = 0;
299 break;
300 }
301 // The only thing we are interested in is making sure our sizes stay
302 // in sync, so do a catch-all case.
303 case QEvent::Resize:
304 d->updateGeometry();
305 break;
306 case QEvent::Move:
307 d->updateGeometry();
308 break;
309 case QEvent::PolishRequest:
310 d->updateGeometry();
311 break;
312 case QEvent::Show:
313 d->updateUsesNativeWidgets();
314 if (d->isStillAnOrphan()) {
315 d->window->setParent(d->usesNativeWidgets
316 ? windowHandle()
317 : window()->windowHandle());
318 d->fakeParent.destroy();
319 }
320 if (d->window->parent()) {
321 d->markParentChain();
322 d->window->show();
323 }
324 break;
325 case QEvent::Hide:
326 if (d->window->parent())
327 d->window->hide();
328 break;
329 case QEvent::FocusIn:
330 if (d->window->parent()) {
331 if (d->oldFocusWindow != d->window) {
332 d->window->requestActivate();
333 } else {
334 QWidget *next = nextInFocusChain();
335 next->setFocus();
336 }
337 }
338 break;
339#if QT_CONFIG(draganddrop)
340 case QEvent::Drop:
341 case QEvent::DragMove:
342 case QEvent::DragLeave:
343 QCoreApplication::sendEvent(d->window, e);
344 return e->isAccepted();
345 case QEvent::DragEnter:
346 // Don't reject drag events for the entire widget when one
347 // item rejects the drag enter
348 QCoreApplication::sendEvent(d->window, e);
349 e->accept();
350 return true;
351#endif
352
353 case QEvent::Paint:
354 {
355 static bool needsPunch = !QGuiApplicationPrivate::platformIntegration()->hasCapability(
356 QPlatformIntegration::TopStackedNativeChildWindows);
357 if (needsPunch) {
358 QPainter p(this);
359 p.setCompositionMode(QPainter::CompositionMode_Source);
360 p.fillRect(rect(), Qt::transparent);
361 }
362 break;
363 }
364
365 default:
366 break;
367 }
368
369 return QWidget::event(e);
370}
371
372typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent);
373static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback)
374{
375 const QObjectList &children = parent->children();
376 for (int i=0; i<children.size(); ++i) {
377 QWidget *w = qobject_cast<QWidget *>(children.at(i));
378 if (w) {
379 QWidgetPrivate *wd = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w));
380 if (wd->extra && wd->extra->hasWindowContainer)
381 callback(w);
382 }
383 }
384}
385
386void QWindowContainer::toplevelAboutToBeDestroyed(QWidget *parent)
387{
388 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
389 d->window->setParent(&d->fakeParent);
390 }
391 qwindowcontainer_traverse(parent, toplevelAboutToBeDestroyed);
392}
393
394void QWindowContainer::parentWasChanged(QWidget *parent)
395{
396 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
397 if (d->window->parent()) {
398 d->updateUsesNativeWidgets();
399 d->markParentChain();
400 QWidget *toplevel = d->usesNativeWidgets ? parent : parent->window();
401 if (!toplevel->windowHandle()) {
402 QWidgetPrivate *tld = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(toplevel));
403 tld->createTLExtra();
404 tld->createTLSysExtra();
405 Q_ASSERT(toplevel->windowHandle());
406 }
407 d->window->setParent(toplevel->windowHandle());
408 d->fakeParent.destroy();
409 d->updateGeometry();
410 }
411 }
412 qwindowcontainer_traverse(parent, parentWasChanged);
413}
414
415void QWindowContainer::parentWasMoved(QWidget *parent)
416{
417 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
418 if (d->window->parent())
419 d->updateGeometry();
420 }
421 qwindowcontainer_traverse(parent, parentWasMoved);
422}
423
424void QWindowContainer::parentWasRaised(QWidget *parent)
425{
426 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
427 if (d->window->parent())
428 d->window->raise();
429 }
430 qwindowcontainer_traverse(parent, parentWasRaised);
431}
432
433void QWindowContainer::parentWasLowered(QWidget *parent)
434{
435 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(parent)) {
436 if (d->window->parent())
437 d->window->lower();
438 }
439 qwindowcontainer_traverse(parent, parentWasLowered);
440}
441
442QT_END_NAMESPACE
443
444#include "moc_qwindowcontainer_p.cpp"
445