From a04b5ffedc7b6f34bc472ed9803439b01ea6c6d6 Mon Sep 17 00:00:00 2001 From: Eism Date: Tue, 28 Jan 2025 18:44:11 +0200 Subject: [PATCH] implemented tours system --- src/app/CMakeLists.txt | 4 + src/app/appfactory.cpp | 8 + src/appshell/qml/AppWindow.qml | 4 + src/framework/CMakeLists.txt | 4 + src/framework/cmake/MuseDeclareOptions.cmake | 2 + .../cmake/muse_framework_config.h.in | 4 + src/framework/dockwindow/view/dockpageview.h | 2 +- src/framework/global/types/uri.h | 9 + src/framework/stubs/CMakeLists.txt | 4 + src/framework/stubs/tours/CMakeLists.txt | 41 +++++ .../tours/qml/Muse/Tours/ToursProvider.qml | 25 +++ .../stubs/tours/qml/Muse/Tours/qmldir | 2 + src/framework/stubs/tours/tours.qrc | 6 + .../stubs/tours/toursconfigurationstub.cpp | 38 ++++ .../stubs/tours/toursconfigurationstub.h | 36 ++++ .../stubs/tours/toursservicestub.cpp | 28 +++ src/framework/stubs/tours/toursservicestub.h | 33 ++++ src/framework/stubs/tours/toursstubmodule.cpp | 54 ++++++ src/framework/stubs/tours/toursstubmodule.h | 35 ++++ .../stubs/tours/view/toursproviderstub.cpp | 28 +++ .../stubs/tours/view/toursproviderstub.h | 35 ++++ src/framework/tours/CMakeLists.txt | 51 ++++++ .../tours/internal/toursconfiguration.cpp | 79 +++++++++ .../tours/internal/toursconfiguration.h | 51 ++++++ src/framework/tours/internal/toursservice.cpp | 105 +++++++++++ src/framework/tours/internal/toursservice.h | 54 ++++++ src/framework/tours/itoursconfiguration.h | 45 +++++ src/framework/tours/itoursprovider.h | 39 +++++ src/framework/tours/itoursservice.h | 39 +++++ .../tours/qml/Muse/Tours/ToursProvider.qml | 114 ++++++++++++ .../qml/Muse/Tours/internal/TourStepPopup.qml | 163 ++++++++++++++++++ src/framework/tours/qml/Muse/Tours/qmldir | 2 + src/framework/tours/tours.qrc | 7 + src/framework/tours/toursmodule.cpp | 75 ++++++++ src/framework/tours/toursmodule.h | 47 +++++ src/framework/tours/tourstypes.h | 50 ++++++ src/framework/tours/view/toursprovider.cpp | 82 +++++++++ src/framework/tours/view/toursprovider.h | 64 +++++++ .../tours/view/toursprovidermodel.cpp | 34 ++++ src/framework/tours/view/toursprovidermodel.h | 44 +++++ src/framework/ui/CMakeLists.txt | 4 + src/framework/ui/inavigation.h | 1 + src/framework/ui/inavigationcontroller.h | 3 + .../ui/internal/interactiveuriregister.h | 9 - .../ui/internal/navigationcontroller.cpp | 10 ++ .../ui/internal/navigationcontroller.h | 3 + src/framework/ui/internal/uiengine.h | 3 + src/framework/ui/uitypes.h | 1 + src/framework/ui/view/abstractnavigation.cpp | 8 +- src/framework/ui/view/abstractnavigation.h | 1 + src/framework/ui/view/navigationcontrol.cpp | 5 + src/framework/ui/view/navigationcontrol.h | 1 + src/framework/ui/view/navigationpanel.cpp | 5 + src/framework/ui/view/navigationpanel.h | 1 + src/framework/ui/view/navigationsection.cpp | 5 + src/framework/ui/view/navigationsection.h | 1 + .../qml/Muse/UiComponents/ButtonBox.qml | 7 +- 57 files changed, 1600 insertions(+), 15 deletions(-) create mode 100644 src/framework/stubs/tours/CMakeLists.txt create mode 100644 src/framework/stubs/tours/qml/Muse/Tours/ToursProvider.qml create mode 100644 src/framework/stubs/tours/qml/Muse/Tours/qmldir create mode 100644 src/framework/stubs/tours/tours.qrc create mode 100644 src/framework/stubs/tours/toursconfigurationstub.cpp create mode 100644 src/framework/stubs/tours/toursconfigurationstub.h create mode 100644 src/framework/stubs/tours/toursservicestub.cpp create mode 100644 src/framework/stubs/tours/toursservicestub.h create mode 100644 src/framework/stubs/tours/toursstubmodule.cpp create mode 100644 src/framework/stubs/tours/toursstubmodule.h create mode 100644 src/framework/stubs/tours/view/toursproviderstub.cpp create mode 100644 src/framework/stubs/tours/view/toursproviderstub.h create mode 100644 src/framework/tours/CMakeLists.txt create mode 100644 src/framework/tours/internal/toursconfiguration.cpp create mode 100644 src/framework/tours/internal/toursconfiguration.h create mode 100644 src/framework/tours/internal/toursservice.cpp create mode 100644 src/framework/tours/internal/toursservice.h create mode 100644 src/framework/tours/itoursconfiguration.h create mode 100644 src/framework/tours/itoursprovider.h create mode 100644 src/framework/tours/itoursservice.h create mode 100644 src/framework/tours/qml/Muse/Tours/ToursProvider.qml create mode 100644 src/framework/tours/qml/Muse/Tours/internal/TourStepPopup.qml create mode 100644 src/framework/tours/qml/Muse/Tours/qmldir create mode 100644 src/framework/tours/tours.qrc create mode 100644 src/framework/tours/toursmodule.cpp create mode 100644 src/framework/tours/toursmodule.h create mode 100644 src/framework/tours/tourstypes.h create mode 100644 src/framework/tours/view/toursprovider.cpp create mode 100644 src/framework/tours/view/toursprovider.h create mode 100644 src/framework/tours/view/toursprovidermodel.cpp create mode 100644 src/framework/tours/view/toursprovidermodel.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6ed9391404491..da1d273a0fe9a 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -159,6 +159,10 @@ if (MUSE_MODULE_UI) list(APPEND LINK_LIB muse::dockwindow) endif() +if (MUSE_MODULE_TOURS) + list(APPEND LINK_LIB muse::tours) +endif() + if (MUSE_MODULE_AUDIOPLUGINS) list(APPEND LINK_LIB muse::audioplugins) endif() diff --git a/src/app/appfactory.cpp b/src/app/appfactory.cpp index 47bb300107b03..06243fd6f33d2 100644 --- a/src/app/appfactory.cpp +++ b/src/app/appfactory.cpp @@ -77,6 +77,12 @@ #include "framework/stubs/shortcuts/shortcutsstubmodule.h" #endif +#ifdef MUSE_MODULE_TOURS +#include "framework/tours/toursmodule.h" +#else +#include "framework/stubs/tours/toursstubmodule.h" +#endif + #ifdef MUSE_MODULE_UI #include "framework/dockwindow/dockmodule.h" #include "framework/ui/uimodule.h" @@ -254,6 +260,7 @@ std::shared_ptr AppFactory::newGuiApp(const CmdOptions& opti app->addModule(new muse::uicomponents::UiComponentsModule()); app->addModule(new muse::dock::DockModule()); #endif + app->addModule(new muse::tours::ToursModule()); app->addModule(new muse::vst::VSTModule()); // modules @@ -360,6 +367,7 @@ std::shared_ptr AppFactory::newConsoleApp(const CmdOptions& app->addModule(new muse::uicomponents::UiComponentsModule()); app->addModule(new muse::dock::DockModule()); #endif + app->addModule(new muse::tours::ToursModule()); app->addModule(new muse::vst::VSTModule()); // modules diff --git a/src/appshell/qml/AppWindow.qml b/src/appshell/qml/AppWindow.qml index 1a9d0ae5133a4..a422c19bd3e7f 100644 --- a/src/appshell/qml/AppWindow.qml +++ b/src/appshell/qml/AppWindow.qml @@ -26,6 +26,8 @@ import Muse.Ui 1.0 import Muse.Shortcuts 1.0 import MuseScore.AppShell 1.0 +import Muse.Tours 1.0 + ApplicationWindow { id: root @@ -69,6 +71,8 @@ ApplicationWindow { ToolTipProvider { } + ToursProvider { } + //! NOTE Need only create Shortcuts { } diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index fc3882f1cc1ed..cef41f2f26086 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -81,6 +81,10 @@ if (MUSE_MODULE_SHORTCUTS) add_subdirectory(shortcuts) endif() +if (MUSE_MODULE_TOURS) + add_subdirectory(tours) +endif() + if (MUSE_MODULE_MULTIINSTANCES) add_subdirectory(multiinstances) endif() diff --git a/src/framework/cmake/MuseDeclareOptions.cmake b/src/framework/cmake/MuseDeclareOptions.cmake index 1ff2880aef354..5c3b4a2221822 100644 --- a/src/framework/cmake/MuseDeclareOptions.cmake +++ b/src/framework/cmake/MuseDeclareOptions.cmake @@ -49,6 +49,8 @@ option(MUSE_MODULE_NETWORK_WEBSOCKET "Enable websocket support" OFF) declare_muse_module_opt(SHORTCUTS ON) +declare_muse_module_opt(TOURS ON) + declare_muse_module_opt(UI ON) option(MUSE_MODULE_UI_DISABLE_MODALITY "Disable dialogs modality for testing purpose" OFF) diff --git a/src/framework/cmake/muse_framework_config.h.in b/src/framework/cmake/muse_framework_config.h.in index c292952b5a7c9..8c3b4a7718cb8 100644 --- a/src/framework/cmake/muse_framework_config.h.in +++ b/src/framework/cmake/muse_framework_config.h.in @@ -106,6 +106,10 @@ #cmakedefine MUSE_MODULE_SHORTCUTS_TESTS 1 #cmakedefine MUSE_MODULE_SHORTCUTS_API 1 +#cmakedefine MUSE_MODULE_TOURS 1 +#cmakedefine MUSE_MODULE_TOURS_TESTS 1 +#cmakedefine MUSE_MODULE_TOURS_API 1 + #cmakedefine MUSE_MODULE_UI 1 #cmakedefine MUSE_MODULE_UI_TESTS 1 #cmakedefine MUSE_MODULE_UI_API 1 diff --git a/src/framework/dockwindow/view/dockpageview.h b/src/framework/dockwindow/view/dockpageview.h index 0a871dfe0cd60..4e8b20ebaf8e9 100644 --- a/src/framework/dockwindow/view/dockpageview.h +++ b/src/framework/dockwindow/view/dockpageview.h @@ -58,7 +58,7 @@ class DockPageView : public QQuickItem, public muse::Injectable Q_PROPERTY(muse::dock::DockCentralView * centralDock READ centralDock WRITE setCentralDock NOTIFY centralDockChanged) Q_PROPERTY(muse::dock::DockStatusBarView * statusBar READ statusBar WRITE setStatusBar NOTIFY statusBarChanged) - muse::Inject navigationController = { this }; + Inject navigationController = { this }; public: explicit DockPageView(QQuickItem* parent = nullptr); diff --git a/src/framework/global/types/uri.h b/src/framework/global/types/uri.h index 683090072e8af..8800cca4a220d 100644 --- a/src/framework/global/types/uri.h +++ b/src/framework/global/types/uri.h @@ -115,4 +115,13 @@ inline muse::logger::Stream& operator<<(muse::logger::Stream& s, const muse::Uri return s; } +template<> +struct std::hash +{ + std::size_t operator()(const muse::Uri& uri) const noexcept + { + return std::hash {}(uri.toString()); + } +}; + #endif // MUSE_GLOBAL_URI_H diff --git a/src/framework/stubs/CMakeLists.txt b/src/framework/stubs/CMakeLists.txt index 4cd00fe43b83a..862cc4e3213e2 100644 --- a/src/framework/stubs/CMakeLists.txt +++ b/src/framework/stubs/CMakeLists.txt @@ -58,6 +58,10 @@ if (NOT MUSE_MODULE_SHORTCUTS) add_subdirectory(shortcuts) endif() +if (NOT MUSE_MODULE_TOURS) + add_subdirectory(tours) +endif() + if (NOT MUSE_MODULE_UPDATE) add_subdirectory(update) endif() diff --git a/src/framework/stubs/tours/CMakeLists.txt b/src/framework/stubs/tours/CMakeLists.txt new file mode 100644 index 0000000000000..49d489cc4e85d --- /dev/null +++ b/src/framework/stubs/tours/CMakeLists.txt @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-only +# MuseScore-CLA-applies +# +# MuseScore +# Music Composition & Notation +# +# Copyright (C) 2025 MuseScore BVBA and others +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +declare_module(muse_tours) +set(MODULE_ALIAS muse::tours) + +set(MODULE_QRC tours.qrc) + +set(MODULE_QML_IMPORT ${CMAKE_CURRENT_LIST_DIR}/qml) + +set(MODULE_SRC + ${CMAKE_CURRENT_LIST_DIR}/toursstubmodule.cpp + ${CMAKE_CURRENT_LIST_DIR}/toursstubmodule.h + ${CMAKE_CURRENT_LIST_DIR}/toursconfigurationstub.cpp + ${CMAKE_CURRENT_LIST_DIR}/toursconfigurationstub.h + ${CMAKE_CURRENT_LIST_DIR}/toursservicestub.cpp + ${CMAKE_CURRENT_LIST_DIR}/toursservicestub.h + + ${CMAKE_CURRENT_LIST_DIR}/view/toursproviderstub.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/toursproviderstub.h + ) + +set(MODULE_IS_STUB ON) +setup_module() diff --git a/src/framework/stubs/tours/qml/Muse/Tours/ToursProvider.qml b/src/framework/stubs/tours/qml/Muse/Tours/ToursProvider.qml new file mode 100644 index 0000000000000..6971285babb7b --- /dev/null +++ b/src/framework/stubs/tours/qml/Muse/Tours/ToursProvider.qml @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 + +Item { +} diff --git a/src/framework/stubs/tours/qml/Muse/Tours/qmldir b/src/framework/stubs/tours/qml/Muse/Tours/qmldir new file mode 100644 index 0000000000000..36d024afb48ed --- /dev/null +++ b/src/framework/stubs/tours/qml/Muse/Tours/qmldir @@ -0,0 +1,2 @@ +module Muse.Tours +ToursProvider 1.0 ToursProvider.qml diff --git a/src/framework/stubs/tours/tours.qrc b/src/framework/stubs/tours/tours.qrc new file mode 100644 index 0000000000000..acc3f7aeccfb0 --- /dev/null +++ b/src/framework/stubs/tours/tours.qrc @@ -0,0 +1,6 @@ + + + qml/Muse/Tours/qmldir + qml/Muse/Tours/ToursProvider.qml + + diff --git a/src/framework/stubs/tours/toursconfigurationstub.cpp b/src/framework/stubs/tours/toursconfigurationstub.cpp new file mode 100644 index 0000000000000..1032dfe607189 --- /dev/null +++ b/src/framework/stubs/tours/toursconfigurationstub.cpp @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursconfigurationstub.h" + +using namespace muse::tours; + +muse::String ToursConfigurationStub::lastShownTourIdForEvent(const String&) const +{ + return u""; +} + +void ToursConfigurationStub::setLastShownTourIdForEvent(const String&, const String&) +{ +} + +muse::io::path_t ToursConfigurationStub::toursFilePath() const +{ + return ""; +} diff --git a/src/framework/stubs/tours/toursconfigurationstub.h b/src/framework/stubs/tours/toursconfigurationstub.h new file mode 100644 index 0000000000000..28e76e826b3db --- /dev/null +++ b/src/framework/stubs/tours/toursconfigurationstub.h @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tours/itoursconfiguration.h" + +namespace muse::tours { +class ToursConfigurationStub : public IToursConfiguration +{ +public: + String lastShownTourIdForEvent(const String& eventCode) const override; + void setLastShownTourIdForEvent(const String& eventCode, const String& tourId) override; + + io::path_t toursFilePath() const override; +}; +} diff --git a/src/framework/stubs/tours/toursservicestub.cpp b/src/framework/stubs/tours/toursservicestub.cpp new file mode 100644 index 0000000000000..60e1ea26ed17f --- /dev/null +++ b/src/framework/stubs/tours/toursservicestub.cpp @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursservicestub.h" + +using namespace muse::tours; + +void ToursServiceStub::onEvent(const String&) +{ +} diff --git a/src/framework/stubs/tours/toursservicestub.h b/src/framework/stubs/tours/toursservicestub.h new file mode 100644 index 0000000000000..a180b1c05377e --- /dev/null +++ b/src/framework/stubs/tours/toursservicestub.h @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tours/itoursservice.h" + +namespace muse::tours { +class ToursServiceStub : public IToursService +{ +public: + void onEvent(const String& eventCode) override; +}; +} diff --git a/src/framework/stubs/tours/toursstubmodule.cpp b/src/framework/stubs/tours/toursstubmodule.cpp new file mode 100644 index 0000000000000..b2e576bf880b9 --- /dev/null +++ b/src/framework/stubs/tours/toursstubmodule.cpp @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursstubmodule.h" + +#include "modularity/ioc.h" + +#include "toursconfigurationstub.h" +#include "toursservicestub.h" + +#include "view/toursproviderstub.h" + +using namespace muse::tours; +using namespace muse::modularity; + +static void tours_init_qrc() +{ + Q_INIT_RESOURCE(tours); +} + +std::string ToursModule::moduleName() const +{ + return "tours_stub"; +} + +void ToursModule::registerExports() +{ + ioc()->registerExport(moduleName(), new ToursConfigurationStub()); + ioc()->registerExport(moduleName(), new ToursServiceStub()); + ioc()->registerExport(moduleName(), new ToursProviderStub()); +} + +void ToursModule::registerResources() +{ + tours_init_qrc(); +} diff --git a/src/framework/stubs/tours/toursstubmodule.h b/src/framework/stubs/tours/toursstubmodule.h new file mode 100644 index 0000000000000..4b46d08445385 --- /dev/null +++ b/src/framework/stubs/tours/toursstubmodule.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "modularity/imodulesetup.h" + +namespace muse::tours { +class ToursModule : public modularity::IModuleSetup +{ +public: + std::string moduleName() const override; + void registerExports() override; + void registerResources() override; +}; +} diff --git a/src/framework/stubs/tours/view/toursproviderstub.cpp b/src/framework/stubs/tours/view/toursproviderstub.cpp new file mode 100644 index 0000000000000..4ae940577e688 --- /dev/null +++ b/src/framework/stubs/tours/view/toursproviderstub.cpp @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursproviderstub.h" + +using namespace muse::tours; + +void ToursProviderStub::showTour(const Tour&) +{ +} diff --git a/src/framework/stubs/tours/view/toursproviderstub.h b/src/framework/stubs/tours/view/toursproviderstub.h new file mode 100644 index 0000000000000..d45a591118760 --- /dev/null +++ b/src/framework/stubs/tours/view/toursproviderstub.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tours/itoursprovider.h" + +namespace muse::tours { +class ToursProviderStub : public QObject, public IToursProvider +{ + Q_OBJECT + +public: + void showTour(const Tour& tour) override; +}; +} diff --git a/src/framework/tours/CMakeLists.txt b/src/framework/tours/CMakeLists.txt new file mode 100644 index 0000000000000..2444029120530 --- /dev/null +++ b/src/framework/tours/CMakeLists.txt @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-3.0-only +# MuseScore-CLA-applies +# +# MuseScore +# Music Composition & Notation +# +# Copyright (C) 2025 MuseScore BVBA and others +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +declare_module(muse_tours) +set(MODULE_ALIAS muse::tours) + +set(MODULE_QRC tours.qrc) + +set(MODULE_QML_IMPORT ${CMAKE_CURRENT_LIST_DIR}/qml) + +set(MODULE_SRC + ${CMAKE_CURRENT_LIST_DIR}/toursmodule.cpp + ${CMAKE_CURRENT_LIST_DIR}/toursmodule.h + ${CMAKE_CURRENT_LIST_DIR}/tourstypes.h + ${CMAKE_CURRENT_LIST_DIR}/itoursconfiguration.h + ${CMAKE_CURRENT_LIST_DIR}/itoursservice.h + ${CMAKE_CURRENT_LIST_DIR}/itoursprovider.h + + ${CMAKE_CURRENT_LIST_DIR}/internal/toursconfiguration.cpp + ${CMAKE_CURRENT_LIST_DIR}/internal/toursconfiguration.h + ${CMAKE_CURRENT_LIST_DIR}/internal/toursservice.cpp + ${CMAKE_CURRENT_LIST_DIR}/internal/toursservice.h + + ${CMAKE_CURRENT_LIST_DIR}/view/toursprovider.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/toursprovider.h + ${CMAKE_CURRENT_LIST_DIR}/view/toursprovidermodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/toursprovidermodel.h + ) + +setup_module() + +if (MUSE_MODULE_TOURS_TESTS) + # add_subdirectory(tests) # todo +endif() diff --git a/src/framework/tours/internal/toursconfiguration.cpp b/src/framework/tours/internal/toursconfiguration.cpp new file mode 100644 index 0000000000000..8f6ece8d9f28a --- /dev/null +++ b/src/framework/tours/internal/toursconfiguration.cpp @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursconfiguration.h" + +#include "settings.h" + +#include "global/configreader.h" + +#include "log.h" + +using namespace muse; +using namespace muse::tours; +using namespace muse::async; + +static const Settings::Key UI_LAST_SHOWN_TOURS_KEY("tours", "tours/lastShownTours"); + +String ToursConfiguration::lastShownTourIdForEvent(const String& eventCode) const +{ + StringList allLastShownTours = lastShownTours(); + for (const String& tourChainId : allLastShownTours) { + if (tourChainId.startsWith(eventCode)) { + String chainId = tourChainId; + return chainId.remove(eventCode + u"/"); + } + } + + return String(); +} + +void ToursConfiguration::setLastShownTourIdForEvent(const String& eventCode, const String& tourId) +{ + StringList allLastShownTours = lastShownTours(); + bool changed = false; + + String newTourId = eventCode + u"/" + tourId; + + for (size_t i = 0; i < allLastShownTours.size(); ++i) { + const String& tourId = allLastShownTours[i]; + if (tourId.startsWith(eventCode)) { + allLastShownTours[i] = newTourId; + changed = true; + } + } + + if (!changed) { + allLastShownTours.push_back(newTourId); + } + + settings()->setSharedValue(UI_LAST_SHOWN_TOURS_KEY, Val(allLastShownTours.join(u",").toStdString())); +} + +io::path_t ToursConfiguration::toursFilePath() const +{ + return ":/resources/tours.json"; +} + +StringList ToursConfiguration::lastShownTours() const +{ + return String::fromStdString(settings()->value(UI_LAST_SHOWN_TOURS_KEY).toString()).split(u","); +} diff --git a/src/framework/tours/internal/toursconfiguration.h b/src/framework/tours/internal/toursconfiguration.h new file mode 100644 index 0000000000000..02ff41a8af5bf --- /dev/null +++ b/src/framework/tours/internal/toursconfiguration.h @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "io/path.h" + +#include "modularity/ioc.h" +#include "iglobalconfiguration.h" + +#include "tourstypes.h" + +#include "itoursconfiguration.h" + +namespace muse::tours { +class ToursConfiguration : public IToursConfiguration, public Injectable +{ + Inject globalConfiguration = { this }; + +public: + ToursConfiguration(const modularity::ContextPtr& iocCtx) + : Injectable(iocCtx) {} + + String lastShownTourIdForEvent(const String& eventCode) const override; + void setLastShownTourIdForEvent(const String& eventCode, const String& tourId) override; + + io::path_t toursFilePath() const override; + +private: + StringList lastShownTours() const; +}; +} diff --git a/src/framework/tours/internal/toursservice.cpp b/src/framework/tours/internal/toursservice.cpp new file mode 100644 index 0000000000000..3f7ecebfa82f4 --- /dev/null +++ b/src/framework/tours/internal/toursservice.cpp @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursservice.h" + +#include "io/file.h" +#include "serialization/json.h" + +#include "log.h" + +using namespace muse::tours; + +void ToursService::init() +{ + initTours(); +} + +void ToursService::onEvent(const String& eventCode) +{ + if (!muse::contains(m_eventsMap, eventCode)) { + return; + } + + const Tour& tour = m_eventsMap[eventCode]; + + String lastTourId = toursConfiguration()->lastShownTourIdForEvent(eventCode); + if (lastTourId == tour.id) { + return; + } + + toursProvider()->showTour(tour); + + toursConfiguration()->setLastShownTourIdForEvent(eventCode, tour.id); +} + +void ToursService::initTours() +{ + io::path_t filePath = toursConfiguration()->toursFilePath(); + std::unordered_map result; + + ByteArray data; + Ret ret = io::File::readFile(filePath, data); + if (!ret) { + LOGE() << "failed read file: " << filePath << ", err: " << ret.toString(); + return; + } + + std::string err; + JsonObject obj = JsonDocument::fromJson(data, &err).rootObject(); + if (!err.empty()) { + LOGE() << "failed parse file: " << filePath << ", err: " << err; + return; + } + + for (const std::string& eventCode : obj.keys()) { + Tour tour; + + JsonObject tourObj = obj.value(eventCode).toObject(); + tour.id = tourObj.value("id").toString(); + + JsonArray steps = tourObj.value("steps").toArray(); + if (steps.empty()) { + continue; + } + + for (size_t itemIdx = 0; itemIdx < steps.size(); ++itemIdx) { + JsonObject itemObj = steps.at(itemIdx).toObject(); + if (itemObj.empty()) { + continue; + } + + TourStep step; + step.title = itemObj.value("title").toString(); + step.description = itemObj.value("description").toString(); + step.videoExplanationUrl = itemObj.value("video_explanation_url").toString(); + step.controlUri = Uri(itemObj.value("control_uri").toString()); + + tour.steps.emplace_back(step); + } + + if (!tour.steps.empty()) { + result.insert({ String::fromStdString(eventCode), tour }); + } + } + + m_eventsMap = result; +} diff --git a/src/framework/tours/internal/toursservice.h b/src/framework/tours/internal/toursservice.h new file mode 100644 index 0000000000000..769f24264bf04 --- /dev/null +++ b/src/framework/tours/internal/toursservice.h @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "async/asyncable.h" + +#include "modularity/ioc.h" +#include "iinteractive.h" +#include "itoursprovider.h" +#include "itoursconfiguration.h" + +#include "../itoursservice.h" + +namespace muse::tours { +class ToursService : public IToursService, public Injectable, public async::Asyncable +{ + Inject interactive = { this }; + Inject toursProvider = { this }; + Inject toursConfiguration = { this }; + +public: + ToursService(const muse::modularity::ContextPtr& ctx) + : Injectable(ctx) {} + + void init(); + + void onEvent(const String& eventCode) override; + +private: + void initTours(); + + std::unordered_map m_eventsMap; +}; +} diff --git a/src/framework/tours/itoursconfiguration.h b/src/framework/tours/itoursconfiguration.h new file mode 100644 index 0000000000000..913a5c5c7d323 --- /dev/null +++ b/src/framework/tours/itoursconfiguration.h @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "types/string.h" +#include "io/path.h" + +#include "modularity/imoduleinterface.h" + +#include "tourstypes.h" + +namespace muse::tours { +class IToursConfiguration : MODULE_EXPORT_INTERFACE +{ + INTERFACE_ID(IToursConfiguration) + +public: + virtual ~IToursConfiguration() = default; + + virtual String lastShownTourIdForEvent(const String& eventCode) const = 0; + virtual void setLastShownTourIdForEvent(const String& eventCode, const String& tourId) = 0; + + virtual io::path_t toursFilePath() const = 0; +}; +} diff --git a/src/framework/tours/itoursprovider.h b/src/framework/tours/itoursprovider.h new file mode 100644 index 0000000000000..860f9e7b9b537 --- /dev/null +++ b/src/framework/tours/itoursprovider.h @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "modularity/imoduleinterface.h" + +#include "tourstypes.h" + +namespace muse::tours { +class IToursProvider : MODULE_EXPORT_INTERFACE +{ + INTERFACE_ID(IToursProvider) + +public: + virtual ~IToursProvider() = default; + + virtual void showTour(const Tour& tour) = 0; +}; +} diff --git a/src/framework/tours/itoursservice.h b/src/framework/tours/itoursservice.h new file mode 100644 index 0000000000000..df4ae276e9145 --- /dev/null +++ b/src/framework/tours/itoursservice.h @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "types/string.h" + +#include "modularity/imoduleinterface.h" + +namespace muse::tours { +class IToursService : MODULE_EXPORT_INTERFACE +{ + INTERFACE_ID(IToursService) + +public: + virtual ~IToursService() = default; + + virtual void onEvent(const String& eventCode) = 0; +}; +} diff --git a/src/framework/tours/qml/Muse/Tours/ToursProvider.qml b/src/framework/tours/qml/Muse/Tours/ToursProvider.qml new file mode 100644 index 0000000000000..353824d25b085 --- /dev/null +++ b/src/framework/tours/qml/Muse/Tours/ToursProvider.qml @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 + +import Muse.Ui 1.0 +import Muse.UiComponents 1.0 + +import Muse.Tours 1.0 + +import "internal" + +Item { + id: root + + anchors.fill: parent + + property var provider: providerModel.toursProvider + + ToursProviderModel { + id: providerModel + } + + Loader { + id: tourStepLoader + + anchors.fill: parent + + active: false + + sourceComponent: TourStepPopup { + onHideRequested: { + Qt.callLater(unloadTourStep) + } + + onNextRequested: { + Qt.callLater(root.provider.showNext) + } + } + + onLoaded: { + var tourStepPopup = tourStepLoader.item + tourStepPopup.calculateSize() + } + + function loadTourStepPopup() { + tourStepLoader.active = true + } + + function unloadTourStep() { + tourStepLoader.active = false + } + + function open(parent, title, description, videoExplanationUrl, index, total) { + loadTourStepPopup() + + update(parent, title, description, videoExplanationUrl, index, total) + + var tourStepPopup = tourStepLoader.item + tourStepPopup.open() + } + + function close() { + var tourStepPopup = tourStepLoader.item + if (!Boolean(tourStepPopup)) { + return + } + + tourStepPopup.close() + } + + function update(parent, title, description, videoExplanationUrl, index, total) { + var tourStepPopup = tourStepLoader.item + if (!Boolean(tourStepPopup)) { + return + } + + root.parent = parent + tourStepPopup.title = title + tourStepPopup.description = description + tourStepPopup.videoExplanationUrl = videoExplanationUrl + tourStepPopup.index = index + tourStepPopup.total = total + + tourStepPopup.calculateSize() + } + } + + Connections { + target: root.provider + + function onOpenTourStep(parent, title, description, videoExplanationUrl, index, total) { + tourStepLoader.open(parent, title, description, videoExplanationUrl, index, total) + } + } +} diff --git a/src/framework/tours/qml/Muse/Tours/internal/TourStepPopup.qml b/src/framework/tours/qml/Muse/Tours/internal/TourStepPopup.qml new file mode 100644 index 0000000000000..18464df13dfd7 --- /dev/null +++ b/src/framework/tours/qml/Muse/Tours/internal/TourStepPopup.qml @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import Muse.Ui 1.0 +import Muse.UiComponents 1.0 + +StyledPopupView { + id: root + + property alias title: titleLabel.text + property alias description: descriptionLabel.text + property string videoExplanationUrl: "" + + property int index: 0 + property int total: 0 + + padding: 8 + margins: 8 + + signal hideRequested() + signal nextRequested() + + function calculateSize() { + contentWidth = Math.min(content.implicitWidth, 300 - margins * 2) + contentHeight = content.implicitHeight + + x = root.parent.width / 2 - (contentWidth + padding * 2 + margins * 2) / 2 + y = root.parent.height + } + + ColumnLayout { + id: content + + anchors.fill: parent + spacing: 8 + + RowLayout { + id: row + + spacing: 6 + + StyledTextLabel { + id: titleLabel + + font: ui.theme.largeBodyBoldFont + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + maximumLineCount: 3 + } + + Rectangle { + Layout.preferredWidth: newLabel.implicitWidth + 4 + Layout.preferredHeight: newLabel.implicitHeight + 4 + + color: ui.theme.fontPrimaryColor + radius: 2 + + StyledTextLabel { + id: newLabel + + anchors.centerIn: parent + + text: qsTrc("tours", "New") + font: ui.theme.bodyBoldFont + color: ui.theme.backgroundPrimaryColor + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + StyledTextLabel { + text: (root.index) + "/" + root.total + + visible: root.total > 1 + } + } + + StyledTextLabel { + id: descriptionLabel + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + + visible: Boolean(root.description) + } + + ButtonBox { + id: box + + Layout.fillWidth: true + //! hack: it looks like ButtonBox doesn't work well in ColumnLayout + Layout.leftMargin: -4 + + spacing: 4 + + FlatButton { + id: watchVideoBtn + + Layout.preferredWidth: (content.width - box.spacing) / 2 + + text: qsTrc("tours", "Watch video") + icon: IconCode.OPEN_LINK + orientation: Qt.Horizontal + + buttonRole: ButtonBoxModel.CustomRole + buttonId: ButtonBoxModel.CustomButton + 1 + + visible: root.videoExplanationUrl !== "" + + onClicked: { + api.launcher.openUrl(root.videoExplanationUrl) + } + } + + FlatButton { + Layout.preferredWidth: watchVideoBtn.visible ? (content.width - box.spacing) / 2 : 54 + Layout.alignment: Qt.AlignRight + + property bool isLastStep: root.index == root.total + + text: isLastStep ? qsTrc("tours", "Got it") : qsTrc("tours", "Next") + + buttonRole: isLastStep ? ButtonBoxModel.Apply : ButtonBoxModel.Next + buttonId: isLastStep ? ButtonBoxModel.ApplyRole : ButtonBoxModel.ContinueRole + accentButton: true + isNarrow: !watchVideoBtn.visible + + onClicked: { + if (isLastStep) { + root.hideRequested() + } else { + root.nextRequested() + } + } + } + } + } +} diff --git a/src/framework/tours/qml/Muse/Tours/qmldir b/src/framework/tours/qml/Muse/Tours/qmldir new file mode 100644 index 0000000000000..36d024afb48ed --- /dev/null +++ b/src/framework/tours/qml/Muse/Tours/qmldir @@ -0,0 +1,2 @@ +module Muse.Tours +ToursProvider 1.0 ToursProvider.qml diff --git a/src/framework/tours/tours.qrc b/src/framework/tours/tours.qrc new file mode 100644 index 0000000000000..3aad34e0964cb --- /dev/null +++ b/src/framework/tours/tours.qrc @@ -0,0 +1,7 @@ + + + qml/Muse/Tours/ToursProvider.qml + qml/Muse/Tours/internal/TourStepPopup.qml + qml/Muse/Tours/qmldir + + diff --git a/src/framework/tours/toursmodule.cpp b/src/framework/tours/toursmodule.cpp new file mode 100644 index 0000000000000..465ecd503faa1 --- /dev/null +++ b/src/framework/tours/toursmodule.cpp @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursmodule.h" + +#include + +#include "modularity/ioc.h" + +#include "internal/toursservice.h" +#include "internal/toursconfiguration.h" + +#include "view/toursprovider.h" +#include "view/toursprovidermodel.h" + +using namespace muse::tours; +using namespace muse::modularity; + +static void tours_init_qrc() +{ + Q_INIT_RESOURCE(tours); +} + +std::string ToursModule::moduleName() const +{ + return "tours"; +} + +void ToursModule::registerExports() +{ + m_service = std::make_shared(iocContext()); + m_configuration = std::make_shared(iocContext()); + m_provider = std::make_shared(iocContext()); + + ioc()->registerExport(moduleName(), m_service); + ioc()->registerExport(moduleName(), m_configuration); + ioc()->registerExport(moduleName(), m_provider); +} + +void ToursModule::registerResources() +{ + tours_init_qrc(); +} + +void ToursModule::registerUiTypes() +{ + qmlRegisterType("Muse.Tours", 1, 0, "ToursProviderModel"); +} + +void ToursModule::onInit(const IApplication::RunMode& mode) +{ + if (mode != IApplication::RunMode::GuiApp) { + return; + } + + m_service->init(); +} diff --git a/src/framework/tours/toursmodule.h b/src/framework/tours/toursmodule.h new file mode 100644 index 0000000000000..3a9cbded34b35 --- /dev/null +++ b/src/framework/tours/toursmodule.h @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#include "modularity/imodulesetup.h" + +namespace muse::tours { +class ToursService; +class ToursConfiguration; +class ToursProvider; +class ToursModule : public modularity::IModuleSetup +{ +public: + std::string moduleName() const override; + void registerExports() override; + void registerResources() override; + void registerUiTypes() override; + void onInit(const IApplication::RunMode& mode) override; + +private: + std::shared_ptr m_service; + std::shared_ptr m_configuration; + std::shared_ptr m_provider; +}; +} diff --git a/src/framework/tours/tourstypes.h b/src/framework/tours/tourstypes.h new file mode 100644 index 0000000000000..00f64997ce4e8 --- /dev/null +++ b/src/framework/tours/tourstypes.h @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "types/string.h" +#include "types/uri.h" + +namespace muse::tours { +struct TourStep +{ + String title; + String description; + String videoExplanationUrl; + + Uri controlUri; +}; + +using TourStepList = std::vector; + +struct Tour +{ + String id; + TourStepList steps; + + bool operator==(const Tour& other) const + { + return id == other.id; + } +}; +} diff --git a/src/framework/tours/view/toursprovider.cpp b/src/framework/tours/view/toursprovider.cpp new file mode 100644 index 0000000000000..41b7a524f4fa3 --- /dev/null +++ b/src/framework/tours/view/toursprovider.cpp @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursprovider.h" + +#include "log.h" + +using namespace muse; +using namespace muse::tours; + +ToursProvider::ToursProvider(const modularity::ContextPtr& iocCtx) + : QObject(), Injectable(iocCtx) +{ + QObject::connect(&m_openTimer, &QTimer::timeout, this, &ToursProvider::doShow); +} + +void ToursProvider::showTour(const Tour& tour) +{ + m_tour = tour; + m_currentStep = 0; + m_totalSteps = m_tour.steps.size(); + + showNext(); +} + +void ToursProvider::showNext() +{ + m_openTimer.start(); +} + +void ToursProvider::doShow() +{ + m_openTimer.stop(); + + const TourStep& step = m_tour.steps[m_currentStep]; + int index = m_currentStep + 1; + + m_currentStep++; + + QQuickItem* parentItem = findControl(step.controlUri); + IF_ASSERT_FAILED(parentItem) { + return; + } + + emit openTourStep(parentItem, step.title, step.description, step.videoExplanationUrl, index, m_totalSteps); +} + +QQuickItem* ToursProvider::findControl(const Uri& controlUri) +{ + String controlPath = String::fromStdString(controlUri.path()); + + StringList pathItems = controlPath.split('/'); + IF_ASSERT_FAILED(pathItems.size() == 3) { + LOGE() << "Invalid control uri: " << controlUri; + return nullptr; + } + + std::string section = pathItems[0].toStdString(); + std::string panel = pathItems[1].toStdString(); + std::string controlName = pathItems[2].toStdString(); + + const ui::INavigationControl* control = navigationController()->findControl(section, panel, controlName); + return control ? control->visualItem() : nullptr; +} diff --git a/src/framework/tours/view/toursprovider.h b/src/framework/tours/view/toursprovider.h new file mode 100644 index 0000000000000..5d03146df56c8 --- /dev/null +++ b/src/framework/tours/view/toursprovider.h @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include "modularity/ioc.h" +#include "ui/inavigationcontroller.h" + +#include "../itoursprovider.h" + +namespace muse::tours { +class ToursProvider : public QObject, public IToursProvider, public Injectable +{ + Q_OBJECT + + Inject navigationController = { this }; + +public: + explicit ToursProvider(const modularity::ContextPtr& iocCtx); + + void showTour(const Tour& tour) override; + + Q_INVOKABLE void showNext(); + +private slots: + void doShow(); + +signals: + void openTourStep(const QQuickItem* parentItem, const QString& title, const QString& description, const QString& videoExplanationUrl, + size_t index, size_t total); + +private: + QQuickItem* findControl(const Uri& controlUri); + + QTimer m_openTimer; + + Tour m_tour; + size_t m_currentStep = 0; + size_t m_totalSteps = 0; +}; +} diff --git a/src/framework/tours/view/toursprovidermodel.cpp b/src/framework/tours/view/toursprovidermodel.cpp new file mode 100644 index 0000000000000..c2389ba7fd379 --- /dev/null +++ b/src/framework/tours/view/toursprovidermodel.cpp @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "toursprovidermodel.h" + +using namespace muse::tours; + +ToursProviderModel::ToursProviderModel(QObject* parent) + : QObject(parent) +{ +} + +muse::tours::ToursProvider* ToursProviderModel::toursProvider() const +{ + return dynamic_cast(provider().get()); +} diff --git a/src/framework/tours/view/toursprovidermodel.h b/src/framework/tours/view/toursprovidermodel.h new file mode 100644 index 0000000000000..571e0002052b5 --- /dev/null +++ b/src/framework/tours/view/toursprovidermodel.h @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#include "modularity/ioc.h" +#include "tours/view/toursprovider.h" + +namespace muse::tours { +class ToursProviderModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(muse::tours::ToursProvider * toursProvider READ toursProvider CONSTANT) + + Inject provider; + +public: + explicit ToursProviderModel(QObject* parent = nullptr); + + muse::tours::ToursProvider* toursProvider() const; +}; +} diff --git a/src/framework/ui/CMakeLists.txt b/src/framework/ui/CMakeLists.txt index 16d3b6c2f0c46..f47741f2fd4d7 100644 --- a/src/framework/ui/CMakeLists.txt +++ b/src/framework/ui/CMakeLists.txt @@ -162,6 +162,10 @@ if (OS_IS_MAC) set(MODULE_LINK ${MODULE_LINK} ${AppKit}) endif() +set(MODULE_LINK ${MODULE_LINK} + muse::tours +) + setup_module() if (MUSE_MODULE_UI_TESTS) diff --git a/src/framework/ui/inavigation.h b/src/framework/ui/inavigation.h index 8347e5548dc07..4e785f3d96ce5 100644 --- a/src/framework/ui/inavigation.h +++ b/src/framework/ui/inavigation.h @@ -103,6 +103,7 @@ class INavigation virtual async::Channel activeChanged() const = 0; virtual QWindow* window() const = 0; + virtual QQuickItem* visualItem() const = 0; virtual void onEvent(EventPtr e) = 0; }; diff --git a/src/framework/ui/inavigationcontroller.h b/src/framework/ui/inavigationcontroller.h index 79c46e3d2d556..b111440e9dff3 100644 --- a/src/framework/ui/inavigationcontroller.h +++ b/src/framework/ui/inavigationcontroller.h @@ -48,6 +48,9 @@ class INavigationController : MODULE_EXPORT_INTERFACE virtual INavigationPanel* activePanel() const = 0; virtual INavigationControl* activeControl() const = 0; + virtual const INavigationControl* findControl(const std::string& section, const std::string& panel, + const std::string& controlName) const = 0; + virtual void setDefaultNavigationControl(INavigationControl* control) = 0; virtual async::Notification navigationChanged() const = 0; diff --git a/src/framework/ui/internal/interactiveuriregister.h b/src/framework/ui/internal/interactiveuriregister.h index 4a7fc276b43b5..f52445c98f7c9 100644 --- a/src/framework/ui/internal/interactiveuriregister.h +++ b/src/framework/ui/internal/interactiveuriregister.h @@ -24,15 +24,6 @@ #include "iinteractiveuriregister.h" -template<> -struct std::hash -{ - std::size_t operator()(const muse::Uri& uri) const noexcept - { - return std::hash {}(uri.toString()); - } -}; - namespace muse::ui { class InteractiveUriRegister : public IInteractiveUriRegister { diff --git a/src/framework/ui/internal/navigationcontroller.cpp b/src/framework/ui/internal/navigationcontroller.cpp index d34a4f6429d76..c27ab1497ed5f 100644 --- a/src/framework/ui/internal/navigationcontroller.cpp +++ b/src/framework/ui/internal/navigationcontroller.cpp @@ -648,6 +648,16 @@ INavigationControl* NavigationController::activeControl() const return findActive(activePanel->controls()); } +const INavigationControl* NavigationController::findControl(const std::string& section, const std::string& panel, + const std::string& controlName) const +{ + const INavigationSection* sec = findByName(m_sections, QString::fromStdString(section)); + const INavigationPanel* pnl = sec ? findByName(sec->panels(), QString::fromStdString(panel)) : nullptr; + const INavigationControl* ctrl = pnl ? findByName(pnl->controls(), QString::fromStdString(controlName)) : nullptr; + + return ctrl; +} + void NavigationController::setDefaultNavigationControl(INavigationControl* control) { m_defaultNavigationControl = control; diff --git a/src/framework/ui/internal/navigationcontroller.h b/src/framework/ui/internal/navigationcontroller.h index 0923b93a5bb05..074a7182a5337 100644 --- a/src/framework/ui/internal/navigationcontroller.h +++ b/src/framework/ui/internal/navigationcontroller.h @@ -68,6 +68,9 @@ class NavigationController : public QObject, public INavigationController, publi INavigationPanel* activePanel() const override; INavigationControl* activeControl() const override; + const INavigationControl* findControl(const std::string& section, const std::string& panel, + const std::string& controlName) const override; + void setDefaultNavigationControl(INavigationControl* control) override; void resetNavigation() override; diff --git a/src/framework/ui/internal/uiengine.h b/src/framework/ui/internal/uiengine.h index a5b08f297994f..0a4dc8b53c3e3 100644 --- a/src/framework/ui/internal/uiengine.h +++ b/src/framework/ui/internal/uiengine.h @@ -65,6 +65,7 @@ class UiEngine : public QObject, public IUiEngine, public Injectable api::ThemeApi* theme() const; QmlToolTip* tooltip() const; QmlDataFormatter* df() const; + InteractiveProvider* interactiveProvider_property() const; std::shared_ptr interactiveProvider() const; @@ -104,7 +105,9 @@ public slots: QStringList m_sourceImportPaths; api::ThemeApi* m_theme = nullptr; QmlTranslation* m_translation = nullptr; + std::shared_ptr m_interactiveProvider = nullptr; + QmlApi* m_api = nullptr; QmlToolTip* m_tooltip = nullptr; QmlDataFormatter* m_dataFormatter = nullptr; diff --git a/src/framework/ui/uitypes.h b/src/framework/ui/uitypes.h index 0bceb8137e9fd..1e5053c131399 100644 --- a/src/framework/ui/uitypes.h +++ b/src/framework/ui/uitypes.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "view/iconcodes.h" // IWYU pragma: export #include "workspace/workspacetypes.h" diff --git a/src/framework/ui/view/abstractnavigation.cpp b/src/framework/ui/view/abstractnavigation.cpp index f0ed644dc162a..7415a46908ed2 100644 --- a/src/framework/ui/view/abstractnavigation.cpp +++ b/src/framework/ui/view/abstractnavigation.cpp @@ -224,12 +224,18 @@ void AbstractNavigation::onEvent(INavigation::EventPtr e) } QWindow* AbstractNavigation::window() const +{ + QQuickItem* visualItem = this->visualItem(); + return visualItem ? visualItem->window() : nullptr; +} + +QQuickItem* AbstractNavigation::visualItem() const { QObject* prn = parent(); while (prn) { QQuickItem* vitem = qobject_cast(prn); if (vitem) { - return vitem->window(); + return vitem; } prn = prn->parent(); diff --git a/src/framework/ui/view/abstractnavigation.h b/src/framework/ui/view/abstractnavigation.h index d5bb16b81ea51..fd8f5232b6eac 100644 --- a/src/framework/ui/view/abstractnavigation.h +++ b/src/framework/ui/view/abstractnavigation.h @@ -83,6 +83,7 @@ class AbstractNavigation : public QObject, public QQmlParserStatus, public Injec void onEvent(INavigation::EventPtr e); QWindow* window() const; + QQuickItem* visualItem() const; // QQmlParserStatus void classBegin() override; diff --git a/src/framework/ui/view/navigationcontrol.cpp b/src/framework/ui/view/navigationcontrol.cpp index 97d6c46dc559a..ea7c627aaf1fe 100644 --- a/src/framework/ui/view/navigationcontrol.cpp +++ b/src/framework/ui/view/navigationcontrol.cpp @@ -100,6 +100,11 @@ QWindow* NavigationControl::window() const return AbstractNavigation::window(); } +QQuickItem* muse::ui::NavigationControl::visualItem() const +{ + return AbstractNavigation::visualItem(); +} + void NavigationControl::trigger() { emit triggered(); diff --git a/src/framework/ui/view/navigationcontrol.h b/src/framework/ui/view/navigationcontrol.h index 6cd75fe2b7814..3415d9581e869 100644 --- a/src/framework/ui/view/navigationcontrol.h +++ b/src/framework/ui/view/navigationcontrol.h @@ -59,6 +59,7 @@ class NavigationControl : public AbstractNavigation, public INavigationControl void onEvent(EventPtr e) override; QWindow* window() const override; + QQuickItem* visualItem() const override; void trigger() override; diff --git a/src/framework/ui/view/navigationpanel.cpp b/src/framework/ui/view/navigationpanel.cpp index 2f6d3b19937cb..c097dc7a6a2d8 100644 --- a/src/framework/ui/view/navigationpanel.cpp +++ b/src/framework/ui/view/navigationpanel.cpp @@ -114,6 +114,11 @@ QWindow* NavigationPanel::window() const return AbstractNavigation::window(); } +QQuickItem* muse::ui::NavigationPanel::visualItem() const +{ + return AbstractNavigation::visualItem(); +} + void NavigationPanel::setDirection(QmlDirection direction) { if (m_direction == direction) { diff --git a/src/framework/ui/view/navigationpanel.h b/src/framework/ui/view/navigationpanel.h index 36f6f7430c746..c88464f1c573d 100644 --- a/src/framework/ui/view/navigationpanel.h +++ b/src/framework/ui/view/navigationpanel.h @@ -67,6 +67,7 @@ class NavigationPanel : public AbstractNavigation, public INavigationPanel void onEvent(EventPtr e) override; QWindow* window() const override; + QQuickItem* visualItem() const override; QmlDirection direction_property() const; QString directionInfo() const; diff --git a/src/framework/ui/view/navigationsection.cpp b/src/framework/ui/view/navigationsection.cpp index 0424a207c73e2..a7165d02990b5 100644 --- a/src/framework/ui/view/navigationsection.cpp +++ b/src/framework/ui/view/navigationsection.cpp @@ -120,6 +120,11 @@ QWindow* NavigationSection::window() const return AbstractNavigation::window(); } +QQuickItem* muse::ui::NavigationSection::visualItem() const +{ + return AbstractNavigation::visualItem(); +} + void NavigationSection::addPanel(NavigationPanel* panel) { TRACEFUNC; diff --git a/src/framework/ui/view/navigationsection.h b/src/framework/ui/view/navigationsection.h index 067a25d076131..e27203e33c61b 100644 --- a/src/framework/ui/view/navigationsection.h +++ b/src/framework/ui/view/navigationsection.h @@ -72,6 +72,7 @@ class NavigationSection : public AbstractNavigation, public INavigationSection void onEvent(EventPtr e) override; QWindow* window() const override; + QQuickItem* visualItem() const override; const std::set& panels() const override; async::Notification panelsListChanged() const override; diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/ButtonBox.qml b/src/framework/uicomponents/qml/Muse/UiComponents/ButtonBox.qml index 1fef8ee01c087..3bebe53a16a84 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/ButtonBox.qml +++ b/src/framework/uicomponents/qml/Muse/UiComponents/ButtonBox.qml @@ -39,6 +39,7 @@ Container { contentHeight + topPadding + bottomPadding) padding: 0 + spacing: 12 property bool isAccessibilityDisabledWhenInit: false property NavigationPanel navigationPanel: NavigationPanel { @@ -112,7 +113,7 @@ Container { } contentItem: RowLayout { - spacing: prv.spacing + spacing: root.spacing Repeater { model: root.contentModel } @@ -148,8 +149,6 @@ Container { QtObject { id: prv - property int spacing: 12 - function layoutButtons() { var buttonsTypes = buttonBoxModel.load() @@ -185,7 +184,7 @@ Container { } } - if (buttonsWidths + buttonsTypes.length * prv.spacing > root.width) { + if (buttonsWidths + buttonsTypes.length * root.spacing > root.width) { return }