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 : }
|