Libplasma

dialog.cpp
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/config-x11.h"
12#include "appletquickitem.h"
13#include "config-plasma.h"
14#include "configview.h"
15#include "debug_p.h"
16#include "dialogbackground_p.h"
17#include "dialogshadows_p.h"
18#include "sharedqmlengine.h"
19
20#include <QLayout>
21#include <QMenu>
22#include <QPlatformSurfaceEvent>
23#include <QPointer>
24#include <QQuickItem>
25#include <QScreen>
26
27#include <KWindowInfo>
28#include <KWindowSystem>
29#include <KX11Extras>
30
31#include <KWindowEffects>
32#include <Plasma/Corona>
33
34#include <QDebug>
35#include <optional>
36
37#if HAVE_X11
38#include <qpa/qplatformwindow_p.h>
39#endif
40
41#include "plasmashellwaylandintegration.h"
42
43// Unfortunately QWINDOWSIZE_MAX is not exported
44#define DIALOGSIZE_MAX ((1 << 24) - 1)
45
46namespace PlasmaQuick
47{
48class DialogPrivate
49{
50public:
51 DialogPrivate(Dialog *dialog)
52 : q(dialog)
53 , location(Plasma::Types::BottomEdge)
54 , dialogBackground(new DialogBackground(q->contentItem()))
55 , hasMask(false)
56 , type(Dialog::Normal)
57 , hideOnWindowDeactivate(false)
58 , outputOnly(false)
59 , visible(false)
60 , resizableEdges({})
61 , floating(0)
62 , overridingCursor(false)
63 , appletInterface(nullptr)
64 , componentComplete(dialog->parent() == nullptr)
65 , needsSetupNextExpose(true)
66 , backgroundHints(Dialog::StandardBackground)
67 {
68 }
69
70 // SLOTS
71 /**
72 * Sync Borders updates the enabled borders of the dialogBackground depending
73 * on the geometry of the window.
74 *
75 * \param windowGeometry The window geometry which should be taken into
76 * consideration when activating/deactivating certain borders
77 */
78 void syncBorders(const QRect &windowGeometry);
79
80 /**
81 * This function sets the blurBehind, background contrast and shadows. It
82 * does so wrt the dialogBackground. So make sure the dialogBackground is the
83 * correct size before calling this function.
84 */
85 void updateTheme();
86 void updateVisibility(bool visible);
87
88 void updateMinimumWidth();
89 void updateMinimumHeight();
90 void updateMaximumWidth();
91 void updateMaximumHeight();
92 void updateResizableEdges();
93 void updateSizeFromAppletInterface();
94
95 /**
96 * Gets the maximum and minimum size hints for the window based on the contents. it doesn't actually resize anything
97 */
98 void getSizeHints(QSize &min, QSize &max) const;
99
100 /**
101 * This function is an optimized version of updateMaximumHeight,
102 * updateMaximumWidth,updateMinimumWidth and updateMinimumHeight.
103 * It should be called when you need to call all 4 of these functions
104 * AND you have called syncToMainItemSize before.
105 */
106 void updateLayoutParameters();
107
108 QRect availableScreenGeometryForPosition(const QPoint &pos) const;
109
110 /**
111 * This function checks the current position of the dialog and repositions
112 * it so that no part of it is not on the screen
113 */
114 void repositionIfOffScreen();
115
116 void slotMainItemSizeChanged();
117 void slotWindowPositionChanged();
118
119 void syncToMainItemSize();
120
121 bool mainItemContainsPosition(const QPointF &point) const;
122 QPointF positionAdjustedForMainItem(const QPointF &point) const;
123
124 void applyType();
125
126 bool updateMouseCursor(const QPointF &globalMousePos);
127 Qt::Edges hitTest(const QPointF &pos);
128 bool hitTestLeft(const QPointF &pos);
129 bool hitTestRight(const QPointF &pos);
130 bool hitTestTop(const QPointF &pos);
131 bool hitTestBottom(const QPointF &pos);
132
133 Dialog *q;
135 DialogBackground *dialogBackground;
136 QPointer<QQuickItem> mainItem;
137 QPointer<QQuickItem> visualParent;
138
139 QRect cachedGeometry;
140 bool hasMask;
141 Dialog::WindowType type;
142 bool hideOnWindowDeactivate;
143 bool outputOnly;
144 bool visible;
145 Qt::Edges resizableEdges;
146 int floating;
147 bool overridingCursor;
148 AppletQuickItem *appletInterface;
149 Plasma::Theme theme;
150 bool componentComplete;
151 bool needsSetupNextExpose;
152 Dialog::BackgroundHints backgroundHints;
153
154 // Attached Layout property of mainItem, if any
155 QPointer<QObject> mainItemLayout;
156};
157
158static bool isRunningInKWin()
159{
160 static bool check = QGuiApplication::platformName() == QLatin1String("wayland-org.kde.kwin.qpa");
161 return check;
162}
163
164QRect DialogPrivate::availableScreenGeometryForPosition(const QPoint &pos) const
165{
166 // FIXME: QWindow::screen() never ever changes if the window is moved across
167 // virtual screens (normal two screens with X), this seems to be intentional
168 // as it's explicitly mentioned in the docs. Until that's changed or some
169 // more proper way of howto get the current QScreen for given QWindow is found,
170 // we simply iterate over the virtual screens and pick the one our QWindow
171 // says it's at.
172 QRect avail;
173 const auto screens = QGuiApplication::screens();
174 for (QScreen *screen : screens) {
175 // we check geometry() but then take availableGeometry()
176 // to reliably check in which screen a position is, we need the full
177 // geometry, including areas for panels
178 if (screen->geometry().contains(pos)) {
179 avail = screen->availableGeometry();
180 break;
181 }
182 }
183
184 /*
185 * if the heuristic fails (because the topleft of the dialog is offscreen)
186 * use at least our screen()
187 * the screen should be correctly updated now on Qt 5.3+ so should be
188 * more reliable anyways (could be tried to remove the whole for loop
189 * above at this point)
190 *
191 * important: screen can be a nullptr... see bug 345173
192 */
193 if (avail.isEmpty() && q->screen()) {
194 avail = q->screen()->availableGeometry();
195 }
196
197 return avail;
198}
199
200void DialogPrivate::syncBorders(const QRect &geom)
201{
202 QRect avail = availableScreenGeometryForPosition(geom.topLeft());
203 int borders = KSvg::FrameSvg::AllBorders;
204
205 // Tooltips always have all the borders
206 // floating windows have all borders
207 if (!q->flags().testFlag(Qt::ToolTip) && location != Plasma::Types::Floating && floating == 0) {
208 if (geom.x() <= avail.x() || location == Plasma::Types::LeftEdge) {
209 borders = borders & ~KSvg::FrameSvg::LeftBorder;
210 }
211 if (geom.y() <= avail.y() || location == Plasma::Types::TopEdge) {
212 borders = borders & ~KSvg::FrameSvg::TopBorder;
213 }
214 if (avail.right() <= geom.x() + geom.width() || location == Plasma::Types::RightEdge) {
215 borders = borders & ~KSvg::FrameSvg::RightBorder;
216 }
217 if (avail.bottom() <= geom.y() + geom.height() || location == Plasma::Types::BottomEdge) {
218 borders = borders & ~KSvg::FrameSvg::BottomBorder;
219 }
220 }
221
222 if (dialogBackground->enabledBorders() != (KSvg::FrameSvg::EnabledBorder)borders) {
223 dialogBackground->setEnabledBorders((KSvg::FrameSvg::EnabledBorder)borders);
224 }
225}
226
227void DialogPrivate::updateTheme()
228{
229 if (backgroundHints == Dialog::NoBackground) {
230 dialogBackground->setImagePath(QString());
233 q->setMask(QRegion());
234 DialogShadows::instance()->removeWindow(q);
235 } else {
236 auto prefix = QStringLiteral("");
237 if ((backgroundHints & Dialog::SolidBackground) == Dialog::SolidBackground) {
238 prefix = QStringLiteral("solid/");
239 }
240 if (type == Dialog::Tooltip) {
241 dialogBackground->setImagePath(prefix + QStringLiteral("widgets/tooltip"));
242 } else {
243 dialogBackground->setImagePath(prefix + QStringLiteral("dialogs/background"));
244 }
245
246 const QRegion mask = dialogBackground->mask();
247 KWindowEffects::enableBlurBehind(q, theme.blurBehindEnabled(), mask);
248
250 theme.backgroundContrastEnabled(),
251 theme.backgroundContrast(),
252 theme.backgroundIntensity(),
253 theme.backgroundSaturation(),
254 mask);
255
257 if (hasMask) {
258 hasMask = false;
259 q->setMask(QRegion());
260 }
261 } else {
262 hasMask = true;
263 q->setMask(dialogBackground->mask());
264 }
265 if (q->isVisible()) {
266 DialogShadows::instance()->addWindow(q, dialogBackground->enabledBorders());
267 }
268 }
269}
270
271void DialogPrivate::updateVisibility(bool visible)
272{
273 if (visible) {
274 if (visualParent && visualParent->window()) {
275 q->setTransientParent(visualParent->window());
276 }
277
278 if (q->location() == Plasma::Types::FullScreen) {
279 dialogBackground->setEnabledBorders(KSvg::FrameSvg::NoBorder);
280
281 // We cache the original size of the item, to retrieve it
282 // when the dialog is switched back from fullscreen.
283 if (q->geometry() != q->screen()->availableGeometry()) {
284 cachedGeometry = q->geometry();
285 }
286 q->setGeometry(q->screen()->availableGeometry());
287 } else {
288 if (!cachedGeometry.isNull()) {
289 q->resize(cachedGeometry.size());
290 slotWindowPositionChanged();
291 if (visualParent) {
292 q->setPosition(q->popupPosition(visualParent, q->size()));
293 }
294 cachedGeometry = QRect();
295 }
296
297 if (mainItem) {
298 syncToMainItemSize();
299 }
300 if (mainItemLayout) {
301 updateLayoutParameters();
302 }
303
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 (type != Dialog::OnScreenDisplay) {
307 PlasmaShellWaylandIntegration::get(q)->setPosition(q->position());
308 }
309 }
310 }
311
312 if (!q->flags().testFlag(Qt::ToolTip) && type != Dialog::Notification && type != Dialog::CriticalNotification) {
313 KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
314
315 switch (location) {
317 slideLocation = KWindowEffects::TopEdge;
318 break;
320 slideLocation = KWindowEffects::LeftEdge;
321 break;
323 slideLocation = KWindowEffects::RightEdge;
324 break;
326 slideLocation = KWindowEffects::BottomEdge;
327 break;
328 // no edge, no slide
329 default:
330 break;
331 }
332
333 KWindowEffects::slideWindow(q, slideLocation, -1);
334 }
335
336 if (visible) {
337 q->raise();
338
339 applyType();
340 }
341}
342
343void DialogPrivate::updateMinimumWidth()
344{
345 Q_ASSERT(mainItem);
346 Q_ASSERT(mainItemLayout);
347
348 if (!componentComplete) {
349 return;
350 }
351
352 q->setMinimumWidth(0);
353
354 // this is to try to get the internal item resized a tad before, but
355 // the flicker almost always happen anyways, so is *probably* useless
356 // this other kind of flicker is the view not being always focused exactly
357 // on the scene
358 int minimumWidth = mainItemLayout->property("minimumWidth").toInt() + dialogBackground->leftMargin() + dialogBackground->rightMargin();
359 if (q->screen()) {
360 minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth);
361 }
362 q->contentItem()->setWidth(qMax(q->width(), minimumWidth));
363 q->setWidth(qMax(q->width(), minimumWidth));
364
365 updateLayoutParameters();
366}
367
368void DialogPrivate::updateMinimumHeight()
369{
370 Q_ASSERT(mainItem);
371 Q_ASSERT(mainItemLayout);
372
373 if (!componentComplete) {
374 return;
375 }
376
377 q->setMinimumHeight(0);
378
379 // this is to try to get the internal item resized a tad before, but
380 // the flicker almost always happen anyways, so is *probably* useless
381 // this other kind of flicker is the view not being always focused exactly
382 // on the scene
383 int minimumHeight = mainItemLayout->property("minimumHeight").toInt() + dialogBackground->topMargin() + dialogBackground->bottomMargin();
384 if (q->screen()) {
385 minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight);
386 }
387 q->contentItem()->setHeight(qMax(q->height(), minimumHeight));
388 q->setHeight(qMax(q->height(), minimumHeight));
389
390 updateLayoutParameters();
391}
392
393void DialogPrivate::updateMaximumWidth()
394{
395 Q_ASSERT(mainItem);
396 Q_ASSERT(mainItemLayout);
397
398 if (!componentComplete) {
399 return;
400 }
401
402 q->setMaximumWidth(DIALOGSIZE_MAX);
403
404 int maximumWidth = mainItemLayout->property("maximumWidth").toInt() + dialogBackground->leftMargin() + dialogBackground->rightMargin();
405 if (q->screen()) {
406 maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth);
407 }
408 q->contentItem()->setWidth(qMin(q->width(), maximumWidth));
409 q->setWidth(qMin(q->width(), maximumWidth));
410
411 updateLayoutParameters();
412}
413
414void DialogPrivate::updateMaximumHeight()
415{
416 Q_ASSERT(mainItem);
417 Q_ASSERT(mainItemLayout);
418
419 if (!componentComplete) {
420 return;
421 }
422
423 q->setMaximumHeight(DIALOGSIZE_MAX);
424
425 int maximumHeight = mainItemLayout->property("maximumHeight").toInt() + dialogBackground->topMargin() + dialogBackground->bottomMargin();
426 if (q->screen()) {
427 maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight);
428 }
429 q->contentItem()->setHeight(qMin(q->height(), maximumHeight));
430 q->setHeight(qMin(q->height(), maximumHeight));
431
432 updateLayoutParameters();
433}
434
435void DialogPrivate::updateResizableEdges()
436{
437 if (!appletInterface) {
438 resizableEdges = {};
439 return;
440 }
441
442 QSize min;
443 QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
444 getSizeHints(min, max);
445 if (min == max) {
446 resizableEdges = {};
447 return;
448 }
449
450 switch (q->location()) {
452 resizableEdges = Qt::LeftEdge | Qt::TopEdge | Qt::RightEdge;
453 break;
455 resizableEdges = Qt::LeftEdge | Qt::BottomEdge | Qt::RightEdge;
456 break;
458 resizableEdges = Qt::TopEdge | Qt::BottomEdge | Qt::RightEdge;
459 break;
461 resizableEdges = Qt::LeftEdge | Qt::TopEdge | Qt::BottomEdge;
462 break;
466 resizableEdges = {};
467 break;
468 }
469}
470
471void DialogPrivate::updateSizeFromAppletInterface()
472{
473 if (!appletInterface) {
474 return;
475 }
476 if (!mainItem) {
477 return;
478 }
479 if (!mainItemLayout) {
480 return;
481 }
482
483 QSize min;
484 QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
485 getSizeHints(min, max);
486 if (min == max) {
487 return;
488 }
489
490 QVariant prefHeight = mainItemLayout->property("preferredHeight");
491 QVariant prefWidth = mainItemLayout->property("preferredWidth");
492 int defHeight = prefHeight.isNull() ? min.height() : prefHeight.toInt();
493 int defWidth = prefWidth.isNull() ? min.width() : prefWidth.toInt();
494
495 KConfigGroup config = appletInterface->applet()->config();
496 qreal popupWidth = config.readEntry("popupWidth", static_cast<qreal>(defWidth));
497 qreal popupHeight = config.readEntry("popupHeight", static_cast<qreal>(defHeight));
498 mainItemLayout->setProperty("preferredWidth", popupWidth);
499 mainItemLayout->setProperty("preferredHeight", popupHeight);
500 mainItem->setWidth(popupWidth);
501 mainItem->setHeight(popupHeight);
502 updateLayoutParameters();
503}
504
505void DialogPrivate::getSizeHints(QSize &min, QSize &max) const
506{
507 if (!componentComplete || !mainItem || !mainItemLayout) {
508 return;
509 }
510 Q_ASSERT(mainItem);
511 Q_ASSERT(mainItemLayout);
512
513 int minimumHeight = mainItemLayout->property("minimumHeight").toInt();
514 int maximumHeight = mainItemLayout->property("maximumHeight").toInt();
515 maximumHeight = maximumHeight > 0 ? qMax(minimumHeight, maximumHeight) : DIALOGSIZE_MAX;
516
517 int minimumWidth = mainItemLayout->property("minimumWidth").toInt();
518 int maximumWidth = mainItemLayout->property("maximumWidth").toInt();
519 maximumWidth = maximumWidth > 0 ? qMax(minimumWidth, maximumWidth) : DIALOGSIZE_MAX;
520
521 minimumHeight += dialogBackground->topMargin() + dialogBackground->bottomMargin();
522 maximumHeight += dialogBackground->topMargin() + dialogBackground->bottomMargin();
523 minimumWidth += dialogBackground->leftMargin() + dialogBackground->rightMargin();
524 maximumWidth += dialogBackground->leftMargin() + dialogBackground->rightMargin();
525
526 if (q->screen()) {
527 minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth);
528 minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight);
529 maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth);
530 maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight);
531 }
532
533 // Make sure that we never return min that would be larger than max
534 min = QSize(qMin(minimumWidth, maximumWidth), qMin(minimumHeight, maximumHeight));
535 max = QSize(maximumWidth, maximumHeight);
536}
537
538void DialogPrivate::updateLayoutParameters()
539{
540 if (!componentComplete || !mainItem || !mainItemLayout || q->visibility() == QWindow::Hidden) {
541 return;
542 }
543
544 mainItem->disconnect(q);
545
546 QSize min;
547 QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
548 getSizeHints(min, max);
549
550 const QSize finalSize(qBound(min.width(), q->width(), std::max(max.width(), min.width())),
551 qBound(min.height(), q->height(), std::max(max.height(), min.height())));
552
553 if (visualParent) {
554 // it's important here that we're using re->size() as size, we don't want to do recursive resizeEvents
555 const QRect geom(q->popupPosition(visualParent, finalSize), finalSize);
556 q->adjustGeometry(geom);
557 } else {
558 q->resize(finalSize);
559 }
560
561 mainItem->setPosition(QPointF(dialogBackground->leftMargin(), dialogBackground->topMargin()));
562 mainItem->setSize(QSizeF(q->width() - dialogBackground->leftMargin() - dialogBackground->rightMargin(),
563 q->height() - dialogBackground->topMargin() - dialogBackground->bottomMargin()));
564
565 dialogBackground->setSize(QSizeF(q->width(), q->height()));
566
567 if (!needsSetupNextExpose && visible) {
568 // Only reposition after successful setup; otherwise repositionIfOffScreen will override the default position set by kwin under wayland
569 repositionIfOffScreen();
570 }
571 updateTheme();
572
573 // setting the minimum or maximum size will resize the window instantly and min <= max is enforced
574 // so we have to set maximum first in that case, but also care about the new maximum being smaller
575 // than the current minimum
576 // QTBUG-113233
577 q->setMaximumSize(max.expandedTo(q->maximumSize()));
578 q->setMinimumSize(min);
579 q->setMaximumSize(max);
580
581 QObject::connect(mainItem, SIGNAL(widthChanged()), q, SLOT(slotMainItemSizeChanged()));
582 QObject::connect(mainItem, SIGNAL(heightChanged()), q, SLOT(slotMainItemSizeChanged()));
583}
584
585void DialogPrivate::repositionIfOffScreen()
586{
587 if (!componentComplete) {
588 return;
589 }
590 const QRect avail = availableScreenGeometryForPosition(q->position());
591
592 int x = q->x();
593 int y = q->y();
594
595 if (x < avail.left()) {
596 x = avail.left();
597 } else if (x + q->width() > avail.right()) {
598 x = avail.right() - q->width() + 1;
599 }
600
601 if (y < avail.top()) {
602 y = avail.top();
603 } else if (y + q->height() > avail.bottom()) {
604 y = avail.bottom() - q->height() + 1;
605 }
606
607 q->setX(x);
608 q->setY(y);
609}
610
611void DialogPrivate::syncToMainItemSize()
612{
613 Q_ASSERT(mainItem);
614
615 if (!componentComplete || q->visibility() == QWindow::Hidden) {
616 return;
617 }
618 if (mainItem->width() <= 0 || mainItem->height() <= 0) {
619 qCWarning(LOG_PLASMAQUICK) << "trying to show an empty dialog";
620 }
621
622 updateTheme();
623 if (visualParent) {
624 const QSize fullSize = QSize(mainItem->width(), mainItem->height())
625 + QSize(dialogBackground->leftMargin() + dialogBackground->rightMargin(), dialogBackground->topMargin() + dialogBackground->bottomMargin());
626
627 // We get the popup position with the fullsize as we need the popup
628 // position in order to determine our actual size, as the position
629 // determines which borders will be shown.
630 const QRect geom(q->popupPosition(visualParent, fullSize), fullSize);
631
632 // We're then moving the window to where we think we would be with all
633 // the borders. This way when syncBorders is called, it has a geometry
634 // to work with.
635 syncBorders(geom);
636 } else {
637 syncBorders(q->geometry());
638 }
639
640 QSize s = QSize(mainItem->width(), mainItem->height())
641 + QSize(dialogBackground->leftMargin() + dialogBackground->rightMargin(), dialogBackground->topMargin() + dialogBackground->bottomMargin());
642
643 QSize min;
644 QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX);
645 getSizeHints(min, max);
646 s = QSize(qBound(min.width(), s.width(), max.width()), qBound(min.height(), s.height(), max.height()));
647
648 q->contentItem()->setSize(s);
649
650 dialogBackground->setSize(s);
651
652 if (visualParent) {
653 const QRect geom(q->popupPosition(visualParent, s), s);
654
655 if (geom == q->geometry()) {
656 return;
657 }
658
659 q->adjustGeometry(geom);
660 // The borders will instantly be updated but the geometry might take a
661 // while as sub-classes can reimplement adjustGeometry and animate it.
662 syncBorders(geom);
663
664 } else {
665 q->resize(s);
666 }
667
668 mainItem->setPosition(QPointF(dialogBackground->leftMargin(), dialogBackground->topMargin()));
669
670 updateTheme();
671}
672
673void DialogPrivate::slotWindowPositionChanged()
674{
675 // Tooltips always have all the borders
676 // floating windows have all borders
677 if (!q->isVisible() || q->flags().testFlag(Qt::ToolTip) || location == Plasma::Types::Floating || floating > 0) {
678 return;
679 }
680
681 syncBorders(q->geometry());
682 updateTheme();
683
684 if (mainItem) {
685 mainItem->setPosition(QPoint(dialogBackground->leftMargin(), dialogBackground->topMargin()));
686 mainItem->setSize(QSize(q->width() - dialogBackground->leftMargin() - dialogBackground->rightMargin(),
687 q->height() - dialogBackground->topMargin() - dialogBackground->bottomMargin()));
688 }
689}
690
691bool DialogPrivate::mainItemContainsPosition(const QPointF &point) const
692{
693 if (!mainItem) {
694 return false;
695 }
696
697 return QRectF(mainItem->mapToScene(QPoint(0, 0)), QSizeF(mainItem->width(), mainItem->height())).contains(point);
698}
699
700QPointF DialogPrivate::positionAdjustedForMainItem(const QPointF &point) const
701{
702 if (!mainItem) {
703 return point;
704 }
705
706 QRectF itemRect(mainItem->mapToScene(QPoint(0, 0)), QSizeF(mainItem->width(), mainItem->height()));
707
708 return QPointF(qBound(itemRect.left(), point.x(), itemRect.right()), qBound(itemRect.top(), point.y(), itemRect.bottom()));
709}
710
711void DialogPrivate::applyType()
712{
713 /*QXcbWindowFunctions::WmWindowType*/ int wmType = 0;
714
715#if HAVE_X11
717 switch (type) {
718 case Dialog::Normal:
719 q->setFlags(Qt::FramelessWindowHint | q->flags());
720 break;
721 case Dialog::Dock:
722 wmType = QNativeInterface::Private::QXcbWindow::Dock;
723 break;
724 case Dialog::DialogWindow:
725 wmType = QNativeInterface::Private::QXcbWindow::Dialog;
726 break;
727 case Dialog::PopupMenu:
728 wmType = QNativeInterface::Private::QXcbWindow::PopupMenu;
729 break;
730 case Dialog::Tooltip:
731 wmType = QNativeInterface::Private::QXcbWindow::Tooltip;
732 break;
733 case Dialog::Notification:
734 wmType = QNativeInterface::Private::QXcbWindow::Notification;
735 break;
736 case Dialog::OnScreenDisplay:
737 case Dialog::CriticalNotification:
738 case Dialog::AppletPopup:
739 // Not supported by Qt
740 break;
741 }
742
743 if (wmType) {
744 // QXcbWindow isn't installed and thus inaccessible to us, but it does read this magic property from the window...
745 q->setProperty("_q_xcb_wm_window_type", wmType);
746 }
747 }
748#endif
749
750 if (!wmType && type != Dialog::Normal && KWindowSystem::isPlatformX11()) {
751 KX11Extras::setType(q->winId(), static_cast<NET::WindowType>(type));
752 }
753 if (q->flags() & Qt::WindowStaysOnTopHint) {
754 // If the AppletPopup type is not explicitly requested, then use the Dock type in this case
755 // to avoid bug #454635.
756 if (type != Dialog::AppletPopup && type != Dialog::Tooltip) {
757 type = Dialog::Dock;
758 PlasmaShellWaylandIntegration::get(q)->setPanelBehavior(QtWayland::org_kde_plasma_surface::panel_behavior_windows_go_below);
759 } else {
760 PlasmaShellWaylandIntegration::get(q)->setPanelBehavior(QtWayland::org_kde_plasma_surface::panel_behavior_always_visible);
761 }
762 }
763 switch (type) {
764 case Dialog::Dock:
765 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_panel);
766 break;
767 case Dialog::Tooltip:
768 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_tooltip);
769 break;
770 case Dialog::Notification:
771 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_notification);
772 break;
773 case Dialog::OnScreenDisplay:
774 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_onscreendisplay);
775 break;
776 case Dialog::CriticalNotification:
777 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_criticalnotification);
778 break;
779 case Dialog::Normal:
780 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_normal);
781 break;
782 case Dialog::AppletPopup:
783 PlasmaShellWaylandIntegration::get(q)->setRole(QtWayland::org_kde_plasma_surface::role_appletpopup);
784 break;
785 default:
786 break;
787 }
788
789 // an OSD can't be a Dialog, as qt xcb would attempt to set a transient parent for it
790 // see bug 370433
791 if (type == Dialog::OnScreenDisplay) {
792 Qt::WindowFlags flags = (q->flags() & ~Qt::Dialog) | Qt::Window;
793 if (outputOnly) {
795 } else {
796 flags &= ~Qt::WindowTransparentForInput;
797 }
798 q->setFlags(flags);
799 }
800
801 if (backgroundHints == Dialog::NoBackground) {
802 dialogBackground->setImagePath(QString());
803 } else {
804 auto prefix = QStringLiteral("");
805 if ((backgroundHints & Dialog::SolidBackground) == Dialog::SolidBackground) {
806 prefix = QStringLiteral("solid/");
807 }
808 if (type == Dialog::Tooltip) {
809 dialogBackground->setImagePath(prefix + QStringLiteral("widgets/tooltip"));
810 } else {
811 dialogBackground->setImagePath(prefix + QStringLiteral("dialogs/background"));
812 }
813 }
814
816 if (type == Dialog::Dock || type == Dialog::Notification || type == Dialog::OnScreenDisplay || type == Dialog::CriticalNotification) {
817 KX11Extras::setOnAllDesktops(q->winId(), true);
818 } else {
819 KX11Extras::setOnAllDesktops(q->winId(), false);
820 }
821 }
822
823 PlasmaShellWaylandIntegration::get(q)->setTakesFocus(!q->flags().testFlag(Qt::WindowDoesNotAcceptFocus));
824}
825
826bool DialogPrivate::updateMouseCursor(const QPointF &globalMousePos)
827{
828 Qt::Edges sides = hitTest(globalMousePos) & resizableEdges;
829 if (!sides) {
830 if (overridingCursor) {
831 q->unsetCursor();
832 overridingCursor = false;
833 }
834 return false;
835 }
836
837 if (sides == Qt::Edges(Qt::LeftEdge | Qt::TopEdge)) {
838 q->setCursor(Qt::SizeFDiagCursor);
839 } else if (sides == Qt::Edges(Qt::RightEdge | Qt::TopEdge)) {
840 q->setCursor(Qt::SizeBDiagCursor);
841 } else if (sides == Qt::Edges(Qt::LeftEdge | Qt::BottomEdge)) {
842 q->setCursor(Qt::SizeBDiagCursor);
843 } else if (sides == Qt::Edges(Qt::RightEdge | Qt::BottomEdge)) {
844 q->setCursor(Qt::SizeFDiagCursor);
845 } else if (sides.testFlag(Qt::TopEdge)) {
846 q->setCursor(Qt::SizeVerCursor);
847 } else if (sides.testFlag(Qt::LeftEdge)) {
848 q->setCursor(Qt::SizeHorCursor);
849 } else if (sides.testFlag(Qt::RightEdge)) {
850 q->setCursor(Qt::SizeHorCursor);
851 } else {
852 q->setCursor(Qt::SizeVerCursor);
853 }
854
855 overridingCursor = true;
856 return true;
857}
858
859Qt::Edges DialogPrivate::hitTest(const QPointF &pos)
860{
861 bool left = hitTestLeft(pos);
862 bool right = hitTestRight(pos);
863 bool top = hitTestTop(pos);
864 bool bottom = hitTestBottom(pos);
865 Qt::Edges edges;
866 if (left) {
867 edges.setFlag(Qt::LeftEdge);
868 }
869 if (right) {
870 edges.setFlag(Qt::RightEdge);
871 }
872 if (bottom) {
873 edges.setFlag(Qt::BottomEdge);
874 }
875 if (top) {
876 edges.setFlag(Qt::TopEdge);
877 }
878
879 return edges;
880}
881
882bool DialogPrivate::hitTestLeft(const QPointF &pos)
883{
884 const QRect geometry = q->geometry();
885 const QRectF rect(geometry.x(), geometry.y(), dialogBackground->leftMargin(), geometry.height());
886 return rect.contains(pos);
887}
888
889bool DialogPrivate::hitTestRight(const QPointF &pos)
890{
891 const QRect geometry = q->geometry();
892 const QRectF rect(geometry.x() + geometry.width() - dialogBackground->rightMargin(), geometry.y(), dialogBackground->rightMargin(), geometry.height());
893 return rect.contains(pos);
894}
895
896bool DialogPrivate::hitTestTop(const QPointF &pos)
897{
898 const QRect geometry = q->geometry();
899 const QRectF rect(geometry.x(), geometry.y(), geometry.width(), dialogBackground->topMargin());
900 return rect.contains(pos);
901}
902
903bool DialogPrivate::hitTestBottom(const QPointF &pos)
904{
905 const QRect geometry = q->geometry();
906 const QRectF rect(geometry.x(), geometry.y() + geometry.height() - dialogBackground->bottomMargin(), geometry.width(), dialogBackground->bottomMargin());
907 return rect.contains(pos);
908}
909
910Dialog::Dialog(QQuickItem *parent)
911 : QQuickWindow(parent ? parent->window() : nullptr)
912 , d(new DialogPrivate(this))
913{
914 setColor(QColor(Qt::transparent));
916
917 connect(this, &QWindow::xChanged, [this]() {
918 d->slotWindowPositionChanged();
919 });
920 connect(this, &QWindow::yChanged, [this]() {
921 d->slotWindowPositionChanged();
922 });
923 connect(this, &Dialog::locationChanged, this, [&] {
924 d->updateResizableEdges();
925 });
926
927 // Given dialogs are skip task bar and don't have a decoration
928 // minimizing them using e.g. "minimize all" should just close them
929 connect(this, &QWindow::windowStateChanged, this, [this](Qt::WindowState newState) {
930 if (newState == Qt::WindowMinimized) {
931 setVisible(false);
932 }
933 });
934
935 connect(this, &QWindow::visibleChanged, this, &Dialog::visibleChangedProxy);
936
937 // HACK: this property is invoked due to the initialization that gets done to contentItem() in the getter
938 property("data");
939
940 // This is needed as a transition thing for KWayland
941 // FIXME: is this valid anymore?
942 // setProperty("__plasma_frameSvg", QVariant::fromValue(d->dialogBackground->frameSvg()));
943
944 connect(&d->theme, SIGNAL(themeChanged()), this, SLOT(updateTheme()));
945}
946
947Dialog::~Dialog()
948{
949 // Prevent signals from super-class destructor invoking our now-destroyed slots
950 disconnect(this, nullptr, this, nullptr);
951}
952
953QQuickItem *Dialog::mainItem() const
954{
955 return d->mainItem;
956}
957
958void Dialog::setMainItem(QQuickItem *mainItem)
959{
960 if (d->mainItem != mainItem) {
961 if (d->mainItem) {
962 disconnect(d->mainItem, nullptr, this, nullptr);
963 d->mainItem->setParentItem(nullptr);
964 }
965
966 if (d->mainItemLayout) {
967 disconnect(d->mainItemLayout, nullptr, this, nullptr);
968 }
969
970 d->mainItem = mainItem;
971
972 if (mainItem) {
973 mainItem->setParentItem(contentItem());
974
975 connect(mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged()));
976 connect(mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged()));
977 d->slotMainItemSizeChanged();
978
979 // Extract the representation's Layout, if any
980 QObject *layout = nullptr;
981 setMinimumSize(QSize(0, 0));
982 setMaximumSize(QSize(DIALOGSIZE_MAX, DIALOGSIZE_MAX));
983
984 // Search a child that has the needed Layout properties
985 // HACK: here we are not type safe, but is the only way to access to a pointer of Layout
986 const auto lstChild = mainItem->children();
987 for (QObject *child : lstChild) {
988 // find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight
989 if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid()
990 && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid()
991 && child->property("fillWidth").isValid() && child->property("fillHeight").isValid()) {
992 layout = child;
993 break;
994 }
995 }
996
997 d->mainItemLayout = layout;
998
999 if (layout) {
1000 // These connections are direct. They run on the GUI thread.
1001 // If the underlying QQuickItem is sane, these properties should be updated atomically in one cycle
1002 // of the GUI thread event loop, denying the chance for the event loop to run a QQuickItem::update() call in between.
1003 // So we avoid rendering a frame in between with inconsistent geometry properties which would cause flickering issues.
1004 connect(layout, SIGNAL(minimumWidthChanged()), this, SLOT(updateMinimumWidth()));
1005 connect(layout, SIGNAL(minimumHeightChanged()), this, SLOT(updateMinimumHeight()));
1006 connect(layout, SIGNAL(maximumWidthChanged()), this, SLOT(updateMaximumWidth()));
1007 connect(layout, SIGNAL(maximumHeightChanged()), this, SLOT(updateMaximumHeight()));
1008
1009 d->updateLayoutParameters();
1010 }
1011 }
1012
1013 // if this is called in Component.onCompleted we have to wait a loop the item is added to a scene
1014 Q_EMIT mainItemChanged();
1015 }
1016}
1017
1018void DialogPrivate::slotMainItemSizeChanged()
1019{
1020 syncToMainItemSize();
1021}
1022
1023QQuickItem *Dialog::visualParent() const
1024{
1025 return d->visualParent;
1026}
1027
1028void Dialog::setVisualParent(QQuickItem *visualParent)
1029{
1030 if (d->visualParent == visualParent) {
1031 return;
1032 }
1033
1034 d->visualParent = visualParent;
1035 Q_EMIT visualParentChanged();
1036 if (visualParent) {
1037 if (visualParent->window()) {
1038 setTransientParent(visualParent->window());
1039 }
1040 if (d->mainItem) {
1041 d->syncToMainItemSize();
1042 }
1043 }
1044}
1045
1047{
1048 if (!item) {
1049 // If no item was specified try to align at the center of the parent view
1051 if (parentItem) {
1052 QScreen *screen = parentItem->window()->screen();
1053
1054 switch (d->location) {
1056 return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().y());
1058 return QPoint(screen->availableGeometry().x(), screen->availableGeometry().center().y() - size.height() / 2);
1060 return QPoint(screen->availableGeometry().right() - size.width(), screen->availableGeometry().center().y() - size.height() / 2);
1062 return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().bottom() - size.height());
1063 // Default center in the screen
1064 default:
1065 return screen->geometry().center() - QPoint(size.width() / 2, size.height() / 2);
1066 }
1067 } else {
1068 return QPoint();
1069 }
1070 }
1071
1072 QPointF pos = item->mapToScene(QPointF(0, 0));
1073
1074 if (item->window()) {
1075 pos = item->window()->mapToGlobal(pos.toPoint());
1076 } else {
1077 return QPoint();
1078 }
1079
1080 // if the item is in a window that ignores WM we want to position the popups outside
1081 bool outsideParentWindow = (item->window()->flags() & Qt::X11BypassWindowManagerHint) && item->window()->mask().isNull();
1082
1084 // on X11 we also consider windows with the type Dock
1085 const KWindowInfo winInfo(item->window()->winId(), NET::WMWindowType);
1086 outsideParentWindow = outsideParentWindow || (winInfo.windowType(NET::AllTypesMask) == NET::Dock && item->window()->mask().isNull());
1087 }
1088
1089 QRect parentGeometryBounds;
1090 if (outsideParentWindow) {
1091 parentGeometryBounds = item->window()->geometry();
1092 } else {
1093 parentGeometryBounds = item->mapRectToScene(item->boundingRect()).toRect();
1094 if (item->window()) {
1095 parentGeometryBounds.moveTopLeft(item->window()->mapToGlobal(parentGeometryBounds.topLeft()));
1096 pos = parentGeometryBounds.topLeft();
1097 }
1098 }
1099
1100 const QRectF itemSceneBoundingRect = item->mapRectToScene(item->boundingRect());
1101 const QPoint centerPoint(pos.x() + (itemSceneBoundingRect.width() - size.width()) / 2, //
1102 pos.y() + (itemSceneBoundingRect.height() - size.height()) / 2);
1103
1104 const QPoint topPoint(centerPoint.x(), parentGeometryBounds.top() - size.height());
1105 const QPoint bottomPoint(centerPoint.x(), parentGeometryBounds.bottom());
1106
1107 const QPoint leftPoint(parentGeometryBounds.left() - size.width(), centerPoint.y());
1108 const QPoint rightPoint(parentGeometryBounds.right(), centerPoint.y());
1109
1110 QPoint dialogPos;
1111 if (d->location == Plasma::Types::TopEdge) {
1112 dialogPos = bottomPoint;
1113 } else if (d->location == Plasma::Types::LeftEdge) {
1114 dialogPos = rightPoint;
1115 } else if (d->location == Plasma::Types::RightEdge) {
1116 dialogPos = leftPoint;
1117 } else { // Types::BottomEdge
1118 dialogPos = topPoint;
1119 }
1120
1121 // find the correct screen for the item
1122 // we do not rely on item->window()->screen() because
1123 // QWindow::screen() is always only the screen where the window gets first created
1124 // not actually the current window. See QWindow::screen() documentation
1125 QRect avail = item->window()->screen()->availableGeometry();
1126 avail.adjust(d->floating, d->floating, -d->floating, -d->floating);
1127
1128 if (outsideParentWindow && d->dialogBackground->enabledBorders() != KSvg::FrameSvg::AllBorders) {
1129 // make the panel look it's inside the panel, in order to not make it look cut
1130 switch (d->location) {
1133 avail.setTop(qMax(avail.top(), parentGeometryBounds.top()));
1134 avail.setBottom(qMin(avail.bottom(), parentGeometryBounds.bottom()));
1135 break;
1136 default:
1137 avail.setLeft(qMax(avail.left(), parentGeometryBounds.left()));
1138 avail.setRight(qMin(avail.right(), parentGeometryBounds.right()));
1139 break;
1140 }
1141 }
1142
1143 // If the dialog is from opening an applet in the panel and it's close enough to the center that
1144 // it would still cover the original applet in the panel if it was centered, then we manually center it.
1145 if (d->type == Dialog::AppletPopup) {
1146 QRectF parentRect = item->mapRectToScene(item->boundingRect());
1147 switch (d->location) {
1150 if (qAbs(dialogPos.x() + size.width() / 2 - avail.center().x()) < size.width() / 2 - parentRect.width() / 3) {
1151 dialogPos.setX(avail.center().x() - size.width() / 2);
1152 }
1153 break;
1156 if (qAbs(dialogPos.y() + size.height() / 2 - avail.center().y()) < size.height() / 2 - parentRect.height() / 3) {
1157 dialogPos.setY(avail.center().y() - size.height() / 2);
1158 }
1159 break;
1160 default:
1161 break;
1162 }
1163 }
1164
1165 // For top & bottom the inner conditions are intentionally different from thouse for left & right,
1166 // because we want floating popups to flip vertically, but only push them in bounds horizontally.
1167
1168 // If popup goes out of bounds...
1169 // ...at the left edge
1170 if (dialogPos.x() < avail.left()) {
1171 if (d->location != Plasma::Types::LeftEdge) {
1172 // move it in bounds
1173 // Note: floating popup goes here.
1174 dialogPos.setX(avail.left());
1175 } else {
1176 // flip it around
1177 dialogPos.setX(rightPoint.x());
1178 }
1179 }
1180 // ...at the right edge
1181 if (dialogPos.x() + size.width() > avail.right()) {
1182 if (d->location != Plasma::Types::RightEdge) {
1183 // move it in bounds
1184 // Note: floating popup goes here.
1185 dialogPos.setX(qMax(avail.left(), (avail.right() - size.width() + 1)));
1186 } else {
1187 // flip it around
1188 dialogPos.setX(leftPoint.x());
1189 }
1190 }
1191 // ...at the top edge
1192 if (dialogPos.y() < avail.top()) {
1193 if (d->location == Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) {
1194 // move it in bounds
1195 dialogPos.setY(avail.top());
1196 } else {
1197 // flip it around
1198 // Note: floating popup goes here.
1199 dialogPos.setY(bottomPoint.y());
1200 }
1201 }
1202 // ...at the bottom edge
1203 if (dialogPos.y() + size.height() > avail.bottom()) {
1204 if (d->location == Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) {
1205 // move it in bounds
1206 dialogPos.setY(qMax(avail.top(), (avail.bottom() - size.height() + 1)));
1207 } else {
1208 // flip it around
1209 // Note: floating popup goes here.
1210 dialogPos.setY(topPoint.y());
1211 }
1212 }
1213
1214 return dialogPos;
1215}
1216
1218{
1219 return d->location;
1220}
1221
1222void Dialog::setLocation(Plasma::Types::Location location)
1223{
1224 if (d->location == location) {
1225 return;
1226 }
1227 d->location = location;
1228 Q_EMIT locationChanged();
1229
1230 if (d->mainItem) {
1231 d->syncToMainItemSize();
1232 }
1233}
1234
1235QObject *Dialog::margins() const
1236{
1237 return d->dialogBackground->fixedMargins();
1238}
1239
1240QObject *Dialog::inset() const
1241{
1242 return d->dialogBackground->inset();
1243}
1244
1245void Dialog::setFramelessFlags(Qt::WindowFlags flags)
1246{
1247 if (d->type == Dialog::Normal) {
1248 flags |= Qt::Dialog;
1249 }
1251 d->applyType();
1252 Q_EMIT flagsChanged();
1253}
1254
1256{
1257 if (isExposed()) {
1258 Q_ASSERT(!geom.isEmpty());
1259 }
1260 setGeometry(geom);
1261}
1262
1263void Dialog::resizeEvent(QResizeEvent *re)
1264{
1266
1267 if (d->resizableEdges) {
1268 d->updateMouseCursor(QCursor::pos());
1269 }
1270
1271 // it's a spontaneous event generated in qguiapplication.cpp QGuiApplicationPrivate::processWindowScreenChangedEvent
1272 // QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window), QRect());
1273 // This happens before the first show event when there is more than one screen,
1274 // right after the window has been created, the window is still 0x0,
1275 // but the resize event gets delivered with 0x0 again and executed with all the bad side effects
1276 // this seems to happen for every window when there are multiple screens, so something we have probably to watch out for in the future
1277 if (re->size().isEmpty() || re->size() == re->oldSize()) {
1278 return;
1279 }
1280
1281 // A dialog can be resized even if no mainItem has ever been set
1282 if (!d->mainItem || !isExposed()) {
1283 return;
1284 }
1285
1286 d->mainItem->disconnect(this);
1287
1288 d->dialogBackground->setSize(QSizeF(re->size().width(), re->size().height()));
1289 d->mainItem->setPosition(QPointF(d->dialogBackground->leftMargin(), d->dialogBackground->topMargin()));
1290
1291 d->mainItem->setSize(QSize(re->size().width() - d->dialogBackground->leftMargin() - d->dialogBackground->rightMargin(),
1292 re->size().height() - d->dialogBackground->topMargin() - d->dialogBackground->bottomMargin()));
1293
1294 d->updateTheme();
1295
1296 QObject::connect(d->mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged()));
1297 QObject::connect(d->mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged()));
1298}
1299
1300void Dialog::setType(WindowType type)
1301{
1302 if (type == d->type) {
1303 return;
1304 }
1305
1306 d->type = type;
1307 d->applyType();
1308 Q_EMIT typeChanged();
1309}
1310
1311Dialog::WindowType Dialog::type() const
1312{
1313 return d->type;
1314}
1315
1316void Dialog::focusInEvent(QFocusEvent *ev)
1317{
1319}
1320
1321void Dialog::focusOutEvent(QFocusEvent *ev)
1322{
1323 if (d->hideOnWindowDeactivate) {
1324 bool parentHasFocus = false;
1325
1326 QWindow *parentWindow = transientParent();
1327
1328 while (parentWindow) {
1329 if (parentWindow->isActive() && !(parentWindow->flags() & Qt::WindowDoesNotAcceptFocus)) {
1330 parentHasFocus = true;
1331
1332 break;
1333 }
1334
1335 parentWindow = parentWindow->transientParent();
1336 }
1337
1338 const QWindow *focusWindow = QGuiApplication::focusWindow();
1339 bool childHasFocus = focusWindow && ((focusWindow->isActive() && isAncestorOf(focusWindow)) || (focusWindow->type() & Qt::Popup) == Qt::Popup);
1340
1341 const bool viewClicked = qobject_cast<const PlasmaQuick::SharedQmlEngine *>(focusWindow) || qobject_cast<const ConfigView *>(focusWindow);
1342
1343 if (viewClicked || (!parentHasFocus && !childHasFocus)) {
1344 setVisible(false);
1345 Q_EMIT windowDeactivated();
1346 }
1347 }
1348
1350}
1351
1352void Dialog::showEvent(QShowEvent *event)
1353{
1354 d->updateResizableEdges();
1355 d->updateSizeFromAppletInterface();
1356
1357 if (d->backgroundHints != Dialog::NoBackground) {
1358 DialogShadows::instance()->addWindow(this, d->dialogBackground->enabledBorders());
1359 }
1360
1363 }
1365}
1366
1367bool Dialog::event(QEvent *event)
1368{
1369 if (event->type() == QEvent::Expose) {
1370 if (!KWindowSystem::isPlatformWayland() || isRunningInKWin() || !isExposed()) {
1371 return QQuickWindow::event(event);
1372 }
1373
1374 /*
1375 * expose event is the only place where to correctly
1376 * register our wayland extensions, as showevent is a bit too
1377 * soon and the platform window isn't shown yet
1378 * (only the first expose event, guarded by needsSetupNextExpose bool)
1379 * and tear it down when the window gets hidden
1380 * see https://phabricator.kde.org/T6064
1381 */
1382 // sometimes non null regions arrive even for non visible windows
1383 // for which surface creation would fail
1384 if (d->needsSetupNextExpose && isVisible()) {
1385 d->updateVisibility(true);
1386 const bool ret = QQuickWindow::event(event);
1387 d->updateTheme();
1388 d->needsSetupNextExpose = false;
1389 return ret;
1390 }
1391 } else if (event->type() == QEvent::Show) {
1392 d->updateVisibility(true);
1393 } else if (event->type() == QEvent::Hide) {
1394 d->updateVisibility(false);
1395 d->needsSetupNextExpose = true;
1396 } else if (event->type() == QEvent::Move) {
1397 if (d->type != Dialog::OnScreenDisplay) {
1398 QMoveEvent *me = static_cast<QMoveEvent *>(event);
1399 PlasmaShellWaylandIntegration::get(this)->setPosition(me->pos());
1400 }
1401 }
1402
1403 /*Fitt's law: if the containment has margins, and the mouse cursor clicked
1404 * on the mouse edge, forward the click in the containment boundaries
1405 */
1406 if (d->mainItem && !d->mainItem->size().isEmpty()) {
1407 switch (event->type()) {
1408 case QEvent::MouseMove:
1411 QMouseEvent *me = static_cast<QMouseEvent *>(event);
1412 if (d->resizableEdges) {
1413 if (event->type() == QEvent::MouseMove && d->updateMouseCursor(me->globalPosition())) {
1414 return QQuickWindow::event(event);
1415 }
1416 if (event->type() == QEvent::MouseButtonPress) {
1417 const QPointF globalMousePos = me->globalPosition();
1418 const Qt::Edges sides = d->hitTest(globalMousePos) & d->resizableEdges;
1419 if (sides) {
1420 startSystemResize(sides);
1421 return true;
1422 }
1423 }
1424 }
1425
1426 // don't mess with position if the cursor is actually outside the view:
1427 // somebody is doing a click and drag that must not break when the cursor is outside
1428 if (geometry().contains(me->globalPosition().toPoint()) && !d->mainItemContainsPosition(me->scenePosition())) {
1429 QMouseEvent me2(me->type(),
1430 d->positionAdjustedForMainItem(me->scenePosition()),
1431 d->positionAdjustedForMainItem(me->scenePosition()),
1432 d->positionAdjustedForMainItem(me->scenePosition()) + position(),
1433 me->button(),
1434 me->buttons(),
1435 me->modifiers());
1436 me2.setTimestamp(me->timestamp());
1437
1438 if (isVisible()) {
1439 QCoreApplication::sendEvent(this, &me2);
1440 }
1441 return true;
1442 }
1443 break;
1444 }
1445
1446 case QEvent::Wheel: {
1447 QWheelEvent *we = static_cast<QWheelEvent *>(event);
1448
1449 const QPoint pos = we->position().toPoint();
1450
1451 if (!d->mainItemContainsPosition(pos)) {
1452 QWheelEvent we2(d->positionAdjustedForMainItem(pos),
1453 d->positionAdjustedForMainItem(pos) + position(),
1454 we->pixelDelta(),
1455 we->angleDelta(),
1456 we->buttons(),
1457 we->modifiers(),
1458 we->phase(),
1459 false /*inverted*/);
1460 we2.setTimestamp(we->timestamp());
1461
1462 if (isVisible()) {
1463 QCoreApplication::sendEvent(this, &we2);
1464 }
1465 return true;
1466 }
1467 break;
1468 }
1469
1470 case QEvent::DragEnter: {
1471 QDragEnterEvent *de = static_cast<QDragEnterEvent *>(event);
1472 if (!d->mainItemContainsPosition(de->position())) {
1473 QDragEnterEvent de2(d->positionAdjustedForMainItem(de->position()).toPoint(),
1474 de->possibleActions(),
1475 de->mimeData(),
1476 de->buttons(),
1477 de->modifiers());
1478
1479 if (isVisible()) {
1480 QCoreApplication::sendEvent(this, &de2);
1481 }
1482 return true;
1483 }
1484 break;
1485 }
1486 // DragLeave just works
1487 case QEvent::DragLeave:
1488 break;
1489 case QEvent::DragMove: {
1490 QDragMoveEvent *de = static_cast<QDragMoveEvent *>(event);
1491 if (!d->mainItemContainsPosition(de->position())) {
1492 QDragMoveEvent de2(d->positionAdjustedForMainItem(de->position()).toPoint(),
1493 de->possibleActions(),
1494 de->mimeData(),
1495 de->buttons(),
1496 de->modifiers());
1497
1498 if (isVisible()) {
1499 QCoreApplication::sendEvent(this, &de2);
1500 }
1501 return true;
1502 }
1503 break;
1504 }
1505 case QEvent::Drop: {
1506 QDropEvent *de = static_cast<QDropEvent *>(event);
1507 if (!d->mainItemContainsPosition(de->position())) {
1508 QDropEvent de2(d->positionAdjustedForMainItem(de->position()).toPoint(), de->possibleActions(), de->mimeData(), de->buttons(), de->modifiers());
1509
1510 if (isVisible()) {
1511 QCoreApplication::sendEvent(this, &de2);
1512 }
1513 return true;
1514 }
1515 break;
1516 }
1517
1518 default:
1519 break;
1520 }
1521 }
1522
1523 return QQuickWindow::event(event);
1524}
1525
1526void Dialog::hideEvent(QHideEvent *event)
1527{
1528 // Persist the size if this contains an applet
1529 if (d->appletInterface && d->mainItem) {
1530 KConfigGroup config = d->appletInterface->applet()->config();
1531 qreal w = d->mainItem->width();
1532 qreal h = d->mainItem->height();
1533 config.writeEntry("popupWidth", w);
1534 config.writeEntry("popupHeight", h);
1535 config.sync();
1536 }
1537
1539}
1540
1541void Dialog::moveEvent(QMoveEvent *e)
1542{
1544 if (d->resizableEdges) {
1545 d->updateMouseCursor(QCursor::pos());
1546 }
1547}
1548
1549void Dialog::classBegin()
1550{
1551 d->componentComplete = false;
1552}
1553
1554void Dialog::componentComplete()
1555{
1556 d->componentComplete = true;
1557 QQuickWindow::setVisible(d->visible);
1558 d->updateTheme();
1559}
1560
1561bool Dialog::hideOnWindowDeactivate() const
1562{
1563 return d->hideOnWindowDeactivate;
1564}
1565
1566void Dialog::setHideOnWindowDeactivate(bool hide)
1567{
1568 if (d->hideOnWindowDeactivate == hide) {
1569 return;
1570 }
1571 d->hideOnWindowDeactivate = hide;
1572 Q_EMIT hideOnWindowDeactivateChanged();
1573}
1574
1575bool Dialog::isOutputOnly() const
1576{
1577 return d->outputOnly;
1578}
1579
1580void Dialog::setOutputOnly(bool outputOnly)
1581{
1582 if (d->outputOnly == outputOnly) {
1583 return;
1584 }
1585 d->outputOnly = outputOnly;
1586 d->applyType();
1587 Q_EMIT outputOnlyChanged();
1588}
1589
1590void Dialog::setFloating(int floating)
1591{
1592 d->floating = floating;
1593 Q_EMIT floatingChanged();
1594}
1595
1596int Dialog::floating() const
1597{
1598 return d->floating;
1599}
1600
1601void Dialog::setVisible(bool visible)
1602{
1603 // only update real visibility when we have finished component completion
1604 // and all flags have been set
1605
1606 d->visible = visible;
1607 if (d->componentComplete) {
1608 if (visible && d->visualParent) {
1609 setPosition(popupPosition(d->visualParent, size()));
1610 }
1611
1612 // Bug 381242: Qt remembers minimize state and re-applies it when showing
1613 setWindowStates(windowStates() & ~Qt::WindowMinimized);
1614 QQuickWindow::setVisible(visible);
1615 // signal will be emitted and proxied from the QQuickWindow code
1616 } else {
1617 Q_EMIT visibleChangedProxy();
1618 }
1619}
1620
1621bool Dialog::isVisible() const
1622{
1623 if (d->componentComplete) {
1624 return QQuickWindow::isVisible();
1625 }
1626 return d->visible;
1627}
1628
1629void Dialog::setAppletInterface(QQuickItem *appletInterface)
1630{
1631 if (d->appletInterface == appletInterface) {
1632 return;
1633 }
1634 d->appletInterface = qobject_cast<AppletQuickItem *>(appletInterface);
1635 Q_EMIT appletInterfaceChanged();
1636}
1637
1638QQuickItem *Dialog::appletInterface() const
1639{
1640 return d->appletInterface;
1641}
1642
1643Dialog::BackgroundHints Dialog::backgroundHints() const
1644{
1645 return d->backgroundHints;
1646}
1647
1648void Dialog::setBackgroundHints(Dialog::BackgroundHints hints)
1649{
1650 if (d->backgroundHints == hints) {
1651 return;
1652 }
1653
1654 d->backgroundHints = hints;
1655 d->updateTheme();
1656 Q_EMIT backgroundHintsChanged();
1657}
1658
1659}
1660
1661#include "moc_dialog.cpp"
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
KConfig * config()
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool sync() override
NET::WindowType windowType(NET::WindowTypes supported_types) const
static bool isPlatformX11()
static bool isPlatformWayland()
static void setOnAllDesktops(WId win, bool b)
static void setState(WId win, NET::States state)
bool compositingActive
static void setType(WId win, NET::WindowType windowType)
SkipTaskbar
SkipSwitcher
AllTypesMask
WindowType
virtual void adjustGeometry(const QRect &geom)
set the dialog position.
Definition dialog.cpp:1255
@ SolidBackground
The solid version of the background is preferred.
Definition dialog.h:178
@ NoBackground
Not drawing a background under the applet, the dialog has its own implementation.
Definition dialog.h:176
@ StandardBackground
The standard background from the theme is drawn.
Definition dialog.h:177
Plasma::Types::Location location
Plasma Location of the dialog window.
Definition dialog.h:102
virtual QPoint popupPosition(QQuickItem *item, const QSize &size)
Definition dialog.cpp:1046
Qt::WindowFlags flags
This property holds the window flags of the window.
Definition dialog.h:134
static PlasmaShellWaylandIntegration * get(QWindow *window)
Returns the relevant PlasmaWaylandShellIntegration instance for this window creating one if needed.
Location
The Location enumeration describes where on screen an element, such as an Applet or its managing cont...
Definition plasma.h:81
@ RightEdge
Along the right side of the screen.
Definition plasma.h:90
@ Floating
Free floating.
Definition plasma.h:82
@ FullScreen
Full screen.
Definition plasma.h:86
@ TopEdge
Along the top of the screen.
Definition plasma.h:87
@ Desktop
On the planar desktop layer, extending across the full screen from edge to edge.
Definition plasma.h:84
@ LeftEdge
Along the left side of the screen.
Definition plasma.h:89
@ BottomEdge
Along the bottom of the screen.
Definition plasma.h:88
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Type type(const QSqlDatabase &db)
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
QVariant location(const QVariant &res)
QWidget * window(QObject *job)
void slideWindow(QWindow *window, SlideFromLocation location, int offset=-1)
void enableBlurBehind(QWindow *window, bool enable=true, const QRegion &region=QRegion())
void enableBackgroundContrast(QWindow *window, bool enable=true, qreal contrast=1, qreal intensity=1, qreal saturation=1, const QRegion &region=QRegion())
The EdgeEventForwarder class This class forwards edge events to be replayed within the given margin T...
Definition action.h:20
bool sendEvent(QObject *receiver, QEvent *event)
QPoint pos()
Qt::MouseButtons buttons() const const
const QMimeData * mimeData() const const
Qt::KeyboardModifiers modifiers() const const
QPointF position() const const
Qt::DropActions possibleActions() const const
Type type() const const
QWindow * focusWindow()
QList< QScreen * > screens()
Qt::KeyboardModifiers modifiers() const const
quint64 timestamp() const const
const QPoint & pos() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
void setX(int x)
void setY(int y)
int x() const const
int y() const const
QPoint toPoint() const const
qreal x() const const
qreal y() const const
virtual QRectF boundingRect() const const
QRectF mapRectToScene(const QRectF &rect) const const
QPointF mapToScene(const QPointF &point) const const
void setParentItem(QQuickItem *parent)
QQuickWindow * window() const const
virtual bool event(QEvent *event) override
virtual void focusInEvent(QFocusEvent *ev) override
virtual void focusOutEvent(QFocusEvent *ev) override
virtual void hideEvent(QHideEvent *) override
virtual void resizeEvent(QResizeEvent *ev) override
virtual void showEvent(QShowEvent *) override
void adjust(int dx1, int dy1, int dx2, int dy2)
int bottom() const const
QPoint center() const const
int height() const const
bool isEmpty() const const
int left() const const
void moveTopLeft(const QPoint &position)
int right() const const
void setBottom(int y)
void setLeft(int x)
void setRight(int x)
void setTop(int y)
int top() const const
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
qreal height() const const
QRect toRect() const const
qreal width() const const
bool isNull() const const
const QSize & oldSize() const const
const QSize & size() const const
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
QPointF position() const const
QPointF scenePosition() const const
QSize expandedTo(const QSize &otherSize) const const
int height() const const
bool isEmpty() const const
int width() const const
SizeFDiagCursor
BottomEdge
transparent
WindowState
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isNull() const const
int toInt(bool *ok) const const
QPoint angleDelta() const const
Qt::ScrollPhase phase() const const
QPoint pixelDelta() const const
QRect geometry() const const
bool isActive() const const
bool isExposed() const const
QPoint mapToGlobal(const QPoint &pos) const const
QRegion mask() const const
virtual void moveEvent(QMoveEvent *ev)
QWindow * parent(AncestorMode mode) const const
QScreen * screen() const const
void setGeometry(const QRect &rect)
virtual QSize size() const const override
Qt::WindowType type() const const
void visibleChanged(bool arg)
WId winId() const const
void windowStateChanged(Qt::WindowState windowState)
void xChanged(int arg)
void yChanged(int arg)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:56:56 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.