LCOV - code coverage report
Current view: top level - src/playback/qml/MuseScore/Playback - soundflagsettingsmodel.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 268 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) 2024 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 "soundflagsettingsmodel.h"
      24              : 
      25              : #include "audio/common/audioutils.h"
      26              : 
      27              : #include "engraving/dom/utils.h"
      28              : #include "engraving/dom/stafftext.h"
      29              : #include "engraving/dom/soundflag.h"
      30              : #include "engraving/types/types.h"
      31              : 
      32              : #include "notation/inotation.h"
      33              : #include "notation/inotationinteraction.h" // IWYU pragma: keep
      34              : 
      35              : #include "project/inotationproject.h" // IWYU pragma: keep
      36              : #include "project/iprojectaudiosettings.h"
      37              : 
      38              : #include "translation.h"
      39              : #include "log.h"
      40              : 
      41              : using namespace mu;
      42              : using namespace mu::playback;
      43              : using namespace mu::engraving;
      44              : using namespace muse::audio;
      45              : 
      46              : static const QString RESET_MENU_ID = "reset";
      47              : static const QString MULTI_SELECTION_MENU_ID = "multi-selection";
      48              : static const QString APPLY_TO_ALL_STAVES_MENU_ID = "apply-to-all-staves";
      49              : 
      50            0 : static QVariantList buildAvailablePresetsModel(const SoundPresetList& availablePresets)
      51              : {
      52            0 :     QVariantList model;
      53              : 
      54            0 :     if (availablePresets.size() <= 1) {
      55            0 :         return model; // only default preset is available; don't show it
      56              :     }
      57              : 
      58            0 :     for (const SoundPreset& preset : availablePresets) {
      59            0 :         QVariantMap item;
      60            0 :         item["code"] = preset.code.toQString();
      61            0 :         item["name"] = preset.name.toQString();
      62              : 
      63            0 :         model << item;
      64            0 :     }
      65              : 
      66            0 :     return model;
      67            0 : }
      68              : 
      69            0 : static QVariantList buildAvailablePlayingTechniquesModel(const std::set<muse::String>& availableTechniqueCodes)
      70              : {
      71            0 :     QVariantList model;
      72              : 
      73            0 :     if (availableTechniqueCodes.empty()) {
      74            0 :         return model;
      75              :     }
      76              : 
      77            0 :     QVariantMap ordinaryItem;
      78            0 :     ordinaryItem["code"] = muse::mpe::ORDINARY_PLAYING_TECHNIQUE_CODE.toQString();
      79            0 :     ordinaryItem["name"] = muse::qtrc("playback", "Ord. (default)");
      80            0 :     model << ordinaryItem;
      81              : 
      82            0 :     for (const muse::String& playingTechniqueCode : availableTechniqueCodes) {
      83            0 :         QVariantMap item;
      84            0 :         item["code"] = playingTechniqueCode.toQString();
      85            0 :         item["name"] = playingTechniqueCode.toQString();
      86              : 
      87            0 :         model << item;
      88            0 :     }
      89              : 
      90            0 :     return model;
      91            0 : }
      92              : 
      93            0 : SoundFlagSettingsModel::SoundFlagSettingsModel(QObject* parent)
      94            0 :     : AbstractElementPopupModel(PopupModelType::TYPE_SOUND_FLAG, parent)
      95              : {
      96            0 : }
      97              : 
      98            0 : bool SoundFlagSettingsModel::inited() const
      99              : {
     100            0 :     return m_availablePresetsInited;
     101              : }
     102              : 
     103            0 : void SoundFlagSettingsModel::init()
     104              : {
     105            0 :     TRACEFUNC;
     106              : 
     107            0 :     connect(this, &SoundFlagSettingsModel::itemRectChanged, this, [this](const QRect&) {
     108            0 :         QRect rect = iconRect();
     109            0 :         if (rect.isValid()) {
     110            0 :             emit iconRectChanged(rect);
     111              :         }
     112            0 :     });
     113              : 
     114            0 :     AbstractElementPopupModel::init();
     115              : 
     116            0 :     connect(this, &AbstractElementPopupModel::dataChanged, [this]() {
     117            0 :         load();
     118            0 :     });
     119              : 
     120            0 :     load();
     121            0 : }
     122              : 
     123            0 : void SoundFlagSettingsModel::load()
     124              : {
     125            0 :     IF_ASSERT_FAILED(m_item && m_item->isSoundFlag()) {
     126            0 :         return;
     127              :     }
     128              : 
     129            0 :     initTitle();
     130            0 :     initAvailablePresets();
     131              : 
     132            0 :     emit contextMenuModelChanged();
     133              : }
     134              : 
     135            0 : void SoundFlagSettingsModel::initTitle()
     136              : {
     137            0 :     const AudioInputParams& params = currentAudioInputParams();
     138              : 
     139            0 :     QString name = muse::audio::audioSourceName(params).toQString();
     140            0 :     QString category = muse::audio::audioSourceCategoryName(params).toQString();
     141            0 :     QString title = category + ": " + name;
     142              : 
     143            0 :     setTitle(title);
     144            0 : }
     145              : 
     146            0 : void SoundFlagSettingsModel::initAvailablePresets()
     147              : {
     148            0 :     m_availablePresetsInited = false;
     149              : 
     150            0 :     playbackController()->availableSoundPresets(makeInstrumentTrackId(m_item))
     151            0 :     .onResolve(this, [this](const SoundPresetList& presets) {
     152            0 :         setAvailableSoundPresets(presets);
     153              : 
     154            0 :         m_availablePresetsInited = true;
     155            0 :         emit initedChanged();
     156            0 :     });
     157              : 
     158            0 :     emit selectedPresetCodesChanged();
     159            0 :     emit selectedPlayingTechniqueCodeChanged();
     160            0 : }
     161              : 
     162            0 : void SoundFlagSettingsModel::togglePreset(const QString& presetCode)
     163              : {
     164            0 :     if (!m_item) {
     165            0 :         return;
     166              :     }
     167              : 
     168            0 :     QStringList newPresetCodes;
     169              : 
     170            0 :     if (playbackConfiguration()->soundPresetsMultiSelectionEnabled()) {
     171            0 :         newPresetCodes = selectedPresetCodes();
     172              : 
     173            0 :         if (newPresetCodes.contains(presetCode)) {
     174            0 :             if (newPresetCodes.size() == 1) {
     175            0 :                 return;
     176              :             }
     177              : 
     178            0 :             newPresetCodes.removeOne(presetCode);
     179              :         } else {
     180            0 :             newPresetCodes.push_back(presetCode);
     181              :         }
     182              :     } else {
     183            0 :         newPresetCodes = QStringList{ presetCode };
     184              :     }
     185              : 
     186            0 :     SoundFlag* soundFlag = toSoundFlag(m_item);
     187              : 
     188            0 :     beginCommand(TranslatableString("undoableAction", "Toggle sound flag preset"));
     189            0 :     soundFlag->undoChangeSoundFlag(StringList(newPresetCodes), soundFlag->playingTechnique());
     190            0 :     bool needUpdateNotation = updateStaffText();
     191            0 :     endCommand();
     192              : 
     193            0 :     if (currentNotation()->interaction()->isTextEditingStarted()) {
     194            0 :         currentNotation()->interaction()->endEditText();
     195              :     }
     196              : 
     197            0 :     if (needUpdateNotation) {
     198            0 :         updateNotation();
     199              :     }
     200              : 
     201            0 :     loadAvailablePlayingTechniques();
     202              : 
     203            0 :     emit selectedPresetCodesChanged();
     204            0 :     emit contextMenuModelChanged();
     205            0 : }
     206              : 
     207            0 : void SoundFlagSettingsModel::togglePlayingTechnique(const QString& playingTechniqueCode)
     208              : {
     209            0 :     if (!m_item) {
     210            0 :         return;
     211              :     }
     212              : 
     213            0 :     SoundFlag* soundFlag = toSoundFlag(m_item);
     214              : 
     215            0 :     beginCommand(TranslatableString("undoableAction", "Toggle sound flag playing technique"));
     216            0 :     soundFlag->undoChangeSoundFlag(soundFlag->soundPresets(), muse::String(playingTechniqueCode));
     217            0 :     bool needUpdateNotation = updateStaffText();
     218            0 :     endCommand();
     219              : 
     220            0 :     if (currentNotation()->interaction()->isTextEditingStarted()) {
     221            0 :         currentNotation()->interaction()->endEditText();
     222              :     }
     223              : 
     224            0 :     if (needUpdateNotation) {
     225            0 :         updateNotation();
     226              :     }
     227              : 
     228            0 :     emit selectedPlayingTechniqueCodeChanged();
     229            0 :     emit contextMenuModelChanged();
     230              : }
     231              : 
     232            0 : muse::uicomponents::MenuItem* SoundFlagSettingsModel::buildMenuItem(const QString& itemId,
     233              :                                                                     const muse::TranslatableString& title,
     234              :                                                                     bool enabled)
     235              : {
     236            0 :     muse::uicomponents::MenuItem* item = new muse::uicomponents::MenuItem(this);
     237            0 :     item->setId(itemId);
     238            0 :     item->setTitle(title);
     239            0 :     item->setEnabled(enabled);
     240              : 
     241            0 :     return item;
     242              : }
     243              : 
     244            0 : QString SoundFlagSettingsModel::defaultPresetCode() const
     245              : {
     246            0 :     return !m_availablePresets.empty() ? m_availablePresets.front().code.toQString() : QString();
     247              : }
     248              : 
     249            0 : QString SoundFlagSettingsModel::defaultPlayingTechniqueCode() const
     250              : {
     251            0 :     return !m_availablePlayingTechniquesModel.empty()
     252            0 :            ? muse::String(m_availablePlayingTechniquesModel.front().toMap()["code"].toString())
     253            0 :            : muse::String();
     254              : }
     255              : 
     256            0 : QVariantList SoundFlagSettingsModel::contextMenuModel()
     257              : {
     258            0 :     muse::uicomponents::MenuItemList items;
     259              : 
     260            0 :     SoundFlag* soundFlag = toSoundFlag(m_item);
     261            0 :     if (!soundFlag) {
     262            0 :         return {};
     263              :     }
     264              : 
     265            0 :     auto isResetEnabled = [this, soundFlag]() {
     266            0 :         bool enabled = false;
     267              : 
     268            0 :         const SoundFlag::PresetCodes& activePresetCodes = soundFlag->soundPresets();
     269            0 :         const muse::String playingTechnique = soundFlag->playingTechnique();
     270              : 
     271            0 :         if (!activePresetCodes.empty()) {
     272            0 :             enabled = activePresetCodes != StringList { defaultPresetCode() };
     273              :         }
     274              : 
     275            0 :         if (!playingTechnique.empty()) {
     276            0 :             enabled |= playingTechnique != defaultPlayingTechniqueCode();
     277              :         }
     278              : 
     279            0 :         return enabled;
     280            0 :     };
     281              : 
     282            0 :     muse::uicomponents::MenuItem* resetItem = buildMenuItem(RESET_MENU_ID, TranslatableString("playback", "Reset to default sound"),
     283            0 :                                                             isResetEnabled());
     284              : 
     285            0 :     resetItem->setIcon(muse::ui::IconCode::Code::UNDO);
     286              : 
     287            0 :     items << resetItem;
     288              : 
     289            0 :     bool isMultiSelectionEnabled = !m_availablePresetsModel.isEmpty();
     290              : 
     291              :     muse::uicomponents::MenuItem* multiSelectionItem
     292            0 :         = buildMenuItem(MULTI_SELECTION_MENU_ID, TranslatableString("playback", "Allow multiple selection"), isMultiSelectionEnabled);
     293              : 
     294            0 :     multiSelectionItem->setCheckable(true);
     295            0 :     multiSelectionItem->setChecked(playbackConfiguration()->soundPresetsMultiSelectionEnabled());
     296              : 
     297            0 :     items << multiSelectionItem;
     298              : 
     299            0 :     muse::uicomponents::MenuItem* applyToAllStavesItem = buildMenuItem(APPLY_TO_ALL_STAVES_MENU_ID,
     300            0 :                                                                        TranslatableString("playback", "Apply selection to all staves"));
     301              : 
     302            0 :     applyToAllStavesItem->setCheckable(true);
     303            0 :     applyToAllStavesItem->setChecked(soundFlag->applyToAllStaves());
     304              : 
     305            0 :     items << applyToAllStavesItem;
     306              : 
     307            0 :     return muse::uicomponents::menuItemListToVariantList(items);
     308            0 : }
     309              : 
     310            0 : void SoundFlagSettingsModel::handleContextMenuItem(const QString& menuId)
     311              : {
     312            0 :     SoundFlag* soundFlag = toSoundFlag(m_item);
     313            0 :     if (!soundFlag) {
     314            0 :         return;
     315              :     }
     316              : 
     317            0 :     if (menuId == RESET_MENU_ID) {
     318            0 :         beginCommand(TranslatableString("undoableAction", "Reset sound flag"));
     319              : 
     320            0 :         const SoundFlag::PresetCodes oldPresetCodes = soundFlag->soundPresets();
     321            0 :         const SoundFlag::PresetCodes newPresetCodes = { defaultPresetCode() };
     322            0 :         soundFlag->undoChangeSoundFlag(newPresetCodes, defaultPlayingTechniqueCode());
     323              : 
     324            0 :         soundFlag->undoResetProperty(Pid::APPLY_TO_ALL_STAVES);
     325              : 
     326            0 :         bool needUpdateNotation = updateStaffText();
     327            0 :         bool needUpdateAvailablePlayingTechniques = oldPresetCodes != newPresetCodes;
     328              : 
     329            0 :         endCommand();
     330              : 
     331            0 :         if (needUpdateNotation) {
     332            0 :             updateNotation();
     333              :         }
     334              : 
     335            0 :         emit selectedPresetCodesChanged();
     336              : 
     337            0 :         if (needUpdateAvailablePlayingTechniques) {
     338            0 :             loadAvailablePlayingTechniques();
     339              :         }
     340            0 :     } else if (menuId == MULTI_SELECTION_MENU_ID) {
     341            0 :         playbackConfiguration()->setSoundPresetsMultiSelectionEnabled(!playbackConfiguration()->soundPresetsMultiSelectionEnabled());
     342            0 :         emit contextMenuModelChanged();
     343            0 :     } else if (menuId == APPLY_TO_ALL_STAVES_MENU_ID) {
     344            0 :         beginCommand(TranslatableString("undoableAction", "Toggle ‘Apply sound flag to all staves’"));
     345            0 :         soundFlag->undoChangeProperty(Pid::APPLY_TO_ALL_STAVES, !soundFlag->applyToAllStaves());
     346            0 :         endCommand();
     347              : 
     348            0 :         emit contextMenuModelChanged();
     349              :     }
     350            0 : }
     351              : 
     352            0 : bool SoundFlagSettingsModel::updateStaffText()
     353              : {
     354            0 :     EngravingObject* parent = m_item->ownershipParent();
     355            0 :     if (!parent || !parent->isStaffText()) {
     356            0 :         return false;
     357              :     }
     358              : 
     359            0 :     muse::String newText = muse::mtrc("engraving", "Staff text");
     360            0 :     const SoundFlag* soundFlag = toSoundFlag(m_item);
     361            0 :     const SoundFlag::PresetCodes& activePresetCodes = soundFlag->soundPresets();
     362              : 
     363            0 :     StringList strs;
     364              : 
     365            0 :     for (const SoundPreset& preset : m_availablePresets) {
     366            0 :         if (preset.name.empty()) {
     367            0 :             continue;
     368              :         }
     369              : 
     370            0 :         if (muse::contains(activePresetCodes, preset.code)) {
     371            0 :             strs << preset.name;
     372              :         }
     373              :     }
     374              : 
     375            0 :     const SoundFlag::PlayingTechniqueCode& techniqueCode = soundFlag->playingTechnique();
     376            0 :     if (!techniqueCode.empty()) {
     377            0 :         if (techniqueCode == muse::mpe::ORDINARY_PLAYING_TECHNIQUE_CODE) {
     378            0 :             strs << muse::mtrc("playback", "ordinary");
     379              :         } else {
     380            0 :             strs << soundFlag->playingTechnique();
     381              :         }
     382              :     }
     383              : 
     384            0 :     if (!strs.empty()) {
     385            0 :         newText = strs.join(u", ").toLower();
     386              :     }
     387              : 
     388            0 :     StaffText* staffTextItem = toStaffText(parent);
     389            0 :     bool isTextChanged = staffTextItem->xmlText() != newText;
     390              : 
     391            0 :     if (isTextChanged) {
     392            0 :         staffTextItem->undoChangeProperty(Pid::TEXT, newText);
     393              :     }
     394              : 
     395            0 :     return isTextChanged;
     396            0 : }
     397              : 
     398            0 : project::IProjectAudioSettingsPtr SoundFlagSettingsModel::audioSettings() const
     399              : {
     400            0 :     IF_ASSERT_FAILED(projectSession()->project()) {
     401            0 :         return nullptr;
     402              :     }
     403              : 
     404            0 :     return projectSession()->project()->audioSettings();
     405              : }
     406              : 
     407            0 : const AudioInputParams& SoundFlagSettingsModel::currentAudioInputParams() const
     408              : {
     409            0 :     return audioSettings()->trackInputParams(makeInstrumentTrackId(m_item));
     410              : }
     411              : 
     412            0 : QString SoundFlagSettingsModel::title() const
     413              : {
     414            0 :     return m_title;
     415              : }
     416              : 
     417            0 : void SoundFlagSettingsModel::setTitle(const QString& title)
     418              : {
     419            0 :     if (m_title == title) {
     420            0 :         return;
     421              :     }
     422              : 
     423            0 :     m_title = title;
     424            0 :     emit titleChanged();
     425              : }
     426              : 
     427            0 : QRect SoundFlagSettingsModel::iconRect() const
     428              : {
     429            0 :     return m_item ? fromLogical(m_item->canvasBoundingRect()).toQRect() : QRect();
     430              : }
     431              : 
     432            0 : QVariantList SoundFlagSettingsModel::availablePresets() const
     433              : {
     434            0 :     return m_availablePresetsModel;
     435              : }
     436              : 
     437            0 : QStringList SoundFlagSettingsModel::selectedPresetCodes() const
     438              : {
     439            0 :     if (!m_item) {
     440            0 :         return {};
     441              :     }
     442              : 
     443            0 :     QStringList result;
     444            0 :     for (const muse::String& presetCode : toSoundFlag(m_item)->soundPresets()) {
     445            0 :         result << presetCode.toQString();
     446              :     }
     447              : 
     448            0 :     return result;
     449            0 : }
     450              : 
     451            0 : QVariantList SoundFlagSettingsModel::availablePlayingTechniques() const
     452              : {
     453            0 :     return m_availablePlayingTechniquesModel;
     454              : }
     455              : 
     456            0 : void SoundFlagSettingsModel::setAvailableSoundPresets(const SoundPresetList& presets)
     457              : {
     458            0 :     if (m_availablePresets == presets) {
     459            0 :         return;
     460              :     }
     461              : 
     462            0 :     m_availablePresets = presets;
     463            0 :     m_availablePresetsModel = buildAvailablePresetsModel(presets);
     464            0 :     emit availablePresetsChanged();
     465              : 
     466            0 :     loadAvailablePlayingTechniques();
     467              : 
     468            0 :     emit contextMenuModelChanged();
     469              : }
     470              : 
     471            0 : void SoundFlagSettingsModel::loadAvailablePlayingTechniques()
     472              : {
     473            0 :     QStringList selectedPresetCodes = this->selectedPresetCodes();
     474            0 :     std::set<muse::String> availablePlayingTechniqueCodes;
     475              : 
     476            0 :     for (const SoundPreset& preset : m_availablePresets) {
     477            0 :         if (selectedPresetCodes.empty()) {
     478            0 :             if (!preset.isDefault) {
     479            0 :                 continue;
     480              :             }
     481            0 :         } else if (!selectedPresetCodes.contains(preset.code)) {
     482            0 :             continue;
     483              :         }
     484              : 
     485            0 :         auto techniqueIt = preset.attributes.find(PLAYING_TECHNIQUES_ATTRIBUTE);
     486            0 :         if (techniqueIt == preset.attributes.end()) {
     487            0 :             continue;
     488              :         }
     489              : 
     490            0 :         for (const muse::String& code : techniqueIt->second.split(u"|")) {
     491            0 :             availablePlayingTechniqueCodes << code;
     492            0 :         }
     493              :     }
     494              : 
     495            0 :     QVariantList newModel = buildAvailablePlayingTechniquesModel(availablePlayingTechniqueCodes);
     496            0 :     if (m_availablePlayingTechniquesModel == newModel) {
     497            0 :         return;
     498              :     }
     499              : 
     500            0 :     m_availablePlayingTechniquesModel = std::move(newModel);
     501            0 :     emit availablePlayingTechniquesChanged();
     502            0 :     emit selectedPlayingTechniqueCodeChanged();
     503            0 : }
     504              : 
     505            0 : QString SoundFlagSettingsModel::selectedPlayingTechniqueCode() const
     506              : {
     507            0 :     if (!m_item) {
     508            0 :         return QString();
     509              :     }
     510              : 
     511            0 :     QString result = toSoundFlag(m_item)->playingTechnique().toQString();
     512              : 
     513            0 :     return result;
     514            0 : }
        

Generated by: LCOV version 2.0-1