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

KDE's Doxygen guidelines are available online.