LCOV - code coverage report
Current view: top level - muse/framework/uicomponents/qml/Muse/UiComponents - popupview.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 240 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "popupview.h"
      24              : #include "modularity/ioc.h"
      25              : 
      26              : #if defined(Q_OS_MAC)
      27              : #include "internal/platform/macos/macospopupviewclosecontroller.h"
      28              : #elif defined(Q_OS_WIN)
      29              : #include "internal/platform/win/winpopupviewclosecontroller.h"
      30              : #endif
      31              : 
      32              : using namespace muse::uicomponents;
      33              : 
      34            0 : static Qt::WindowFlags resolveWindowFlags()
      35              : {
      36            0 :     Qt::WindowFlags flags;
      37            0 :     if (qGuiApp->platformName().contains("wayland")) {
      38            0 :         flags = Qt::Popup;
      39              :     } else {
      40            0 :         flags = Qt::Tool;
      41              :     }
      42              : 
      43              :     static int sIsKde = -1;
      44            0 :     if (sIsKde == -1) {
      45            0 :         QString desktop = qEnvironmentVariable("XDG_CURRENT_DESKTOP").toLower();
      46            0 :         QString session = qEnvironmentVariable("XDG_SESSION_DESKTOP").toLower();
      47            0 :         sIsKde = desktop.contains("kde") || session.contains("kde");
      48            0 :     }
      49            0 :     if (!sIsKde) {
      50            0 :         flags |= Qt::BypassWindowManagerHint;        // Otherwise, it does not work correctly on Gnome (Linux) when resizing)
      51              :     }
      52              : 
      53              :     flags |= Qt::FramelessWindowHint        // Without border
      54            0 :              | Qt::NoDropShadowWindowHint;   // Without system shadow
      55              : 
      56            0 :     return flags;
      57              : }
      58              : 
      59            0 : PopupView::PopupView(QQuickItem* parent)
      60            0 :     : WindowView(parent)
      61              : {
      62            0 :     setObjectName("PopupView");
      63              : 
      64            0 :     setShowArrow(true);
      65            0 :     setPadding(12);
      66            0 : }
      67              : 
      68            0 : PopupView::~PopupView()
      69              : {
      70            0 :     if (m_closeController) {
      71            0 :         delete m_closeController;
      72              :     }
      73            0 : }
      74              : 
      75            0 : void PopupView::initView()
      76              : {
      77            0 :     QQuickWindow::setDefaultAlphaBuffer(true);
      78              : 
      79            0 :     WindowView::initView();
      80              : 
      81            0 :     m_view->setFlags(resolveWindowFlags());
      82            0 :     m_view->setColor(Qt::transparent);
      83            0 : }
      84              : 
      85            0 : void PopupView::initCloseController()
      86              : {
      87            0 :     muse::modularity::ContextPtr iocCtx = muse::iocCtxForQmlObject(this)();
      88              : #if defined(Q_OS_MAC)
      89              :     m_closeController = new MacOSPopupViewCloseController(iocCtx);
      90              : #elif defined(Q_OS_WIN)
      91              :     m_closeController = new WinPopupViewCloseController(iocCtx);
      92              : #else
      93            0 :     m_closeController = new PopupViewCloseController(iocCtx);
      94              : #endif
      95              : 
      96            0 :     m_closeController->init();
      97              : 
      98            0 :     m_closeController->setParentItem(parentItem());
      99            0 :     m_closeController->setWindow(window());
     100            0 :     m_closeController->setIsCloseOnPressOutsideParent(m_closePolicies & ClosePolicy::CloseOnPressOutsideParent);
     101            0 :     m_closeController->setCanClose(!m_closePolicies.testFlag(ClosePolicy::NoAutoClose));
     102              : 
     103            0 :     m_closeController->closeNotification().onNotify(this, [this]() {
     104            0 :         close();
     105            0 :     });
     106            0 : }
     107              : 
     108            0 : void PopupView::setParentItem(QQuickItem* parent)
     109              : {
     110            0 :     WindowView::setParentItem(parent);
     111              : 
     112            0 :     if (m_closeController) {
     113            0 :         m_closeController->setParentItem(parent);
     114              :     }
     115            0 : }
     116              : 
     117            0 : void PopupView::beforeOpen()
     118              : {
     119            0 :     WindowView::beforeOpen();
     120              : 
     121            0 :     if (!m_closeController) {
     122            0 :         initCloseController();
     123              :     }
     124              : 
     125            0 :     m_closeController->setActive(true);
     126              : 
     127            0 :     qApp->installEventFilter(this);
     128            0 : }
     129              : 
     130            0 : void PopupView::onHidden()
     131              : {
     132            0 :     WindowView::onHidden();
     133              : 
     134            0 :     if (m_closeController) {
     135            0 :         m_closeController->setActive(false);
     136              :     }
     137              : 
     138            0 :     qApp->removeEventFilter(this);
     139            0 : }
     140              : 
     141            0 : bool PopupView::eventFilter(QObject* watched, QEvent* event)
     142              : {
     143            0 :     if (event->type() == QEvent::Move && watched == m_parentWindow) {
     144            0 :         repositionWindowIfNeed();
     145              :     }
     146              : 
     147            0 :     return WindowView::eventFilter(watched, event);
     148              : }
     149              : 
     150            0 : PopupView::ClosePolicies PopupView::closePolicies() const
     151              : {
     152            0 :     return m_closePolicies;
     153              : }
     154              : 
     155            0 : void PopupView::setClosePolicies(ClosePolicies closePolicies)
     156              : {
     157            0 :     if (m_closePolicies == closePolicies) {
     158            0 :         return;
     159              :     }
     160              : 
     161            0 :     m_closePolicies = closePolicies;
     162              : 
     163            0 :     if (m_closeController) {
     164            0 :         m_closeController->setIsCloseOnPressOutsideParent(closePolicies & ClosePolicy::CloseOnPressOutsideParent);
     165              :     }
     166              : 
     167            0 :     emit closePoliciesChanged(closePolicies);
     168              : }
     169              : 
     170            0 : QQuickItem* PopupView::anchorItem() const
     171              : {
     172            0 :     return m_anchorItem;
     173              : }
     174              : 
     175            0 : void PopupView::setAnchorItem(QQuickItem* anchorItem)
     176              : {
     177            0 :     if (m_anchorItem == anchorItem) {
     178            0 :         return;
     179              :     }
     180              : 
     181            0 :     m_anchorItem = anchorItem;
     182            0 :     emit anchorItemChanged(m_anchorItem);
     183              : }
     184              : 
     185            0 : QRectF PopupView::anchorGeometry() const
     186              : {
     187            0 :     QRectF geometry = currentScreenGeometry();
     188            0 :     if (m_anchorItem) {
     189            0 :         QPointF anchorItemTopLeft = m_anchorItem->mapToGlobal(QPoint(0, 0));
     190            0 :         geometry &= QRectF(anchorItemTopLeft, m_anchorItem->size());
     191              :     }
     192              : 
     193            0 :     return geometry;
     194              : }
     195              : 
     196            0 : PopupView::PlacementPolicies PopupView::placementPolicies() const
     197              : {
     198            0 :     return m_placementPolicies;
     199              : }
     200              : 
     201            0 : void PopupView::setPlacementPolicies(PlacementPolicies placementPolicies)
     202              : {
     203            0 :     if (m_placementPolicies == placementPolicies) {
     204            0 :         return;
     205              :     }
     206              : 
     207            0 :     m_placementPolicies = placementPolicies;
     208            0 :     emit placementPoliciesChanged(placementPolicies);
     209              : }
     210              : 
     211            0 : PopupPosition::Type PopupView::popupPosition() const
     212              : {
     213            0 :     return m_popupPosition;
     214              : }
     215              : 
     216            0 : void PopupView::setPopupPosition(PopupPosition::Type position)
     217              : {
     218            0 :     if (m_popupPosition == position) {
     219            0 :         return;
     220              :     }
     221              : 
     222            0 :     m_popupPosition = position;
     223            0 :     emit popupPositionChanged(m_popupPosition);
     224              : }
     225              : 
     226            0 : qreal PopupView::localX() const
     227              : {
     228            0 :     return m_localPos.x();
     229              : }
     230              : 
     231            0 : qreal PopupView::localY() const
     232              : {
     233            0 :     return m_localPos.y();
     234              : }
     235              : 
     236            0 : void PopupView::setLocalX(qreal x)
     237              : {
     238            0 :     if (qFuzzyCompare(m_localPos.x(), x)) {
     239            0 :         return;
     240              :     }
     241              : 
     242            0 :     m_localPos.setX(x);
     243            0 :     emit xChanged(m_localPos.x());
     244              : 
     245            0 :     repositionWindowIfNeed();
     246              : }
     247              : 
     248            0 : void PopupView::setLocalY(qreal y)
     249              : {
     250            0 :     if (qFuzzyCompare(m_localPos.y(), y)) {
     251            0 :         return;
     252              :     }
     253              : 
     254            0 :     m_localPos.setY(y);
     255            0 :     emit yChanged(m_localPos.y());
     256              : 
     257            0 :     repositionWindowIfNeed();
     258              : }
     259              : 
     260            0 : bool PopupView::showArrow() const
     261              : {
     262            0 :     return m_showArrow;
     263              : }
     264              : 
     265            0 : void PopupView::setShowArrow(bool showArrow)
     266              : {
     267            0 :     if (m_showArrow == showArrow) {
     268            0 :         return;
     269              :     }
     270              : 
     271            0 :     m_showArrow = showArrow;
     272            0 :     emit showArrowChanged(m_showArrow);
     273              : }
     274              : 
     275            0 : int PopupView::arrowX() const
     276              : {
     277            0 :     return m_arrowX;
     278              : }
     279              : 
     280            0 : int PopupView::arrowY() const
     281              : {
     282            0 :     return m_arrowY;
     283              : }
     284              : 
     285            0 : void PopupView::setArrowX(int arrowX)
     286              : {
     287            0 :     if (m_arrowX == arrowX) {
     288            0 :         return;
     289              :     }
     290              : 
     291            0 :     m_arrowX = arrowX;
     292            0 :     emit arrowXChanged(m_arrowX);
     293              : }
     294              : 
     295            0 : void PopupView::setArrowY(int arrowY)
     296              : {
     297            0 :     if (m_arrowY == arrowY) {
     298            0 :         return;
     299              :     }
     300              : 
     301            0 :     m_arrowY = arrowY;
     302            0 :     emit arrowYChanged(m_arrowY);
     303              : }
     304              : 
     305            0 : int PopupView::padding() const
     306              : {
     307            0 :     return m_padding;
     308              : }
     309              : 
     310            0 : void PopupView::setPadding(int padding)
     311              : {
     312            0 :     if (m_padding == padding) {
     313            0 :         return;
     314              :     }
     315              : 
     316            0 :     m_padding = padding;
     317            0 :     emit paddingChanged(m_padding);
     318              : }
     319              : 
     320            0 : void PopupView::close(bool force)
     321              : {
     322            0 :     if (m_closeController) {
     323            0 :         m_closeController->setCanClose(true);
     324              :     }
     325            0 :     WindowView::close(force);
     326            0 : }
     327              : 
     328            0 : void PopupView::repositionWindowIfNeed()
     329              : {
     330            0 :     if (!isOpened()) {
     331            0 :         return;
     332              :     }
     333              : 
     334            0 :     m_globalPos = QPointF();
     335            0 :     updateGeometry();
     336            0 :     m_view->setPosition(m_globalPos.toPoint());
     337            0 :     m_globalPos = QPoint();
     338              : }
     339              : 
     340            0 : void PopupView::updateGeometry()
     341              : {
     342            0 :     const QQuickItem* parent = parentItem();
     343            0 :     IF_ASSERT_FAILED(parent) {
     344            0 :         return;
     345              :     }
     346              : 
     347            0 :     QPointF parentTopLeft = parent->mapToGlobal(QPoint(0, 0));
     348              : 
     349            0 :     if (m_globalPos.isNull()) {
     350            0 :         m_globalPos = parentTopLeft + m_localPos;
     351              :     }
     352              : 
     353            0 :     QRectF anchorRect = anchorGeometry();
     354            0 :     QRectF viewRect = viewGeometry();
     355              : 
     356            0 :     auto movePos = [this, &viewRect](qreal x, qreal y) {
     357            0 :         m_globalPos.setX(x);
     358            0 :         m_globalPos.setY(y);
     359              : 
     360            0 :         viewRect.moveTopLeft(m_globalPos);
     361            0 :     };
     362              : 
     363            0 :     bool ignoreFit = m_placementPolicies.testFlag(PlacementPolicy::IgnoreFit);
     364            0 :     bool canFitAbove = !ignoreFit ? viewRect.height() < parentTopLeft.y() : true;
     365            0 :     bool canFitBelow = !ignoreFit ? viewRect.bottom() < anchorRect.bottom() : true;
     366            0 :     bool canFitLeft = !ignoreFit ? viewRect.width() < parentTopLeft.x() : true;
     367            0 :     bool canFitRight = !ignoreFit ? viewRect.right() < anchorRect.right() : true;
     368              : 
     369            0 :     auto moveBelow = [&]() {
     370            0 :         movePos(m_globalPos.x(), parentTopLeft.y() + parent->height());
     371            0 :         setPopupPosition(PopupPosition::Bottom);
     372            0 :     };
     373              : 
     374            0 :     auto moveAbove = [&]() {
     375            0 :         movePos(m_globalPos.x(), parentTopLeft.y() - viewRect.height());
     376            0 :         setPopupPosition(PopupPosition::Top);
     377            0 :     };
     378              : 
     379            0 :     auto moveLeft = [&]() {
     380            0 :         movePos(parentTopLeft.x() - viewRect.width(), m_globalPos.y());
     381            0 :         setPopupPosition(PopupPosition::Left);
     382            0 :     };
     383              : 
     384            0 :     auto moveRight = [&]() {
     385            0 :         movePos(parentTopLeft.x() + parent->width(), m_globalPos.y());
     386            0 :         setPopupPosition(PopupPosition::Right);
     387            0 :     };
     388              : 
     389            0 :     bool placementDefault = m_placementPolicies.testFlag(PlacementPolicy::Default);
     390            0 :     bool preferBelow = m_placementPolicies.testFlag(PlacementPolicy::PreferBelow);
     391            0 :     bool preferAbove = m_placementPolicies.testFlag(PlacementPolicy::PreferAbove);
     392            0 :     bool preferLeft = m_placementPolicies.testFlag(PlacementPolicy::PreferLeft);
     393            0 :     bool preferRight = m_placementPolicies.testFlag(PlacementPolicy::PreferRight);
     394              : 
     395            0 :     if ((preferBelow || placementDefault) && canFitBelow) {
     396            0 :         moveBelow();
     397            0 :     } else if ((preferAbove || placementDefault) && canFitAbove) {
     398            0 :         moveAbove();
     399            0 :     } else if (preferLeft && canFitLeft) {
     400            0 :         moveLeft();
     401            0 :     } else if (preferRight && canFitRight) {
     402            0 :         moveRight();
     403            0 :     } else if (!canFitBelow && canFitAbove && (preferBelow || placementDefault)) {
     404            0 :         moveAbove();
     405            0 :     } else if (!canFitAbove && canFitBelow && (preferAbove || placementDefault)) {
     406            0 :         moveBelow();
     407            0 :     } else if (!canFitLeft && canFitRight && preferLeft) {
     408            0 :         moveRight();
     409            0 :     } else if (!canFitRight && canFitLeft && preferRight) {
     410            0 :         moveLeft();
     411              :     } else {
     412              :         // move to the right of the parent and move to top to an area that doesn't fit
     413            0 :         movePos(parentTopLeft.x() + parent->width(), m_globalPos.y() - (viewRect.bottom() - anchorRect.bottom()) + padding());
     414            0 :         setPopupPosition(PopupPosition::Right);
     415              :     }
     416              : 
     417            0 :     if (viewRect.left() < anchorRect.left()) {
     418              :         // move to the right to an area that doesn't fit
     419            0 :         movePos(m_globalPos.x() + anchorRect.left() - viewRect.left(), m_globalPos.y());
     420              :     }
     421              : 
     422            0 :     if (viewRect.right() > anchorRect.right()) {
     423              :         // move to the left to an area that doesn't fit
     424            0 :         movePos(m_globalPos.x() - (viewRect.right() - anchorRect.right()), m_globalPos.y());
     425              :     }
     426              : 
     427            0 :     if (!showArrow()) {
     428            0 :         if (popupPosition() == PopupPosition::Bottom || popupPosition() == PopupPosition::Top) {
     429            0 :             movePos(m_globalPos.x() - padding(), m_globalPos.y());
     430            0 :         } else if (popupPosition() == PopupPosition::Left || popupPosition() == PopupPosition::Right) {
     431            0 :             movePos(m_globalPos.x(), m_globalPos.y() - padding());
     432              :         }
     433              :     }
     434              : 
     435            0 :     updateContentPosition();
     436              : }
     437              : 
     438            0 : void PopupView::updateContentPosition()
     439              : {
     440            0 :     if (!showArrow()) {
     441            0 :         return;
     442              :     }
     443              : 
     444            0 :     const QQuickItem* parent = parentItem();
     445            0 :     IF_ASSERT_FAILED(parent) {
     446            0 :         return;
     447              :     }
     448              : 
     449            0 :     QPointF parentTopLeft = parent->mapToGlobal(QPoint(0, 0));
     450              : 
     451            0 :     QRect viewGeometry = this->viewGeometry();
     452            0 :     QPointF viewTopLeft = QPointF(viewGeometry.x(), viewGeometry.y());
     453            0 :     QPointF viewTopRight = QPointF(viewGeometry.x() + viewGeometry.width(), viewGeometry.y());
     454              : 
     455            0 :     if (parentTopLeft.x() < viewTopLeft.x() || parentTopLeft.x() > viewTopRight.x()) {
     456            0 :         setArrowX(viewGeometry.width() / 2);
     457              :     } else {
     458            0 :         setArrowX(parentTopLeft.x() + (parent->width() / 2) - m_globalPos.x());
     459              :     }
     460              : 
     461            0 :     if (parentTopLeft.y() < viewTopLeft.y() || parentTopLeft.y() > viewGeometry.bottom()) {
     462            0 :         setArrowY(viewGeometry.height() / 2);
     463              :     } else {
     464            0 :         setArrowY(parentTopLeft.y() + (parent->height() / 2) - m_globalPos.y());
     465              :     }
     466              : }
        

Generated by: LCOV version 2.0-1