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) 2026 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 "guiapplication.h"
24 :
25 : #include <algorithm>
26 :
27 : #include <QTimer>
28 : #include <QQuickWindow>
29 : #include <QQmlApplicationEngine>
30 : #include <QQmlContext>
31 : #include <QQmlComponent>
32 :
33 : #include "ui/graphicsapiprovider.h"
34 : #include "ui/iuiengine.h"
35 :
36 : #include "global/globalmodule.h"
37 :
38 : #include "muse_framework_config.h"
39 :
40 : using namespace muse;
41 : using namespace muse::ui;
42 :
43 : static constexpr int DELAYED_INIT_INTERVAL_MS = 5000;
44 :
45 0 : GuiApplication::GuiApplication(const std::shared_ptr<CmdOptions>& options)
46 0 : : BaseApplication(options)
47 : {
48 0 : }
49 :
50 0 : void GuiApplication::doSetup(const std::shared_ptr<CmdOptions>& options)
51 : {
52 0 : BaseApplication::doSetup(options);
53 :
54 : // ====================================================
55 : // Setup modules: onDelayedInit
56 : // ====================================================
57 0 : m_delayedInitTimer.setSingleShot(true);
58 0 : m_delayedInitTimer.setInterval(DELAYED_INIT_INTERVAL_MS);
59 0 : QObject::connect(&m_delayedInitTimer, &QTimer::timeout, [this]() {
60 0 : m_globalModule->onDelayedInit();
61 0 : for (modularity::IModuleSetup* m : m_modules) {
62 0 : m->onDelayedInit();
63 : }
64 0 : });
65 0 : m_delayedInitTimer.start();
66 :
67 : // ====================================================
68 : // Setup Graphics Api check
69 : // ====================================================
70 : //! Needs to be set because we use transparent windows for PopupView.
71 : //! Needs to be called before any QQuickWindows are shown.
72 0 : QQuickWindow::setDefaultAlphaBuffer(true);
73 :
74 0 : setupGraphicsApi();
75 0 : }
76 :
77 0 : void GuiApplication::setupGraphicsApi()
78 : {
79 : //! NOTE Adjust GS Api
80 : //! We can hide this algorithm in GSApiProvider,
81 : //! but it is intentionally left here to illustrate what is happening.
82 :
83 0 : GraphicsApiProvider* gApiProvider = new GraphicsApiProvider(BaseApplication::appVersion());
84 :
85 0 : GraphicsApi required = gApiProvider->requiredGraphicsApi();
86 0 : if (required != GraphicsApi::Default) {
87 0 : LOGI() << "Setting required graphics api: " << GraphicsApiProvider::apiName(required);
88 0 : GraphicsApiProvider::setGraphicsApi(required);
89 : }
90 :
91 0 : LOGI() << "Using graphics api: " << GraphicsApiProvider::graphicsApiName();
92 0 : LOGI() << "Gui platform: " << QGuiApplication::platformName();
93 :
94 0 : if (GraphicsApiProvider::graphicsApi() == GraphicsApi::Software) {
95 0 : gApiProvider->destroy();
96 : } else {
97 0 : LOGI() << "Detecting problems with graphics api";
98 0 : gApiProvider->listen([this, gApiProvider, required](bool res) {
99 0 : if (res) {
100 0 : LOGI() << "No problems detected with graphics api";
101 0 : gApiProvider->setGraphicsApiStatus(required, GraphicsApiProvider::Status::Checked);
102 : } else {
103 0 : GraphicsApi next = gApiProvider->switchToNextGraphicsApi(required);
104 0 : LOGE() << "Detected problems with graphics api; switching from " << GraphicsApiProvider::apiName(required)
105 0 : << " to " << GraphicsApiProvider::apiName(next);
106 :
107 0 : this->restart();
108 : }
109 0 : gApiProvider->destroy();
110 0 : });
111 : }
112 0 : }
113 :
114 0 : void GuiApplication::doFinish()
115 : {
116 0 : m_delayedInitTimer.stop();
117 :
118 0 : BaseApplication::doFinish();
119 0 : }
120 :
121 0 : void GuiApplication::startupScenario(const muse::modularity::ContextPtr& ctxId)
122 : {
123 0 : TRACEFUNC;
124 :
125 0 : QMetaObject::invokeMethod(qApp, [this, ctxId]() {
126 0 : bool ok = loadMainWindow(ctxId);
127 0 : if (ok) {
128 0 : QMetaObject::invokeMethod(qApp, [this, ctxId]() {
129 0 : doStartupScenario(ctxId);
130 :
131 0 : QTimer::singleShot(DELAYED_INIT_INTERVAL_MS, qApp, [this, ctxId]() {
132 0 : contextDelayedInit(ctxId);
133 0 : });
134 0 : }, Qt::QueuedConnection);
135 : }
136 0 : }, Qt::QueuedConnection);
137 0 : }
138 :
139 0 : void GuiApplication::contextDelayedInit(const muse::modularity::ContextPtr& ctxId)
140 : {
141 : //! NOTE: The context may have been destroyed (its window closed) in the meantime
142 0 : auto it = std::find_if(m_contexts.begin(), m_contexts.end(), [&ctxId](const ContextData& c) {
143 0 : return c.ctxId->id == ctxId->id;
144 : });
145 :
146 0 : if (it == m_contexts.end()) {
147 0 : return;
148 : }
149 :
150 0 : for (modularity::IContextSetup* s : it->setups) {
151 0 : s->onDelayedInit();
152 : }
153 : }
154 :
155 0 : bool GuiApplication::loadMainWindow(const muse::modularity::ContextPtr& ctxId)
156 : {
157 0 : TRACEFUNC;
158 :
159 0 : IF_ASSERT_FAILED(ctxId) {
160 0 : return false;
161 : }
162 :
163 : #if defined(Q_OS_MAC)
164 : QString platform = "mac";
165 : #elif defined(Q_OS_WIN)
166 : QString platform = "win";
167 : #else
168 0 : QString platform = "linux";
169 : #endif
170 :
171 0 : auto uiengine = muse::modularity::ioc(ctxId)->resolve<muse::ui::IUiEngine>("app");
172 0 : IF_ASSERT_FAILED(uiengine) {
173 0 : return false;
174 : }
175 :
176 0 : QQmlApplicationEngine* engine = uiengine->qmlAppEngine();
177 0 : IF_ASSERT_FAILED(engine) {
178 0 : return false;
179 : }
180 :
181 0 : QString path = mainWindowQmlPath(platform);
182 0 : QQmlComponent component = QQmlComponent(engine, path);
183 0 : if (!component.isReady()) {
184 0 : LOGE() << "Failed to load main qml file, err: " << component.errorString();
185 0 : return false;
186 : }
187 :
188 0 : QQmlContext* qmlCtx = new QQmlContext(engine);
189 0 : qmlCtx->setObjectName(QString("QQmlContext: %1").arg(ctxId->id));
190 0 : QmlIoCContext* iocCtx = new QmlIoCContext(qmlCtx);
191 0 : iocCtx->ctx = ctxId;
192 0 : qmlCtx->setContextProperty("ioc_context", QVariant::fromValue(iocCtx));
193 :
194 0 : QObject* obj = component.create(qmlCtx);
195 0 : if (!obj) {
196 0 : LOGE() << "failed Qml load\n";
197 0 : QCoreApplication::exit(-1);
198 0 : return false;
199 : }
200 :
201 : // The main window must be shown at this point so KDDockWidgets can read its size correctly
202 : // and scale all sizes properly. https://github.com/musescore/MuseScore/issues/21148
203 0 : QQuickWindow* window = dynamic_cast<QQuickWindow*>(obj);
204 0 : window->setVisible(true);
205 :
206 0 : m_windows[ctxId->id] = window;
207 :
208 0 : return true;
209 0 : }
210 :
211 0 : void GuiApplication::doDestroyContext(const ContextData& data)
212 : {
213 0 : auto wit = m_windows.find(data.ctxId->id);
214 0 : if (wit != m_windows.end()) {
215 0 : QQuickWindow* window = wit->second;
216 0 : window->setVisible(false);
217 0 : m_windows.erase(wit);
218 0 : delete window;
219 : }
220 :
221 : // Engine quit
222 0 : muse::modularity::ioc(data.ctxId)->resolve<muse::ui::IUiEngine>("app")->quit();
223 :
224 0 : BaseApplication::doDestroyContext(data);
225 0 : }
|