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 Qt Quick Dialogs 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 "qquickabstractdialog_p.h"
41 #include "qquickitem.h"
42 
43 #include <private/qguiapplication_p.h>
44 #include <private/qqmlglobal_p.h>
45 #include <QLoggingCategory>
46 #include <QWindow>
47 #include <QQmlComponent>
48 #include <QQuickWindow>
49 #include <qpa/qplatformintegration.h>
50 
51 QT_BEGIN_NAMESPACE
52 
53 Q_LOGGING_CATEGORY(lcWindow, "qt.quick.dialogs.window")
54 
55 QUrl QQuickAbstractDialog::m_decorationComponentUrl = QUrl();
56 
QQuickAbstractDialog(QObject * parent)57 QQuickAbstractDialog::QQuickAbstractDialog(QObject *parent)
58     : QObject(parent)
59     , m_parentWindow(0)
60     , m_visible(false)
61     , m_modality(Qt::WindowModal)
62     , m_contentItem(0)
63     , m_dialogWindow(0)
64     , m_windowDecoration(0)
65     , m_hasNativeWindows(QGuiApplicationPrivate::platformIntegration()->
66         hasCapability(QPlatformIntegration::MultipleWindows) &&
67         QGuiApplicationPrivate::platformIntegration()->
68         hasCapability(QPlatformIntegration::WindowManagement))
69     , m_hasAspiredPosition(false)
70     , m_visibleChangedConnected(false)
71     , m_dialogHelperInUse(false)
72 {
73 }
74 
~QQuickAbstractDialog()75 QQuickAbstractDialog::~QQuickAbstractDialog()
76 {
77 }
78 
setVisible(bool v)79 void QQuickAbstractDialog::setVisible(bool v)
80 {
81     if (m_visible == v) return;
82     m_visible = v;
83 
84     if (m_dialogHelperInUse || v) {
85         // To show the dialog, we first check if there is a dialog helper that can be used
86         // and that show succeeds given the current configuration. Otherwise we fall back
87         // to use the pure QML version.
88         if (QPlatformDialogHelper *dialogHelper = helper()) {
89             if (v) {
90                 Qt::WindowFlags flags = Qt::Dialog;
91                 if (!title().isEmpty())
92                     flags |= Qt::WindowTitleHint;
93                 if (dialogHelper->show(flags, m_modality, parentWindow())) {
94                     qCDebug(lcWindow) << "Show dialog using helper:" << dialogHelper;
95                     m_dialogHelperInUse = true;
96                     emit visibilityChanged();
97                     return;
98                 }
99             } else {
100                 qCDebug(lcWindow) << "Hide dialog using helper:" << dialogHelper;
101                 dialogHelper->hide();
102                 emit visibilityChanged();
103                 return;
104             }
105         }
106     }
107 
108     qCDebug(lcWindow) << "Show/hide dialog using pure QML";
109     m_dialogHelperInUse = false;
110 
111     // Pure QML implementation: wrap the contentItem in a window, or fake it
112     if (!m_dialogWindow && m_contentItem) {
113         if (v)
114             emit __maximumDimensionChanged();
115         if (m_hasNativeWindows)
116             m_dialogWindow = m_contentItem->window();
117         // An Item-based dialog implementation doesn't come with a window, so
118         // we have to instantiate one iff the platform allows it.
119         if (!m_dialogWindow && m_hasNativeWindows) {
120             QQuickWindow *win = new QQuickWindow;
121             ((QObject *)win)->setParent(this); // memory management only
122             win->setFlags(Qt::Dialog);
123             m_dialogWindow = win;
124             m_contentItem->setParentItem(win->contentItem());
125             QSize minSize = QSize(m_contentItem->implicitWidth(), m_contentItem->implicitHeight());
126             QVariant minHeight = m_contentItem->property("minimumHeight");
127             if (minHeight.isValid()) {
128                 if (minHeight.toInt() > minSize.height())
129                     minSize.setHeight(minHeight.toDouble());
130                 connect(m_contentItem, SIGNAL(minimumHeightChanged()), this, SLOT(minimumHeightChanged()));
131             }
132             QVariant minWidth = m_contentItem->property("minimumWidth");
133             if (minWidth.isValid()) {
134                 if (minWidth.toInt() > minSize.width())
135                     minSize.setWidth(minWidth.toInt());
136                 connect(m_contentItem, SIGNAL(minimumWidthChanged()), this, SLOT(minimumWidthChanged()));
137             }
138             m_dialogWindow->setMinimumSize(minSize);
139             connect(win, SIGNAL(widthChanged(int)), this, SLOT(windowGeometryChanged()));
140             connect(win, SIGNAL(heightChanged(int)), this, SLOT(windowGeometryChanged()));
141             qCDebug(lcWindow) << "created window" << win << "with min size" << win->minimumSize() << "geometry" << win->geometry();
142         }
143 
144         if (!m_dialogWindow) {
145             if (Q_UNLIKELY(!parentWindow())) {
146                 qWarning("cannot set dialog visible: no window");
147                 return;
148             }
149             m_dialogWindow = parentWindow();
150 
151             // If the platform does not support multiple windows, but the dialog is
152             // implemented as an Item, then try to decorate it as a fake window and make it visible.
153             if (!m_windowDecoration) {
154                 if (!m_decorationComponent)
155                     m_decorationComponent = new QQmlComponent(qmlEngine(this), m_decorationComponentUrl, QQmlComponent::Asynchronous, this);
156                 if (m_decorationComponent) {
157                     if (m_decorationComponent->isLoading())
158                         connect(m_decorationComponent, SIGNAL(statusChanged(QQmlComponent::Status)),
159                                 this, SLOT(decorationLoaded()));
160                     else
161                         decorationLoaded(); // do the reparenting of contentItem on top of it
162                 }
163                 // Window decoration wasn't possible, so just reparent it into the scene
164                 else {
165                     qCDebug(lcWindow) << "no window and no decoration";
166                     m_contentItem->setParentItem(parentWindow()->contentItem());
167                     m_contentItem->setZ(10000);
168                 }
169             }
170         }
171     }
172     if (m_dialogWindow) {
173         // "grow up" to the size and position expected to achieve
174         if (!m_sizeAspiration.isNull()) {
175             if (m_hasAspiredPosition) {
176                 qCDebug(lcWindow) << "geometry aspiration" << m_sizeAspiration;
177                 m_dialogWindow->setGeometry(m_sizeAspiration);
178             } else {
179                 qCDebug(lcWindow) << "size aspiration" << m_sizeAspiration.size();
180                 if (m_sizeAspiration.width() > 0)
181                     m_dialogWindow->setWidth(m_sizeAspiration.width());
182                 if (m_sizeAspiration.height() > 0)
183                     m_dialogWindow->setHeight(m_sizeAspiration.height());
184             }
185             connect(m_dialogWindow, SIGNAL(xChanged(int)), this, SLOT(setX(int)));
186             connect(m_dialogWindow, SIGNAL(yChanged(int)), this, SLOT(setY(int)));
187             connect(m_dialogWindow, SIGNAL(widthChanged(int)), this, SLOT(setWidth(int)));
188             connect(m_dialogWindow, SIGNAL(heightChanged(int)), this, SLOT(setHeight(int)));
189             connect(m_contentItem, SIGNAL(implicitHeightChanged()), this, SLOT(implicitHeightChanged()));
190         }
191         if (!m_visibleChangedConnected) {
192             connect(m_dialogWindow, &QQuickWindow::visibleChanged, this, &QQuickAbstractDialog::visibleChanged);
193             m_visibleChangedConnected = true;
194         }
195     }
196     if (m_windowDecoration) {
197         setDecorationDismissBehavior();
198         m_windowDecoration->setVisible(v);
199     } else if (m_dialogWindow) {
200         if (v) {
201             m_dialogWindow->setTransientParent(parentWindow());
202             m_dialogWindow->setTitle(title());
203             m_dialogWindow->setModality(m_modality);
204         }
205         m_dialogWindow->setVisible(v);
206     }
207 
208     emit visibilityChanged();
209 }
210 
decorationLoaded()211 void QQuickAbstractDialog::decorationLoaded()
212 {
213     bool ok = false;
214     Q_ASSERT(parentWindow());
215     QQuickItem *parentItem = parentWindow()->contentItem();
216     Q_ASSERT(parentItem);
217     if (m_decorationComponent->isError()) {
218         qWarning() << m_decorationComponent->errors();
219     } else {
220         QObject *decoration = m_decorationComponent->create();
221         m_windowDecoration = qobject_cast<QQuickItem *>(decoration);
222         if (m_windowDecoration) {
223             m_windowDecoration->setParentItem(parentItem);
224             // Give the window decoration its content to manage
225             QVariant contentVariant;
226             contentVariant.setValue<QQuickItem*>(m_contentItem);
227             m_windowDecoration->setProperty("content", contentVariant);
228             setDecorationDismissBehavior();
229             connect(m_windowDecoration, SIGNAL(dismissed()), this, SLOT(reject()));
230             ok = true;
231             qCDebug(lcWindow) << "using synthetic window decoration" << m_windowDecoration << "from" << m_decorationComponent->url();
232         } else {
233             qWarning() << m_decorationComponent->url() <<
234                 "cannot be used as a window decoration because it's not an Item";
235             delete decoration;
236             delete m_decorationComponent;
237             m_decorationComponent = nullptr;
238         }
239     }
240     // Window decoration wasn't possible, so just reparent it into the scene
241     if (!ok) {
242         m_contentItem->setParentItem(parentItem);
243         m_contentItem->setZ(10000);
244         qCDebug(lcWindow) << "no decoration";
245     }
246 }
247 
setModality(Qt::WindowModality m)248 void QQuickAbstractDialog::setModality(Qt::WindowModality m)
249 {
250     if (m_modality == m) return;
251     qCDebug(lcWindow) << "modality" << m;
252     m_modality = m;
253     emit modalityChanged();
254 }
255 
accept()256 void QQuickAbstractDialog::accept()
257 {
258     setVisible(false);
259     emit accepted();
260 }
261 
reject()262 void QQuickAbstractDialog::reject()
263 {
264     setVisible(false);
265     emit rejected();
266 }
267 
visibleChanged(bool v)268 void QQuickAbstractDialog::visibleChanged(bool v)
269 {
270     m_visible = v;
271     qCDebug(lcWindow) << "visible" << v;
272     emit visibilityChanged();
273 }
274 
windowGeometryChanged()275 void QQuickAbstractDialog::windowGeometryChanged()
276 {
277     if (m_dialogWindow && m_contentItem) {
278         qCDebug(lcWindow) << m_dialogWindow->geometry();
279         m_contentItem->setWidth(m_dialogWindow->width());
280         m_contentItem->setHeight(m_dialogWindow->height());
281     }
282 }
283 
minimumWidthChanged()284 void QQuickAbstractDialog::minimumWidthChanged()
285 {
286     qreal min = m_contentItem->property("minimumWidth").toReal();
287     qreal implicitOrMin = qMax(m_contentItem->implicitWidth(), min);
288     qCDebug(lcWindow) << "content implicitWidth" << m_contentItem->implicitWidth() << "minimumWidth" << min;
289     if (m_dialogWindow->width() < implicitOrMin)
290         m_dialogWindow->setWidth(implicitOrMin);
291     m_dialogWindow->setMinimumWidth(implicitOrMin);
292 }
293 
minimumHeightChanged()294 void QQuickAbstractDialog::minimumHeightChanged()
295 {
296     qreal min = m_contentItem->property("minimumHeight").toReal();
297     qreal implicitOrMin = qMax(m_contentItem->implicitHeight(), min);
298     qCDebug(lcWindow) << "content implicitHeight" << m_contentItem->implicitHeight() << "minimumHeight" << min;
299     if (m_dialogWindow->height() < implicitOrMin)
300         m_dialogWindow->setHeight(implicitOrMin);
301     m_dialogWindow->setMinimumHeight(implicitOrMin);
302 }
303 
implicitHeightChanged()304 void QQuickAbstractDialog::implicitHeightChanged()
305 {
306     qCDebug(lcWindow) << "content implicitHeight" << m_contentItem->implicitHeight()
307                       << "window minimumHeight" << m_dialogWindow->minimumHeight();
308     if (m_contentItem->implicitHeight() < m_dialogWindow->minimumHeight())
309         m_dialogWindow->setMinimumHeight(m_contentItem->implicitHeight());
310 }
311 
parentWindow()312 QQuickWindow *QQuickAbstractDialog::parentWindow()
313 {
314     if (!m_parentWindow) {
315         // Usually a dialog is declared inside an Item; but if its QObject parent
316         // is a Window, that's the window we are interested in. (QTBUG-38578)
317         QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
318         m_parentWindow = (parentItem ? parentItem->window() : qmlobject_cast<QQuickWindow *>(parent()));
319     }
320     return m_parentWindow;
321 }
322 
setDecorationDismissBehavior()323 void QQuickAbstractDialog::setDecorationDismissBehavior()
324 {
325     m_windowDecoration->setProperty("dismissOnOuterClick", (m_modality == Qt::NonModal));
326 }
327 
setContentItem(QQuickItem * obj)328 void QQuickAbstractDialog::setContentItem(QQuickItem *obj)
329 {
330     m_contentItem = obj;
331     qCDebug(lcWindow) << obj;
332     if (m_dialogWindow) {
333         disconnect(m_dialogWindow, &QQuickWindow::visibleChanged, this, &QQuickAbstractDialog::visibleChanged);
334         // Can't necessarily delete because m_dialogWindow might have been provided by the QML.
335         m_dialogWindow = 0;
336     }
337 }
338 
x() const339 int QQuickAbstractDialog::x() const
340 {
341     if (m_dialogWindow)
342         return m_dialogWindow->x();
343     return m_sizeAspiration.x();
344 }
345 
y() const346 int QQuickAbstractDialog::y() const
347 {
348     if (m_dialogWindow)
349         return m_dialogWindow->y();
350     return m_sizeAspiration.y();
351 }
352 
width() const353 int QQuickAbstractDialog::width() const
354 {
355     if (m_dialogWindow)
356         return m_dialogWindow->width();
357     return m_sizeAspiration.width();
358 }
359 
height() const360 int QQuickAbstractDialog::height() const
361 {
362     if (m_dialogWindow)
363         return m_dialogWindow->height();
364     return m_sizeAspiration.height();
365 }
366 
367 /*
368     A non-fullscreen dialog is not allowed to be too large
369     to fit on the screen in either orientation (portrait or landscape).
370     That way on platforms which can do rotation, the dialog does not
371     change its size when the screen is rotated.  So the value returned
372     here is the maximum for both width and height.  We need to know
373     at init time, not wait until the dialog's content item is shown in
374     a window so that the desktopAvailableWidth and Height will be valid
375     in the Screen attached property.  And to allow space for window borders,
376     the max is further reduced by 10%.
377 */
__maximumDimension() const378 int QQuickAbstractDialog::__maximumDimension() const
379 {
380     QScreen *screen = QGuiApplication::primaryScreen();
381     qCDebug(lcWindow) << "__maximumDimension checking screen" << screen << "geometry" << screen->availableVirtualGeometry();
382     return (screen ?
383                 qMin(screen->availableVirtualGeometry().width(), screen->availableVirtualGeometry().height()) :
384                 480) * 9 / 10;
385 }
386 
setX(int arg)387 void QQuickAbstractDialog::setX(int arg)
388 {
389     m_hasAspiredPosition = true;
390     m_sizeAspiration.moveLeft(arg);
391     if (helper()) {
392         // TODO
393     } else if (m_dialogWindow) {
394         if (sender() != m_dialogWindow)
395             m_dialogWindow->setX(arg);
396     } else if (m_contentItem) {
397         m_contentItem->setX(arg);
398     }
399     qCDebug(lcWindow) << arg;
400     emit geometryChanged();
401 }
402 
setY(int arg)403 void QQuickAbstractDialog::setY(int arg)
404 {
405     m_hasAspiredPosition = true;
406     m_sizeAspiration.moveTop(arg);
407     if (helper()) {
408         // TODO
409     } else if (m_dialogWindow) {
410         if (sender() != m_dialogWindow)
411             m_dialogWindow->setY(arg);
412     } else if (m_contentItem) {
413         m_contentItem->setY(arg);
414     }
415     qCDebug(lcWindow) << arg;
416     emit geometryChanged();
417 }
418 
setWidth(int arg)419 void QQuickAbstractDialog::setWidth(int arg)
420 {
421     m_sizeAspiration.setWidth(arg);
422     if (helper()) {
423         // TODO
424     } else if (m_dialogWindow) {
425         if (sender() != m_dialogWindow)
426             m_dialogWindow->setWidth(arg);
427     } else if (m_contentItem) {
428         m_contentItem->setWidth(arg);
429     }
430     qCDebug(lcWindow) << arg;
431     emit geometryChanged();
432 }
433 
setHeight(int arg)434 void QQuickAbstractDialog::setHeight(int arg)
435 {
436     m_sizeAspiration.setHeight(arg);
437     if (helper()) {
438         // TODO
439     } else if (m_dialogWindow) {
440         if (sender() != m_dialogWindow)
441             m_dialogWindow->setHeight(arg);
442     } else if (m_contentItem) {
443         m_contentItem->setHeight(arg);
444     }
445     qCDebug(lcWindow) << arg;
446     emit geometryChanged();
447 }
448 
449 QT_END_NAMESPACE
450