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
51QT_BEGIN_NAMESPACE
52
53Q_LOGGING_CATEGORY(lcWindow, "qt.quick.dialogs.window")
54
55QUrl QQuickAbstractDialog::m_decorationComponentUrl = QUrl();
56
57QQuickAbstractDialog::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(cap: QPlatformIntegration::MultipleWindows) &&
67 QGuiApplicationPrivate::platformIntegration()->
68 hasCapability(cap: QPlatformIntegration::WindowManagement))
69 , m_hasAspiredPosition(false)
70 , m_visibleChangedConnected(false)
71 , m_dialogHelperInUse(false)
72{
73}
74
75QQuickAbstractDialog::~QQuickAbstractDialog()
76{
77}
78
79void 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(windowFlags: flags, windowModality: m_modality, parent: 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(name: "minimumHeight");
127 if (minHeight.isValid()) {
128 if (minHeight.toInt() > minSize.height())
129 minSize.setHeight(minHeight.toDouble());
130 connect(sender: m_contentItem, SIGNAL(minimumHeightChanged()), receiver: this, SLOT(minimumHeightChanged()));
131 }
132 QVariant minWidth = m_contentItem->property(name: "minimumWidth");
133 if (minWidth.isValid()) {
134 if (minWidth.toInt() > minSize.width())
135 minSize.setWidth(minWidth.toInt());
136 connect(sender: m_contentItem, SIGNAL(minimumWidthChanged()), receiver: this, SLOT(minimumWidthChanged()));
137 }
138 m_dialogWindow->setMinimumSize(minSize);
139 connect(sender: win, SIGNAL(widthChanged(int)), receiver: this, SLOT(windowGeometryChanged()));
140 connect(sender: win, SIGNAL(heightChanged(int)), receiver: 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(msg: "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(sender: m_decorationComponent, SIGNAL(statusChanged(QQmlComponent::Status)),
159 receiver: 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(sender: m_dialogWindow, SIGNAL(xChanged(int)), receiver: this, SLOT(setX(int)));
186 connect(sender: m_dialogWindow, SIGNAL(yChanged(int)), receiver: this, SLOT(setY(int)));
187 connect(sender: m_dialogWindow, SIGNAL(widthChanged(int)), receiver: this, SLOT(setWidth(int)));
188 connect(sender: m_dialogWindow, SIGNAL(heightChanged(int)), receiver: this, SLOT(setHeight(int)));
189 connect(sender: m_contentItem, SIGNAL(implicitHeightChanged()), receiver: this, SLOT(implicitHeightChanged()));
190 }
191 if (!m_visibleChangedConnected) {
192 connect(sender: m_dialogWindow, signal: &QQuickWindow::visibleChanged, receiver: this, slot: &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
211void 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 *>(object: 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(name: "content", value: contentVariant);
228 setDecorationDismissBehavior();
229 connect(sender: m_windowDecoration, SIGNAL(dismissed()), receiver: 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
248void 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
256void QQuickAbstractDialog::accept()
257{
258 setVisible(false);
259 emit accepted();
260}
261
262void QQuickAbstractDialog::reject()
263{
264 setVisible(false);
265 emit rejected();
266}
267
268void QQuickAbstractDialog::visibleChanged(bool v)
269{
270 m_visible = v;
271 qCDebug(lcWindow) << "visible" << v;
272 emit visibilityChanged();
273}
274
275void 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
284void QQuickAbstractDialog::minimumWidthChanged()
285{
286 qreal min = m_contentItem->property(name: "minimumWidth").toReal();
287 qreal implicitOrMin = qMax(a: m_contentItem->implicitWidth(), b: 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
294void QQuickAbstractDialog::minimumHeightChanged()
295{
296 qreal min = m_contentItem->property(name: "minimumHeight").toReal();
297 qreal implicitOrMin = qMax(a: m_contentItem->implicitHeight(), b: 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
304void 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
312QQuickWindow *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 *>(object: parent());
318 m_parentWindow = (parentItem ? parentItem->window() : qmlobject_cast<QQuickWindow *>(object: parent()));
319 }
320 return m_parentWindow;
321}
322
323void QQuickAbstractDialog::setDecorationDismissBehavior()
324{
325 m_windowDecoration->setProperty(name: "dismissOnOuterClick", value: (m_modality == Qt::NonModal));
326}
327
328void QQuickAbstractDialog::setContentItem(QQuickItem *obj)
329{
330 m_contentItem = obj;
331 qCDebug(lcWindow) << obj;
332 if (m_dialogWindow) {
333 disconnect(sender: m_dialogWindow, signal: &QQuickWindow::visibleChanged, receiver: this, slot: &QQuickAbstractDialog::visibleChanged);
334 // Can't necessarily delete because m_dialogWindow might have been provided by the QML.
335 m_dialogWindow = 0;
336 }
337}
338
339int QQuickAbstractDialog::x() const
340{
341 if (m_dialogWindow)
342 return m_dialogWindow->x();
343 return m_sizeAspiration.x();
344}
345
346int QQuickAbstractDialog::y() const
347{
348 if (m_dialogWindow)
349 return m_dialogWindow->y();
350 return m_sizeAspiration.y();
351}
352
353int QQuickAbstractDialog::width() const
354{
355 if (m_dialogWindow)
356 return m_dialogWindow->width();
357 return m_sizeAspiration.width();
358}
359
360int 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*/
378int QQuickAbstractDialog::__maximumDimension() const
379{
380 QScreen *screen = QGuiApplication::primaryScreen();
381 qCDebug(lcWindow) << "__maximumDimension checking screen" << screen << "geometry" << screen->availableVirtualGeometry();
382 return (screen ?
383 qMin(a: screen->availableVirtualGeometry().width(), b: screen->availableVirtualGeometry().height()) :
384 480) * 9 / 10;
385}
386
387void QQuickAbstractDialog::setX(int arg)
388{
389 m_hasAspiredPosition = true;
390 m_sizeAspiration.moveLeft(pos: 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
403void QQuickAbstractDialog::setY(int arg)
404{
405 m_hasAspiredPosition = true;
406 m_sizeAspiration.moveTop(pos: 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
419void 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
434void 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
449QT_END_NAMESPACE
450

source code of qtquickcontrols/src/dialogs/qquickabstractdialog.cpp