diff --git a/muse b/muse index 3e3d513e2161b..02949a9e51b5f 160000 --- a/muse +++ b/muse @@ -1 +1 @@ -Subproject commit 3e3d513e2161b4fa62bdc52800b7c49f21b33b93 +Subproject commit 02949a9e51b5f9607ea71e3b9ca6d9b8c592fb8f diff --git a/src/project/internal/recentfilescontroller.cpp b/src/project/internal/recentfilescontroller.cpp index c0abb70873ab0..26b4fd0a8e54a 100644 --- a/src/project/internal/recentfilescontroller.cpp +++ b/src/project/internal/recentfilescontroller.cpp @@ -33,6 +33,8 @@ #include "global/concurrency/concurrent.h" #endif +#include "log.h" + using namespace mu::project; using namespace muse; using namespace muse::async; @@ -99,6 +101,8 @@ void RecentFilesController::prependRecentFile(const RecentFile& newFile) void RecentFilesController::moveRecentFile(const muse::io::path_t& before, const RecentFile& after) { + TRACEFUNC; + bool moved = false; RecentFilesList newList = m_recentFilesList; @@ -115,6 +119,33 @@ void RecentFilesController::moveRecentFile(const muse::io::path_t& before, const } } +void RecentFilesController::removeRecentFile(const muse::io::path_t& path) +{ + if (path.empty()) { + return; + } + + TRACEFUNC; + + RecentFilesList newList; + newList.reserve(m_recentFilesList.size()); + + bool removed = false; + + for (const RecentFile& file : m_recentFilesList) { + if (file.path == path) { + removed = true; + continue; + } + + newList.push_back(file); + } + + if (removed) { + setRecentFilesList(newList, true); + } +} + void RecentFilesController::clearRecentFiles() { setRecentFilesList({}, true); diff --git a/src/project/internal/recentfilescontroller.h b/src/project/internal/recentfilescontroller.h index 99296e1c64c8f..b81f5cf64791b 100644 --- a/src/project/internal/recentfilescontroller.h +++ b/src/project/internal/recentfilescontroller.h @@ -51,6 +51,7 @@ class RecentFilesController : public IRecentFilesController, public muse::async: void prependRecentFile(const RecentFile& file) override; void moveRecentFile(const muse::io::path_t& before, const RecentFile& after) override; + void removeRecentFile(const muse::io::path_t& path) override; void clearRecentFiles() override; muse::async::Promise thumbnail(const muse::io::path_t& file) const override; diff --git a/src/project/irecentfilescontroller.h b/src/project/irecentfilescontroller.h index e7a860bc9c748..21d62ac91be3e 100644 --- a/src/project/irecentfilescontroller.h +++ b/src/project/irecentfilescontroller.h @@ -44,6 +44,7 @@ class IRecentFilesController : MODULE_CONTEXT_INTERFACE virtual void prependRecentFile(const RecentFile& file) = 0; virtual void moveRecentFile(const muse::io::path_t& before, const RecentFile& after) = 0; + virtual void removeRecentFile(const muse::io::path_t& path) = 0; virtual void clearRecentFiles() = 0; virtual muse::async::Promise thumbnail(const muse::io::path_t& filePath) const = 0; diff --git a/src/project/qml/MuseScore/Project/CMakeLists.txt b/src/project/qml/MuseScore/Project/CMakeLists.txt index 2199e53819df9..289f346a8b2d7 100644 --- a/src/project/qml/MuseScore/Project/CMakeLists.txt +++ b/src/project/qml/MuseScore/Project/CMakeLists.txt @@ -107,6 +107,7 @@ qt_add_qml_module(project_qml internal/ScoresPage/CloudScoresView.qml internal/ScoresPage/RecentScoresView.qml internal/ScoresPage/ScoreGridItem.qml + internal/ScoresPage/ScoreItemMenuButton.qml internal/ScoresPage/ScoreListItem.qml internal/ScoresPage/ScoresGridView.qml internal/ScoresPage/ScoresListView.qml diff --git a/src/project/qml/MuseScore/Project/ScoresPage.qml b/src/project/qml/MuseScore/Project/ScoresPage.qml index 000a4d201d71c..38713cb50a4cf 100644 --- a/src/project/qml/MuseScore/Project/ScoresPage.qml +++ b/src/project/qml/MuseScore/Project/ScoresPage.qml @@ -261,6 +261,14 @@ FocusScope { onOpenScoreRequested: function(scorePath, displayName) { Qt.callLater(scoresPageModel.openScore, scorePath, displayName) } + + onRevealInFileBrowserRequested: function(scorePath) { + Qt.callLater(scoresPageModel.revealInFileBrowser, scorePath) + } + + onViewOnlineRequested: function(scoreId) { + Qt.callLater(scoresPageModel.viewOnline, scoreId) + } } } @@ -288,6 +296,14 @@ FocusScope { Qt.callLater(scoresPageModel.openScore, scorePath, displayName) } + onRevealInFileBrowserRequested: function(scorePath) { + Qt.callLater(scoresPageModel.revealInFileBrowser, scorePath) + } + + onViewOnlineRequested: function(scoreId) { + Qt.callLater(scoresPageModel.viewOnline, scoreId) + } + Connections { target: refreshButton diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/CloudScoresView.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/CloudScoresView.qml index 7bb59874c10ae..ca813f5f7d634 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/CloudScoresView.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/CloudScoresView.qml @@ -82,6 +82,14 @@ ScoresView { onOpenScoreRequested: function(scorePath, displayName) { root.openScoreRequested(scorePath, displayName) } + + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } } } @@ -107,6 +115,14 @@ ScoresView { onOpenScoreRequested: function(scorePath, displayName) { root.openScoreRequested(scorePath, displayName) } + + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } } } diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/RecentScoresView.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/RecentScoresView.qml index 6c878688a582d..f7489c1782cc7 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/RecentScoresView.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/RecentScoresView.qml @@ -47,6 +47,7 @@ ScoresView { model: recentScoresModel searchText: root.searchText + allowRemoveFromRecentFiles: true isNoResultsMessageAllowed: false // provided by the model instead @@ -65,6 +66,18 @@ ScoresView { onOpenScoreRequested: function(scorePath, displayName) { root.openScoreRequested(scorePath, displayName) } + + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } + + onRemoveFromRecentFilesRequested: function(scorePath) { + recentScoresModel.removeRecentScore(scorePath) + } } } @@ -78,6 +91,7 @@ ScoresView { model: recentScoresModel searchText: root.searchText + allowRemoveFromRecentFiles: true backgroundColor: root.backgroundColor sideMargin: root.sideMargin @@ -97,6 +111,18 @@ ScoresView { root.openScoreRequested(scorePath, displayName) } + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } + + onRemoveFromRecentFilesRequested: function(scorePath) { + recentScoresModel.removeRecentScore(scorePath) + } + columns: [ ScoresListView.ColumnItem { id: modifiedColumn diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreGridItem.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreGridItem.qml index 2b423909cc49e..c1bb0e48e2c05 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreGridItem.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreGridItem.qml @@ -39,10 +39,14 @@ FocusScope { property bool isNoResultsFound: false property bool isCloud: false property int cloudScoreId: 0 + property bool showRemoveFromRecentFiles: false property alias navigation: navCtrl signal clicked() + signal revealInFileBrowserRequested(string scorePath) + signal viewOnlineRequested(int scoreId) + signal removeFromRecentFilesRequested(string scorePath) NavigationControl { id: navCtrl @@ -62,13 +66,23 @@ FocusScope { } MouseArea { - id: mouseArea + id: rootMouseArea anchors.fill: parent enabled: root.enabled hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: function(mouse) { + navCtrl.requestActiveByInteraction() + + if (mouse.button === Qt.RightButton) { + if (contextMenu.menuModel.length > 0) { + contextMenu.show(Qt.point(mouse.x, mouse.y), root) + } + return + } - onClicked: { root.clicked() } } @@ -98,7 +112,7 @@ FocusScope { sourceComponent: { if (root.isCreateNew) { - return addComp + return createNewComp } if (root.isNoResultsFound) { @@ -133,7 +147,7 @@ FocusScope { states: [ State { name: "NORMAL" - when: !mouseArea.containsMouse && !mouseArea.pressed + when: !rootMouseArea.containsMouse && !rootMouseArea.pressed PropertyChanges { target: thumbnail @@ -143,7 +157,7 @@ FocusScope { State { name: "HOVERED" - when: mouseArea.containsMouse && !mouseArea.pressed + when: rootMouseArea.containsMouse && !rootMouseArea.pressed PropertyChanges { target: thumbnail @@ -154,7 +168,7 @@ FocusScope { State { name: "PRESSED" - when: mouseArea.pressed + when: rootMouseArea.pressed PropertyChanges { target: thumbnail @@ -173,6 +187,35 @@ FocusScope { } } + ScoreItemMenuButton { + id: contextMenu + + anchors.top: parent.top + anchors.topMargin: 8 + anchors.right: parent.right + anchors.rightMargin: 8 + visible: menuModel.length > 0 + && (rootMouseArea.containsMouse + || mouseArea.containsMouse + || root.navigation.active + || navigation.active + || isMenuOpenedByButton) + + isCreateNew: root.isCreateNew + isNoResultsFound: root.isNoResultsFound + isCloud: root.isCloud + showRemoveFromRecentFiles: root.showRemoveFromRecentFiles + + navigation.panel: root.navigation.panel + navigation.row: root.navigation.row + navigation.column: root.navigation.column + 1 + + onOpenRequested: root.clicked() + onViewOnlineRequested: root.viewOnlineRequested(root.cloudScoreId) + onRevealInFileBrowserRequested: root.revealInFileBrowserRequested(root.path) + onRemoveFromRecentFilesRequested: root.removeFromRecentFilesRequested(root.path) + } + Loader { active: root.isCloud @@ -208,7 +251,7 @@ FocusScope { navigation.panel: root.navigation.panel navigation.row: root.navigation.row - navigation.column: root.navigation.column + 1 + navigation.column: root.navigation.column + 2 } CloudScoreIndicatorButton { @@ -219,7 +262,7 @@ FocusScope { navigation.panel: root.navigation.panel navigation.row: root.navigation.row - navigation.column: root.navigation.column + 2 + navigation.column: root.navigation.column + 3 onClicked: { if (isProgress) { @@ -264,7 +307,7 @@ FocusScope { } Component { - id: addComp + id: createNewComp Rectangle { anchors.fill: parent diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreItemMenuButton.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreItemMenuButton.qml new file mode 100644 index 0000000000000..1407c80c862b9 --- /dev/null +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreItemMenuButton.qml @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited 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 + +import Muse.Ui +import Muse.UiComponents + +FlatButton { + id: root + + property bool isCreateNew: false + property bool isNoResultsFound: false + property bool isCloud: false + property bool showRemoveFromRecentFiles: false + property alias isMenuOpened: menuLoader.isMenuOpened + property bool isMenuOpenedByButton: menuLoader.isMenuOpened && menuLoader.parent === root + property alias menuAnchorItem: menuLoader.menuAnchorItem + property alias parentWindow: menuLoader.parentWindow + property int menuAlign: 0 + + signal openRequested() + signal revealInFileBrowserRequested() + signal viewOnlineRequested() + signal removeFromRecentFilesRequested() + + function show(position, item) { + let target = item ? item : root + menuLoader.parent = target + + if (menuLoader.isMenuOpened) { + menuLoader.update(root.menuModel, position.x, position.y) + } else { + menuLoader.open(root.menuModel, position.x, position.y) + } + } + + function toggleMenu(item, x, y) { + menuLoader.parent = item ? item : root + menuLoader.toggleOpened(root.menuModel, x, y) + } + + function closeMenu() { + menuLoader.close() + } + + readonly property var menuModel: { + if (root.isCreateNew || root.isNoResultsFound) { + return [] + } + + let result = [ + { id: "open", title: qsTrc("project", "Open") } + ] + + if (root.isCloud) { + result.push({ id: "view-online", title: qsTrc("project", "View online") }) + } else { + result.push({ id: "reveal-in-file-browser", title: qsTrc("project", "Reveal in file browser") }) + } + + if (root.showRemoveFromRecentFiles) { + result.push({}) // separator + result.push({ id: "remove-from-recent-files", title: qsTrc("project", "Remove from recent files list") }) + } + + return result + } + + enabled: root.menuModel.length > 0 + + icon: IconCode.MENU_THREE_DOTS + transparent: !isMenuOpenedByButton + accentButton: isMenuOpenedByButton + width: 20 + height: 20 + + navigation.accessible.name: qsTrc("ui", "Menu") + + StyledMenuLoader { + id: menuLoader + + onHandleMenuItem: function(itemId) { + switch (itemId) { + case "open": + root.openRequested() + break + case "view-online": + root.viewOnlineRequested() + break + case "reveal-in-file-browser": + root.revealInFileBrowserRequested() + break + case "remove-from-recent-files": + root.removeFromRecentFilesRequested() + break + } + } + } + + onClicked: { + menuLoader.parent = root + + if (root.menuAlign !== 0) { + menuLoader.toggleOpenedWithAlign(root.menuModel, root.menuAlign) + } else { + menuLoader.toggleOpened(root.menuModel) + } + } +} diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreListItem.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreListItem.qml index bf9674d0e9722..87864346a644e 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreListItem.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoreListItem.qml @@ -38,6 +38,11 @@ ListItemBlank { property real itemInset: 12 property real columnSpacing: 44 property alias showBottomBorder: bottomBorder.visible + property bool showRemoveFromRecentFiles: false + + signal revealInFileBrowserRequested(string scorePath) + signal viewOnlineRequested(int scoreId) + signal removeFromRecentFilesRequested(string scorePath) implicitHeight: 64 @@ -50,6 +55,17 @@ ListItemBlank { focusBorder.anchors.bottomMargin: bottomBorder.visible ? bottomBorder.height : 0 + MouseArea { + anchors.fill: parent + enabled: root.visible && root.enabled + acceptedButtons: Qt.RightButton + onClicked: function(mouse) { + if (contextMenu.menuModel.length > 0) { + contextMenu.show(Qt.point(mouse.x, mouse.y), root) + } + } + } + RowLayout { anchors.fill: parent anchors.leftMargin: root.itemInset @@ -86,6 +102,34 @@ ListItemBlank { horizontalAlignment: Text.AlignLeft } + ScoreItemMenuButton { + id: contextMenu + + isCreateNew: root.score.isCreateNew ?? false + isNoResultsFound: root.score.isNoResultsFound ?? false + isCloud: root.score.isCloud ?? false + showRemoveFromRecentFiles: root.showRemoveFromRecentFiles + + Layout.alignment: Qt.AlignTrailing | Qt.AlignVCenter + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + visible: menuModel.length > 0 + && (root.mouseArea.containsMouse + || mouseArea.containsMouse + || root.navigation.active + || navigation.active + || isMenuOpenedByButton) + + navigation.panel: root.navigation.panel + navigation.row: root.navigation.row + navigation.column: 1 + + onOpenRequested: root.clicked(null) + onViewOnlineRequested: root.viewOnlineRequested(root.score.scoreId ?? 0) + onRevealInFileBrowserRequested: root.revealInFileBrowserRequested(root.score.path ?? "") + onRemoveFromRecentFilesRequested: root.removeFromRecentFilesRequested(root.score.path ?? "") + } + Loader { active: root.score.isCloud ?? false diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresGridView.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresGridView.qml index 84c0dcbb1fffd..ccd1d3526038f 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresGridView.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresGridView.qml @@ -33,6 +33,7 @@ Item { property string searchText property bool isNoResultsMessageAllowed: true + property bool allowRemoveFromRecentFiles: false property color backgroundColor: ui.theme.backgroundSecondaryColor property real sideMargin: 46 @@ -43,6 +44,9 @@ Item { signal createNewScoreRequested() signal openScoreRequested(var scorePath, var displayName) + signal revealInFileBrowserRequested(var scorePath) + signal viewOnlineRequested(var scoreId) + signal removeFromRecentFilesRequested(var scorePath) clip: true @@ -145,7 +149,7 @@ Item { navigation.panel: navPanel navigation.row: view.columns === 0 ? 0 : Math.floor(model.index / view.columns) - navigation.column: (model.index - (navigation.row * view.columns)) * 3 // * 3 because of controls inside ScoreItem + navigation.column: (model.index - (navigation.row * view.columns)) * 4 // * 4 because of controls inside ScoreItem navigation.onActiveChanged: { if (navigation.active) { view.positionViewAtIndex(index, GridView.Contain) @@ -161,6 +165,7 @@ Item { isCloud: score.isCloud cloudScoreId: score.scoreId ?? 0 timeSinceModified: score.timeSinceModified ?? "" + showRemoveFromRecentFiles: root.allowRemoveFromRecentFiles onClicked: { if (isCreateNew) { @@ -169,6 +174,18 @@ Item { root.openScoreRequested(score.path, score.name) } } + + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } + + onRemoveFromRecentFilesRequested: function(scorePath) { + root.removeFromRecentFilesRequested(scorePath) + } } } } diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresListView.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresListView.qml index 17d847d000e02..775c3b1d3742e 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresListView.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresListView.qml @@ -34,6 +34,7 @@ Item { property list columns property alias showNewScoreItem: newScoreItem.visible property string searchText + property bool allowRemoveFromRecentFiles: false property color backgroundColor: ui.theme.backgroundSecondaryColor property real sideMargin: 46 @@ -44,6 +45,9 @@ Item { signal createNewScoreRequested() signal openScoreRequested(var scorePath, var displayName) + signal revealInFileBrowserRequested(var scorePath) + signal viewOnlineRequested(var scoreId) + signal removeFromRecentFilesRequested(var scorePath) component ColumnItem : QtObject { property string header @@ -100,7 +104,8 @@ Item { navigation.column: 0 score: { - "name": qsTrc("project", "New score") + "name": qsTrc("project", "New score"), + "isCreateNew": true } thumbnailComponent: Rectangle { @@ -210,6 +215,7 @@ Item { itemInset: view.itemInset implicitHeight: view.rowHeight columnSpacing: view.columnSpacing + showRemoveFromRecentFiles: root.allowRemoveFromRecentFiles navigation.panel: navPanel navigation.row: index + 1 @@ -218,6 +224,18 @@ Item { onClicked: { root.openScoreRequested(score.path, score.name) } + + onRevealInFileBrowserRequested: function(scorePath) { + root.revealInFileBrowserRequested(scorePath) + } + + onViewOnlineRequested: function(scoreId) { + root.viewOnlineRequested(scoreId) + } + + onRemoveFromRecentFilesRequested: function(scorePath) { + root.removeFromRecentFilesRequested(scorePath) + } } } } diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresView.qml b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresView.qml index 5e36714e09692..776dc52783b4e 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresView.qml +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/ScoresView.qml @@ -40,4 +40,6 @@ Loader { signal createNewScoreRequested() signal openScoreRequested(var scorePath, var displayName) + signal revealInFileBrowserRequested(var scorePath) + signal viewOnlineRequested(var scoreId) } diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.cpp b/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.cpp index c9a44bb430f9d..a497191ab5721 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.cpp +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.cpp @@ -46,6 +46,11 @@ void RecentScoresModel::load() }); } +void RecentScoresModel::removeRecentScore(const QString& scorePath) +{ + recentFilesController()->removeRecentFile(scorePath); +} + void RecentScoresModel::setRecentScores(const std::vector& items) { if (m_items == items) { diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.h b/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.h index e4cc14167e18b..03388c296bb87 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.h +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/recentscoresmodel.h @@ -46,6 +46,7 @@ class RecentScoresModel : public AbstractScoresModel, public muse::async::Asynca RecentScoresModel(QObject* parent = nullptr); void load() override; + Q_INVOKABLE void removeRecentScore(const QString& scorePath); QList nonScoreItemIndices() const override; diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.cpp b/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.cpp index 0a656a49286cb..8f0aea2a0f7e0 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.cpp +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.cpp @@ -25,6 +25,7 @@ #include #include "actions/actiontypes.h" +#include "log.h" using namespace mu::project; using namespace muse::actions; @@ -49,6 +50,35 @@ void ScoresPageModel::openScore(const QString& scorePath, const QString& display dispatcher()->dispatch("file-open", ActionData::make_arg2(QUrl::fromLocalFile(scorePath), displayNameOverride)); } +void ScoresPageModel::revealInFileBrowser(const QString& scorePath) +{ + muse::Ret ret = platformInteractive()->revealInFileBrowser(scorePath); + if (!ret) { + LOGE() << ret.toString(); + } +} + +void ScoresPageModel::viewOnline(int scoreId) +{ + if (scoreId <= 0) { + return; + } + + muse::RetVal scoreInfo = museScoreComService()->downloadScoreInfo(scoreId); + if (!scoreInfo.ret) { + LOGE() << scoreInfo.ret.toString(); + return; + } + + QUrl scoreUrl = QUrl::fromUserInput(scoreInfo.val.url); + if (!scoreUrl.isValid() || scoreUrl.isEmpty()) { + LOGE() << "Invalid score URL for cloud score" << scoreId << ":" << scoreInfo.val.url; + return; + } + + platformInteractive()->openUrl(scoreUrl); +} + void ScoresPageModel::openScoreManager() { platformInteractive()->openUrl(museScoreComService()->scoreManagerUrl()); diff --git a/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.h b/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.h index c156960a2137c..621646ea3469a 100644 --- a/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.h +++ b/src/project/qml/MuseScore/Project/internal/ScoresPage/scorespagemodel.h @@ -66,6 +66,8 @@ class ScoresPageModel : public QObject, public muse::Contextable Q_INVOKABLE void createNewScore(); Q_INVOKABLE void openOther(); Q_INVOKABLE void openScore(const QString& scorePath, const QString& displayNameOverride); + Q_INVOKABLE void revealInFileBrowser(const QString& scorePath); + Q_INVOKABLE void viewOnline(int scoreId); Q_INVOKABLE void openScoreManager(); signals: