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