File indexing completed on 2025-02-09 06:41:28

0001 /*
0002     SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "popupplasmawindow.h"
0007 
0008 #include <kwindoweffects.h>
0009 #include <kwindowsystem.h>
0010 
0011 #include "debug_p.h"
0012 #include <QGuiApplication>
0013 #include <QScreen>
0014 #include <qnamespace.h>
0015 #include <qtmetamacros.h>
0016 
0017 #include "private/utils.h"
0018 #include "transientplacementhint_p.h"
0019 #include "waylandintegration_p.h"
0020 
0021 namespace PlasmaQuick
0022 {
0023 
0024 class PopupPlasmaWindowPrivate
0025 {
0026 public:
0027     PopupPlasmaWindowPrivate(PopupPlasmaWindow *_q);
0028 
0029     void updateEffectivePopupDirection(const QRect &anchorRect, const QRect &relativePopupPosition);
0030     void updateSlideEffect();
0031     void updatePosition();
0032     void updatePositionX11(const QPoint &position);
0033     void updatePositionWayland(const QPoint &position);
0034     void updateBorders(const QRect &globalPosition);
0035     void updateVisualParentWindow();
0036 
0037     PopupPlasmaWindow *q;
0038     QPointer<QQuickItem> m_visualParent;
0039     QPointer<QQuickWindow> m_visualParentWindow;
0040     PopupPlasmaWindow::RemoveBorders m_removeBorderStrategy = PopupPlasmaWindow::Never;
0041     bool m_needsReposition = false;
0042     bool m_floating = false;
0043     bool m_animated = false;
0044     int m_margin = 0;
0045     Qt::Edge m_popupDirection = Qt::TopEdge;
0046     Qt::Edge m_effectivePopupDirection = Qt::TopEdge;
0047 };
0048 
0049 PopupPlasmaWindowPrivate::PopupPlasmaWindowPrivate(PopupPlasmaWindow *_q)
0050     : q(_q)
0051 {
0052 }
0053 
0054 /**
0055  * PopupPlasmaWindowPrivate::updateSlideEffect
0056  * @param anchorRect - the rect around where the popup should be placed relative to the parent window
0057  * @param relativePopupPosition - the final rect of the popup relative to the parent window
0058  *
0059  * This is based purely on position in prepartion for being called in a wayland configure event
0060  */
0061 void PopupPlasmaWindowPrivate::updateEffectivePopupDirection(const QRect &anchorRect, const QRect &relativePopupPosition)
0062 {
0063     Qt::Edge effectivePopupDirection = Qt::TopEdge;
0064     if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
0065         if (relativePopupPosition.center().y() >= anchorRect.center().y()) {
0066             effectivePopupDirection = Qt::BottomEdge;
0067         } else {
0068             effectivePopupDirection = Qt::TopEdge;
0069         }
0070     }
0071     if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
0072         if (relativePopupPosition.center().x() >= anchorRect.center().x()) {
0073             effectivePopupDirection = Qt::RightEdge;
0074         } else {
0075             effectivePopupDirection = Qt::LeftEdge;
0076         }
0077     }
0078 
0079     if (effectivePopupDirection != m_effectivePopupDirection) {
0080         Q_EMIT q->effectivePopupDirectionChanged();
0081         m_effectivePopupDirection = effectivePopupDirection;
0082     }
0083 }
0084 
0085 void PopupPlasmaWindowPrivate::updateSlideEffect()
0086 {
0087     KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
0088     if (!m_animated) {
0089         KWindowEffects::slideWindow(q, slideLocation);
0090         return;
0091     }
0092     switch (m_effectivePopupDirection) {
0093     case Qt::TopEdge:
0094         slideLocation = KWindowEffects::BottomEdge;
0095         break;
0096     case Qt::BottomEdge:
0097         slideLocation = KWindowEffects::TopEdge;
0098         break;
0099     case Qt::LeftEdge:
0100         slideLocation = KWindowEffects::RightEdge;
0101         break;
0102     case Qt::RightEdge:
0103         slideLocation = KWindowEffects::LeftEdge;
0104         break;
0105     }
0106     KWindowEffects::slideWindow(q, slideLocation);
0107 }
0108 
0109 void PopupPlasmaWindowPrivate::updatePosition()
0110 {
0111     m_needsReposition = false;
0112 
0113     if (!m_visualParent || !m_visualParent->window()) {
0114         qCWarning(LOG_PLASMAQUICK) << "Exposed with no visual parent. Window positioning broken.";
0115         return;
0116     }
0117     q->setTransientParent(m_visualParent->window());
0118     TransientPlacementHint placementHint;
0119     QRectF parentAnchorRect = QRectF(m_visualParent->mapToScene(QPointF(0, 0)), m_visualParent->size());
0120 
0121     if (!m_floating) {
0122         // pad parentAnchorRect to the window it's in, so that the popup appears outside the panel
0123         // even if the tooltip area does not fill it
0124         if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
0125             parentAnchorRect.setTop(0);
0126             parentAnchorRect.setBottom(m_visualParent->window()->height());
0127         }
0128         if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
0129             parentAnchorRect.setLeft(0);
0130             parentAnchorRect.setRight(m_visualParent->window()->width());
0131         }
0132     }
0133 
0134     placementHint.setParentAnchorArea(parentAnchorRect.toRect());
0135     placementHint.setParentAnchor(m_popupDirection);
0136     placementHint.setPopupAnchor(PlasmaQuickPrivate::oppositeEdge(m_popupDirection));
0137     placementHint.setFlipConstraintAdjustments(m_floating ? Qt::Vertical : Qt::Orientations());
0138     placementHint.setMargin(m_margin);
0139 
0140     const QRect popupPosition = TransientPlacementHelper::popupRect(q, placementHint);
0141 
0142     QRect relativePopupPosition = popupPosition;
0143     if (m_visualParent->window()) {
0144         relativePopupPosition = relativePopupPosition.translated(-m_visualParent->window()->position());
0145     }
0146     updateEffectivePopupDirection(parentAnchorRect.toRect(), relativePopupPosition);
0147     updateSlideEffect();
0148 
0149     if (KWindowSystem::isPlatformX11()) {
0150         updatePositionX11(popupPosition.topLeft());
0151     } else if (KWindowSystem::isPlatformWayland()) {
0152         updatePositionWayland(popupPosition.topLeft());
0153     }
0154 
0155     updateBorders(popupPosition);
0156 }
0157 
0158 void PopupPlasmaWindowPrivate::updatePositionX11(const QPoint &position)
0159 {
0160     q->setPosition(position);
0161 }
0162 
0163 void PopupPlasmaWindowPrivate::updatePositionWayland(const QPoint &position)
0164 {
0165     // still update's Qt internal reference as it's used by the next dialog
0166     // this can be dropped when we're using true semantic positioning in the backend
0167     q->setPosition(position);
0168 
0169     PlasmaShellWaylandIntegration::get(q)->setPosition(position);
0170 }
0171 
0172 void PopupPlasmaWindowPrivate::updateBorders(const QRect &globalPosition)
0173 {
0174     // disables borders for the edges that are touching the screen edge
0175 
0176     QScreen *screen = QGuiApplication::screenAt(globalPosition.center());
0177     if (!screen) {
0178         return;
0179     }
0180     const QRect screenGeometry = screen->geometry();
0181 
0182     Qt::Edges enabledBorders = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge;
0183 
0184     if (m_margin) {
0185         q->setBorders(enabledBorders);
0186         return;
0187     }
0188 
0189     if (m_removeBorderStrategy & PopupPlasmaWindow::AtScreenEdges) {
0190         if (globalPosition.top() <= screenGeometry.top()) {
0191             enabledBorders.setFlag(Qt::TopEdge, false);
0192         }
0193         if (globalPosition.bottom() >= screenGeometry.bottom()) {
0194             enabledBorders.setFlag(Qt::BottomEdge, false);
0195         }
0196         if (globalPosition.left() <= screenGeometry.left()) {
0197             enabledBorders.setFlag(Qt::LeftEdge, false);
0198         }
0199         if (globalPosition.right() >= screenGeometry.right()) {
0200             enabledBorders.setFlag(Qt::RightEdge, false);
0201         }
0202     }
0203     if (m_removeBorderStrategy & PopupPlasmaWindow::AtPanelEdges) {
0204         switch (m_popupDirection) {
0205         case Qt::LeftEdge:
0206             enabledBorders.setFlag(Qt::RightEdge, false);
0207             break;
0208         case Qt::RightEdge:
0209             enabledBorders.setFlag(Qt::LeftEdge, false);
0210             break;
0211         case Qt::BottomEdge:
0212             enabledBorders.setFlag(Qt::TopEdge, false);
0213             break;
0214         case Qt::TopEdge:
0215         default:
0216             enabledBorders.setFlag(Qt::BottomEdge, false);
0217             break;
0218         }
0219     }
0220     q->setBorders(enabledBorders);
0221 }
0222 
0223 void PopupPlasmaWindowPrivate::updateVisualParentWindow()
0224 {
0225     if (m_visualParentWindow) {
0226         QObject::disconnect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
0227         QObject::disconnect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
0228     }
0229 
0230     m_visualParentWindow = m_visualParent ? m_visualParent->window() : nullptr;
0231 
0232     if (m_visualParentWindow) {
0233         QObject::connect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
0234         QObject::connect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
0235     }
0236 }
0237 
0238 PopupPlasmaWindow::PopupPlasmaWindow(const QString &svgPrefix)
0239     : PlasmaWindow(svgPrefix)
0240     , d(new PopupPlasmaWindowPrivate(this))
0241 {
0242 }
0243 
0244 PopupPlasmaWindow::~PopupPlasmaWindow()
0245 {
0246 }
0247 
0248 void PopupPlasmaWindow::setVisualParent(QQuickItem *item)
0249 {
0250     if (item == d->m_visualParent) {
0251         return;
0252     }
0253 
0254     if (d->m_visualParent) {
0255         disconnect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
0256     }
0257 
0258     d->m_visualParent = item;
0259     d->updateVisualParentWindow();
0260 
0261     if (d->m_visualParent) {
0262         connect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
0263     }
0264 
0265     Q_EMIT visualParentChanged();
0266     queuePositionUpdate();
0267 }
0268 
0269 QQuickItem *PopupPlasmaWindow::visualParent() const
0270 {
0271     return d->m_visualParent;
0272 }
0273 
0274 Qt::Edge PopupPlasmaWindow::popupDirection() const
0275 {
0276     return d->m_popupDirection;
0277 }
0278 
0279 void PopupPlasmaWindow::setPopupDirection(Qt::Edge popupDirection)
0280 {
0281     if (popupDirection == d->m_popupDirection) {
0282         return;
0283     }
0284     d->m_popupDirection = popupDirection;
0285 
0286     if (isExposed()) {
0287         qCWarning(LOG_PLASMAQUICK) << "location should be set before showing popup window";
0288     }
0289     queuePositionUpdate();
0290 
0291     Q_EMIT popupDirectionChanged();
0292 }
0293 
0294 Qt::Edge PopupPlasmaWindow::effectivePopupDirection() const
0295 {
0296     return d->m_effectivePopupDirection;
0297 }
0298 
0299 bool PopupPlasmaWindow::floating() const
0300 {
0301     return d->m_floating;
0302 }
0303 
0304 void PopupPlasmaWindow::setFloating(bool floating)
0305 {
0306     if (floating == d->m_floating) {
0307         return;
0308     }
0309     d->m_floating = floating;
0310     queuePositionUpdate();
0311     Q_EMIT floatingChanged();
0312 }
0313 
0314 bool PopupPlasmaWindow::animated() const
0315 {
0316     return d->m_animated;
0317 }
0318 
0319 void PopupPlasmaWindow::setAnimated(bool animated)
0320 {
0321     d->m_animated = animated;
0322     queuePositionUpdate();
0323     Q_EMIT animatedChanged();
0324 }
0325 
0326 PopupPlasmaWindow::RemoveBorders PopupPlasmaWindow::removeBorderStrategy() const
0327 {
0328     return d->m_removeBorderStrategy;
0329 }
0330 
0331 void PopupPlasmaWindow::setRemoveBorderStrategy(PopupPlasmaWindow::RemoveBorders strategy)
0332 {
0333     if (d->m_removeBorderStrategy == strategy) {
0334         return;
0335     }
0336 
0337     d->m_removeBorderStrategy = strategy;
0338     queuePositionUpdate(); // This will update borders as well
0339     Q_EMIT removeBorderStrategyChanged();
0340 }
0341 
0342 int PopupPlasmaWindow::margin() const
0343 {
0344     return d->m_margin;
0345 }
0346 
0347 void PopupPlasmaWindow::setMargin(int margin)
0348 {
0349     if (d->m_margin == margin) {
0350         return;
0351     }
0352 
0353     d->m_margin = margin;
0354     queuePositionUpdate();
0355     Q_EMIT marginChanged();
0356 }
0357 
0358 bool PopupPlasmaWindow::event(QEvent *event)
0359 {
0360     switch (event->type()) {
0361     case QEvent::UpdateRequest:
0362         if (d->m_needsReposition) {
0363             d->updatePosition();
0364         }
0365         break;
0366     case QEvent::Show:
0367         d->updatePosition();
0368         break;
0369     case QEvent::Resize:
0370         d->updatePosition();
0371         break;
0372     default:
0373         break;
0374     }
0375     return PlasmaQuick::PlasmaWindow::event(event);
0376 }
0377 
0378 void PopupPlasmaWindow::queuePositionUpdate()
0379 {
0380     d->m_needsReposition = true;
0381 }
0382 }
0383 
0384 #include "moc_popupplasmawindow.cpp"