LCOV - code coverage report
Current view: top level - src/appshell/qml/MuseScore/AppShell - notationstatusbarmodel.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 228 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-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 "notationstatusbarmodel.h"
      24              : 
      25              : #include "defer.h"
      26              : #include "log.h"
      27              : #include "thirdparty/kors_logger/src/log_base.h"
      28              : #include "types/translatablestring.h"
      29              : 
      30              : #include "muse_framework_config.h"
      31              : #include <algorithm>
      32              : 
      33              : #ifdef MUSE_MODULE_WORKSPACE
      34              : #include "workspace/qml/Muse/Workspace/workspacesmenumodel.h"
      35              : #endif
      36              : 
      37              : #include "engraving/dom/score.h"
      38              : 
      39              : #include "notation/inotation.h"
      40              : #include "notation/inotationaccessibility.h"
      41              : #include "notation/inotationstyle.h" // IWYU pragma: keep
      42              : #include "notation/inotationundostack.h" // IWYU pragma: keep
      43              : #include "notation/inotationviewstate.h" // IWYU pragma: keep
      44              : 
      45              : #include "notationscene/notationcommands.h"
      46              : #include "workspace/workspacecommands.h"
      47              : 
      48              : using namespace mu::appshell;
      49              : using namespace mu::notation;
      50              : using namespace muse;
      51              : using namespace muse::rcommand;
      52              : using namespace muse::ui;
      53              : using namespace muse::uicomponents;
      54              : using namespace muse::workspace;
      55              : 
      56              : static const QString ICON_KEY("icon");
      57              : static const QString SELECTABLE_KEY("selectable");
      58              : static const QString SELECTED_KEY("selected");
      59              : static const QString TYPE_KEY("type");
      60              : static const QString ID_KEY("id");
      61              : static const QString VALUE_KEY("value");
      62              : 
      63              : static constexpr int MIN_DISPLAYED_ZOOM_PERCENTAGE = 25;
      64              : 
      65              : static const std::map<ViewMode, Command> ALL_VIEW_MODE_MAP {
      66              :     { ViewMode::PAGE, VIEW_MODE_PAGE_COMMAND },
      67              :     { ViewMode::LINE, VIEW_MODE_CONTINUOUS_COMMAND },
      68              :     { ViewMode::SYSTEM, VIEW_MODE_SINGLE_COMMAND },
      69              :     { ViewMode::FLOAT, VIEW_MODE_FLOAT_COMMAND },
      70              : };
      71              : 
      72            0 : static Command zoomTypeToCommand(ZoomType type)
      73              : {
      74            0 :     switch (type) {
      75            0 :     case ZoomType::Percentage: return ZOOM_TO_PERCENT_COMMAND;
      76            0 :     case ZoomType::PageWidth: return ZOOM_TO_PAGE_WIDTH_COMMAND;
      77            0 :     case ZoomType::WholePage: return ZOOM_TO_WHOLE_PAGE_COMMAND;
      78            0 :     case ZoomType::TwoPages: return ZOOM_TO_TWO_PAGES_COMMAND;
      79              :     }
      80              : 
      81            0 :     return {};
      82              : }
      83              : 
      84            0 : NotationStatusBarModel::NotationStatusBarModel(QObject* parent)
      85            0 :     : QObject(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
      86              : {
      87              : #ifdef MUSE_MODULE_WORKSPACE
      88            0 :     m_workspacesMenuModel = std::make_shared<WorkspacesMenuModel>(this);
      89              : #endif
      90            0 : }
      91              : 
      92            0 : void NotationStatusBarModel::classBegin()
      93              : {
      94            0 :     init();
      95            0 : }
      96              : 
      97            0 : void NotationStatusBarModel::init()
      98              : {
      99            0 :     TRACEFUNC;
     100              : 
     101            0 :     m_concertPitchItem = makeMenuItem(TOGGLE_CONCERT_PITCH_COMMAND);
     102            0 :     m_currentWorkspaceItem = makeMenuItem(WORKSPACES_CONFIGURE_COMMAND);
     103              : 
     104            0 :     setNotation(context()->currentNotation());
     105              : 
     106            0 :     context()->currentNotationChanged().onNotify(this, [this]() {
     107            0 :         setNotation(context()->currentNotation());
     108            0 :     });
     109              : 
     110              : #ifdef MUSE_MODULE_WORKSPACE
     111            0 :     m_workspacesMenuModel->load();
     112            0 :     connect(m_workspacesMenuModel.get(), &WorkspacesMenuModel::itemsChanged, this, [this]() {
     113            0 :         updateCurrentWorkspaceItem();
     114            0 :     });
     115            0 :     workspaceConfiguration()->currentWorkspaceNameChanged().onReceive(this, [this](const std::string&){
     116            0 :         updateCurrentWorkspaceItem();
     117            0 :     });
     118              : #endif
     119              : 
     120            0 :     updateCurrentWorkspaceItem();
     121            0 : }
     122              : 
     123            0 : QString NotationStatusBarModel::accessibilityInfo() const
     124              : {
     125            0 :     return accessibility() ? QString::fromStdString(accessibility()->accessibilityInfo().val) : QString();
     126              : }
     127              : 
     128            0 : MenuItem* NotationStatusBarModel::concertPitchItem()
     129              : {
     130            0 :     return m_concertPitchItem;
     131              : }
     132              : 
     133            0 : void NotationStatusBarModel::updateConcertPitchItem()
     134              : {
     135            0 :     m_concertPitchItem->setEnabled(m_notation ? true : false);
     136            0 :     m_concertPitchItem->setChecked(m_notation ? m_notation->style()->styleValue(StyleId::concertPitch).toBool() : false);
     137            0 : }
     138              : 
     139            0 : MenuItem* NotationStatusBarModel::currentWorkspaceItem()
     140              : {
     141            0 :     return m_currentWorkspaceItem;
     142              : }
     143              : 
     144            0 : void NotationStatusBarModel::updateCurrentWorkspaceItem()
     145              : {
     146            0 :     m_currentWorkspaceItem->setTitle(
     147            0 :         muse::TranslatableString::untranslatable("%1 %2")
     148            0 :         .arg(muse::TranslatableString("workspace", "Workspace:"),
     149            0 :              String::fromStdString(workspaceConfiguration()->currentWorkspaceName())));
     150              : 
     151              : #ifdef MUSE_MODULE_WORKSPACE
     152            0 :     m_currentWorkspaceItem->setSubitems(m_workspacesMenuModel->items());
     153              : #endif
     154            0 : }
     155              : 
     156            0 : MenuItem* NotationStatusBarModel::makeMenuItem(const Command& command)
     157              : {
     158            0 :     MenuItem* item = new MenuItem(commandsRegister()->commandInfo(command), this);
     159            0 :     item->setCommandState(commandsState()->commandState(command));
     160            0 :     return item;
     161              : }
     162              : 
     163            0 : MenuItem* NotationStatusBarModel::currentViewMode()
     164              : {
     165            0 :     ViewMode viewMode = m_notation ? m_notation->viewMode() : ViewMode::PAGE;
     166              : 
     167            0 :     for (MenuItem* modeItem : m_availableViewModeList) {
     168            0 :         ViewMode mode = muse::key(ALL_VIEW_MODE_MAP, modeItem->command());
     169              : 
     170            0 :         if (mode == viewMode) {
     171            0 :             if (viewMode == ViewMode::LINE || viewMode == ViewMode::SYSTEM) {
     172              :                 // In continuous view, we don't want to see "horizontal" or "vertical" (those should only be visible in the menu)
     173            0 :                 modeItem->setTitle(muse::TranslatableString("notation", "Continuous view"));
     174              :             }
     175              : 
     176            0 :             return modeItem;
     177              :         }
     178              :     }
     179              : 
     180            0 :     return nullptr;
     181              : }
     182              : 
     183            0 : void NotationStatusBarModel::initAvailableViewModeList()
     184              : {
     185            0 :     TRACEFUNC;
     186              : 
     187            0 :     qDeleteAll(m_availableViewModeList);
     188            0 :     m_availableViewModeList.clear();
     189              : 
     190            0 :     DEFER {
     191            0 :         emit availableViewModeListChanged();
     192            0 :         emit currentViewModeChanged();
     193            0 :     };
     194              : 
     195            0 :     if (!m_notation) {
     196            0 :         return;
     197              :     }
     198              : 
     199            0 :     ViewMode currentViewMode = m_notation->viewMode();
     200              : 
     201            0 :     for (const auto& pair: ALL_VIEW_MODE_MAP) {
     202            0 :         if (pair.first == ViewMode::FLOAT && !globalConfiguration()->devModeEnabled()) {
     203            0 :             continue;
     204              :         }
     205              : 
     206            0 :         MenuItem* viewModeItem = makeMenuItem(pair.second);
     207            0 :         viewModeItem->setEnabled(true);
     208            0 :         viewModeItem->setSelectable(true);
     209            0 :         viewModeItem->setSelected(currentViewMode == pair.first);
     210              : 
     211            0 :         m_availableViewModeList << viewModeItem;
     212              :     }
     213            0 : }
     214              : 
     215            0 : bool NotationStatusBarModel::zoomEnabled() const
     216              : {
     217            0 :     return m_notation != nullptr;
     218              : }
     219              : 
     220            0 : int NotationStatusBarModel::currentZoomPercentage() const
     221              : {
     222            0 :     if (!m_notation) {
     223            0 :         return 100;
     224              :     }
     225              : 
     226            0 :     return m_notation->viewState()->zoomPercentage().val;
     227              : }
     228              : 
     229            0 : void NotationStatusBarModel::setCurrentZoomPercentage(int zoomPercentage)
     230              : {
     231            0 :     if (zoomPercentage == currentZoomPercentage()) {
     232            0 :         return;
     233              :     }
     234              : 
     235            0 :     dispatch(zoomTypeToCommand(ZoomType::Percentage), { { "percent", Val(zoomPercentage) } });
     236            0 : }
     237              : 
     238            0 : ZoomType NotationStatusBarModel::currentZoomType() const
     239              : {
     240            0 :     if (!m_notation) {
     241            0 :         return ZoomType::Percentage;
     242              :     }
     243              : 
     244            0 :     return m_notation->viewState()->zoomType().val;
     245              : }
     246              : 
     247            0 : void NotationStatusBarModel::setNotation(const INotationPtr& notation)
     248              : {
     249            0 :     if (m_notation == notation) {
     250            0 :         return;
     251              :     }
     252              : 
     253            0 :     if (m_notation) {
     254            0 :         m_notation->undoStack()->changesChannel().disconnect(this);
     255            0 :         m_notation->viewModeChanged().disconnect(this);
     256            0 :         m_notation->viewState()->zoomPercentage().ch.disconnect(this);
     257            0 :         m_notation->viewState()->zoomType().ch.disconnect(this);
     258            0 :         m_notation->accessibility()->accessibilityInfo().ch.disconnect(this);
     259              :     }
     260              : 
     261            0 :     m_notation = notation;
     262              : 
     263            0 :     emit zoomEnabledChanged();
     264            0 :     updateConcertPitchItem();
     265              : 
     266            0 :     initAvailableViewModeList();
     267            0 :     initAvailableZoomList();
     268              : 
     269            0 :     if (!notation) {
     270            0 :         return;
     271              :     }
     272              : 
     273            0 :     notation->undoStack()->changesChannel().onReceive(this, [this](const mu::engraving::ScoreChanges& changes) {
     274            0 :         if (muse::contains(changes.changedStyleIdSet, mu::engraving::Sid::concertPitch)) {
     275            0 :             updateConcertPitchItem();
     276              :         }
     277            0 :     });
     278              : 
     279            0 :     notation->viewModeChanged().onNotify(this, [this]() {
     280            0 :         initAvailableViewModeList();
     281            0 :     });
     282              : 
     283            0 :     notation->viewState()->zoomPercentage().ch.onReceive(this, [this](int) {
     284            0 :         initAvailableZoomList();
     285            0 :     });
     286              : 
     287            0 :     notation->viewState()->zoomType().ch.onReceive(this, [this](ZoomType) {
     288            0 :         initAvailableZoomList();
     289            0 :     });
     290              : 
     291            0 :     emit accessibilityInfoChanged();
     292              : 
     293            0 :     notation->accessibility()->accessibilityInfo().ch.onReceive(this, [this](const std::string&) {
     294            0 :         emit accessibilityInfoChanged();
     295            0 :     });
     296              : }
     297              : 
     298            0 : void NotationStatusBarModel::toggleConcertPitch()
     299              : {
     300            0 :     dispatch(TOGGLE_CONCERT_PITCH_COMMAND);
     301            0 : }
     302              : 
     303            0 : void NotationStatusBarModel::setCurrentViewMode(const QString& modeCode)
     304              : {
     305            0 :     dispatch(Command(modeCode.toStdString()));
     306            0 : }
     307              : 
     308            0 : void NotationStatusBarModel::initAvailableZoomList()
     309              : {
     310            0 :     TRACEFUNC;
     311              : 
     312            0 :     qDeleteAll(m_availableZoomList);
     313            0 :     m_availableZoomList.clear();
     314              : 
     315            0 :     DEFER {
     316            0 :         emit availableZoomListChanged();
     317            0 :         emit currentZoomPercentageChanged();
     318            0 :     };
     319              : 
     320            0 :     if (!m_notation) {
     321            0 :         return;
     322              :     }
     323              : 
     324            0 :     int currZoomPercentage = currentZoomPercentage();
     325            0 :     ZoomType currZoomType = currentZoomType();
     326              : 
     327            0 :     auto zoomPercentageTitle = [](int percentage) {
     328            0 :         return muse::TranslatableString::untranslatable("%1%").arg(percentage);
     329              :     };
     330              : 
     331              :     auto buildZoomItem
     332            0 :         = [this, currZoomType, currZoomPercentage](ZoomType type, const muse::TranslatableString& title = {}, int value = 0) {
     333            0 :         MenuItem* menuItem = new MenuItem(this);
     334            0 :         menuItem->setId(QString::number(static_cast<int>(type)) + QString::number(value));
     335            0 :         menuItem->setTitle(title.isEmpty() ? zoomTypeTitle(type) : title);
     336            0 :         menuItem->setSelectable(true);
     337            0 :         if (currZoomType == type) {
     338            0 :             menuItem->setSelected(type == ZoomType::Percentage ? value == currZoomPercentage : true);
     339              :         }
     340              : 
     341            0 :         menuItem->setParams({ { "type", Val(type) }, { "percent", Val(value) } });
     342              : 
     343            0 :         return menuItem;
     344            0 :     };
     345              : 
     346            0 :     QList<int> possibleZoomList = possibleZoomPercentageList();
     347              : 
     348            0 :     for (int zoom : possibleZoomList) {
     349            0 :         m_availableZoomList << buildZoomItem(ZoomType::Percentage, zoomPercentageTitle(zoom), zoom);
     350              :     }
     351              : 
     352            0 :     m_availableZoomList << buildZoomItem(ZoomType::PageWidth);
     353            0 :     m_availableZoomList << buildZoomItem(ZoomType::WholePage);
     354            0 :     m_availableZoomList << buildZoomItem(ZoomType::TwoPages);
     355              : 
     356            0 :     bool isCustomZoom = currZoomType == ZoomType::Percentage && !possibleZoomList.contains(currZoomPercentage);
     357            0 :     if (isCustomZoom) {
     358            0 :         MenuItem* customZoom = buildZoomItem(ZoomType::Percentage, zoomPercentageTitle(currZoomPercentage), currZoomPercentage);
     359            0 :         customZoom->setSelected(true);
     360            0 :         m_availableZoomList << customZoom;
     361              :     }
     362            0 : }
     363              : 
     364            0 : void NotationStatusBarModel::setCurrentZoom(const QString& zoomId)
     365              : {
     366            0 :     auto it = std::find_if(m_availableZoomList.begin(), m_availableZoomList.end(), [zoomId](const MenuItem* item) {
     367            0 :         return item->id() == zoomId;
     368              :     });
     369              : 
     370            0 :     if (it == m_availableZoomList.end()) {
     371            0 :         return;
     372              :     }
     373              : 
     374            0 :     const Params& params = (*it)->params();
     375            0 :     ZoomType type = params.at("type").toEnum<ZoomType>();
     376            0 :     int percent = params.at("percent").toInt();
     377              : 
     378            0 :     dispatch(zoomTypeToCommand(type), { { "percent", Val(percent) } });
     379              : 
     380            0 :     emit availableZoomListChanged();
     381            0 : }
     382              : 
     383            0 : int NotationStatusBarModel::minZoomPercentage() const
     384              : {
     385            0 :     return 5;
     386              : }
     387              : 
     388            0 : int NotationStatusBarModel::maxZoomPercentage() const
     389              : {
     390            0 :     return possibleZoomPercentageList().last();
     391              : }
     392              : 
     393            0 : void NotationStatusBarModel::zoomIn()
     394              : {
     395            0 :     dispatch(ZOOM_IN_COMMAND);
     396            0 : }
     397              : 
     398            0 : void NotationStatusBarModel::zoomOut()
     399              : {
     400            0 :     dispatch(ZOOM_OUT_COMMAND);
     401            0 : }
     402              : 
     403            0 : void NotationStatusBarModel::handleMenuItem(const QString& itemId)
     404              : {
     405            0 :     LOGDA() << itemId;
     406            0 :     if (m_concertPitchItem->id() == itemId) {
     407            0 :         dispatch(m_concertPitchItem->command());
     408            0 :     } else if (m_currentWorkspaceItem->id() == itemId) {
     409            0 :         dispatch(m_currentWorkspaceItem->command());
     410              :     } else {
     411            0 :         UNREACHABLE;
     412              :     }
     413            0 : }
     414              : 
     415            0 : void NotationStatusBarModel::handleWorkspacesMenuItem(const QString& itemId)
     416              : {
     417              : #ifdef MUSE_MODULE_WORKSPACE
     418            0 :     m_workspacesMenuModel->handleMenuItem(itemId);
     419              : #else
     420              :     UNUSED(itemId);
     421              : #endif
     422            0 : }
     423              : 
     424            0 : INotationPtr NotationStatusBarModel::notation() const
     425              : {
     426            0 :     return context()->currentNotation();
     427              : }
     428              : 
     429            0 : INotationAccessibilityPtr NotationStatusBarModel::accessibility() const
     430              : {
     431            0 :     return notation() ? notation()->accessibility() : nullptr;
     432              : }
     433              : 
     434            0 : void NotationStatusBarModel::dispatch(const muse::rcommand::Command& command, const muse::rcommand::Params& params)
     435              : {
     436            0 :     dispatcher()->dispatch(command, params);
     437            0 : }
     438              : 
     439            0 : QList<int> NotationStatusBarModel::possibleZoomPercentageList() const
     440              : {
     441            0 :     QList<int> result;
     442              : 
     443            0 :     for (int zoom : notationConfiguration()->possibleZoomPercentageList()) {
     444            0 :         if (zoom >= MIN_DISPLAYED_ZOOM_PERCENTAGE) {
     445            0 :             result << zoom;
     446              :         }
     447            0 :     }
     448              : 
     449            0 :     return result;
     450            0 : }
     451              : 
     452            0 : QVariantList NotationStatusBarModel::availableViewModeList_property()
     453              : {
     454            0 :     return menuItemListToVariantList(m_availableViewModeList);
     455              : }
     456              : 
     457            0 : QVariantList NotationStatusBarModel::availableZoomList_property()
     458              : {
     459            0 :     return menuItemListToVariantList(m_availableZoomList);
     460              : }
     461              : 
     462            0 : QVariantList NotationStatusBarModel::menuItemListToVariantList(const MenuItemList& list) const
     463              : {
     464            0 :     QVariantList result;
     465            0 :     for (MenuItem* item: list) {
     466            0 :         result << QVariant::fromValue(item);
     467              :     }
     468              : 
     469            0 :     return result;
     470            0 : }
        

Generated by: LCOV version 2.0-1