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) 2021 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 "popupviewclosecontroller.h"
24 :
25 : #include <QGuiApplication>
26 : #include <QQuickWindow>
27 :
28 : using namespace muse::uicomponents;
29 :
30 0 : PopupViewCloseController::PopupViewCloseController(const modularity::ContextPtr& iocCtx, QObject* parent)
31 0 : : QObject(parent), muse::Contextable(iocCtx)
32 : {
33 0 : }
34 :
35 0 : void PopupViewCloseController::init()
36 : {
37 0 : connect(qApp, &QGuiApplication::applicationStateChanged, this, &PopupViewCloseController::onApplicationStateChanged);
38 :
39 0 : interactive()->currentUriAboutToBeChanged().onNotify(this, [this]() {
40 0 : notifyAboutClose();
41 0 : });
42 0 : }
43 :
44 0 : bool PopupViewCloseController::active() const
45 : {
46 0 : return m_active;
47 : }
48 :
49 0 : void PopupViewCloseController::setActive(bool active)
50 : {
51 0 : if (m_active == active) {
52 0 : return;
53 : }
54 :
55 0 : m_active = active;
56 :
57 0 : doUpdateEventFilters();
58 : }
59 :
60 0 : QQuickItem* PopupViewCloseController::parentItem() const
61 : {
62 0 : return m_parentItem;
63 : }
64 :
65 0 : void PopupViewCloseController::setParentItem(QQuickItem* parentItem)
66 : {
67 0 : if (m_parentItem) {
68 0 : m_parentItem->disconnect(this);
69 : }
70 :
71 0 : m_parentItem = parentItem;
72 :
73 0 : connect(m_parentItem, &QQuickItem::visibleChanged, this, [this]() {
74 0 : if (!m_parentItem || !m_parentItem->isVisible()) {
75 0 : notifyAboutClose();
76 : }
77 0 : });
78 :
79 0 : connect(m_parentItem, &QQuickItem::destroyed, this, [this]() {
80 0 : qApp->removeEventFilter(this);
81 0 : notifyAboutClose();
82 0 : });
83 0 : }
84 :
85 0 : void PopupViewCloseController::setWindow(QWindow* window)
86 : {
87 0 : m_popupWindow = window;
88 0 : }
89 :
90 0 : void PopupViewCloseController::setIsCloseOnPressOutsideParent(bool arg)
91 : {
92 0 : m_isCloseOnPressOutsideParent = arg;
93 0 : }
94 :
95 0 : void PopupViewCloseController::setCanClose(bool arg)
96 : {
97 0 : m_canClose = arg;
98 0 : }
99 :
100 0 : muse::async::Notification PopupViewCloseController::closeNotification() const
101 : {
102 0 : return m_closeNotification;
103 : }
104 :
105 0 : bool PopupViewCloseController::eventFilter(QObject* watched, QEvent* event)
106 : {
107 0 : if (QEvent::Close == event->type() && watched == parentWindow()) {
108 0 : notifyAboutClose();
109 0 : } else if (QEvent::MouseButtonPress == event->type()) {
110 0 : doFocusOut(static_cast<QMouseEvent*>(event)->globalPosition());
111 0 : } else if (QEvent::FocusOut == event->type() && watched == popupWindow()) {
112 0 : doFocusOut(QCursor::pos());
113 0 : } else if (QEvent::Close == event->type() && watched == popupWindow()) {
114 0 : if (!m_canClose) {
115 0 : event->ignore();
116 : }
117 : }
118 :
119 0 : return QObject::eventFilter(watched, event);
120 : }
121 :
122 0 : void PopupViewCloseController::onApplicationStateChanged(Qt::ApplicationState state)
123 : {
124 0 : if (!m_active || !m_isCloseOnPressOutsideParent) {
125 0 : return;
126 : }
127 :
128 : // Hack for https://github.com/musescore/MuseScore/issues/29656 on Linux
129 0 : if (interactive()->isSelectColorOpened()) {
130 0 : return;
131 : }
132 :
133 0 : if (state != Qt::ApplicationActive) {
134 0 : notifyAboutClose();
135 : }
136 : }
137 :
138 0 : void PopupViewCloseController::doFocusOut(const QPointF& mousePos)
139 : {
140 0 : if (m_isCloseOnPressOutsideParent) {
141 0 : if (!isMouseWithinBoundaries(mousePos)) {
142 0 : notifyAboutClose();
143 : }
144 : }
145 0 : }
146 :
147 0 : void PopupViewCloseController::doUpdateEventFilters()
148 : {
149 0 : if (active()) {
150 0 : qApp->installEventFilter(this);
151 : } else {
152 0 : qApp->removeEventFilter(this);
153 : }
154 0 : }
155 :
156 0 : bool PopupViewCloseController::isMouseWithinBoundaries(const QPointF& mousePos) const
157 : {
158 0 : QWindow* window = popupWindow();
159 0 : if (!window) {
160 0 : return false;
161 : }
162 :
163 0 : QRectF viewRect = window->geometry();
164 0 : if (viewRect.contains(mousePos)) {
165 0 : return true;
166 : }
167 :
168 : //! NOTE We also check the parent because often clicking on the parent should toggle the popup,
169 : //! but if we don't check a parent here, the popup will be closed and reopened.
170 0 : QQuickItem* parent = parentItem();
171 0 : QPointF localPos = parent->mapFromGlobal(mousePos);
172 0 : QRectF parentRect = QRectF(0, 0, parent->width(), parent->height());
173 0 : if (parentRect.contains(localPos)) {
174 0 : return true;
175 : }
176 :
177 : //! NOTE We also check child windows
178 0 : for (QWindow* child : QGuiApplication::allWindows()) {
179 0 : if (!child->isVisible()) {
180 0 : continue;
181 : }
182 :
183 0 : if (!window->isAncestorOf(child, QWindow::IncludeTransients)) {
184 0 : continue;
185 : }
186 :
187 0 : QRectF childRect = child->geometry();
188 0 : if (childRect.contains(mousePos)) {
189 0 : return true;
190 : }
191 0 : }
192 :
193 : // Hack for https://github.com/musescore/MuseScore/issues/29656
194 0 : if (interactive()->isSelectColorOpened()) {
195 0 : return true;
196 : }
197 :
198 0 : return false;
199 : }
200 :
201 0 : void PopupViewCloseController::notifyAboutClose()
202 : {
203 0 : m_closeNotification.notify();
204 0 : }
205 :
206 0 : QWindow* PopupViewCloseController::parentWindow() const
207 : {
208 0 : if (m_parentItem && m_parentItem->window()) {
209 0 : return m_parentItem->window();
210 : }
211 :
212 0 : return mainWindow()->qWindow();
213 : }
214 :
215 0 : QWindow* PopupViewCloseController::popupWindow() const
216 : {
217 0 : return m_popupWindow;
218 : }
|