LCOV - code coverage report
Current view: top level - muse/framework/interactive/internal - interactive.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 708 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) 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 : }
        

Generated by: LCOV version 2.0-1