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"