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 "windowview.h"
24 :
25 : #include <QQuickView>
26 : #include <QScreen>
27 : #include <QTimer>
28 : #include <QUrl>
29 :
30 : #include "ui/inavigation.h"
31 :
32 : #include "log.h"
33 :
34 : using namespace muse::uicomponents;
35 :
36 0 : WindowView::WindowView(QQuickItem* parent)
37 0 : : QObject(parent), Contextable(muse::iocCtxForQmlObject(this))
38 : {
39 0 : }
40 :
41 0 : void WindowView::componentComplete()
42 : {
43 0 : init();
44 0 : }
45 :
46 0 : void WindowView::init()
47 : {
48 0 : IF_ASSERT_FAILED(engine()) {
49 0 : return;
50 : }
51 :
52 0 : initView();
53 0 : setViewContent(m_contentItem);
54 :
55 0 : connect(this, &WindowView::isContentReadyChanged, this, [this]() {
56 0 : if (isContentReady() && m_shouldOpenOnReady) {
57 0 : doOpen();
58 : }
59 0 : });
60 :
61 0 : navigationController()->navigationChanged().onNotify(this, [this]() {
62 0 : if (!(m_focusPolicies & FocusPolicy::TabFocus)) {
63 0 : return;
64 : }
65 :
66 0 : ui::INavigationPanel* navigationPanel = navigationController()->activePanel();
67 0 : if (!navigationPanel) {
68 0 : return;
69 : }
70 :
71 0 : if (navigationPanel->window() == m_view && !hasActiveFocus()) {
72 0 : forceActiveFocus();
73 : }
74 : });
75 :
76 0 : emit windowChanged();
77 : }
78 :
79 0 : void WindowView::initView()
80 : {
81 : //! NOTE: do not set the window when constructing the view
82 : //! This causes different bugs on different OS (e.g., no transparency for popups on windows)
83 0 : m_view = new QQuickView(engine(), nullptr);
84 :
85 : //! NOTE: We must set the parent
86 : //! Otherwise, the garbage collector may take ownership of the view and destroy it when we don't expect it
87 0 : m_view->QObject::setParent(this);
88 :
89 0 : m_view->setObjectName(objectName() + "_WindowView_QQuickView");
90 0 : m_view->setResizeMode(QQuickView::SizeRootObjectToView);
91 :
92 : //! NOTE It is important that there is a connection to this signal with an error,
93 : //! otherwise the default action will be performed - displaying a message and terminating.
94 : //! We will not be able to switch to another backend.
95 0 : QObject::connect(m_view, &QQuickWindow::sceneGraphError, this, [this](QQuickWindow::SceneGraphError, const QString& msg) {
96 0 : LOGE() << "[" << objectName() << "] scene graph error: " << msg;
97 0 : });
98 :
99 0 : QObject::connect(m_view, &QQuickView::closing, this, [this](QQuickCloseEvent* event){
100 : // Ugly hack because QQuickCloseEvent is defined in a private Qt header
101 0 : emit aboutToClose(reinterpret_cast<QObject*>(event));
102 0 : });
103 :
104 0 : m_view->installEventFilter(this);
105 0 : }
106 :
107 0 : void WindowView::setViewContent(QQuickItem* item)
108 : {
109 0 : if (!m_view || !item) {
110 0 : return;
111 : }
112 :
113 0 : m_view->setContent(QUrl(), nullptr, item);
114 :
115 0 : connect(item, &QQuickItem::implicitWidthChanged, this, [this, item]() {
116 0 : if (!m_view->isVisible()) {
117 0 : return;
118 : }
119 0 : if (item->implicitWidth() != m_view->width()) {
120 0 : updateSize(QSize(item->implicitWidth(), item->implicitHeight()));
121 : }
122 : });
123 :
124 0 : connect(item, &QQuickItem::implicitHeightChanged, this, [this, item]() {
125 0 : if (!m_view->isVisible()) {
126 0 : return;
127 : }
128 0 : if (item->implicitHeight() != m_view->height()) {
129 0 : updateSize(QSize(item->implicitWidth(), item->implicitHeight()));
130 : }
131 : });
132 : }
133 :
134 0 : void WindowView::open()
135 : {
136 0 : if ((m_openPolicies & OpenPolicy::OpenOnContentReady) && !m_isContentReady) {
137 0 : m_shouldOpenOnReady = true;
138 0 : return;
139 : }
140 :
141 0 : doOpen();
142 : }
143 :
144 0 : void WindowView::beforeOpen()
145 : {
146 0 : }
147 :
148 0 : void WindowView::doOpen()
149 : {
150 0 : if (isOpened()) {
151 0 : repositionWindowIfNeed();
152 0 : return;
153 : }
154 :
155 0 : IF_ASSERT_FAILED(m_view) {
156 0 : return;
157 : }
158 :
159 0 : resolveParentWindow();
160 0 : updateGeometry();
161 :
162 0 : beforeOpen();
163 :
164 0 : resolveNavigationParentControl();
165 :
166 0 : showView();
167 :
168 0 : m_globalPos = QPointF(); // invalidate
169 :
170 0 : emit isOpenedChanged();
171 0 : emit opened();
172 : }
173 :
174 0 : void WindowView::showView()
175 : {
176 0 : if (!m_view) {
177 0 : return;
178 : }
179 :
180 0 : const QRect geometry = viewGeometry();
181 0 : m_view->setGeometry(geometry);
182 :
183 0 : QScreen* screen = resolveScreen();
184 0 : m_view->setScreen(screen);
185 :
186 0 : if (m_openPolicies & OpenPolicy::NoActivateFocus) {
187 0 : m_view->setFlag(Qt::WindowDoesNotAcceptFocus);
188 : }
189 :
190 0 : assert(m_parentWindow);
191 0 : m_view->setTransientParent(m_parentWindow);
192 :
193 0 : connect(m_parentWindow, &QWindow::visibleChanged, this, [this](){
194 0 : if (!m_view->transientParent() || !m_view->transientParent()->isVisible()) {
195 0 : close();
196 : }
197 0 : });
198 :
199 0 : m_view->show();
200 0 : afterShow();
201 :
202 0 : const QQuickItem* item = m_view->rootObject();
203 0 : if (item) {
204 0 : updateSize(QSize(item->implicitWidth(), item->implicitHeight()));
205 : }
206 :
207 0 : if (!(m_openPolicies & OpenPolicy::NoActivateFocus)) {
208 0 : QTimer::singleShot(0, this, [this]() {
209 0 : forceActiveFocus();
210 0 : });
211 : }
212 : }
213 :
214 0 : void WindowView::onHidden()
215 : {
216 0 : emit isOpenedChanged();
217 0 : emit closed(m_forceClosed);
218 :
219 0 : activateNavigationParentControl();
220 0 : }
221 :
222 0 : void WindowView::close(bool force)
223 : {
224 0 : if (!isOpened()) {
225 0 : return;
226 : }
227 :
228 0 : IF_ASSERT_FAILED(m_view) {
229 0 : return;
230 : }
231 :
232 0 : m_forceClosed = force;
233 0 : m_view->close();
234 : }
235 :
236 0 : void WindowView::toggleOpened()
237 : {
238 0 : if (isOpened()) {
239 0 : close();
240 : } else {
241 0 : open();
242 : }
243 0 : }
244 :
245 0 : bool WindowView::isOpened() const
246 : {
247 0 : return m_view ? m_view->isVisible() : false;
248 : }
249 :
250 0 : bool WindowView::eventFilter(QObject* watched, QEvent* event)
251 : {
252 0 : if (watched == m_view) {
253 0 : switch (event->type()) {
254 0 : case QEvent::Hide:
255 0 : onHidden();
256 0 : break;
257 0 : case QEvent::FocusIn:
258 0 : if (m_view->rootObject()) {
259 0 : m_view->rootObject()->forceActiveFocus();
260 : }
261 0 : break;
262 0 : case QEvent::FocusOut:
263 0 : if (!(m_focusPolicies & FocusPolicy::ClickFocus)) {
264 : // Undo what's done in TextInput{Field,Area}.qml ensureActiveFocus
265 0 : m_view->setFlag(Qt::WindowDoesNotAcceptFocus);
266 : }
267 0 : break;
268 0 : case QEvent::MouseButtonPress:
269 0 : if (m_focusPolicies & FocusPolicy::ClickFocus) {
270 0 : forceActiveFocus();
271 : }
272 0 : break;
273 0 : default:
274 0 : break;
275 : }
276 : }
277 :
278 0 : return QObject::eventFilter(watched, event);
279 : }
280 :
281 0 : QQmlEngine* WindowView::engine() const
282 : {
283 0 : if (m_engine) {
284 0 : return m_engine;
285 : }
286 :
287 0 : return qmlEngine(this);
288 : }
289 :
290 0 : void WindowView::setEngine(QQmlEngine* engine)
291 : {
292 0 : if (m_engine == engine) {
293 0 : return;
294 : }
295 :
296 0 : m_engine = engine;
297 : }
298 :
299 0 : QQuickItem* WindowView::parentItem() const
300 : {
301 0 : if (!parent()) {
302 0 : return nullptr;
303 : }
304 :
305 0 : return qobject_cast<QQuickItem*>(parent());
306 : }
307 :
308 0 : void WindowView::setParentItem(QQuickItem* parent)
309 : {
310 0 : if (parentItem() == parent) {
311 0 : return;
312 : }
313 :
314 0 : QObject::setParent(parent);
315 0 : emit parentItemChanged();
316 : }
317 :
318 0 : QQuickItem* WindowView::contentItem() const
319 : {
320 0 : return m_contentItem;
321 : }
322 :
323 0 : void WindowView::setContentItem(QQuickItem* content)
324 : {
325 0 : if (m_contentItem == content) {
326 0 : return;
327 : }
328 :
329 0 : m_contentItem = content;
330 0 : emit contentItemChanged();
331 : }
332 :
333 0 : int WindowView::contentWidth() const
334 : {
335 0 : return m_contentWidth;
336 : }
337 :
338 0 : void WindowView::setContentWidth(int contentWidth)
339 : {
340 0 : if (m_contentWidth == contentWidth) {
341 0 : return;
342 : }
343 :
344 0 : m_contentWidth = contentWidth;
345 0 : emit contentWidthChanged();
346 : }
347 :
348 0 : int WindowView::contentHeight() const
349 : {
350 0 : return m_contentHeight;
351 : }
352 :
353 0 : void WindowView::setContentHeight(int contentHeight)
354 : {
355 0 : if (m_contentHeight == contentHeight) {
356 0 : return;
357 : }
358 :
359 0 : m_contentHeight = contentHeight;
360 0 : emit contentHeightChanged();
361 : }
362 :
363 0 : QWindow* WindowView::window() const
364 : {
365 0 : return m_view;
366 : }
367 :
368 0 : QRect WindowView::geometry() const
369 : {
370 0 : return m_view ? m_view->geometry() : QRect();
371 : }
372 :
373 0 : bool WindowView::hasActiveFocus()
374 : {
375 0 : return m_view && m_view->activeFocusItem() != nullptr;
376 : }
377 :
378 0 : void WindowView::forceActiveFocus()
379 : {
380 0 : if (!m_view) {
381 0 : return;
382 : }
383 :
384 0 : m_view->setFlag(Qt::WindowDoesNotAcceptFocus, false);
385 0 : m_view->requestActivate();
386 :
387 0 : QQuickItem* rootObject = m_view->rootObject();
388 0 : if (!rootObject) {
389 0 : return;
390 : }
391 :
392 0 : if (!rootObject->hasActiveFocus()) {
393 0 : rootObject->forceActiveFocus();
394 : }
395 : }
396 :
397 0 : QWindow* WindowView::parentWindow() const
398 : {
399 0 : return m_parentWindow;
400 : }
401 :
402 0 : void WindowView::setParentWindow(QWindow* window)
403 : {
404 0 : if (m_parentWindow == window) {
405 0 : return;
406 : }
407 :
408 0 : m_parentWindow = window;
409 0 : emit parentWindowChanged();
410 : }
411 :
412 0 : void WindowView::resolveParentWindow()
413 : {
414 0 : if (m_parentWindow) {
415 0 : return;
416 : }
417 :
418 0 : if (QQuickItem* parent = parentItem()) {
419 0 : if (QWindow* window = parent->window()) {
420 0 : setParentWindow(window);
421 0 : return;
422 : }
423 : }
424 :
425 0 : setParentWindow(interactive()->topWindow());
426 : }
427 :
428 0 : QScreen* WindowView::resolveScreen() const
429 : {
430 0 : QScreen* screen = m_parentWindow ? m_parentWindow->screen() : nullptr;
431 0 : if (!screen) {
432 0 : screen = QGuiApplication::primaryScreen();
433 : }
434 0 : return screen;
435 : }
436 :
437 0 : QRect WindowView::currentScreenGeometry() const
438 : {
439 0 : QScreen* screen = resolveScreen();
440 :
441 0 : QRect geometry;
442 :
443 : // See PR #10558
444 : #ifndef Q_OS_MAC
445 0 : geometry = mainWindow()->isFullScreen() ? screen->geometry() : screen->availableGeometry();
446 : #else
447 : geometry = screen->availableGeometry();
448 : #endif
449 :
450 0 : return geometry;
451 : }
452 :
453 0 : QRect WindowView::viewGeometry() const
454 : {
455 0 : return QRect(m_globalPos.toPoint(), contentItem()->size().toSize());
456 : }
457 :
458 0 : void WindowView::updateSize(const QSize& newSize)
459 : {
460 0 : if (!m_view) {
461 0 : return;
462 : }
463 :
464 0 : if (m_resizable) {
465 0 : m_view->setMinimumSize(newSize);
466 0 : m_view->setMaximumSize(QSize(16777215, 16777215));
467 0 : m_view->resize(m_view->size().expandedTo(newSize));
468 : } else {
469 0 : m_view->setMinimumSize(newSize);
470 0 : m_view->setMaximumSize(newSize);
471 0 : m_view->resize(newSize);
472 : }
473 : }
474 :
475 0 : WindowView::OpenPolicies WindowView::openPolicies() const
476 : {
477 0 : return m_openPolicies;
478 : }
479 :
480 0 : void WindowView::setOpenPolicies(WindowView::OpenPolicies openPolicies)
481 : {
482 0 : if (m_openPolicies == openPolicies) {
483 0 : return;
484 : }
485 :
486 0 : m_openPolicies = openPolicies;
487 0 : emit openPoliciesChanged(m_openPolicies);
488 : }
489 :
490 0 : WindowView::FocusPolicies WindowView::focusPolicies() const
491 : {
492 0 : return m_focusPolicies;
493 : }
494 :
495 0 : void WindowView::setFocusPolicies(const FocusPolicies& policies)
496 : {
497 0 : if (m_focusPolicies == policies) {
498 0 : return;
499 : }
500 :
501 0 : m_focusPolicies = policies;
502 0 : emit focusPoliciesChanged();
503 : }
504 :
505 0 : bool WindowView::activateParentOnClose() const
506 : {
507 0 : return m_activateParentOnClose;
508 : }
509 :
510 0 : void WindowView::setActivateParentOnClose(bool activateParentOnClose)
511 : {
512 0 : if (m_activateParentOnClose == activateParentOnClose) {
513 0 : return;
514 : }
515 :
516 0 : m_activateParentOnClose = activateParentOnClose;
517 0 : emit activateParentOnCloseChanged(m_activateParentOnClose);
518 : }
519 :
520 0 : muse::ui::INavigationControl* WindowView::navigationParentControl() const
521 : {
522 0 : return m_navigationParentControl;
523 : }
524 :
525 0 : void WindowView::setNavigationParentControl(ui::INavigationControl* navigationParentControl)
526 : {
527 0 : if (m_navigationParentControl == navigationParentControl) {
528 0 : return;
529 : }
530 :
531 0 : m_navigationParentControl = navigationParentControl;
532 0 : emit navigationParentControlChanged(m_navigationParentControl);
533 : }
534 :
535 0 : void WindowView::resolveNavigationParentControl()
536 : {
537 0 : ui::INavigationControl* ctrl = navigationController()->activeControl();
538 0 : setNavigationParentControl(ctrl);
539 :
540 : //! NOTE At the moment we have only qml navigation controls
541 0 : QObject* qmlCtrl = dynamic_cast<QObject*>(ctrl);
542 :
543 0 : if (qmlCtrl) {
544 0 : connect(qmlCtrl, &QObject::destroyed, this, [this]() {
545 0 : setNavigationParentControl(nullptr);
546 0 : });
547 : }
548 0 : }
549 :
550 0 : void WindowView::activateNavigationParentControl()
551 : {
552 0 : if (m_activateParentOnClose && m_navigationParentControl) {
553 0 : m_navigationParentControl->requestActive();
554 : }
555 0 : }
556 :
557 0 : bool WindowView::isContentReady() const
558 : {
559 0 : return m_isContentReady;
560 : }
561 :
562 0 : void WindowView::setIsContentReady(bool ready)
563 : {
564 0 : if (m_isContentReady == ready) {
565 0 : return;
566 : }
567 :
568 0 : m_isContentReady = ready;
569 0 : emit isContentReadyChanged();
570 : }
|