1 /*
2     SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
3     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
4     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
5     SPDX-FileCopyrightText: 2014 Vishesh Handa <vhanda@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "dialog.h"
11 #include "../declarativeimports/core/framesvgitem.h"
12 #include "config-plasma.h"
13 #include "configview.h"
14 #include "dialogshadows_p.h"
15 #include "view.h"
16 
17 #include <QLayout>
18 #include <QMenu>
19 #include <QPointer>
20 #include <QQuickItem>
21 #include <QScreen>
22 #include <QTimer>
23 
24 #include <QPlatformSurfaceEvent>
25 
26 #include <KWindowSystem/KWindowInfo>
27 #include <KWindowSystem>
28 
29 #include <kquickaddons/quickviewsharedengine.h>
30 
31 #include <KWindowEffects>
32 #include <Plasma/Corona>
33 
34 #include <QDebug>
35 
36 #if HAVE_KWAYLAND
37 #include "waylandintegration_p.h"
38 #include <KWayland/Client/plasmashell.h>
39 #include <KWayland/Client/surface.h>
40 #endif
41 
42 #if HAVE_XCB_SHAPE
43 #include <QX11Info>
44 #include <xcb/shape.h>
45 #endif
46 
47 #if HAVE_X11
48 #include <QtPlatformHeaders/QXcbWindowFunctions>
49 #endif
50 
51 // Unfortunately QWINDOWSIZE_MAX is not exported
52 #define DIALOGSIZE_MAX ((1 << 24) - 1)
53 
54 namespace PlasmaQuick
55 {
56 class DialogPrivate
57 {
58 public:
DialogPrivate(Dialog * dialog)59     DialogPrivate(Dialog *dialog)
60         : q(dialog)
61         , location(Plasma::Types::BottomEdge)
62         , frameSvgItem(nullptr)
63         , hasMask(false)
64         , type(Dialog::Normal)
65         , hideOnWindowDeactivate(false)
66         , outputOnly(false)
67         , visible(false)
68         , componentComplete(dialog->parent() == nullptr)
69         , backgroundHints(Dialog::StandardBackground)
70     {
71         hintsCommitTimer.setSingleShot(true);
72         hintsCommitTimer.setInterval(0);
73         QObject::connect(&hintsCommitTimer, SIGNAL(timeout()), q, SLOT(updateLayoutParameters()));
74     }
75 
76     void updateInputShape();
77 
78     // SLOTS
79     /**
80      * Sync Borders updates the enabled borders of the frameSvgItem depending
81      * on the geometry of the window.
82      *
83      * \param windowGeometry The window geometry which should be taken into
84      * consideration when activating/deactivating certain borders
85      */
86     void syncBorders(const QRect &windowGeometry);
87 
88     /**
89      * This function sets the blurBehind, background contrast and shadows. It
90      * does so wrt the frameSvgItem. So make sure the frameSvgItem is the
91      * correct size before calling this function.
92      */
93     void updateTheme();
94     void updateVisibility(bool visible);
95 
96     void updateMinimumWidth();
97     void updateMinimumHeight();
98     void updateMaximumWidth();
99     void updateMaximumHeight();
100 
101     /**
102      * Gets the maximum and minimum size hints for the window based on the contents. it doesn't actually resize anything
103      */
104     void getSizeHints(QSize &min, QSize &max) const;
105 
106     /**
107      * This function is an optimized version of updateMaximumHeight,
108      * updateMaximumWidth,updateMinimumWidth and updateMinimumHeight.
109      * It should be called when you need to call all 4 of these functions
110      * AND you have called syncToMainItemSize before.
111      */
112     void updateLayoutParameters();
113 
114     QRect availableScreenGeometryForPosition(const QPoint &pos) const;
115 
116     /**
117      * This function checks the current position of the dialog and repositions
118      * it so that no part of it is not on the screen
119      */
120     void repositionIfOffScreen();
121 
122     void slotMainItemSizeChanged();
123     void slotWindowPositionChanged();
124 
125     void syncToMainItemSize();
126 
127     bool mainItemContainsPosition(const QPointF &point) const;
128     QPointF positionAdjustedForMainItem(const QPointF &point) const;
129 
130     void setupWaylandIntegration();
131 
132     void applyType();
133 
134     Dialog *q;
135     Plasma::Types::Location location;
136     Plasma::FrameSvgItem *frameSvgItem;
137     QPointer<QQuickItem> mainItem;
138     QPointer<QQuickItem> visualParent;
139     QTimer hintsCommitTimer;
140 #if HAVE_KWAYLAND
141     QPointer<KWayland::Client::PlasmaShellSurface> shellSurface;
142 #endif
143 
144     QRect cachedGeometry;
145     bool hasMask;
146     Dialog::WindowType type;
147     bool hideOnWindowDeactivate;
148     bool outputOnly;
149     bool visible;
150     Plasma::Theme theme;
151     bool componentComplete;
152     Dialog::BackgroundHints backgroundHints;
153 
154     // Attached Layout property of mainItem, if any
155     QPointer<QObject> mainItemLayout;
156 };
157 
availableScreenGeometryForPosition(const QPoint & pos) const158 QRect DialogPrivate::availableScreenGeometryForPosition(const QPoint &pos) const
159 {
160     // FIXME: QWindow::screen() never ever changes if the window is moved across
161     //        virtual screens (normal two screens with X), this seems to be intentional
162     //        as it's explicitly mentioned in the docs. Until that's changed or some
163     //        more proper way of howto get the current QScreen for given QWindow is found,
164     //        we simply iterate over the virtual screens and pick the one our QWindow
165     //        says it's at.
166     QRect avail;
167     const auto screens = QGuiApplication::screens();
168     for (QScreen *screen : screens) {
169         // we check geometry() but then take availableGeometry()
170         // to reliably check in which screen a position is, we need the full
171         // geometry, including areas for panels
172         if (screen->geometry().contains(pos)) {
173             avail = screen->availableGeometry();
174             break;
175         }
176     }
177 
178     /*
179      * if the heuristic fails (because the topleft of the dialog is offscreen)
180      * use at least our screen()
181      * the screen should be correctly updated now on Qt 5.3+ so should be
182      * more reliable anyways (could be tried to remove the whole for loop
183      * above at this point)
184      *
185      * important: screen can be a nullptr... see bug 345173
186      */
187     if (avail.isEmpty() && q->screen()) {
188         avail = q->screen()->availableGeometry();
189     }
190 
191     return avail;
192 }
193 
syncBorders(const QRect & geom)194 void DialogPrivate::syncBorders(const QRect &geom)
195 {
196     QRect avail = availableScreenGeometryForPosition(geom.topLeft());
197     int borders = Plasma::FrameSvg::AllBorders;
198 
199     // Tooltips always have all the borders
200     // floating windows have all borders
201     if (!q->flags().testFlag(Qt::ToolTip) && location != Plasma::Types::Floating) {
202         if (geom.x() <= avail.x() || location == Plasma::Types::LeftEdge) {
203             borders = borders & ~Plasma::FrameSvg::LeftBorder;
204         }
205         if (geom.y() <= avail.y() || location == Plasma::Types::TopEdge) {
206             borders = borders & ~Plasma::FrameSvg::TopBorder;
207         }
208         if (avail.right() <= geom.x() + geom.width() || location == Plasma::Types::RightEdge) {
209             borders = borders & ~Plasma::FrameSvg::RightBorder;
210         }
211         if (avail.bottom() <= geom.y() + geom.height() || location == Plasma::Types::BottomEdge) {
212             borders = borders & ~Plasma::FrameSvg::BottomBorder;
213         }
214     }
215 
216     if (frameSvgItem->enabledBorders() != (Plasma::FrameSvg::EnabledBorder)borders) {
217         frameSvgItem->setEnabledBorders((Plasma::FrameSvg::EnabledBorder)borders);
218     }
219 }
220 
updateTheme()221 void DialogPrivate::updateTheme()
222 {
223     if (backgroundHints == Dialog::NoBackground) {
224         frameSvgItem->setImagePath(QString());
225         KWindowEffects::enableBlurBehind(q, false);
226         KWindowEffects::enableBackgroundContrast(q, false);
227         q->setMask(QRegion());
228         DialogShadows::self()->removeWindow(q);
229     } else {
230         auto prefix = QStringLiteral("");
231         if ((backgroundHints & Dialog::SolidBackground) == Dialog::SolidBackground) {
232             prefix = QStringLiteral("solid/");
233         }
234         if (type == Dialog::Tooltip) {
235             frameSvgItem->setImagePath(prefix + QStringLiteral("widgets/tooltip"));
236         } else {
237             frameSvgItem->setImagePath(prefix + QStringLiteral("dialogs/background"));
238         }
239 
240         // This makes the mask slightly maller than the frame. Since the svg will have antialiasing and the mask not,
241         // there will be artifacts at the corners, if they go under the svg they're less evident
242         frameSvgItem->frameSvg()->resizeFrame(q->size() - QSize(2,2));
243         const QRegion mask = frameSvgItem->frameSvg()->mask().translated(1,1);
244         KWindowEffects::enableBlurBehind(q, theme.blurBehindEnabled(), mask);
245 
246         KWindowEffects::enableBackgroundContrast(q,
247                                                  theme.backgroundContrastEnabled(),
248                                                  theme.backgroundContrast(),
249                                                  theme.backgroundIntensity(),
250                                                  theme.backgroundSaturation(),
251                                                  mask);
252         frameSvgItem->frameSvg()->resizeFrame(q->size());
253 
254         if (KWindowSystem::compositingActive()) {
255             if (hasMask) {
256                 hasMask = false;
257                 q->setMask(QRegion());
258             }
259         } else {
260             hasMask = true;
261             q->setMask(frameSvgItem->mask());
262         }
263         if (q->isVisible()) {
264             DialogShadows::self()->addWindow(q, frameSvgItem->enabledBorders());
265         }
266     }
267     updateInputShape();
268 }
269 
updateVisibility(bool visible)270 void DialogPrivate::updateVisibility(bool visible)
271 {
272     if (visible) {
273         if (visualParent && visualParent->window()) {
274             q->setTransientParent(visualParent->window());
275         }
276 
277         if (q->location() == Plasma::Types::FullScreen) {
278             frameSvgItem->setEnabledBorders(Plasma::FrameSvg::NoBorder);
279 
280             // We cache the original size of the item, to retrieve it
281             // when the dialog is switched back from fullscreen.
282             if (q->geometry() != q->screen()->availableGeometry()) {
283                 cachedGeometry = q->geometry();
284             }
285             q->setGeometry(q->screen()->availableGeometry());
286         } else {
287             if (!cachedGeometry.isNull()) {
288                 q->resize(cachedGeometry.size());
289                 slotWindowPositionChanged();
290                 if (visualParent) {
291                     q->setPosition(q->popupPosition(visualParent, q->size()));
292                 }
293                 cachedGeometry = QRect();
294             }
295 
296             if (mainItem) {
297                 syncToMainItemSize();
298             }
299             if (mainItemLayout) {
300                 updateLayoutParameters();
301             }
302 
303 #if HAVE_KWAYLAND
304             // if is a wayland window that was hidden, we need
305             // to set its position again as there won't be any move event to sync QWindow::position and shellsurface::position
306             if (shellSurface && type != Dialog::OnScreenDisplay) {
307                 shellSurface->setPosition(q->position());
308             }
309 #endif
310         }
311     }
312 
313     if (!q->flags().testFlag(Qt::ToolTip) && type != Dialog::Notification && type != Dialog::CriticalNotification) {
314         KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
315 
316         switch (location) {
317         case Plasma::Types::TopEdge:
318             slideLocation = KWindowEffects::TopEdge;
319             break;
320         case Plasma::Types::LeftEdge:
321             slideLocation = KWindowEffects::LeftEdge;
322             break;
323         case Plasma::Types::RightEdge:
324             slideLocation = KWindowEffects::RightEdge;
325             break;
326         case Plasma::Types::BottomEdge:
327             slideLocation = KWindowEffects::BottomEdge;
328             break;
329         // no edge, no slide
330         default:
331             break;
332         }
333 
334         KWindowEffects::slideWindow(q, slideLocation, -1);
335     }
336 
337     if (visible) {
338         q->raise();
339 
340         applyType();
341     }
342 }
343 
updateMinimumWidth()344 void DialogPrivate::updateMinimumWidth()
345 {
346     Q_ASSERT(mainItem);
347     Q_ASSERT(mainItemLayout);
348 
349     if (!componentComplete) {
350         return;
351     }
352 
353     q->setMinimumWidth(0);
354 
355     // this is to try to get the internal item resized a tad before, but
356     // the flicker almost always happen anyways, so is *probably* useless
357     // this other kind of flicker is the view not being always focused exactly
358     // on the scene
359     auto margin = frameSvgItem->fixedMargins();
360     int minimumWidth = mainItemLayout->property("minimumWidth").toInt() + margin->left() + margin->right();
361     if (q->screen()) {
362         minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth);
363     }
364     q->contentItem()->setWidth(qMax(q->width(), minimumWidth));
365     q->setWidth(qMax(q->width(), minimumWidth));
366 
367     hintsCommitTimer.start();
368 }
369 
updateMinimumHeight()370 void DialogPrivate::updateMinimumHeight()
371 {
372     Q_ASSERT(mainItem);
373     Q_ASSERT(mainItemLayout);
374 
375     if (!componentComplete) {
376         return;
377     }
378 
379     q->setMinimumHeight(0);
380 
381     // this is to try to get the internal item resized a tad before, but
382     // the flicker almost always happen anyways, so is *probably* useless
383     // this other kind of flicker is the view not being always focused exactly
384     // on the scene
385     auto margin = frameSvgItem->fixedMargins();
386     int minimumHeight = mainItemLayout->property("minimumHeight").toInt() + margin->top() + margin->bottom();
387     if (q->screen()) {
388         minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight);
389     }
390     q->contentItem()->setHeight(qMax(q->height(), minimumHeight));
391     q->setHeight(qMax(q->height(), minimumHeight));
392 
393     hintsCommitTimer.start();
394 }
395 
updateMaximumWidth()396 void DialogPrivate::updateMaximumWidth()
397 {
398     Q_ASSERT(mainItem);
399     Q_ASSERT(mainItemLayout);
400 
401     if (!componentComplete) {
402         return;
403     }
404 
405     q->setMaximumWidth(DIALOGSIZE_MAX);
406 
407     auto margin = frameSvgItem->fixedMargins();
408     int maximumWidth = mainItemLayout->property("maximumWidth").toInt() + margin->left() + margin->right();
409     if (q->screen()) {
410         maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth);
411     }
412     q->contentItem()->setWidth(qMax(q->width(), maximumWidth));
413     q->setWidth(qMax(q->width(), maximumWidth));
414 
415     hintsCommitTimer.start();
416 }
417 
updateMaximumHeight()418 void DialogPrivate::updateMaximumHeight()
419 {
420     Q_ASSERT(mainItem);
421     Q_ASSERT(mainItemLayout);
422 
423     if (!componentComplete) {
424         return;
425     }
426 
427     q->setMaximumHeight(DIALOGSIZE_MAX);
428 
429     auto margin = frameSvgItem->fixedMargins();
430     int maximumHeight = mainItemLayout->property("maximumHeight").toInt() + margin->top() + margin->bottom();
431     if (q->screen()) {
432         maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight);
433     }
434     q->contentItem()->setHeight(qMax(q->height(), maximumHeight));
435     q->setHeight(qMin(q->height(), maximumHeight));
436 
437     hintsCommitTimer.start();
438 }
439 
getSizeHints(QSize & min,QSize & max) const440 void DialogPrivate::getSizeHints(QSize &min, QSize &max) const
441 {
442     if (!componentComplete || !mainItem || !mainItemLayout) {
443         return;
444     }
445     Q_ASSERT(mainItem);
446     Q_ASSERT(mainItemLayout);
447 
448     auto margin = frameSvgItem->fixedMargins();
449 
450     int minimumHeight = mainItemLayout->property("minimumHeight").toInt();
451     int maximumHeight = mainItemLayout->property("maximumHeight").toInt();
452     maximumHeight = maximumHeight ? maximumHeight : DIALOGSIZE_MAX;
453 
454     int minimumWidth = mainItemLayout->property("minimumWidth").toInt();
455     int maximumWidth = mainItemLayout->property("maximumWidth").toInt();
456     maximumWidth = maximumWidth ? maximumWidth : DIALOGSIZE_MAX;
457 
458     minimumHeight += margin->top() + margin->bottom();
459     maximumHeight += margin->top() + margin->bottom();
460     minimumWidth += margin->left() + margin->right();
461     maximumWidth += margin->left() + margin->right();
462 
463     if (q->screen()) {
464         minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth);
465         minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight);
466         maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth);
467         maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight);
468     }
469 
470     min = QSize(minimumWidth, minimumHeight);
471     max = QSize(maximumWidth, maximumHeight);
472 }
473 
updateLayoutParameters()474 void DialogPrivate::updateLayoutParameters()
475 {
476     if (!componentComplete || !mainItem || !mainItemLayout) {
477         return;
478     }
479 
480     mainItem->disconnect(q);
481     auto margin = frameSvgItem->fixedMargins();
482 
483     QSize min;
484     QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
485     getSizeHints(min, max);
486 
487     const QSize finalSize(qBound(min.width(), q->width(), max.width()), qBound(min.height(), q->height(), max.height()));
488 
489     if (visualParent) {
490         // it's important here that we're using re->size() as size, we don't want to do recursive resizeEvents
491         const QRect geom(q->popupPosition(visualParent, finalSize), finalSize);
492         q->adjustGeometry(geom);
493     } else {
494         q->resize(finalSize);
495     }
496 
497     mainItem->setPosition(QPointF(margin->left(), margin->top()));
498     mainItem->setSize(QSizeF(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom()));
499 
500     frameSvgItem->setSize(QSizeF(q->width(), q->height()));
501 
502     repositionIfOffScreen();
503     updateTheme();
504 
505     // FIXME: this seems to interfere with the geometry change that
506     // sometimes is still going on, causing flicker (this one is two repositions happening in quick succession).
507     // it may have to be delayed further
508     q->setMinimumSize(min);
509     q->setMaximumSize(max);
510 
511     QObject::connect(mainItem, SIGNAL(widthChanged()), q, SLOT(slotMainItemSizeChanged()));
512     QObject::connect(mainItem, SIGNAL(heightChanged()), q, SLOT(slotMainItemSizeChanged()));
513 }
514 
repositionIfOffScreen()515 void DialogPrivate::repositionIfOffScreen()
516 {
517     if (!componentComplete) {
518         return;
519     }
520     const QRect avail = availableScreenGeometryForPosition(q->position());
521 
522     int x = q->x();
523     int y = q->y();
524 
525     if (x < avail.left()) {
526         x = avail.left();
527     } else if (x + q->width() > avail.right()) {
528         x = avail.right() - q->width() + 1;
529     }
530 
531     if (y < avail.top()) {
532         y = avail.top();
533     } else if (y + q->height() > avail.bottom()) {
534         y = avail.bottom() - q->height() + 1;
535     }
536 
537     q->setX(x);
538     q->setY(y);
539 }
540 
updateInputShape()541 void DialogPrivate::updateInputShape()
542 {
543     if (!q->isVisible()) {
544         return;
545     }
546 
547 #if HAVE_XCB_SHAPE
548     if (KWindowSystem::isPlatformX11()) {
549         xcb_connection_t *c = QX11Info::connection();
550         static bool s_shapeExtensionChecked = false;
551         static bool s_shapeAvailable = false;
552         if (!s_shapeExtensionChecked) {
553             xcb_prefetch_extension_data(c, &xcb_shape_id);
554             const xcb_query_extension_reply_t *extension = xcb_get_extension_data(c, &xcb_shape_id);
555             if (extension->present) {
556                 // query version
557                 auto cookie = xcb_shape_query_version(c);
558                 QScopedPointer<xcb_shape_query_version_reply_t, QScopedPointerPodDeleter> version(xcb_shape_query_version_reply(c, cookie, nullptr));
559                 if (!version.isNull()) {
560                     s_shapeAvailable = (version->major_version * 0x10 + version->minor_version) >= 0x11;
561                 }
562             }
563             s_shapeExtensionChecked = true;
564         }
565         if (!s_shapeAvailable) {
566             return;
567         }
568         if (outputOnly) {
569             // set input shape, so that it doesn't accept any input events
570             xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, q->winId(), 0, 0, 0, nullptr);
571         } else {
572             // delete the shape
573             xcb_shape_mask(c, XCB_SHAPE_SO_INTERSECT, XCB_SHAPE_SK_INPUT, q->winId(), 0, 0, XCB_PIXMAP_NONE);
574         }
575     }
576 #endif
577 }
578 
syncToMainItemSize()579 void DialogPrivate::syncToMainItemSize()
580 {
581     Q_ASSERT(mainItem);
582 
583     if (!componentComplete) {
584         return;
585     }
586     if (mainItem->width() <= 0 || mainItem->height() <= 0) {
587         qWarning() << "trying to show an empty dialog";
588     }
589 
590     updateTheme();
591     if (visualParent) {
592         // fixedMargins will get all the borders, no matter if they are enabled
593         auto margins = frameSvgItem->fixedMargins();
594 
595         const QSize fullSize = QSize(mainItem->width(), mainItem->height()) + QSize(margins->left() + margins->right(), margins->top() + margins->bottom());
596 
597         // We get the popup position with the fullsize as we need the popup
598         // position in order to determine our actual size, as the position
599         // determines which borders will be shown.
600         const QRect geom(q->popupPosition(visualParent, fullSize), fullSize);
601 
602         // We're then moving the window to where we think we would be with all
603         // the borders. This way when syncBorders is called, it has a geometry
604         // to work with.
605         syncBorders(geom);
606     } else {
607         syncBorders(q->geometry());
608     }
609 
610     QSize s = QSize(mainItem->width(), mainItem->height())
611         + QSize(frameSvgItem->fixedMargins()->left() + frameSvgItem->fixedMargins()->right(),
612                 frameSvgItem->fixedMargins()->top() + frameSvgItem->fixedMargins()->bottom());
613 
614     QSize min;
615     QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
616     getSizeHints(min, max);
617     s = QSize(qBound(min.width(), s.width(), max.width()), qBound(min.height(), s.height(), max.height()));
618 
619     q->contentItem()->setSize(s);
620 
621     frameSvgItem->setSize(s);
622 
623     if (visualParent) {
624         const QRect geom(q->popupPosition(visualParent, s), s);
625 
626         if (geom == q->geometry()) {
627             return;
628         }
629 
630         q->adjustGeometry(geom);
631         // The borders will instantly be updated but the geometry might take a
632         // while as sub-classes can reimplement adjustGeometry and animate it.
633         syncBorders(geom);
634 
635     } else {
636         q->resize(s);
637     }
638 
639     mainItem->setPosition(QPointF(frameSvgItem->fixedMargins()->left(), frameSvgItem->fixedMargins()->top()));
640 
641     updateTheme();
642 }
643 
slotWindowPositionChanged()644 void DialogPrivate::slotWindowPositionChanged()
645 {
646     // Tooltips always have all the borders
647     // floating windows have all borders
648     if (!q->isVisible() || q->flags().testFlag(Qt::ToolTip) || location == Plasma::Types::Floating) {
649         return;
650     }
651 
652     syncBorders(q->geometry());
653     updateTheme();
654 
655     if (mainItem) {
656         auto margin = frameSvgItem->fixedMargins();
657         mainItem->setPosition(QPoint(margin->left(), margin->top()));
658         mainItem->setSize(QSize(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom()));
659     }
660 }
661 
mainItemContainsPosition(const QPointF & point) const662 bool DialogPrivate::mainItemContainsPosition(const QPointF &point) const
663 {
664     if (!mainItem) {
665         return false;
666     }
667 
668     return QRectF(mainItem->mapToScene(QPoint(0, 0)), QSizeF(mainItem->width(), mainItem->height())).contains(point);
669 }
670 
positionAdjustedForMainItem(const QPointF & point) const671 QPointF DialogPrivate::positionAdjustedForMainItem(const QPointF &point) const
672 {
673     if (!mainItem) {
674         return point;
675     }
676 
677     QRectF itemRect(mainItem->mapToScene(QPoint(0, 0)), QSizeF(mainItem->width(), mainItem->height()));
678 
679     return QPointF(qBound(itemRect.left(), point.x(), itemRect.right()), qBound(itemRect.top(), point.y(), itemRect.bottom()));
680 }
681 
setupWaylandIntegration()682 void DialogPrivate::setupWaylandIntegration()
683 {
684 #if HAVE_KWAYLAND
685     if (shellSurface) {
686         // already setup
687         return;
688     }
689 
690     using namespace KWayland::Client;
691     PlasmaShell *interface = WaylandIntegration::self()->waylandPlasmaShell();
692     if (!interface) {
693         return;
694     }
695     Surface *s = Surface::fromWindow(q);
696     if (!s) {
697         return;
698     }
699     shellSurface = interface->createSurface(s, q);
700 #endif
701 }
702 
applyType()703 void DialogPrivate::applyType()
704 {
705     if (type != Dialog::Normal) {
706         /*QXcbWindowFunctions::WmWindowType*/ int wmType = 0;
707 
708 #if HAVE_X11
709         if (KWindowSystem::isPlatformX11()) {
710             switch (type) {
711             case Dialog::Normal:
712                 Q_UNREACHABLE();
713                 break;
714             case Dialog::Dock:
715                 wmType = QXcbWindowFunctions::WmWindowType::Dock;
716                 break;
717             case Dialog::DialogWindow:
718                 wmType = QXcbWindowFunctions::WmWindowType::Dialog;
719                 break;
720             case Dialog::PopupMenu:
721                 wmType = QXcbWindowFunctions::WmWindowType::PopupMenu;
722                 break;
723             case Dialog::Tooltip:
724                 wmType = QXcbWindowFunctions::WmWindowType::Tooltip;
725                 break;
726             case Dialog::Notification:
727                 wmType = QXcbWindowFunctions::WmWindowType::Notification;
728                 break;
729             case Dialog::OnScreenDisplay:
730             case Dialog::CriticalNotification:
731                 // Not supported by Qt
732                 break;
733             }
734 
735             if (wmType) {
736                 QXcbWindowFunctions::setWmWindowType(q, static_cast<QXcbWindowFunctions::WmWindowType>(wmType));
737             }
738         }
739 #endif
740 
741         if (!wmType) {
742             KWindowSystem::setType(q->winId(), static_cast<NET::WindowType>(type));
743         }
744 #if HAVE_KWAYLAND
745         if (shellSurface) {
746             switch (type) {
747                 case Dialog::Dock:
748                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
749                 break;
750             case Dialog::Tooltip:
751                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::ToolTip);
752                 break;
753             case Dialog::Notification:
754                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Notification);
755                 break;
756             case Dialog::OnScreenDisplay:
757                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay);
758                 break;
759             case Dialog::CriticalNotification:
760                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::CriticalNotification);
761                 break;
762             default:
763                 break;
764             }
765         }
766 #endif
767     } else {
768         q->setFlags(Qt::FramelessWindowHint | q->flags());
769 
770 #if HAVE_KWAYLAND
771         // Only possible after setup
772         if (shellSurface) {
773             if (q->flags() & Qt::WindowStaysOnTopHint) {
774                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
775                 shellSurface->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow);
776             } else {
777                 shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Normal);
778                 shellSurface->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::AlwaysVisible);
779             }
780         }
781 #endif
782     }
783 
784     // an OSD can't be a Dialog, as qt xcb would attempt to set a transient parent for it
785     // see bug 370433
786     if (type == Dialog::OnScreenDisplay) {
787         q->setFlags((q->flags() & ~Qt::Dialog) | Qt::Window);
788     }
789 
790     if (backgroundHints == Dialog::NoBackground) {
791         frameSvgItem->setImagePath(QString());
792     } else {
793         auto prefix = QStringLiteral("");
794         if ((backgroundHints & Dialog::SolidBackground) == Dialog::SolidBackground) {
795             prefix = QStringLiteral("solid/");
796         }
797         if (type == Dialog::Tooltip) {
798             frameSvgItem->setImagePath(prefix + QStringLiteral("widgets/tooltip"));
799         } else {
800             frameSvgItem->setImagePath(prefix + QStringLiteral("dialogs/background"));
801         }
802     }
803 
804     if (type == Dialog::Dock || type == Dialog::Notification || type == Dialog::OnScreenDisplay || type == Dialog::CriticalNotification) {
805         KWindowSystem::setOnAllDesktops(q->winId(), true);
806     } else {
807         KWindowSystem::setOnAllDesktops(q->winId(), false);
808     }
809 
810 #if HAVE_KWAYLAND
811     if (shellSurface) {
812         shellSurface->setPanelTakesFocus(!q->flags().testFlag(Qt::WindowDoesNotAcceptFocus));
813     }
814 #endif
815 }
816 
Dialog(QQuickItem * parent)817 Dialog::Dialog(QQuickItem *parent)
818     : QQuickWindow(parent ? parent->window() : nullptr)
819     , d(new DialogPrivate(this))
820 {
821     setColor(QColor(Qt::transparent));
822     setFlags(Qt::FramelessWindowHint | Qt::Dialog);
823 
824     connect(this, &QWindow::xChanged, [=]() {
825         d->slotWindowPositionChanged();
826     });
827     connect(this, &QWindow::yChanged, [=]() {
828         d->slotWindowPositionChanged();
829     });
830 
831     // Given dialogs are skip task bar and don't have a decoration
832     // minimizing them using e.g. "minimize all" should just close them
833     connect(this, &QWindow::windowStateChanged, this, [this](Qt::WindowState newState) {
834         if (newState == Qt::WindowMinimized) {
835             setVisible(false);
836         }
837     });
838 
839     connect(this, &QWindow::visibleChanged, this, &Dialog::visibleChangedProxy);
840     connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(updateInputShape()));
841     connect(this, SIGNAL(outputOnlyChanged()), this, SLOT(updateInputShape()));
842 
843     // HACK: this property is invoked due to the initialization that gets done to contentItem() in the getter
844     property("data");
845     // Create the FrameSvg background.
846     d->frameSvgItem = new Plasma::FrameSvgItem(contentItem());
847     // This is needed as a transition thing for KWayland
848     setProperty("__plasma_frameSvg", QVariant::fromValue(d->frameSvgItem->frameSvg()));
849 
850     connect(&d->theme, SIGNAL(themeChanged()), this, SLOT(updateTheme()));
851 }
852 
~Dialog()853 Dialog::~Dialog()
854 {
855     if (!QCoreApplication::instance()->closingDown()) {
856         DialogShadows::self()->removeWindow(this);
857     }
858 
859     // Prevent signals from super-class destructor invoking our now-destroyed slots
860     disconnect(this, nullptr, this, nullptr);
861 }
862 
mainItem() const863 QQuickItem *Dialog::mainItem() const
864 {
865     return d->mainItem;
866 }
867 
setMainItem(QQuickItem * mainItem)868 void Dialog::setMainItem(QQuickItem *mainItem)
869 {
870     if (d->mainItem != mainItem) {
871         d->hintsCommitTimer.stop();
872 
873         if (d->mainItem) {
874             disconnect(d->mainItem, nullptr, this, nullptr);
875             d->mainItem->setParentItem(nullptr);
876         }
877 
878         if (d->mainItemLayout) {
879             disconnect(d->mainItemLayout, nullptr, this, nullptr);
880         }
881 
882         d->mainItem = mainItem;
883 
884         if (mainItem) {
885             mainItem->setParentItem(contentItem());
886 
887             connect(mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged()));
888             connect(mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged()));
889             d->slotMainItemSizeChanged();
890 
891             // Extract the representation's Layout, if any
892             QObject *layout = nullptr;
893             setMinimumSize(QSize(0, 0));
894             setMaximumSize(QSize(DIALOGSIZE_MAX, DIALOGSIZE_MAX));
895 
896             // Search a child that has the needed Layout properties
897             // HACK: here we are not type safe, but is the only way to access to a pointer of Layout
898             const auto lstChild = mainItem->children();
899             for (QObject *child : lstChild) {
900                 // find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight
901                 if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid()
902                     && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid()
903                     && child->property("fillWidth").isValid() && child->property("fillHeight").isValid()) {
904                     layout = child;
905                     break;
906                 }
907             }
908 
909             d->mainItemLayout = layout;
910 
911             if (layout) {
912                 // Why queued connections?
913                 // we need to be sure that the properties are
914                 // already *all* updated when we call the management code
915                 connect(layout, SIGNAL(minimumWidthChanged()), this, SLOT(updateMinimumWidth()));
916                 connect(layout, SIGNAL(minimumHeightChanged()), this, SLOT(updateMinimumHeight()));
917                 connect(layout, SIGNAL(maximumWidthChanged()), this, SLOT(updateMaximumWidth()));
918                 connect(layout, SIGNAL(maximumHeightChanged()), this, SLOT(updateMaximumHeight()));
919 
920                 d->updateLayoutParameters();
921             }
922         }
923 
924         // if this is called in Component.onCompleted we have to wait a loop the item is added to a scene
925         Q_EMIT mainItemChanged();
926     }
927 }
928 
slotMainItemSizeChanged()929 void DialogPrivate::slotMainItemSizeChanged()
930 {
931     syncToMainItemSize();
932 }
933 
visualParent() const934 QQuickItem *Dialog::visualParent() const
935 {
936     return d->visualParent;
937 }
938 
setVisualParent(QQuickItem * visualParent)939 void Dialog::setVisualParent(QQuickItem *visualParent)
940 {
941     if (d->visualParent == visualParent) {
942         return;
943     }
944 
945     d->visualParent = visualParent;
946     Q_EMIT visualParentChanged();
947     if (visualParent) {
948         if (visualParent->window()) {
949             setTransientParent(visualParent->window());
950         }
951         if (d->mainItem) {
952             d->syncToMainItemSize();
953         }
954     }
955 }
956 
popupPosition(QQuickItem * item,const QSize & size)957 QPoint Dialog::popupPosition(QQuickItem *item, const QSize &size)
958 {
959     if (!item) {
960         // If no item was specified try to align at the center of the parent view
961         QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
962         if (parentItem) {
963             QScreen *screen = parentItem->window()->screen();
964 
965             switch (d->location) {
966             case Plasma::Types::TopEdge:
967                 return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().y());
968             case Plasma::Types::LeftEdge:
969                 return QPoint(screen->availableGeometry().x(), screen->availableGeometry().center().y() - size.height() / 2);
970             case Plasma::Types::RightEdge:
971                 return QPoint(screen->availableGeometry().right() - size.width(), screen->availableGeometry().center().y() - size.height() / 2);
972             case Plasma::Types::BottomEdge:
973                 return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().bottom() - size.height());
974             // Default center in the screen
975             default:
976                 return screen->geometry().center() - QPoint(size.width() / 2, size.height() / 2);
977             }
978         } else {
979             return QPoint();
980         }
981     }
982 
983     QPointF pos = item->mapToScene(QPointF(0, 0));
984 
985     if (item->window()) {
986         pos = item->window()->mapToGlobal(pos.toPoint());
987     } else {
988         return QPoint();
989     }
990 
991     // if the item is in a dock or in a window that ignores WM we want to position the popups outside of the dock
992     const KWindowInfo winInfo(item->window()->winId(), NET::WMWindowType);
993     const bool outsideParentWindow =
994         ((winInfo.windowType(NET::AllTypesMask) == NET::Dock) || (item->window()->flags() & Qt::X11BypassWindowManagerHint)) && item->window()->mask().isNull();
995 
996     QRect parentGeometryBounds;
997     if (outsideParentWindow) {
998         parentGeometryBounds = item->window()->geometry();
999     } else {
1000         parentGeometryBounds = item->mapRectToScene(item->boundingRect()).toRect();
1001         if (item->window()) {
1002             parentGeometryBounds.moveTopLeft(item->window()->mapToGlobal(parentGeometryBounds.topLeft()));
1003             pos = parentGeometryBounds.topLeft();
1004         }
1005     }
1006 
1007     const QPoint topPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.top() - size.height());
1008     const QPoint bottomPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.bottom());
1009 
1010     const QPoint leftPoint(parentGeometryBounds.left() - size.width(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2);
1011 
1012     const QPoint rightPoint(parentGeometryBounds.right(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2);
1013 
1014     QPoint dialogPos;
1015     if (d->location == Plasma::Types::TopEdge) {
1016         dialogPos = bottomPoint;
1017     } else if (d->location == Plasma::Types::LeftEdge) {
1018         dialogPos = rightPoint;
1019     } else if (d->location == Plasma::Types::RightEdge) {
1020         dialogPos = leftPoint;
1021     } else { // Types::BottomEdge
1022         dialogPos = topPoint;
1023     }
1024 
1025     // find the correct screen for the item
1026     // we do not rely on item->window()->screen() because
1027     // QWindow::screen() is always only the screen where the window gets first created
1028     // not actually the current window. See QWindow::screen() documentation
1029     QRect avail = item->window()->screen()->availableGeometry();
1030 
1031     if (outsideParentWindow && d->frameSvgItem->enabledBorders() != Plasma::FrameSvg::AllBorders) {
1032         // make the panel look it's inside the panel, in order to not make it look cut
1033         switch (d->location) {
1034         case Plasma::Types::LeftEdge:
1035         case Plasma::Types::RightEdge:
1036             avail.setTop(qMax(avail.top(), parentGeometryBounds.top()));
1037             avail.setBottom(qMin(avail.bottom(), parentGeometryBounds.bottom()));
1038             break;
1039         default:
1040             avail.setLeft(qMax(avail.left(), parentGeometryBounds.left()));
1041             avail.setRight(qMin(avail.right(), parentGeometryBounds.right()));
1042             break;
1043         }
1044     }
1045 
1046     if (dialogPos.x() < avail.left()) {
1047         // popup hits lhs
1048         if (d->location != Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) {
1049             // move it
1050             dialogPos.setX(avail.left());
1051         } else {
1052             // swap edge
1053             dialogPos.setX(rightPoint.x());
1054         }
1055     }
1056     if (dialogPos.x() + size.width() > avail.right()) {
1057         // popup hits rhs
1058         if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) {
1059             dialogPos.setX(qMax(avail.left(), (avail.right() - size.width() + 1)));
1060         } else {
1061             dialogPos.setX(leftPoint.x());
1062         }
1063     }
1064     if (dialogPos.y() < avail.top()) {
1065         // hitting top
1066         if (d->location == Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) {
1067             dialogPos.setY(avail.top());
1068         } else {
1069             dialogPos.setY(bottomPoint.y());
1070         }
1071     }
1072 
1073     if (dialogPos.y() + size.height() > avail.bottom()) {
1074         // hitting bottom
1075         if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) {
1076             dialogPos.setY(topPoint.y());
1077         } else {
1078             dialogPos.setY(qMax(avail.top(), (avail.bottom() - size.height() + 1)));
1079         }
1080     }
1081 
1082     return dialogPos;
1083 }
1084 
location() const1085 Plasma::Types::Location Dialog::location() const
1086 {
1087     return d->location;
1088 }
1089 
setLocation(Plasma::Types::Location location)1090 void Dialog::setLocation(Plasma::Types::Location location)
1091 {
1092     if (d->location == location) {
1093         return;
1094     }
1095     d->location = location;
1096     Q_EMIT locationChanged();
1097 
1098     if (d->mainItem) {
1099         d->syncToMainItemSize();
1100     }
1101 }
1102 
margins() const1103 QObject *Dialog::margins() const
1104 {
1105     return d->frameSvgItem->fixedMargins();
1106 }
1107 
inset() const1108 QObject *Dialog::inset() const
1109 {
1110     return d->frameSvgItem->inset();
1111 }
1112 
setFramelessFlags(Qt::WindowFlags flags)1113 void Dialog::setFramelessFlags(Qt::WindowFlags flags)
1114 {
1115     if (d->type == Dialog::Normal) {
1116         flags |= Qt::Dialog;
1117     }
1118     setFlags(Qt::FramelessWindowHint | flags);
1119     d->applyType();
1120     Q_EMIT flagsChanged();
1121 }
1122 
adjustGeometry(const QRect & geom)1123 void Dialog::adjustGeometry(const QRect &geom)
1124 {
1125     setGeometry(geom);
1126 }
1127 
resizeEvent(QResizeEvent * re)1128 void Dialog::resizeEvent(QResizeEvent *re)
1129 {
1130     QQuickWindow::resizeEvent(re);
1131 
1132     // it's a spontaneous event generated in qguiapplication.cpp QGuiApplicationPrivate::processWindowScreenChangedEvent
1133     // QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window), QRect());
1134     // This happens before the first show event when there is more than one screen,
1135     // right after the window has been created, the window is still 0x0,
1136     // but the resize event gets delivered with 0x0 again and executed with all the bad side effects
1137     // this seems to happen for every window when there are multiple screens, so something we have probably to watch out for in the future
1138     if (re->size().isEmpty() || re->size() == re->oldSize()) {
1139         return;
1140     }
1141 
1142     // A dialog can be resized even if no mainItem has ever been set
1143     if (!d->mainItem) {
1144         return;
1145     }
1146 
1147     d->mainItem->disconnect(this);
1148 
1149     d->frameSvgItem->setSize(QSizeF(re->size().width(), re->size().height()));
1150     auto margin = d->frameSvgItem->fixedMargins();
1151     d->mainItem->setPosition(QPointF(margin->left(), margin->top()));
1152 
1153     d->mainItem->setSize(QSize(re->size().width() - margin->left() - margin->right(), re->size().height() - margin->top() - margin->bottom()));
1154 
1155     QObject::connect(d->mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged()));
1156     QObject::connect(d->mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged()));
1157 }
1158 
setType(WindowType type)1159 void Dialog::setType(WindowType type)
1160 {
1161     if (type == d->type) {
1162         return;
1163     }
1164 
1165     d->type = type;
1166     d->applyType();
1167     Q_EMIT typeChanged();
1168 }
1169 
type() const1170 Dialog::WindowType Dialog::type() const
1171 {
1172     return d->type;
1173 }
1174 
focusInEvent(QFocusEvent * ev)1175 void Dialog::focusInEvent(QFocusEvent *ev)
1176 {
1177     QQuickWindow::focusInEvent(ev);
1178 }
1179 
focusOutEvent(QFocusEvent * ev)1180 void Dialog::focusOutEvent(QFocusEvent *ev)
1181 {
1182     if (d->hideOnWindowDeactivate) {
1183         bool parentHasFocus = false;
1184 
1185         QWindow *parentWindow = transientParent();
1186 
1187         while (parentWindow) {
1188             if (parentWindow->isActive() && !(parentWindow->flags() & Qt::WindowDoesNotAcceptFocus)) {
1189                 parentHasFocus = true;
1190 
1191                 break;
1192             }
1193 
1194             parentWindow = parentWindow->transientParent();
1195         }
1196 
1197         const QWindow *focusWindow = QGuiApplication::focusWindow();
1198         bool childHasFocus = focusWindow && ((focusWindow->isActive() && isAncestorOf(focusWindow)) || (focusWindow->type() & Qt::Popup) == Qt::Popup);
1199 
1200         const bool viewClicked = qobject_cast<const KQuickAddons::QuickViewSharedEngine *>(focusWindow)
1201 #if PLASMAQUICK_BUILD_DEPRECATED_SINCE(5, 83)
1202             || qobject_cast<const View *>(focusWindow)
1203 #endif
1204             || qobject_cast<const ConfigView *>(focusWindow);
1205 
1206         if (viewClicked || (!parentHasFocus && !childHasFocus)) {
1207             setVisible(false);
1208             Q_EMIT windowDeactivated();
1209         }
1210     }
1211 
1212     QQuickWindow::focusOutEvent(ev);
1213 }
1214 
showEvent(QShowEvent * event)1215 void Dialog::showEvent(QShowEvent *event)
1216 {
1217     if (d->backgroundHints != Dialog::NoBackground) {
1218         DialogShadows::self()->addWindow(this, d->frameSvgItem->enabledBorders());
1219     }
1220 
1221     KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher);
1222 
1223     QQuickWindow::showEvent(event);
1224 }
1225 
event(QEvent * event)1226 bool Dialog::event(QEvent *event)
1227 {
1228     if (event->type() == QEvent::Expose) {
1229         if (!KWindowSystem::isPlatformWayland() || !isExposed()) {
1230             return QQuickWindow::event(event);
1231         }
1232 
1233         /*
1234          * expose event is the only place where to correctly
1235          * register our wayland extensions, as showevent is a bit too
1236          * soon and the platform window isn't shown yet
1237          * create a shell surface every time the window gets exposed
1238          * (only the first expose event, guarded by shelldurface existence)
1239          * and tear it down when the window gets hidden
1240          * see https://phabricator.kde.org/T6064
1241          */
1242 #if HAVE_KWAYLAND
1243         // sometimes non null regions arrive even for non visible windows
1244         // for which surface creation would fail
1245         if (!d->shellSurface && isVisible()) {
1246             KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher);
1247             d->setupWaylandIntegration();
1248             d->updateVisibility(true);
1249             const bool ret = QQuickWindow::event(event);
1250             d->updateTheme();
1251             return ret;
1252         }
1253 #endif
1254     } else if (event->type() == QEvent::PlatformSurface) {
1255         const QPlatformSurfaceEvent *pSEvent = static_cast<QPlatformSurfaceEvent *>(event);
1256 
1257         if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
1258             KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher);
1259         }
1260     } else if (event->type() == QEvent::Show) {
1261         d->updateVisibility(true);
1262     } else if (event->type() == QEvent::Hide) {
1263         d->updateVisibility(false);
1264 #if HAVE_KWAYLAND
1265         delete d->shellSurface;
1266         d->shellSurface = nullptr;
1267 #endif
1268 
1269     } else if (event->type() == QEvent::Move) {
1270 #if HAVE_KWAYLAND
1271         if (d->shellSurface) {
1272             QMoveEvent *me = static_cast<QMoveEvent *>(event);
1273             d->shellSurface->setPosition(me->pos());
1274         }
1275 #endif
1276     }
1277 
1278     /*Fitt's law: if the containment has margins, and the mouse cursor clicked
1279      * on the mouse edge, forward the click in the containment boundaries
1280      */
1281     if (d->mainItem && !d->mainItem->size().isEmpty()) {
1282         switch (event->type()) {
1283         case QEvent::MouseMove:
1284         case QEvent::MouseButtonPress:
1285         case QEvent::MouseButtonRelease: {
1286             QMouseEvent *me = static_cast<QMouseEvent *>(event);
1287 
1288             // don't mess with position if the cursor is actually outside the view:
1289             // somebody is doing a click and drag that must not break when the cursor i outside
1290             if (geometry().contains(me->screenPos().toPoint()) && !d->mainItemContainsPosition(me->windowPos())) {
1291                 QMouseEvent me2(me->type(),
1292                                 d->positionAdjustedForMainItem(me->windowPos()),
1293                                 d->positionAdjustedForMainItem(me->windowPos()),
1294                                 d->positionAdjustedForMainItem(me->windowPos()) + position(),
1295                                 me->button(),
1296                                 me->buttons(),
1297                                 me->modifiers());
1298 
1299                 if (isVisible()) {
1300                     QCoreApplication::sendEvent(this, &me2);
1301                 }
1302                 return true;
1303             }
1304             break;
1305         }
1306 
1307         case QEvent::Wheel: {
1308             QWheelEvent *we = static_cast<QWheelEvent *>(event);
1309 
1310             const QPoint pos = we->position().toPoint();
1311 
1312             if (!d->mainItemContainsPosition(pos)) {
1313                 QWheelEvent we2(d->positionAdjustedForMainItem(pos),
1314                                 d->positionAdjustedForMainItem(pos) + position(),
1315                                 we->pixelDelta(),
1316                                 we->angleDelta(),
1317                                 we->buttons(),
1318                                 we->modifiers(),
1319                                 we->phase(),
1320                                 false /*inverted*/);
1321 
1322                 if (isVisible()) {
1323                     QCoreApplication::sendEvent(this, &we2);
1324                 }
1325                 return true;
1326             }
1327             break;
1328         }
1329 
1330         case QEvent::DragEnter: {
1331             QDragEnterEvent *de = static_cast<QDragEnterEvent *>(event);
1332             if (!d->mainItemContainsPosition(de->pos())) {
1333                 QDragEnterEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(),
1334                                     de->possibleActions(),
1335                                     de->mimeData(),
1336                                     de->mouseButtons(),
1337                                     de->keyboardModifiers());
1338 
1339                 if (isVisible()) {
1340                     QCoreApplication::sendEvent(this, &de2);
1341                 }
1342                 return true;
1343             }
1344             break;
1345         }
1346         // DragLeave just works
1347         case QEvent::DragLeave:
1348             break;
1349         case QEvent::DragMove: {
1350             QDragMoveEvent *de = static_cast<QDragMoveEvent *>(event);
1351             if (!d->mainItemContainsPosition(de->pos())) {
1352                 QDragMoveEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(),
1353                                    de->possibleActions(),
1354                                    de->mimeData(),
1355                                    de->mouseButtons(),
1356                                    de->keyboardModifiers());
1357 
1358                 if (isVisible()) {
1359                     QCoreApplication::sendEvent(this, &de2);
1360                 }
1361                 return true;
1362             }
1363             break;
1364         }
1365         case QEvent::Drop: {
1366             QDropEvent *de = static_cast<QDropEvent *>(event);
1367             if (!d->mainItemContainsPosition(de->pos())) {
1368                 QDropEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(),
1369                                de->possibleActions(),
1370                                de->mimeData(),
1371                                de->mouseButtons(),
1372                                de->keyboardModifiers());
1373 
1374                 if (isVisible()) {
1375                     QCoreApplication::sendEvent(this, &de2);
1376                 }
1377                 return true;
1378             }
1379             break;
1380         }
1381 
1382         default:
1383             break;
1384         }
1385     }
1386 
1387     return QQuickWindow::event(event);
1388 }
1389 
hideEvent(QHideEvent * event)1390 void Dialog::hideEvent(QHideEvent *event)
1391 {
1392     QQuickWindow::hideEvent(event);
1393 }
1394 
classBegin()1395 void Dialog::classBegin()
1396 {
1397     d->componentComplete = false;
1398 }
1399 
componentComplete()1400 void Dialog::componentComplete()
1401 {
1402     d->componentComplete = true;
1403     QQuickWindow::setVisible(d->visible);
1404     d->updateTheme();
1405 }
1406 
hideOnWindowDeactivate() const1407 bool Dialog::hideOnWindowDeactivate() const
1408 {
1409     return d->hideOnWindowDeactivate;
1410 }
1411 
setHideOnWindowDeactivate(bool hide)1412 void Dialog::setHideOnWindowDeactivate(bool hide)
1413 {
1414     if (d->hideOnWindowDeactivate == hide) {
1415         return;
1416     }
1417     d->hideOnWindowDeactivate = hide;
1418     Q_EMIT hideOnWindowDeactivateChanged();
1419 }
1420 
isOutputOnly() const1421 bool Dialog::isOutputOnly() const
1422 {
1423     return d->outputOnly;
1424 }
1425 
setOutputOnly(bool outputOnly)1426 void Dialog::setOutputOnly(bool outputOnly)
1427 {
1428     if (d->outputOnly == outputOnly) {
1429         return;
1430     }
1431     d->outputOnly = outputOnly;
1432     Q_EMIT outputOnlyChanged();
1433 }
1434 
setVisible(bool visible)1435 void Dialog::setVisible(bool visible)
1436 {
1437     // only update real visibility when we have finished component completion
1438     // and all flags have been set
1439 
1440     d->visible = visible;
1441     if (d->componentComplete) {
1442         if (visible && d->visualParent) {
1443             setPosition(popupPosition(d->visualParent, size()));
1444         }
1445 
1446         // Bug 381242: Qt remembers minimize state and re-applies it when showing
1447         setWindowStates(windowStates() & ~Qt::WindowMinimized);
1448         QQuickWindow::setVisible(visible);
1449         // signal will be emitted and proxied from the QQuickWindow code
1450     } else {
1451         Q_EMIT visibleChangedProxy();
1452     }
1453 }
1454 
isVisible() const1455 bool Dialog::isVisible() const
1456 {
1457     if (d->componentComplete) {
1458         return QQuickWindow::isVisible();
1459     }
1460     return d->visible;
1461 }
1462 
backgroundHints() const1463 Dialog::BackgroundHints Dialog::backgroundHints() const
1464 {
1465     return d->backgroundHints;
1466 }
1467 
setBackgroundHints(Dialog::BackgroundHints hints)1468 void Dialog::setBackgroundHints(Dialog::BackgroundHints hints)
1469 {
1470     if (d->backgroundHints == hints) {
1471         return;
1472     }
1473 
1474     d->backgroundHints = hints;
1475     d->updateTheme();
1476     Q_EMIT backgroundHintsChanged();
1477 }
1478 
1479 }
1480 
1481 #include "moc_dialog.cpp"
1482