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 "interactive.h"
24 :
25 : #include <QColorDialog>
26 : #include <QDialog>
27 : #include <QFileDialog>
28 : #include <QGuiApplication>
29 : #include <QMetaProperty>
30 : #include <QMetaType>
31 : #include <QQmlEngine>
32 : #include <QUrl>
33 : #include <QWidget>
34 : #include <QWindow>
35 :
36 : #include "async/async.h"
37 : #include "io/path.h"
38 : #include "translation.h"
39 :
40 : #include "diagnostics/diagnosticutils.h"
41 :
42 : #include "filedialogfilters.h"
43 : #include "widgetdialogadapter.h"
44 : #include "ui/view/widgetdialog.h"
45 :
46 : #include "muse_framework_config.h"
47 :
48 : #include "defer.h"
49 : #include "log.h"
50 :
51 : using namespace muse;
52 : using namespace muse::interactive;
53 : using namespace muse::async;
54 :
55 : // === QmlLaunchData ===
56 :
57 0 : QmlLaunchData::QmlLaunchData(QObject* parent)
58 0 : : QObject(parent)
59 : {
60 0 : }
61 :
62 0 : QVariant QmlLaunchData::value(const QString& key) const
63 : {
64 0 : return m_data.value(key);
65 : }
66 :
67 0 : void QmlLaunchData::setValue(const QString& key, const QVariant& val)
68 : {
69 0 : m_data[key] = val;
70 0 : }
71 :
72 0 : QVariant QmlLaunchData::data() const
73 : {
74 0 : return m_data;
75 : }
76 :
77 : // === Interactive ===
78 :
79 0 : Interactive::Interactive(const muse::modularity::ContextPtr& ctx)
80 0 : : QObject(), Contextable(ctx)
81 : {
82 0 : connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow* window) {
83 0 : raiseWindowInStack(window);
84 0 : });
85 :
86 0 : connect(qApp, &QGuiApplication::focusObjectChanged, this, [this](QObject* obj) {
87 0 : auto widget = dynamic_cast<QWidget*>(obj);
88 :
89 0 : if (widget && widget->isWindow()) {
90 0 : raiseWindowInStack(widget);
91 : }
92 0 : });
93 0 : }
94 :
95 0 : void Interactive::raiseWindowInStack(QObject* newActiveWindow)
96 : {
97 0 : if (!newActiveWindow || m_stack.isEmpty() || m_stack.top().window == newActiveWindow) {
98 0 : return;
99 : }
100 :
101 0 : for (int i = 0; i < m_stack.size(); ++i) {
102 0 : bool found = m_stack[i].window == newActiveWindow;
103 0 : if (m_stack[i].window && m_stack[i].window->isWidgetType()) {
104 0 : found = newActiveWindow->objectName() == (m_stack[i].window->objectName() + "Window");
105 : }
106 :
107 0 : if (found) {
108 0 : ObjectInfo info = m_stack.takeAt(i);
109 0 : m_stack.push(info);
110 0 : notifyAboutCurrentUriChanged();
111 0 : return;
112 0 : }
113 : }
114 : }
115 :
116 0 : IInteractive::ButtonData Interactive::buttonData(Button b) const
117 : {
118 0 : constexpr bool accent = true;
119 :
120 0 : switch (b) {
121 0 : case IInteractive::Button::NoButton: return ButtonData(int(b), "");
122 0 : case IInteractive::Button::Ok: return ButtonData(int(b), muse::trc("global", "OK"), accent);
123 0 : case IInteractive::Button::Save: return ButtonData(int(b), muse::trc("global", "Save"), accent);
124 0 : case IInteractive::Button::SaveAll: return ButtonData(int(b), muse::trc("global", "Save all"));
125 0 : case IInteractive::Button::DontSave: return ButtonData(int(b), muse::trc("global", "Don’t save"));
126 0 : case IInteractive::Button::Open: return ButtonData(int(b), muse::trc("global", "Open"));
127 0 : case IInteractive::Button::Yes: return ButtonData(int(b), muse::trc("global", "Yes"), accent);
128 0 : case IInteractive::Button::YesToAll: return ButtonData(int(b), muse::trc("global", "Yes to all"), accent);
129 0 : case IInteractive::Button::No: return ButtonData(int(b), muse::trc("global", "No"));
130 0 : case IInteractive::Button::NoToAll: return ButtonData(int(b), muse::trc("global", "No to all"));
131 0 : case IInteractive::Button::Abort: return ButtonData(int(b), muse::trc("global", "Abort"));
132 0 : case IInteractive::Button::Retry: return ButtonData(int(b), muse::trc("global", "Retry"));
133 0 : case IInteractive::Button::Ignore: return ButtonData(int(b), muse::trc("global", "Ignore"));
134 0 : case IInteractive::Button::Close: return ButtonData(int(b), muse::trc("global", "Close"));
135 0 : case IInteractive::Button::Cancel: return ButtonData(int(b), muse::trc("global", "Cancel"));
136 0 : case IInteractive::Button::Discard: return ButtonData(int(b), muse::trc("global", "Discard"));
137 0 : case IInteractive::Button::Help: return ButtonData(int(b), muse::trc("global", "Help"));
138 0 : case IInteractive::Button::Apply: return ButtonData(int(b), muse::trc("global", "Apply"));
139 0 : case IInteractive::Button::Reset: return ButtonData(int(b), muse::trc("global", "Reset"));
140 0 : case IInteractive::Button::Continue: return ButtonData(int(b), muse::trc("global", "Continue"));
141 0 : case IInteractive::Button::Next:
142 : case IInteractive::Button::Back:
143 : case IInteractive::Button::Select:
144 : case IInteractive::Button::Clear:
145 : case IInteractive::Button::Done:
146 : case IInteractive::Button::RestoreDefaults:
147 0 : case IInteractive::Button::CustomButton: break;
148 : }
149 :
150 0 : return ButtonData(int(b), "");
151 : }
152 :
153 0 : UriQuery Interactive::makeQuery(const std::string& type, const std::string& contentTitle,
154 : const Text& text,
155 : const ButtonDatas& buttons, int defBtn,
156 : const Options& options, const std::string& dialogTitle) const
157 : {
158 0 : auto format = [](IInteractive::TextFormat f) {
159 0 : switch (f) {
160 0 : case IInteractive::TextFormat::Auto: return Qt::AutoText;
161 0 : case IInteractive::TextFormat::PlainText: return Qt::PlainText;
162 0 : case IInteractive::TextFormat::RichText: return Qt::RichText;
163 : }
164 0 : return Qt::PlainText;
165 : };
166 :
167 0 : UriQuery q("muse://interactive/standard");
168 0 : q.set("type", type)
169 0 : .set("contentTitle", contentTitle)
170 0 : .set("text", text.text)
171 0 : .set("textFormat", (int)format(text.format))
172 0 : .set("detailedText", text.detailedText)
173 0 : .set("defaultButtonId", defBtn)
174 0 : .set("withIcon", options.testFlag(IInteractive::Option::WithIcon))
175 0 : .set("withDontShowAgainCheckBox", options.testFlag(IInteractive::Option::WithDontShowAgainCheckBox))
176 0 : .set("dialogTitle", dialogTitle);
177 :
178 0 : ValList buttonsList;
179 0 : ValList customButtonsList;
180 0 : if (buttons.empty()) {
181 0 : buttonsList.push_back(Val(static_cast<int>(IInteractive::Button::Ok)));
182 : } else {
183 0 : for (const IInteractive::ButtonData& buttonData: buttons) {
184 0 : ValMap customButton;
185 0 : customButton["text"] = Val(buttonData.text);
186 0 : customButton["buttonId"] = Val(buttonData.btn);
187 0 : customButton["role"] = Val(static_cast<int>(buttonData.role));
188 0 : customButton["isAccent"] = Val(buttonData.accent);
189 0 : customButton["isLeftSide"] = Val(buttonData.leftSide);
190 0 : customButton["icon"] = Val(buttonData.icon);
191 0 : customButtonsList.push_back(Val(customButton));
192 0 : }
193 : }
194 :
195 0 : q.set("buttons", Val(buttonsList))
196 0 : .set("customButtons", Val(customButtonsList));
197 :
198 0 : return q;
199 0 : }
200 :
201 0 : IInteractive::Result Interactive::makeResult(const Val& val) const
202 : {
203 0 : QVariantMap resultMap = val.toQVariant().toMap();
204 0 : int btn = resultMap["buttonId"].toInt();
205 0 : bool showAgain = resultMap["showAgain"].toBool();
206 0 : return IInteractive::Result(btn, showAgain);
207 0 : }
208 :
209 0 : async::Promise<IInteractive::Result> Interactive::openStandardAsync(const std::string& type, const std::string& contentTitle,
210 : const Text& text,
211 : const ButtonDatas& buttons, int defBtn,
212 : const Options& options, const std::string& dialogTitle)
213 : {
214 0 : UriQuery q = makeQuery(type, contentTitle, text, buttons, defBtn, options, dialogTitle);
215 :
216 0 : return openAsync(q).then<IInteractive::Result>(
217 : this,
218 0 : [this](const Val& val, auto resolve, auto /*reject*/) {
219 0 : return resolve(makeResult(val));
220 : },
221 0 : [](int code, const std::string& msg, auto resolve, auto reject) {
222 : //! NOTE To simplify writing the handlers
223 0 : (void)resolve(IInteractive::Result((int)IInteractive::Button::Cancel));
224 0 : return reject(code, msg);
225 0 : });
226 0 : }
227 :
228 0 : IInteractive::Result Interactive::openStandardSync(const std::string& type, const std::string& contentTitle,
229 : const Text& text,
230 : const ButtonDatas& buttons, int defBtn,
231 : const Options& options, const std::string& dialogTitle)
232 : {
233 0 : UriQuery q = makeQuery(type, contentTitle, text, buttons, defBtn, options, dialogTitle);
234 0 : RetVal<Val> rv = openSync(q);
235 :
236 0 : if (rv.ret) {
237 0 : return makeResult(rv.val);
238 : } else {
239 0 : return IInteractive::Result((int)IInteractive::Button::Cancel);
240 : }
241 0 : }
242 :
243 0 : IInteractive::Result Interactive::questionSync(const std::string& contentTitle, const Text& text,
244 : const ButtonDatas& buttons, int defBtn,
245 : const Options& options, const std::string& dialogTitle)
246 : {
247 0 : return openStandardSync("QUESTION", contentTitle, text, buttons, defBtn, options, dialogTitle);
248 : }
249 :
250 0 : async::Promise<IInteractive::Result> Interactive::question(const std::string& contentTitle, const Text& text,
251 : const ButtonDatas& buttons, int defBtn,
252 : const Options& options, const std::string& dialogTitle)
253 : {
254 0 : return openStandardAsync("QUESTION", contentTitle, text, buttons, defBtn, options, dialogTitle);
255 : }
256 :
257 0 : IInteractive::Result Interactive::infoSync(const std::string& contentTitle, const Text& text,
258 : const ButtonDatas& buttons, int defBtn,
259 : const Options& options, const std::string& dialogTitle)
260 : {
261 0 : return openStandardSync("INFO", contentTitle, text, buttons, defBtn, options, dialogTitle);
262 : }
263 :
264 0 : async::Promise<IInteractive::Result> Interactive::info(const std::string& contentTitle, const Text& text,
265 : const ButtonDatas& buttons, int defBtn,
266 : const Options& options, const std::string& dialogTitle)
267 : {
268 0 : return openStandardAsync("INFO", contentTitle, text, buttons, defBtn, options, dialogTitle);
269 : }
270 :
271 0 : IInteractive::Result Interactive::warningSync(const std::string& contentTitle, const Text& text,
272 : const ButtonDatas& buttons, int defBtn,
273 : const Options& options, const std::string& dialogTitle)
274 : {
275 0 : return openStandardSync("WARNING", contentTitle, text, buttons, defBtn, options, dialogTitle);
276 : }
277 :
278 0 : async::Promise<IInteractive::Result> Interactive::warning(const std::string& contentTitle, const Text& text,
279 : const ButtonDatas& buttons, int defBtn,
280 : const Options& options, const std::string& dialogTitle)
281 : {
282 0 : return openStandardAsync("WARNING", contentTitle, text, buttons, defBtn, options, dialogTitle);
283 : }
284 :
285 0 : IInteractive::Result Interactive::errorSync(const std::string& contentTitle, const Text& text,
286 : const ButtonDatas& buttons, int defBtn,
287 : const Options& options, const std::string& dialogTitle)
288 : {
289 0 : return openStandardSync("ERROR", contentTitle, text, buttons, defBtn, options, dialogTitle);
290 : }
291 :
292 0 : async::Promise<IInteractive::Result> Interactive::error(const std::string& contentTitle, const Text& text,
293 : const ButtonDatas& buttons, int defBtn,
294 : const Options& options, const std::string& dialogTitle)
295 : {
296 0 : return openStandardAsync("ERROR", contentTitle, text, buttons, defBtn, options, dialogTitle);
297 : }
298 :
299 0 : void Interactive::showProgress(const std::string& title, Progress progress)
300 : {
301 0 : Uri uri("muse://interactive/progress");
302 0 : QVariantMap params;
303 0 : params["title"] = QString::fromStdString(title);
304 0 : params["progress"] = QVariant::fromValue(progress);
305 :
306 0 : openAsync(uri, params);
307 0 : }
308 :
309 : #ifdef Q_OS_LINUX
310 : // see QQuickPlatformFileDialog::FileMode
311 : enum class FileDialogMode {
312 : OpenFile = 0,
313 : OpenFiles = 1,
314 : SaveFile = 2
315 : };
316 :
317 0 : static UriQuery makeSelectFileQuery(FileDialogMode mode, const std::string& title, const io::path_t& current,
318 : const std::vector<std::string>& filter, const int options = 0)
319 : {
320 0 : UriQuery q("muse://interactive/selectfile");
321 0 : q.set("title", title);
322 :
323 0 : const bool isOpenMode = mode == FileDialogMode::OpenFile || mode == FileDialogMode::OpenFiles;
324 0 : const bool hidesFilterDetails = options & QFileDialog::HideNameFilterDetails;
325 0 : const bool matchCaseInsensitively = isOpenMode && hidesFilterDetails;
326 :
327 0 : ValList filterList;
328 0 : for (const std::string& f : filter) {
329 0 : filterList.push_back(Val(matchCaseInsensitively ? caseInsensitiveNameFilter(f) : f));
330 : }
331 :
332 0 : q.set("nameFilters", filterList);
333 0 : q.set("fileMode", static_cast<int>(mode));
334 0 : q.set("options", options);
335 0 : if (mode == FileDialogMode::OpenFile || mode == FileDialogMode::OpenFiles) {
336 0 : q.set("selectExisting", true);
337 0 : q.set("folder", QUrl::fromLocalFile(current.toQString()).toString().toStdString());
338 0 : } else if (mode == FileDialogMode::SaveFile) {
339 0 : q.set("currentFile", QUrl::fromLocalFile(current.toQString()).toString().toStdString());
340 : }
341 :
342 0 : return q;
343 0 : }
344 :
345 : #endif
346 :
347 : #ifndef Q_OS_LINUX
348 : static QString filterToString(const std::vector<std::string>& filter)
349 : {
350 : QStringList result;
351 : for (const std::string& nameFilter : filter) {
352 : result << QString::fromStdString(nameFilter);
353 : }
354 :
355 : return result.join(";;");
356 : }
357 :
358 : #endif
359 :
360 0 : async::Promise<io::path_t> Interactive::selectOpeningFile(const std::string& title, const io::path_t& dir,
361 : const std::vector<std::string>& filter)
362 : {
363 : #ifndef Q_OS_LINUX
364 : return async::make_promise<io::path_t>([title, dir, filter](auto resolve, auto reject) {
365 : QFileDialog* dlg = new QFileDialog(nullptr, QString::fromStdString(title), dir.toQString(), filterToString(filter));
366 :
367 : dlg->setFileMode(QFileDialog::ExistingFile);
368 :
369 : QObject::connect(dlg, &QFileDialog::finished, [dlg, resolve, reject](int result) {
370 : DEFER {
371 : //! Must be called AFTER resolve/reject, as they may process posted events
372 : dlg->deleteLater();
373 : };
374 :
375 : QStringList files = dlg->selectedFiles();
376 :
377 : if (result != QDialog::Accepted || files.isEmpty()) {
378 : Ret ret = muse::make_ret(Ret::Code::Cancel);
379 : (void)reject(ret.code(), ret.text());
380 : return;
381 : }
382 :
383 : QString file = files.first();
384 : (void)resolve(file);
385 : });
386 :
387 : dlg->open();
388 :
389 : return async::Promise<io::path_t>::Result::unchecked();
390 : }, async::PromiseType::AsyncByBody);
391 :
392 : #else
393 :
394 0 : UriQuery q = makeSelectFileQuery(FileDialogMode::OpenFile, title, dir, filter);
395 :
396 0 : async::Promise<Val> promise = openAsync(q);
397 :
398 0 : return async::make_promise<io::path_t>([promise, this](auto resolve, auto reject) {
399 0 : async::Promise<Val> mut = promise;
400 0 : mut.onResolve(this, [resolve](const Val& val) {
401 0 : (void)resolve(QUrl::fromUserInput(val.toQString()).toLocalFile());
402 0 : }).onReject(this, [resolve, reject](int code, const std::string& err) {
403 0 : (void)reject(code, err);
404 : });
405 0 : return async::Promise<io::path_t>::Result::unchecked();
406 0 : }, async::PromiseType::AsyncByBody);
407 : #endif
408 0 : }
409 :
410 0 : io::path_t Interactive::selectOpeningFileSync(const std::string& title, const io::path_t& dir, const std::vector<std::string>& filter,
411 : const int options)
412 : {
413 : #ifndef Q_OS_LINUX
414 : const QFileDialog::Options qoptions = QFileDialog::Options::fromInt(options);
415 : QString result = QFileDialog::getOpenFileName(nullptr, QString::fromStdString(title), dir.toQString(), filterToString(
416 : filter), nullptr, qoptions);
417 : return result;
418 : #else
419 0 : UriQuery q = makeSelectFileQuery(FileDialogMode::OpenFile, title, dir, filter, options);
420 :
421 0 : RetVal<Val> rv = openSync(q);
422 0 : if (!rv.ret) {
423 0 : return io::path_t();
424 : }
425 :
426 0 : return QUrl::fromUserInput(rv.val.toQString()).toLocalFile();
427 : #endif
428 0 : }
429 :
430 0 : io::paths_t Interactive::selectOpeningFilesSync(const std::string& title, const io::path_t& dir, const std::vector<std::string>& filter,
431 : const int options)
432 : {
433 : #ifndef Q_OS_LINUX
434 : const QFileDialog::Options qoptions = QFileDialog::Options::fromInt(options);
435 : const QStringList result = QFileDialog::getOpenFileNames(nullptr, QString::fromStdString(title), dir.toQString(), filterToString(
436 : filter), nullptr, qoptions);
437 :
438 : io::paths_t paths;
439 : paths.reserve(result.size());
440 : for (const QString& path : result) {
441 : paths.emplace_back(path);
442 : }
443 :
444 : return paths;
445 : #else
446 0 : UriQuery q = makeSelectFileQuery(FileDialogMode::OpenFiles, title, dir, filter, options);
447 :
448 0 : RetVal<Val> rv = openSync(q);
449 0 : if (!rv.ret) {
450 0 : return io::paths_t();
451 : }
452 :
453 0 : ValList urls = rv.val.toList();
454 :
455 0 : io::paths_t paths;
456 0 : paths.reserve(urls.size());
457 0 : for (const Val& url : urls) {
458 0 : paths.emplace_back(QUrl::fromUserInput(url.toQString()).toLocalFile());
459 : }
460 :
461 0 : return paths;
462 : #endif
463 0 : }
464 :
465 0 : async::Promise<io::path_t> Interactive::selectSavingFile(const std::string& title, const io::path_t& dir,
466 : const std::vector<std::string>& filter, bool confirmOverwrite)
467 : {
468 : #ifndef Q_OS_LINUX
469 : return async::make_promise<io::path_t>([title, dir, filter, confirmOverwrite](auto resolve, auto reject) {
470 : QFileDialog* dlg = new QFileDialog(nullptr, QString::fromStdString(title), dir.toQString(), filterToString(filter));
471 :
472 : dlg->setAcceptMode(QFileDialog::AcceptSave);
473 : dlg->setFileMode(QFileDialog::AnyFile);
474 : dlg->setOption(QFileDialog::DontConfirmOverwrite, !confirmOverwrite);
475 :
476 : QObject::connect(dlg, &QFileDialog::finished, [dlg, resolve, reject](int result) {
477 : DEFER {
478 : //! Must be called AFTER resolve/reject, as they may process posted events
479 : dlg->deleteLater();
480 : };
481 :
482 : QStringList files = dlg->selectedFiles();
483 :
484 : if (result != QDialog::Accepted || files.isEmpty()) {
485 : Ret ret = muse::make_ret(Ret::Code::Cancel);
486 : (void)reject(ret.code(), ret.text());
487 : return;
488 : }
489 :
490 : QString file = files.first();
491 : (void)resolve(file);
492 : });
493 :
494 : dlg->open();
495 :
496 : return async::Promise<io::path_t>::Result::unchecked();
497 : });
498 :
499 : #else
500 :
501 : UriQuery q
502 0 : = makeSelectFileQuery(FileDialogMode::SaveFile, title, dir, filter, !confirmOverwrite ? QFileDialog::DontConfirmOverwrite : 0);
503 :
504 0 : async::Promise<Val> promise = openAsync(q);
505 :
506 0 : return async::make_promise<io::path_t>([promise, this](auto resolve, auto reject) {
507 0 : async::Promise<Val> mut = promise;
508 0 : mut.onResolve(this, [resolve](const Val& val) {
509 0 : (void)resolve(QUrl::fromUserInput(val.toQString()).toLocalFile());
510 0 : }).onReject(this, [resolve, reject](int code, const std::string& err) {
511 0 : (void)reject(code, err);
512 : });
513 0 : return async::Promise<io::path_t>::Result::unchecked();
514 0 : }, async::PromiseType::AsyncByBody);
515 : #endif
516 0 : }
517 :
518 0 : io::path_t Interactive::selectSavingFileSync(const std::string& title, const io::path_t& dir, const std::vector<std::string>& filter,
519 : bool confirmOverwrite)
520 : {
521 : #ifndef Q_OS_LINUX
522 : QFileDialog::Options options;
523 : options.setFlag(QFileDialog::DontConfirmOverwrite, !confirmOverwrite);
524 : QString result = QFileDialog::getSaveFileName(nullptr, QString::fromStdString(title), dir.toQString(), filterToString(
525 : filter), nullptr, options);
526 : return result;
527 : #else
528 :
529 : UriQuery q
530 0 : = makeSelectFileQuery(FileDialogMode::SaveFile, title, dir, filter, !confirmOverwrite ? QFileDialog::DontConfirmOverwrite : 0);
531 :
532 0 : RetVal<Val> rv = openSync(q);
533 0 : if (!rv.ret) {
534 0 : return io::path_t();
535 : }
536 :
537 0 : return QUrl::fromUserInput(rv.val.toQString()).toLocalFile();
538 : #endif
539 0 : }
540 :
541 0 : io::path_t Interactive::selectDirectory(const std::string& title, const io::path_t& dir)
542 : {
543 : #ifndef Q_OS_LINUX
544 : QString result = QFileDialog::getExistingDirectory(nullptr, QString::fromStdString(title), dir.toQString());
545 : return result;
546 : #else
547 :
548 0 : UriQuery q("muse://interactive/selectdir");
549 0 : q.set("title", title);
550 0 : q.set("folder", QUrl::fromLocalFile(dir.toQString()).toLocalFile().toStdString());
551 :
552 0 : RetVal<Val> rv = openSync(q);
553 0 : if (!rv.ret) {
554 0 : return io::path_t();
555 : }
556 :
557 0 : return QUrl::fromUserInput(rv.val.toQString()).toLocalFile();
558 : #endif
559 0 : }
560 :
561 0 : io::paths_t Interactive::selectMultipleDirectories(const std::string& title, const io::path_t& dir, const io::paths_t& selectedDirectories)
562 : {
563 0 : UriQuery q("muse://interactive/selectmultipledirectories");
564 0 : q.set("title", title)
565 0 : .set("selectedDirectories", io::pathsToString(selectedDirectories))
566 0 : .set("startDir", dir.toStdString());
567 :
568 0 : RetVal<Val> result = openSync(q);
569 0 : if (!result.ret) {
570 0 : return selectedDirectories;
571 : }
572 :
573 0 : return io::pathsFromString(result.val.toString());
574 0 : }
575 :
576 0 : static std::vector<QColor> getCustomColors()
577 : {
578 0 : const int customColorCount = QColorDialog::customCount();
579 0 : std::vector<QColor> customColors;
580 0 : customColors.reserve(customColorCount);
581 0 : for (int i = 0; i < customColorCount; ++i) {
582 0 : customColors.push_back(QColorDialog::customColor(i));
583 : }
584 :
585 0 : return customColors;
586 0 : }
587 :
588 0 : static void setCustomColors(const std::vector<QColor>& customColors)
589 : {
590 0 : const int customColorCount = std::min(QColorDialog::customCount(), static_cast<int>(customColors.size()));
591 0 : for (int i = 0; i < customColorCount; ++i) {
592 0 : QColorDialog::setCustomColor(i, customColors[i]);
593 : }
594 0 : }
595 :
596 0 : async::Promise<Color> Interactive::selectColor(const Color& color, const std::string& title, bool allowAlpha)
597 : {
598 0 : if (m_isSelectColorOpened) {
599 0 : LOGW() << "already opened";
600 0 : return async::make_promise<Color>([](auto, auto reject) {
601 0 : Ret ret = muse::make_ret(Ret::Code::UnknownError);
602 0 : return reject(ret.code(), ret.text());
603 0 : });
604 : }
605 :
606 0 : m_isSelectColorOpened = true;
607 :
608 0 : setCustomColors(uiConfiguration()->colorDialogCustomColors());
609 :
610 0 : return async::make_promise<Color>([this, color, title, allowAlpha](auto resolve, auto reject) {
611 : //! FIX https://github.com/musescore/MuseScore/issues/23208
612 : #ifdef MUSE_MODULE_SHORTCUTS_V2
613 0 : shortcutsController()->setActive(false);
614 : #else
615 : shortcutsRegister()->setActive(false);
616 : #endif
617 :
618 0 : QColorDialog* dlg = new QColorDialog();
619 0 : if (!title.empty()) {
620 0 : dlg->setWindowTitle(QString::fromStdString(title));
621 : }
622 :
623 0 : QColor currentColor = color.toQColor();
624 :
625 : // If the color is fully transparent, set alpha to opaque, to avoid "Hm, nothing happened" user confusion
626 0 : if (currentColor.alpha() == 0) {
627 0 : currentColor.setAlpha(255);
628 : }
629 :
630 0 : dlg->setCurrentColor(currentColor);
631 0 : dlg->setOption(QColorDialog::ShowAlphaChannel, allowAlpha);
632 :
633 0 : QObject::connect(dlg, &QColorDialog::finished, [this, dlg, resolve, reject](int result) {
634 0 : DEFER {
635 : //! Must be called AFTER resolve/reject, as they may process posted events
636 0 : dlg->deleteLater();
637 : };
638 :
639 0 : uiConfiguration()->setColorDialogCustomColors(getCustomColors());
640 :
641 0 : m_isSelectColorOpened = false;
642 : #ifdef MUSE_MODULE_SHORTCUTS_V2
643 0 : shortcutsController()->setActive(true);
644 : #else
645 : shortcutsRegister()->setActive(true);
646 : #endif
647 :
648 0 : if (result != QDialog::Accepted) {
649 0 : Ret ret = muse::make_ret(Ret::Code::Cancel);
650 0 : (void)reject(ret.code(), ret.text());
651 0 : return;
652 0 : }
653 :
654 0 : QColor selectedColor = dlg->selectedColor();
655 0 : (void)resolve(Color::fromQColor(selectedColor));
656 0 : });
657 :
658 0 : dlg->open();
659 :
660 0 : return async::Promise<Color>::dummy_result();
661 0 : }, async::PromiseType::AsyncByBody);
662 : }
663 :
664 0 : bool Interactive::isSelectColorOpened() const
665 : {
666 0 : return m_isSelectColorOpened;
667 : }
668 :
669 0 : RetVal<Val> Interactive::openSync(const UriQuery& q)
670 : {
671 : #ifndef MUSE_MODULE_INTERACTIVE_SYNC_SUPPORTED
672 : NOT_SUPPORTED;
673 : std::abort();
674 : {
675 : RetVal<Val> rv;
676 : rv.ret = muse::make_ret(Ret::Code::NotSupported);
677 : return rv;
678 : }
679 : #endif
680 :
681 0 : RetVal<Val> rv;
682 0 : QEventLoop loop;
683 0 : bool finished = false;
684 0 : Promise<Val>::Resolve resolve;
685 0 : Promise<Val>::Reject reject;
686 0 : Promise<Val> promise = async::make_promise<Val>([&resolve, &reject](auto res, auto rej) {
687 0 : resolve = res;
688 0 : reject = rej;
689 0 : return Promise<Val>::Result::unchecked();
690 0 : }, PromiseType::AsyncByBody);
691 :
692 0 : promise.onResolve(this, [&rv, &loop, &finished](const Val& val) {
693 0 : rv = RetVal<Val>::make_ok(val);
694 0 : finished = true;
695 0 : loop.quit();
696 0 : });
697 :
698 0 : promise.onReject(this, [&rv, &loop, &finished](int code, const std::string& err) {
699 0 : LOGE() << code << " " << err;
700 0 : rv.ret = make_ret(code, err);
701 0 : finished = true;
702 0 : loop.quit();
703 0 : });
704 :
705 0 : auto func = openFunc(q);
706 0 : func(resolve, reject);
707 :
708 0 : ContainerMeta openMeta = uriRegister()->meta(q.uri());
709 0 : if (openMeta.type == ContainerMeta::PrimaryPage) {
710 0 : LOGW() << "Primary pages should not open in synchronous mode, please fix this.";
711 0 : return rv;
712 : }
713 :
714 : //! NOTE: The dialog may have finished synchronously while opening (e.g. it
715 : //! closed itself on load); quit() before exec() is lost, so don't enter the loop.
716 0 : if (finished) {
717 0 : return rv;
718 : }
719 :
720 0 : loop.exec();
721 :
722 0 : return rv;
723 0 : }
724 :
725 0 : async::Promise<Val> Interactive::open(const UriQuery& uri)
726 : {
727 0 : return openAsync(uri);
728 : }
729 :
730 0 : Promise<Val> Interactive::openAsync(const UriQuery& q)
731 : {
732 0 : return async::make_promise<Val>(openFunc(q), PromiseType::AsyncByBody);
733 : }
734 :
735 0 : async::Promise<Val> Interactive::openAsync(const Uri& uri, const QVariantMap& params)
736 : {
737 0 : return async::make_promise<Val>(openFunc(UriQuery(uri), params), PromiseType::AsyncByBody);
738 : }
739 :
740 0 : Promise<Val>::BodyResolveReject Interactive::openFunc(const UriQuery& q)
741 : {
742 0 : QVariantMap params;
743 0 : const UriQuery::Params& p = q.params();
744 0 : for (auto it = p.cbegin(); it != p.cend(); ++it) {
745 0 : params[QString::fromStdString(it->first)] = it->second.toQVariant();
746 : }
747 :
748 0 : return openFunc(q, params);
749 0 : }
750 :
751 0 : Promise<Val>::BodyResolveReject Interactive::openFunc(const UriQuery& q, const QVariantMap& params)
752 : {
753 0 : auto func = [this, q, params](Promise<Val>::Resolve resolve, Promise<Val>::Reject reject) {
754 0 : IF_ASSERT_FAILED(!m_openingObject.objectId.isValid()) {
755 0 : LOGE() << "The opening of the previous object has not been completed"
756 0 : << ", objectId: " << m_openingObject.objectId.toString()
757 0 : << ", query: " << m_openingObject.query.toString();
758 : }
759 :
760 0 : m_openingObject = { q, resolve, reject, QVariant(), nullptr };
761 :
762 0 : RetVal<OpenData> openedRet;
763 :
764 0 : notifyAboutCurrentUriWillBeChanged();
765 :
766 : //! NOTE Currently, extensions do not replace the default functionality
767 : //! But in the future, we may allow extensions to replace the current functionality
768 : //! (first check for the presence of an extension with this uri,
769 : //! and if it is found, then open it)
770 :
771 0 : ContainerMeta openMeta = uriRegister()->meta(q.uri());
772 0 : switch (openMeta.type) {
773 0 : case ContainerMeta::QWidgetDialog:
774 0 : openedRet = openWidgetDialog(q.uri(), params);
775 0 : break;
776 0 : case ContainerMeta::PrimaryPage:
777 : case ContainerMeta::QmlDialog:
778 0 : openedRet = openQml(q.uri(), params);
779 0 : break;
780 0 : case ContainerMeta::Undefined: {
781 0 : openedRet.ret = make_ret(Ret::Code::UnknownError);
782 0 : } break;
783 : }
784 :
785 0 : if (!openedRet.ret) {
786 0 : LOGE() << "failed open err: " << openedRet.ret.toString() << ", uri: " << q.toString();
787 0 : return reject(openedRet.ret.code(), openedRet.ret.text());
788 : }
789 :
790 0 : return Promise<Val>::Result::unchecked();
791 0 : };
792 :
793 0 : return func;
794 0 : }
795 :
796 0 : RetVal<bool> Interactive::isOpened(const UriQuery& uri) const
797 : {
798 0 : for (const ObjectInfo& objectInfo: allOpenObjects()) {
799 0 : if (objectInfo.query == uri) {
800 0 : return RetVal<bool>::make_ok(true);
801 : }
802 0 : }
803 :
804 0 : return RetVal<bool>::make_ok(false);
805 : }
806 :
807 0 : RetVal<bool> Interactive::isOpened(const Uri& uri) const
808 : {
809 0 : for (const ObjectInfo& objectInfo: allOpenObjects()) {
810 0 : if (objectInfo.query.uri() == uri) {
811 0 : return RetVal<bool>::make_ok(true);
812 : }
813 0 : }
814 :
815 0 : return RetVal<bool>::make_ok(false);
816 : }
817 :
818 0 : RetVal<bool> Interactive::isOpened(const QString& objectId) const
819 : {
820 0 : for (const ObjectInfo& objectInfo: allOpenObjects()) {
821 0 : if (objectInfo.objectId == objectId) {
822 0 : return RetVal<bool>::make_ok(true);
823 : }
824 0 : }
825 :
826 0 : return RetVal<bool>::make_ok(false);
827 : }
828 :
829 0 : async::Channel<Uri> Interactive::opened() const
830 : {
831 0 : return m_opened;
832 : }
833 :
834 0 : void Interactive::raise(const UriQuery& uri)
835 : {
836 0 : for (const ObjectInfo& objectInfo: allOpenObjects()) {
837 0 : if (objectInfo.query != uri) {
838 0 : continue;
839 : }
840 :
841 0 : ContainerMeta openMeta = uriRegister()->meta(objectInfo.query.uri());
842 0 : switch (openMeta.type) {
843 0 : case ContainerMeta::QWidgetDialog: {
844 0 : if (auto window = dynamic_cast<QWidget*>(objectInfo.window)) {
845 0 : window->raise();
846 0 : window->activateWindow();
847 : }
848 0 : } break;
849 0 : case ContainerMeta::QmlDialog:
850 0 : raiseQml(objectInfo.objectId);
851 0 : break;
852 0 : case ContainerMeta::PrimaryPage:
853 : case ContainerMeta::Undefined:
854 0 : break;
855 : }
856 0 : }
857 0 : }
858 :
859 0 : Promise<Ret> Interactive::close(const UriQuery& uri)
860 : {
861 0 : std::vector<ObjectInfo> objs = collectOpenObjects([&uri](const ObjectInfo& obj) {
862 0 : return obj.query == uri;
863 0 : });
864 :
865 0 : return closeObjects(objs);
866 0 : }
867 :
868 0 : Promise<Ret> Interactive::close(const Uri& uri)
869 : {
870 0 : std::vector<ObjectInfo> objs = collectOpenObjects([&uri](const ObjectInfo& obj) {
871 0 : return obj.query.uri() == uri;
872 0 : });
873 :
874 0 : return closeObjects(objs);
875 0 : }
876 :
877 0 : Ret Interactive::closeSync(const UriQuery& uri)
878 : {
879 0 : std::vector<ObjectInfo> objs = collectOpenObjects([&uri](const ObjectInfo& obj) {
880 0 : return obj.query == uri;
881 0 : });
882 :
883 0 : return closeObjectsSync(objs);
884 0 : }
885 :
886 0 : Promise<Ret> Interactive::closeAllDialogs()
887 : {
888 0 : return closeObjects(openDialogs());
889 : }
890 :
891 0 : Ret Interactive::closeAllDialogsSync()
892 : {
893 0 : return closeObjectsSync(openDialogs());
894 : }
895 :
896 0 : std::vector<Interactive::ObjectInfo> Interactive::openDialogs() const
897 : {
898 0 : return collectOpenObjects([this](const ObjectInfo& obj) {
899 0 : if (muse::diagnostics::isDiagnosticsUri(obj.query.uri())) {
900 0 : return false;
901 : }
902 0 : ContainerMeta meta = uriRegister()->meta(obj.query.uri());
903 0 : return meta.type == ContainerMeta::QWidgetDialog || meta.type == ContainerMeta::QmlDialog;
904 0 : });
905 : }
906 :
907 0 : Promise<Ret> Interactive::closeObjects(const std::vector<ObjectInfo>& objs)
908 : {
909 0 : return async::make_promise<Ret>([this, objs](auto resolve, auto) {
910 0 : if (objs.empty()) {
911 0 : return resolve(make_ok());
912 : }
913 :
914 0 : auto count = std::make_shared<size_t>(objs.size());
915 0 : auto ret = std::make_shared<Ret>(make_ok());
916 :
917 0 : for (const ObjectInfo& obj : objs) {
918 0 : const QString objectId = obj.objectId.toString();
919 0 : bool ok = m_onClosedFuncs.try_emplace(objectId, [this, objectId, count, ret, resolve](const Ret& funcRet) {
920 0 : if (!funcRet) {
921 0 : *ret = funcRet;
922 : }
923 :
924 0 : if (--(*count) == 0) {
925 0 : (void)resolve(*ret);
926 : }
927 :
928 0 : muse::remove(m_onClosedFuncs, objectId);
929 : }).second;
930 :
931 0 : IF_ASSERT_FAILED(ok) {
932 0 : if (--(*count) == 0) {
933 0 : (void)resolve(*ret);
934 : }
935 0 : continue;
936 : }
937 :
938 0 : ContainerMeta meta = uriRegister()->meta(obj.query.uri());
939 0 : switch (meta.type) {
940 0 : case ContainerMeta::QWidgetDialog: {
941 0 : if (auto window = dynamic_cast<QWidget*>(obj.window)) {
942 0 : ok = window->close();
943 : } else {
944 0 : ok = false;
945 : }
946 0 : } break;
947 0 : case ContainerMeta::QmlDialog:
948 0 : closeQml(obj.objectId);
949 0 : break;
950 0 : case ContainerMeta::PrimaryPage:
951 : case ContainerMeta::Undefined:
952 0 : break;
953 : }
954 :
955 0 : if (!ok) {
956 0 : if (auto it = m_onClosedFuncs.find(objectId); it != m_onClosedFuncs.end()) {
957 0 : it->second(make_ret(Ret::Code::UnknownError));
958 : }
959 : }
960 : }
961 :
962 0 : return Promise<Ret>::dummy_result();
963 0 : });
964 : }
965 :
966 0 : Ret Interactive::closeObjectsSync(const std::vector<ObjectInfo>& objs)
967 : {
968 0 : if (objs.empty()) {
969 0 : return make_ok();
970 : }
971 :
972 0 : QEventLoop loop;
973 0 : Ret ret = make_ok();
974 :
975 0 : closeObjects(objs).onResolve(this, [&loop, &ret](const Ret& closeRet) {
976 0 : ret = closeRet;
977 0 : loop.quit();
978 0 : });
979 :
980 0 : loop.exec();
981 :
982 0 : return ret;
983 0 : }
984 :
985 0 : void Interactive::fillData(QmlLaunchData* data, const Uri& uri, const QVariantMap& params) const
986 : {
987 0 : ContainerMeta meta = uriRegister()->meta(uri);
988 0 : data->setValue("module", meta.qmlModule);
989 0 : data->setValue("path", meta.qmlPath);
990 0 : data->setValue("type", meta.type);
991 0 : data->setValue("uri", QString::fromStdString(uri.toString()));
992 0 : data->setValue("params", params);
993 0 : data->setValue("modal", params.value("modal", ""));
994 0 : }
995 :
996 0 : void Interactive::fillData(QObject* object, const QVariantMap& params) const
997 : {
998 0 : const QMetaObject* metaObject = object->metaObject();
999 0 : for (int i = 0; i < metaObject->propertyCount(); i++) {
1000 0 : QMetaProperty metaProperty = metaObject->property(i);
1001 0 : if (params.contains(metaProperty.name())) {
1002 0 : object->setProperty(metaProperty.name(), params[metaProperty.name()]);
1003 : }
1004 : }
1005 0 : }
1006 :
1007 0 : ValCh<Uri> Interactive::currentUri() const
1008 : {
1009 0 : ValCh<Uri> v;
1010 0 : if (!m_stack.empty()) {
1011 0 : v.val = m_stack.last().query.uri();
1012 : }
1013 0 : v.ch = m_currentUriChanged;
1014 0 : return v;
1015 0 : }
1016 :
1017 0 : RetVal<bool> Interactive::isCurrentUriDialog() const
1018 : {
1019 0 : if (m_stack.empty()) {
1020 0 : return RetVal<bool>::make_ok(false);
1021 : }
1022 :
1023 0 : const ObjectInfo& last = m_stack.last();
1024 0 : if (!last.window) {
1025 0 : return RetVal<bool>::make_ok(false);
1026 : }
1027 :
1028 0 : return RetVal<bool>::make_ok(last.window != mainWindow()->qWindow());
1029 : }
1030 :
1031 0 : async::Notification Interactive::currentUriAboutToBeChanged() const
1032 : {
1033 0 : return m_currentUriAboutToBeChanged;
1034 : }
1035 :
1036 0 : std::vector<Uri> Interactive::stack() const
1037 : {
1038 0 : std::vector<Uri> uris;
1039 0 : for (const ObjectInfo& info : m_stack) {
1040 0 : uris.push_back(info.query.uri());
1041 : }
1042 0 : return uris;
1043 0 : }
1044 :
1045 0 : QWindow* Interactive::topWindow() const
1046 : {
1047 0 : QWindow* mainWin = mainWindow()->qWindow();
1048 0 : if (!mainWin) {
1049 0 : mainWin = qApp->focusWindow();
1050 : }
1051 :
1052 0 : if (m_stack.empty()) {
1053 0 : LOGE() << "stack is empty";
1054 0 : return mainWin;
1055 : }
1056 :
1057 0 : const ObjectInfo& last = m_stack.last();
1058 0 : if (!last.window) {
1059 0 : return mainWin;
1060 : }
1061 :
1062 0 : if (last.window == mainWin) {
1063 0 : return mainWin;
1064 : }
1065 :
1066 : // TODO/HACK: last.window doesn't seem to have a parent when the top window is a widget....
1067 0 : if (!last.window->parent() && !topWindowIsWidget()) {
1068 0 : ASSERT_X("Window must have a parent!");
1069 : }
1070 :
1071 0 : if (!last.window->isWidgetType()) {
1072 0 : QWindow* qwindow = qobject_cast<QWindow*>(last.window);
1073 0 : IF_ASSERT_FAILED(qwindow) {
1074 0 : return mainWin;
1075 : }
1076 0 : return qwindow;
1077 : }
1078 :
1079 : // QWidget
1080 0 : QWidget* qwidget = qobject_cast<QWidget*>(last.window);
1081 0 : QWindow* qwindow = qwidget->windowHandle();
1082 0 : IF_ASSERT_FAILED(qwindow) {
1083 0 : return mainWin;
1084 : }
1085 0 : return qwindow;
1086 : }
1087 :
1088 0 : bool Interactive::topWindowIsWidget() const
1089 : {
1090 0 : if (m_stack.empty()) {
1091 0 : return false;
1092 : }
1093 :
1094 0 : const ObjectInfo& last = m_stack.last();
1095 0 : if (!last.window) {
1096 0 : return false;
1097 : }
1098 :
1099 0 : return last.window->isWidgetType();
1100 : }
1101 :
1102 0 : QString Interactive::objectId(const QVariant& val) const
1103 : {
1104 : static int count(0);
1105 :
1106 0 : ++count;
1107 :
1108 0 : QString objectId;
1109 0 : if (val.canConvert<QObject*>()) {
1110 0 : QObject* obj = val.value<QObject*>();
1111 0 : IF_ASSERT_FAILED(obj) {
1112 0 : return QString();
1113 : }
1114 :
1115 0 : objectId = QString(obj->metaObject()->className()) + "_" + QString::number(count);
1116 : } else {
1117 0 : objectId = "unknown_" + QString::number(count);
1118 : }
1119 0 : return "object://" + objectId;
1120 0 : }
1121 :
1122 0 : Ret Interactive::toRet(const QVariant& jsr) const
1123 : {
1124 0 : QVariantMap jsobj = jsr.toMap();
1125 0 : IF_ASSERT_FAILED(jsobj.contains("errcode")) {
1126 0 : return make_ret(Ret::Code::UnknownError);
1127 : }
1128 :
1129 0 : Ret ret;
1130 0 : ret.setCode(jsobj.value("errcode").toInt());
1131 0 : ret.setText(jsobj.value("text").toString().toStdString());
1132 0 : return ret;
1133 0 : }
1134 :
1135 0 : RetVal<Val> Interactive::toRetVal(const QVariant& jsrv) const
1136 : {
1137 0 : RetVal<Val> rv;
1138 0 : QVariantMap jsobj = jsrv.toMap();
1139 :
1140 0 : IF_ASSERT_FAILED(jsobj.contains("errcode")) {
1141 0 : rv.ret = make_ret(Ret::Code::UnknownError);
1142 0 : return rv;
1143 : }
1144 :
1145 0 : int errcode = jsobj.value("errcode").toInt();
1146 0 : QVariant val = jsobj.value("value");
1147 :
1148 0 : rv.ret.setCode(errcode);
1149 0 : rv.val = Val::fromQVariant(val);
1150 :
1151 0 : return rv;
1152 0 : }
1153 :
1154 0 : RetVal<Interactive::OpenData> Interactive::openWidgetDialog(const Uri& uri, const QVariantMap& params)
1155 : {
1156 0 : RetVal<OpenData> result;
1157 :
1158 0 : ContainerMeta meta = uriRegister()->meta(uri);
1159 0 : int widgetMetaTypeId = meta.widgetMetaTypeId;
1160 :
1161 : static int count(0);
1162 0 : QString objectId = QString("%1_%2").arg(widgetMetaTypeId).arg(++count);
1163 :
1164 0 : QMetaType metaType = QMetaType(widgetMetaTypeId);
1165 0 : ui::WidgetDialog* dialog = static_cast<ui::WidgetDialog*>(metaType.create());
1166 :
1167 0 : if (!dialog) {
1168 0 : result.ret = make_ret(Ret::Code::UnknownError);
1169 0 : return result;
1170 : }
1171 :
1172 0 : dialog->setProperty("ioc_context", iocContext()->id);
1173 :
1174 0 : dialog->classBegin();
1175 :
1176 0 : fillData(dialog, params);
1177 :
1178 0 : dialog->componentComplete();
1179 :
1180 : //! NOTE Will be deleted with the dialog
1181 0 : WidgetDialogAdapter* adapter = new WidgetDialogAdapter(dialog);
1182 0 : adapter->onShow([this, objectId, dialog]() {
1183 0 : if (isOpened(objectId).val) {
1184 0 : LOGE() << "Dialog is already opened: " << objectId << ", ignoring this show event";
1185 0 : return;
1186 : }
1187 :
1188 0 : async::Async::call(this, [this, objectId, dialog]() {
1189 0 : QQmlEngine::setObjectOwnership(dialog, QQmlEngine::CppOwnership);
1190 0 : QQmlEngine::setObjectOwnership(dialog->windowHandle(), QQmlEngine::CppOwnership);
1191 0 : onOpen(ContainerMeta::QWidgetDialog, objectId, dialog->window());
1192 0 : });
1193 : })
1194 0 : .onHide([this, objectId, dialog]() {
1195 0 : if (!isOpened(objectId).val) {
1196 0 : LOGE() << "Dialog is not opened: " << objectId << ", ignoring this hide event";
1197 0 : return;
1198 : }
1199 :
1200 0 : QDialog::DialogCode dialogCode = static_cast<QDialog::DialogCode>(dialog->result());
1201 0 : Ret::Code errorCode = dialogCode == QDialog::Accepted ? Ret::Code::Ok : Ret::Code::Cancel;
1202 :
1203 0 : QVariantMap ret;
1204 0 : ret["errcode"] = static_cast<int>(errorCode);
1205 :
1206 0 : onClose(objectId, ret);
1207 :
1208 0 : dialog->deleteLater();
1209 0 : });
1210 :
1211 0 : bool modal = params.value("modal", "true") == "true";
1212 0 : dialog->setWindowModality(modal ? Qt::ApplicationModal : Qt::NonModal);
1213 0 : dialog->show();
1214 0 : dialog->activateWindow(); // give keyboard focus to aid blind users
1215 :
1216 0 : result.ret = make_ret(Ret::Code::Ok);
1217 0 : result.val.objectId = objectId;
1218 :
1219 0 : return result;
1220 0 : }
1221 :
1222 0 : RetVal<Interactive::OpenData> Interactive::openQml(const Uri& uri, const QVariantMap& params)
1223 : {
1224 0 : QmlLaunchData data;
1225 0 : fillData(&data, uri, params);
1226 :
1227 0 : m_openRequested.send(&data);
1228 :
1229 0 : RetVal<OpenData> result;
1230 0 : result.ret = toRet(data.value("ret"));
1231 0 : result.val.objectId = data.value("objectId").toString();
1232 :
1233 0 : return result;
1234 0 : }
1235 :
1236 0 : void Interactive::closeQml(const QVariant& objectId)
1237 : {
1238 0 : m_closeRequested.send(objectId);
1239 0 : }
1240 :
1241 0 : void Interactive::raiseQml(const QVariant& objectId)
1242 : {
1243 0 : m_raiseRequested.send(objectId);
1244 0 : }
1245 :
1246 0 : void Interactive::onOpen(const QVariant& type, const QVariant& objectId, QObject* window)
1247 : {
1248 0 : ContainerMeta::Type containerMeta = type.value<ContainerMeta::Type>();
1249 :
1250 0 : IF_ASSERT_FAILED(containerMeta != ContainerMeta::Undefined) {
1251 0 : containerMeta = ContainerMeta::QmlDialog;
1252 : }
1253 :
1254 0 : m_openingObject.objectId = objectId;
1255 0 : m_openingObject.window = window;
1256 0 : if (!m_openingObject.window) {
1257 0 : m_openingObject.window = (containerMeta == ContainerMeta::PrimaryPage) ? mainWindow()->qWindow() : qApp->focusWindow();
1258 : }
1259 :
1260 0 : if (m_openingObject.query.param("floating").toBool()) {
1261 0 : m_floatingObjects.push_back(m_openingObject);
1262 0 : m_openingObject = ObjectInfo(); // clear
1263 0 : return;
1264 : }
1265 :
1266 0 : if (ContainerMeta::PrimaryPage == containerMeta) {
1267 : // Replace bottom item of the stack, because that always reflects the current PrimaryPage
1268 0 : if (m_stack.empty()) {
1269 0 : m_stack.push(m_openingObject);
1270 : } else {
1271 0 : m_stack[0] = m_openingObject;
1272 : }
1273 0 : } else if (ContainerMeta::QmlDialog == containerMeta) {
1274 0 : m_stack.push(m_openingObject);
1275 0 : } else if (ContainerMeta::QWidgetDialog == containerMeta) {
1276 0 : m_stack.push(m_openingObject);
1277 : } else {
1278 0 : IF_ASSERT_FAILED_X(false, "unknown page type") {
1279 : m_stack.push(m_openingObject);
1280 : }
1281 : }
1282 :
1283 0 : notifyAboutCurrentUriChanged();
1284 :
1285 0 : if (ContainerMeta::PrimaryPage == containerMeta && m_openingObject.resolve) {
1286 0 : auto resolve = m_openingObject.resolve;
1287 0 : Async::call(this, [resolve]() {
1288 0 : (void)resolve(Val(true));
1289 0 : });
1290 0 : }
1291 :
1292 0 : Uri uri = m_openingObject.query.uri();
1293 0 : m_openingObject = ObjectInfo(); // clear
1294 :
1295 0 : Async::call(this, [this, uri]() {
1296 0 : m_opened.send(uri);
1297 0 : });
1298 0 : }
1299 :
1300 0 : void Interactive::onClose(const QString& objectId, const QVariant& jsrv)
1301 : {
1302 0 : RetVal<Val> rv = toRetVal(jsrv);
1303 :
1304 0 : ObjectInfo obj;
1305 :
1306 0 : bool inStack = false;
1307 0 : for (int i = 0; i < m_stack.size(); ++i) {
1308 0 : if (m_stack.at(i).objectId == objectId) {
1309 0 : obj = m_stack.at(i);
1310 0 : inStack = true;
1311 0 : m_stack.remove(i);
1312 0 : break;
1313 : }
1314 : }
1315 :
1316 0 : if (!inStack) {
1317 0 : for (size_t i = 0; i < m_floatingObjects.size(); ++i) {
1318 0 : if (m_floatingObjects.at(i).objectId == objectId) {
1319 0 : obj = m_floatingObjects.at(i);
1320 0 : m_floatingObjects.erase(m_floatingObjects.begin() + i);
1321 0 : break;
1322 : }
1323 : }
1324 : }
1325 :
1326 0 : DO_ASSERT(obj.objectId.isValid());
1327 :
1328 0 : if (rv.ret) {
1329 0 : (void)obj.resolve(rv.val);
1330 : } else {
1331 0 : (void)obj.reject(rv.ret.code(), rv.ret.text());
1332 : }
1333 :
1334 0 : if (inStack) {
1335 0 : notifyAboutCurrentUriChanged();
1336 : }
1337 :
1338 0 : Async::call(this, [this, objectId, rv]() {
1339 0 : auto onClosedIt = m_onClosedFuncs.find(objectId);
1340 0 : if (onClosedIt != m_onClosedFuncs.end()) {
1341 0 : onClosedIt->second(rv.ret);
1342 : }
1343 0 : });
1344 0 : }
1345 :
1346 0 : std::vector<Interactive::ObjectInfo> Interactive::allOpenObjects() const
1347 : {
1348 0 : std::vector<ObjectInfo> result;
1349 :
1350 0 : result.insert(result.end(), m_stack.cbegin(), m_stack.cend());
1351 0 : result.insert(result.end(), m_floatingObjects.cbegin(), m_floatingObjects.cend());
1352 :
1353 0 : return result;
1354 0 : }
1355 :
1356 0 : std::vector<Interactive::ObjectInfo> Interactive::collectOpenObjects(std::function<bool(const ObjectInfo&)> accepted) const
1357 : {
1358 0 : std::vector<ObjectInfo> result;
1359 0 : for (const ObjectInfo& obj : allOpenObjects()) {
1360 0 : if (accepted(obj)) {
1361 0 : result.push_back(obj);
1362 : }
1363 0 : }
1364 :
1365 0 : return result;
1366 0 : }
1367 :
1368 0 : void Interactive::notifyAboutCurrentUriChanged()
1369 : {
1370 0 : m_currentUriChanged.send(currentUri().val);
1371 0 : }
1372 :
1373 0 : void Interactive::notifyAboutCurrentUriWillBeChanged()
1374 : {
1375 0 : m_currentUriAboutToBeChanged.notify();
1376 0 : }
|