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 "propertiespanelabstractmodel.h"
24 :
25 : #include "engraving/dom/barline.h"
26 : #include "engraving/dom/dynamic.h"
27 : #include "engraving/dom/property.h"
28 : #include "engraving/dom/score.h"
29 : #include "engraving/dom/tempotext.h"
30 :
31 : #include "notation/inotationinteraction.h" // IWYU pragma: keep
32 : #include "notation/inotationstyle.h"
33 : #include "notation/inotationundostack.h"
34 : #include "notation/utilities/offsetutilities.h"
35 :
36 : #include "modularity/ioc.h"
37 :
38 : #include "types/texttypes.h"
39 :
40 : #include "log.h"
41 :
42 : using namespace mu::propertiespanel;
43 : using namespace mu::notation;
44 : using namespace mu::engraving;
45 :
46 : static const QMap<mu::engraving::ElementType, PropertiesPanelModelType> NOTATION_ELEMENT_MODEL_TYPES = {
47 : { mu::engraving::ElementType::NOTE, PropertiesPanelModelType::TYPE_NOTE },
48 : { mu::engraving::ElementType::STEM, PropertiesPanelModelType::TYPE_NOTE },
49 : { mu::engraving::ElementType::NOTEDOT, PropertiesPanelModelType::TYPE_NOTE },
50 : { mu::engraving::ElementType::NOTEHEAD, PropertiesPanelModelType::TYPE_NOTE },
51 : { mu::engraving::ElementType::NOTELINE, PropertiesPanelModelType::TYPE_NOTE },
52 : { mu::engraving::ElementType::SHADOW_NOTE, PropertiesPanelModelType::TYPE_NOTE },
53 : { mu::engraving::ElementType::HOOK, PropertiesPanelModelType::TYPE_NOTE },
54 : { mu::engraving::ElementType::BEAM, PropertiesPanelModelType::TYPE_NOTE },
55 : { mu::engraving::ElementType::GLISSANDO, PropertiesPanelModelType::TYPE_GLISSANDO },
56 : { mu::engraving::ElementType::GLISSANDO_SEGMENT, PropertiesPanelModelType::TYPE_GLISSANDO },
57 : { mu::engraving::ElementType::VIBRATO, PropertiesPanelModelType::TYPE_VIBRATO },
58 : { mu::engraving::ElementType::VIBRATO_SEGMENT, PropertiesPanelModelType::TYPE_VIBRATO },
59 : { mu::engraving::ElementType::SLUR, PropertiesPanelModelType::TYPE_SLUR },
60 : { mu::engraving::ElementType::SLUR_SEGMENT, PropertiesPanelModelType::TYPE_SLUR },
61 : { mu::engraving::ElementType::HAMMER_ON_PULL_OFF, PropertiesPanelModelType::TYPE_HAMMER_ON_PULL_OFF },
62 : { mu::engraving::ElementType::HAMMER_ON_PULL_OFF_SEGMENT, PropertiesPanelModelType::TYPE_HAMMER_ON_PULL_OFF },
63 : { mu::engraving::ElementType::TIE, PropertiesPanelModelType::TYPE_TIE },
64 : { mu::engraving::ElementType::TIE_SEGMENT, PropertiesPanelModelType::TYPE_TIE },
65 : { mu::engraving::ElementType::LAISSEZ_VIB, PropertiesPanelModelType::TYPE_LAISSEZ_VIB },
66 : { mu::engraving::ElementType::LAISSEZ_VIB_SEGMENT, PropertiesPanelModelType::TYPE_LAISSEZ_VIB },
67 : { mu::engraving::ElementType::PARTIAL_TIE, PropertiesPanelModelType::TYPE_PARTIAL_TIE },
68 : { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, PropertiesPanelModelType::TYPE_PARTIAL_TIE },
69 : { mu::engraving::ElementType::TEMPO_TEXT, PropertiesPanelModelType::TYPE_TEMPO },
70 : { mu::engraving::ElementType::FERMATA, PropertiesPanelModelType::TYPE_FERMATA },
71 : { mu::engraving::ElementType::LAYOUT_BREAK, PropertiesPanelModelType::TYPE_SECTIONBREAK },
72 : { mu::engraving::ElementType::BAR_LINE, PropertiesPanelModelType::TYPE_BARLINE },
73 : { mu::engraving::ElementType::PLAY_COUNT_TEXT, PropertiesPanelModelType::TYPE_PLAY_COUNT_TEXT },
74 : { mu::engraving::ElementType::MARKER, PropertiesPanelModelType::TYPE_MARKER },
75 : { mu::engraving::ElementType::JUMP, PropertiesPanelModelType::TYPE_JUMP },
76 : { mu::engraving::ElementType::KEYSIG, PropertiesPanelModelType::TYPE_KEYSIGNATURE },
77 : { mu::engraving::ElementType::ACCIDENTAL, PropertiesPanelModelType::TYPE_ACCIDENTAL },
78 : { mu::engraving::ElementType::FRET_DIAGRAM, PropertiesPanelModelType::TYPE_FRET_DIAGRAM },
79 : { mu::engraving::ElementType::PEDAL, PropertiesPanelModelType::TYPE_PEDAL },
80 : { mu::engraving::ElementType::PEDAL_SEGMENT, PropertiesPanelModelType::TYPE_PEDAL },
81 : { mu::engraving::ElementType::SPACER, PropertiesPanelModelType::TYPE_SPACER },
82 : { mu::engraving::ElementType::CLEF, PropertiesPanelModelType::TYPE_CLEF },
83 : { mu::engraving::ElementType::HAIRPIN, PropertiesPanelModelType::TYPE_HAIRPIN },
84 : { mu::engraving::ElementType::HAIRPIN_SEGMENT, PropertiesPanelModelType::TYPE_HAIRPIN },
85 : { mu::engraving::ElementType::OTTAVA, PropertiesPanelModelType::TYPE_OTTAVA },
86 : { mu::engraving::ElementType::OTTAVA_SEGMENT, PropertiesPanelModelType::TYPE_OTTAVA },
87 : { mu::engraving::ElementType::VOLTA, PropertiesPanelModelType::TYPE_VOLTA },
88 : { mu::engraving::ElementType::VOLTA_SEGMENT, PropertiesPanelModelType::TYPE_VOLTA },
89 : { mu::engraving::ElementType::STAFFTYPE_CHANGE, PropertiesPanelModelType::TYPE_STAFF_TYPE_CHANGES },
90 : { mu::engraving::ElementType::TBOX, PropertiesPanelModelType::TYPE_TEXT_FRAME },// text frame
91 : { mu::engraving::ElementType::VBOX, PropertiesPanelModelType::TYPE_VERTICAL_FRAME },// vertical frame
92 : { mu::engraving::ElementType::HBOX, PropertiesPanelModelType::TYPE_HORIZONTAL_FRAME },// horizontal frame
93 : { mu::engraving::ElementType::FBOX, PropertiesPanelModelType::TYPE_FRET_FRAME },// fret diagram legend
94 : { mu::engraving::ElementType::ARTICULATION, PropertiesPanelModelType::TYPE_ARTICULATION },
95 : { mu::engraving::ElementType::TAPPING, PropertiesPanelModelType::TYPE_TAPPING },
96 : { mu::engraving::ElementType::TAPPING_HALF_SLUR_SEGMENT, PropertiesPanelModelType::TYPE_TAPPING },
97 : { mu::engraving::ElementType::ORNAMENT, PropertiesPanelModelType::TYPE_ORNAMENT },
98 : { mu::engraving::ElementType::TRILL, PropertiesPanelModelType::TYPE_ORNAMENT },
99 : { mu::engraving::ElementType::TRILL_SEGMENT, PropertiesPanelModelType::TYPE_ORNAMENT },
100 : { mu::engraving::ElementType::IMAGE, PropertiesPanelModelType::TYPE_IMAGE },
101 : { mu::engraving::ElementType::HARMONY, PropertiesPanelModelType::TYPE_CHORD_SYMBOL },
102 : { mu::engraving::ElementType::AMBITUS, PropertiesPanelModelType::TYPE_AMBITUS },
103 : { mu::engraving::ElementType::BRACKET, PropertiesPanelModelType::TYPE_BRACKET },
104 : { mu::engraving::ElementType::TIMESIG, PropertiesPanelModelType::TYPE_TIME_SIGNATURE },
105 : { mu::engraving::ElementType::MMREST, PropertiesPanelModelType::TYPE_MMREST },
106 : { mu::engraving::ElementType::BEND, PropertiesPanelModelType::TYPE_BEND },
107 : { mu::engraving::ElementType::GUITAR_BEND, PropertiesPanelModelType::TYPE_BEND },
108 : { mu::engraving::ElementType::GUITAR_BEND_SEGMENT, PropertiesPanelModelType::TYPE_BEND },
109 : { mu::engraving::ElementType::GUITAR_BEND_HOLD, PropertiesPanelModelType::TYPE_BEND },
110 : { mu::engraving::ElementType::GUITAR_BEND_HOLD_SEGMENT, PropertiesPanelModelType::TYPE_BEND },
111 : { mu::engraving::ElementType::TREMOLOBAR, PropertiesPanelModelType::TYPE_TREMOLOBAR },
112 : { mu::engraving::ElementType::TREMOLO_SINGLECHORD, PropertiesPanelModelType::TYPE_TREMOLO },
113 : { mu::engraving::ElementType::TREMOLO_TWOCHORD, PropertiesPanelModelType::TYPE_TREMOLO },
114 : { mu::engraving::ElementType::MEASURE_REPEAT, PropertiesPanelModelType::TYPE_MEASURE_REPEAT },
115 : { mu::engraving::ElementType::TUPLET, PropertiesPanelModelType::TYPE_TUPLET },
116 : { mu::engraving::ElementType::TEXTLINE, PropertiesPanelModelType::TYPE_TEXT_LINE },
117 : { mu::engraving::ElementType::TEXTLINE_SEGMENT, PropertiesPanelModelType::TYPE_TEXT_LINE },
118 : { mu::engraving::ElementType::PALM_MUTE, PropertiesPanelModelType::TYPE_TEXT_LINE },
119 : { mu::engraving::ElementType::PALM_MUTE_SEGMENT, PropertiesPanelModelType::TYPE_TEXT_LINE },
120 : { mu::engraving::ElementType::LET_RING, PropertiesPanelModelType::TYPE_TEXT_LINE },
121 : { mu::engraving::ElementType::LET_RING_SEGMENT, PropertiesPanelModelType::TYPE_TEXT_LINE },
122 : { mu::engraving::ElementType::WHAMMY_BAR, PropertiesPanelModelType::TYPE_TEXT_LINE },
123 : { mu::engraving::ElementType::WHAMMY_BAR_SEGMENT, PropertiesPanelModelType::TYPE_TEXT_LINE },
124 : { mu::engraving::ElementType::NOTELINE, PropertiesPanelModelType::TYPE_NOTELINE },
125 : { mu::engraving::ElementType::NOTELINE_SEGMENT, PropertiesPanelModelType::TYPE_NOTELINE },
126 : { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE, PropertiesPanelModelType::TYPE_GRADUAL_TEMPO_CHANGE },
127 : { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT, PropertiesPanelModelType::TYPE_GRADUAL_TEMPO_CHANGE },
128 : { mu::engraving::ElementType::INSTRUMENT_NAME, PropertiesPanelModelType::TYPE_INSTRUMENT_NAME },
129 : { mu::engraving::ElementType::LYRICS, PropertiesPanelModelType::TYPE_LYRICS },
130 : { mu::engraving::ElementType::LYRICSLINE, PropertiesPanelModelType::TYPE_LYRICS_LINE },
131 : { mu::engraving::ElementType::LYRICSLINE_SEGMENT, PropertiesPanelModelType::TYPE_LYRICS_LINE },
132 : { mu::engraving::ElementType::PARTIAL_LYRICSLINE, PropertiesPanelModelType::TYPE_PARTIAL_LYRICS_LINE },
133 : { mu::engraving::ElementType::PARTIAL_LYRICSLINE_SEGMENT, PropertiesPanelModelType::TYPE_PARTIAL_LYRICS_LINE },
134 : { mu::engraving::ElementType::REST, PropertiesPanelModelType::TYPE_REST },
135 : { mu::engraving::ElementType::DYNAMIC, PropertiesPanelModelType::TYPE_DYNAMIC },
136 : { mu::engraving::ElementType::EXPRESSION, PropertiesPanelModelType::TYPE_EXPRESSION },
137 : { mu::engraving::ElementType::STRING_TUNINGS, PropertiesPanelModelType::TYPE_STRING_TUNINGS },
138 : { mu::engraving::ElementType::SYMBOL, PropertiesPanelModelType::TYPE_SYMBOL },
139 : { mu::engraving::ElementType::SYSTEM_DIVIDER, PropertiesPanelModelType::TYPE_SYMBOL },
140 : { mu::engraving::ElementType::CHORD_BRACKET, PropertiesPanelModelType::TYPE_CHORD_BRACKET },
141 : };
142 :
143 : static const QMap<mu::engraving::HairpinType, PropertiesPanelModelType> HAIRPIN_ELEMENT_MODEL_TYPES = {
144 : { mu::engraving::HairpinType::CRESC_HAIRPIN, PropertiesPanelModelType::TYPE_HAIRPIN },
145 : { mu::engraving::HairpinType::DIM_HAIRPIN, PropertiesPanelModelType::TYPE_HAIRPIN },
146 : { mu::engraving::HairpinType::CRESC_LINE, PropertiesPanelModelType::TYPE_CRESCENDO },
147 : { mu::engraving::HairpinType::DIM_LINE, PropertiesPanelModelType::TYPE_DIMINUENDO },
148 : };
149 :
150 : static const QMap<mu::engraving::LayoutBreakType, PropertiesPanelModelType> LAYOUT_BREAK_ELEMENT_MODEL_TYPES = {
151 : { mu::engraving::LayoutBreakType::SECTION, PropertiesPanelModelType::TYPE_SECTIONBREAK }
152 : };
153 :
154 : static const QMap<mu::engraving::TempoTextType, PropertiesPanelModelType> TEMPO_TEXT_ELEMENT_MODEL_TYPES = {
155 : { mu::engraving::TempoTextType::NORMAL, PropertiesPanelModelType::TYPE_TEMPO },
156 : { mu::engraving::TempoTextType::A_TEMPO, PropertiesPanelModelType::TYPE_A_TEMPO },
157 : { mu::engraving::TempoTextType::TEMPO_PRIMO, PropertiesPanelModelType::TYPE_TEMPO_PRIMO },
158 : };
159 :
160 0 : QString PropertiesPanelAbstractModel::shortcutsForCommand(const muse::rcommand::Command& command) const
161 : {
162 0 : std::vector<std::string> shortcuts = commandShortcutsRegister()->shortcut(command).sequences;
163 0 : return muse::shortcuts::sequencesToNativeText(shortcuts);
164 0 : }
165 :
166 0 : PropertiesPanelAbstractModel::PropertiesPanelAbstractModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx,
167 : IElementRepositoryService* repository,
168 0 : mu::engraving::ElementType elementType)
169 0 : : QObject(parent), muse::Contextable(iocCtx), m_repository(repository), m_elementType(elementType)
170 : {
171 0 : if (!m_repository) {
172 0 : return;
173 : }
174 :
175 0 : m_repository->elementsUpdated().onReceive(this, [this](const QList<mu::engraving::EngravingItem*>&) {
176 0 : updateProperties();
177 0 : });
178 :
179 0 : connect(this, &PropertiesPanelAbstractModel::requestReloadPropertyItems, this, &PropertiesPanelAbstractModel::updateProperties);
180 0 : }
181 :
182 0 : void PropertiesPanelAbstractModel::init()
183 : {
184 0 : onCurrentNotationChanged();
185 0 : }
186 :
187 0 : bool PropertiesPanelAbstractModel::isSystemObjectBelowBottomStaff() const
188 : {
189 0 : return m_isSystemObjectBelowBottomStaff;
190 : }
191 :
192 0 : MeasurementUnits PropertiesPanelAbstractModel::measurementUnits() const
193 : {
194 0 : return m_measurementUnits;
195 : }
196 :
197 0 : bool PropertiesPanelAbstractModel::shouldUpdateOnScoreChange() const
198 : {
199 0 : return m_shouldUpdateOnScoreChange;
200 : }
201 :
202 0 : bool PropertiesPanelAbstractModel::shouldUpdateWhenEmpty() const
203 : {
204 0 : return false;
205 : }
206 :
207 0 : bool PropertiesPanelAbstractModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const
208 : {
209 0 : return false;
210 : }
211 :
212 0 : void PropertiesPanelAbstractModel::updateIsSystemObjectBelowBottomStaff()
213 : {
214 0 : bool soBelowBottomStaff = false;
215 0 : for (EngravingItem* item : m_elementList) {
216 0 : if (item->isSystemObjectBelowBottomStaff()) {
217 0 : soBelowBottomStaff = true;
218 0 : break;
219 : }
220 : }
221 :
222 0 : if (m_isSystemObjectBelowBottomStaff != soBelowBottomStaff) {
223 0 : m_isSystemObjectBelowBottomStaff = soBelowBottomStaff;
224 0 : emit isSystemObjectBelowBottomStaffChanged(m_isSystemObjectBelowBottomStaff);
225 : }
226 0 : }
227 :
228 0 : void PropertiesPanelAbstractModel::updatemeasurementUnits()
229 : {
230 0 : MeasurementUnits result = MeasurementUnits::UNITS_UNKNOWN;
231 :
232 0 : for (EngravingItem* item : m_elementList) {
233 0 : MeasurementUnits itemUnits = item->offsetIsSpatiumDependent() ? MeasurementUnits::UNITS_SPATIUM : MeasurementUnits::UNITS_MM;
234 0 : if (result != MeasurementUnits::UNITS_UNKNOWN && itemUnits != result) {
235 0 : result = MeasurementUnits::UNITS_UNKNOWN;
236 0 : break;
237 : } else {
238 0 : result = itemUnits;
239 : }
240 : }
241 :
242 0 : if (m_measurementUnits != result) {
243 0 : m_measurementUnits = result;
244 0 : emit measurementUnitsChanged(m_measurementUnits);
245 : }
246 0 : }
247 :
248 0 : QString PropertiesPanelAbstractModel::title() const
249 : {
250 0 : return m_title;
251 : }
252 :
253 0 : int PropertiesPanelAbstractModel::icon() const
254 : {
255 0 : return static_cast<int>(m_icon);
256 : }
257 :
258 0 : PropertiesPanelSectionType PropertiesPanelAbstractModel::sectionType() const
259 : {
260 0 : return m_sectionType;
261 : }
262 :
263 0 : PropertiesPanelModelType PropertiesPanelAbstractModel::modelType() const
264 : {
265 0 : return m_modelType;
266 : }
267 :
268 0 : ElementKey PropertiesPanelAbstractModel::makeKey(const EngravingItem* item)
269 : {
270 0 : switch (item->type()) {
271 0 : case ElementType::TEMPO_TEXT: {
272 0 : const auto tempoText = toTempoText(item);
273 0 : return ElementKey{ ElementType::TEMPO_TEXT, static_cast<int>(tempoText->tempoTextType()) };
274 : }
275 0 : default:
276 0 : return ElementKey{ item->type(), item->subtype() };
277 : }
278 : }
279 :
280 0 : PropertiesPanelModelType PropertiesPanelAbstractModel::modelTypeByElementKey(const ElementKey& elementKey)
281 : {
282 0 : if (elementKey.type == mu::engraving::ElementType::HAIRPIN || elementKey.type == mu::engraving::ElementType::HAIRPIN_SEGMENT) {
283 0 : return HAIRPIN_ELEMENT_MODEL_TYPES.value(static_cast<mu::engraving::HairpinType>(elementKey.subtype),
284 0 : PropertiesPanelModelType::TYPE_UNDEFINED);
285 : }
286 :
287 0 : if (elementKey.type == mu::engraving::ElementType::LAYOUT_BREAK) {
288 0 : return LAYOUT_BREAK_ELEMENT_MODEL_TYPES.value(static_cast<mu::engraving::LayoutBreakType>(elementKey.subtype),
289 0 : PropertiesPanelModelType::TYPE_UNDEFINED);
290 : }
291 :
292 0 : if (elementKey.type == mu::engraving::ElementType::TEMPO_TEXT) {
293 0 : return TEMPO_TEXT_ELEMENT_MODEL_TYPES.value(static_cast<mu::engraving::TempoTextType>(elementKey.subtype),
294 0 : PropertiesPanelModelType::TYPE_UNDEFINED);
295 : }
296 :
297 0 : return NOTATION_ELEMENT_MODEL_TYPES.value(elementKey.type, PropertiesPanelModelType::TYPE_UNDEFINED);
298 : }
299 :
300 0 : PropertiesPanelModelTypeSet PropertiesPanelAbstractModel::modelTypesByElementKeys(const ElementKeySet& elementKeySet)
301 : {
302 0 : PropertiesPanelModelTypeSet types;
303 :
304 0 : for (const ElementKey& key : elementKeySet) {
305 0 : types << modelTypeByElementKey(key);
306 : }
307 :
308 0 : return types;
309 0 : }
310 :
311 0 : static bool isPureDynamics(const QList<mu::engraving::EngravingItem*>& selectedElementList)
312 : {
313 0 : if (selectedElementList.empty()) {
314 0 : return false;
315 : }
316 :
317 0 : for (const EngravingItem* item : selectedElementList) {
318 0 : if (!item->isTextBase()) {
319 0 : continue;
320 : }
321 :
322 0 : if (!item->isDynamic()) {
323 0 : return false;
324 : }
325 :
326 0 : const Dynamic* dynamic = toDynamic(item);
327 0 : if (dynamic->hasCustomText()) {
328 0 : return false;
329 : }
330 : }
331 :
332 0 : return true;
333 : }
334 :
335 0 : static bool barlineWithPlayText(const QList<mu::engraving::EngravingItem*>& selectedElementList)
336 : {
337 0 : if (selectedElementList.empty()) {
338 0 : return false;
339 : }
340 :
341 0 : for (const EngravingItem* item : selectedElementList) {
342 0 : if (!item->isBarLine()) {
343 0 : continue;
344 : }
345 :
346 0 : Segment* seg = toBarLine(item)->segment();
347 0 : PlayCountText* playCountText = toPlayCountText(seg->findAnnotation(ElementType::PLAY_COUNT_TEXT, 0, 0));
348 :
349 0 : if (playCountText) {
350 0 : return true;
351 : }
352 : }
353 :
354 0 : return false;
355 : }
356 :
357 0 : static bool hasValidTextLineBaseSegment(const QList<mu::engraving::EngravingItem*>& elementList)
358 : {
359 0 : if (elementList.empty()) {
360 0 : return false;
361 : }
362 :
363 0 : for (const EngravingItem* item : elementList) {
364 0 : if (!item->isTextLineBaseSegment()) {
365 0 : continue;
366 : }
367 0 : const TextLineBaseSegment* tlbs = toTextLineBaseSegment(item);
368 0 : const TextLineBase* tlb = tlbs ? tlbs->textLineBase() : nullptr;
369 0 : if (!tlb) {
370 0 : continue;
371 : }
372 0 : if (!tlb->beginText().empty() || !tlb->continueText().empty() || !tlb->endText().empty()) {
373 0 : return true;
374 : }
375 : }
376 :
377 0 : return false;
378 : }
379 :
380 0 : PropertiesPanelSectionTypeSet PropertiesPanelAbstractModel::sectionTypesByElementKeys(const ElementKeySet& elementKeySet, bool isRange,
381 : const QList<mu::engraving::EngravingItem*>& elementList)
382 : {
383 0 : PropertiesPanelSectionTypeSet types;
384 :
385 : static const std::unordered_set<ElementType> boxTypes {
386 : ElementType::HBOX,
387 : ElementType::VBOX,
388 : ElementType::TBOX,
389 : ElementType::FBOX,
390 0 : };
391 :
392 0 : for (const ElementKey& key : elementKeySet) {
393 0 : if (NOTATION_ELEMENT_MODEL_TYPES.contains(key.type)
394 0 : && (modelTypeByElementKey(key) != PropertiesPanelModelType::TYPE_UNDEFINED)) {
395 0 : types << PropertiesPanelSectionType::SECTION_NOTATION;
396 : }
397 :
398 : // Don't show the "Text" propertiespanel panel for "pure" dynamics (i.e. without custom text)
399 0 : if ((TEXT_ELEMENT_TYPES.contains(key.type) && !isPureDynamics(elementList)) || barlineWithPlayText(elementList)) {
400 0 : types << PropertiesPanelSectionType::SECTION_TEXT;
401 : }
402 :
403 : // Look for a TextLineBaseSegment with begin, continue, or end text...
404 0 : if (hasValidTextLineBaseSegment(elementList)) {
405 0 : types << PropertiesPanelSectionType::SECTION_TEXT;
406 : }
407 :
408 0 : if (key.type != mu::engraving::ElementType::INSTRUMENT_NAME) {
409 0 : types << PropertiesPanelSectionType::SECTION_GENERAL;
410 : }
411 :
412 0 : if (boxTypes.contains(key.type)) {
413 0 : types << PropertiesPanelSectionType::SECTION_SYSTEM_PAGE_LAYOUT;
414 : }
415 : }
416 :
417 0 : if (isRange) {
418 0 : const bool rangeHasNonBoxes = std::any_of(elementList.cbegin(), elementList.cend(), [](const EngravingItem* item) {
419 0 : return !item->isBox();
420 : });
421 0 : if (rangeHasNonBoxes) {
422 0 : types << PropertiesPanelSectionType::SECTION_MEASURES;
423 0 : types << PropertiesPanelSectionType::SECTION_SYSTEM_PAGE_LAYOUT;
424 0 : types << PropertiesPanelSectionType::SECTION_EMPTY_STAVES;
425 : }
426 : }
427 :
428 0 : if (showPartsSection(elementList)) {
429 0 : types << PropertiesPanelSectionType::SECTION_PARTS;
430 : }
431 :
432 0 : return types;
433 0 : }
434 :
435 0 : bool PropertiesPanelAbstractModel::showPartsSection(const QList<EngravingItem*>& elementList)
436 : {
437 : static const std::unordered_set<ElementType> noAvailableChangePartsSettingsTypes {
438 : ElementType::LAYOUT_BREAK,
439 : ElementType::ACCIDENTAL,
440 : ElementType::SOUND_FLAG
441 0 : };
442 :
443 0 : for (EngravingItem* element : elementList) {
444 0 : if ((!element->score()->isMaster() && !muse::contains(noAvailableChangePartsSettingsTypes, element->type()))
445 0 : || element->canBeExcludedFromOtherParts()) {
446 0 : return true;
447 : }
448 : }
449 :
450 0 : return false;
451 : }
452 :
453 0 : bool PropertiesPanelAbstractModel::isEmpty() const
454 : {
455 0 : return m_elementList.isEmpty();
456 : }
457 :
458 0 : void PropertiesPanelAbstractModel::setTitle(QString title)
459 : {
460 0 : if (m_title == title) {
461 0 : return;
462 : }
463 :
464 0 : m_title = title;
465 0 : emit titleChanged();
466 : }
467 :
468 0 : void PropertiesPanelAbstractModel::setIcon(muse::ui::IconCode::Code icon)
469 : {
470 0 : m_icon = icon;
471 0 : }
472 :
473 0 : void PropertiesPanelAbstractModel::setSectionType(PropertiesPanelSectionType sectionType)
474 : {
475 0 : m_sectionType = sectionType;
476 0 : }
477 :
478 0 : void PropertiesPanelAbstractModel::setModelType(PropertiesPanelModelType modelType)
479 : {
480 0 : m_modelType = modelType;
481 0 : }
482 :
483 0 : void PropertiesPanelAbstractModel::onPropertyValueChanged(const mu::engraving::Pid pid, const QVariant& newValue)
484 : {
485 0 : setPropertyValue(m_elementList, pid, newValue);
486 0 : loadProperties();
487 0 : }
488 :
489 0 : void PropertiesPanelAbstractModel::setPropertyValue(const QList<engraving::EngravingItem*>& items, const mu::engraving::Pid pid,
490 : const QVariant& newValue)
491 : {
492 0 : if (items.empty()) {
493 0 : return;
494 : }
495 :
496 0 : beginCommand(TranslatableString("undoableAction", "Edit %1").arg(propertyUserName(pid)));
497 :
498 0 : for (mu::engraving::EngravingItem* item : items) {
499 0 : IF_ASSERT_FAILED(item) {
500 0 : continue;
501 : }
502 :
503 0 : mu::engraving::PropertyFlags ps = item->propertyFlags(pid);
504 :
505 0 : if (ps == mu::engraving::PropertyFlags::STYLED) {
506 0 : ps = mu::engraving::PropertyFlags::UNSTYLED;
507 : }
508 :
509 0 : PropertyValue propValue = valueToElementUnits(pid, newValue, item);
510 0 : item->undoChangeProperty(pid, propValue, ps);
511 0 : }
512 :
513 0 : updateNotation();
514 0 : endCommand();
515 : }
516 :
517 0 : void PropertiesPanelAbstractModel::onPropertyValueReset(const mu::engraving::Pid pid)
518 : {
519 0 : resetPropertyValue(m_elementList, pid);
520 0 : loadProperties();
521 0 : }
522 :
523 0 : void PropertiesPanelAbstractModel::resetPropertyValue(const QList<engraving::EngravingItem*>& items, const mu::engraving::Pid pid)
524 : {
525 0 : if (items.empty()) {
526 0 : return;
527 : }
528 :
529 0 : beginCommand(TranslatableString("undoableAction", "Reset %1").arg(propertyUserName(pid)));
530 :
531 0 : for (mu::engraving::EngravingItem* item : items) {
532 0 : IF_ASSERT_FAILED(item) {
533 0 : continue;
534 : }
535 :
536 0 : item->undoResetProperty(pid);
537 : }
538 :
539 0 : updateNotation();
540 0 : endCommand();
541 : }
542 :
543 0 : void PropertiesPanelAbstractModel::updateProperties()
544 : {
545 0 : requestElements();
546 :
547 0 : emit isEmptyChanged();
548 :
549 0 : if (!isEmpty()) {
550 0 : loadProperties();
551 : }
552 0 : }
553 :
554 0 : void PropertiesPanelAbstractModel::requestElements()
555 : {
556 0 : if (m_elementType != mu::engraving::ElementType::INVALID) {
557 0 : m_elementList = m_repository->findElementsByType(m_elementType);
558 : }
559 0 : }
560 :
561 0 : void PropertiesPanelAbstractModel::disconnectAll()
562 : {
563 0 : async_disconnectAll();
564 0 : }
565 :
566 0 : void PropertiesPanelAbstractModel::onCurrentNotationChanged()
567 : {
568 0 : }
569 :
570 0 : void PropertiesPanelAbstractModel::onNotationChanged(const mu::engraving::PropertyIdSet&, const mu::engraving::StyleIdSet&)
571 : {
572 0 : }
573 :
574 0 : mu::engraving::Sid PropertiesPanelAbstractModel::styleIdByPropertyId(const mu::engraving::Pid pid) const
575 : {
576 0 : mu::engraving::Sid result = mu::engraving::Sid::NOSTYLE;
577 :
578 0 : for (const mu::engraving::EngravingItem* element : m_elementList) {
579 0 : result = element->getPropertyStyle(pid);
580 :
581 0 : if (result != mu::engraving::Sid::NOSTYLE) {
582 0 : break;
583 : }
584 : }
585 :
586 0 : return result;
587 : }
588 :
589 0 : mu::engraving::PropertyIdSet PropertiesPanelAbstractModel::propertyIdSetFromStyleIdSet(const StyleIdSet& styleIdSet) const
590 : {
591 0 : if (styleIdSet.empty()) {
592 0 : return PropertyIdSet();
593 : }
594 :
595 0 : PropertyIdSet result;
596 :
597 0 : for (const mu::engraving::EngravingItem* element : m_elementList) {
598 0 : const mu::engraving::ElementStyle* style = element->styledProperties();
599 0 : if (!style) {
600 0 : continue;
601 : }
602 :
603 0 : for (const StyledProperty& property : *style) {
604 0 : if (muse::contains(styleIdSet, property.sid)) {
605 0 : result.insert(property.pid);
606 : }
607 : }
608 : }
609 :
610 0 : return result;
611 0 : }
612 :
613 0 : bool PropertiesPanelAbstractModel::updateStyleValue(const mu::engraving::Sid& sid, const QVariant& newValue)
614 : {
615 0 : PropertyValue newVal = PropertyValue::fromQVariant(newValue, mu::engraving::MStyle::valueType(sid));
616 0 : if (style() && style()->styleValue(sid) != newVal) {
617 0 : beginCommand(TranslatableString("undoableAction", "Edit style"));
618 0 : style()->setStyleValue(sid, newVal);
619 0 : endCommand();
620 0 : return true;
621 : }
622 :
623 0 : return false;
624 0 : }
625 :
626 0 : QVariant PropertiesPanelAbstractModel::styleValue(const mu::engraving::Sid& sid) const
627 : {
628 0 : return style() ? style()->styleValue(sid).toQVariant() : QVariant();
629 : }
630 :
631 0 : PropertyValue PropertiesPanelAbstractModel::valueToElementUnits(const mu::engraving::Pid& pid, const QVariant& value,
632 : const mu::engraving::EngravingItem* element) const
633 : {
634 0 : if (mu::engraving::Pid::VERSE == pid) {
635 0 : return value.toInt() - 1;
636 : }
637 :
638 0 : auto toPoint = [](const QVariant& v) {
639 0 : return PointF::fromQPointF(v.value<QPointF>());
640 : };
641 :
642 0 : P_TYPE type = mu::engraving::propertyType(pid);
643 0 : switch (type) {
644 0 : case P_TYPE::POINT: {
645 0 : PointF point = toPoint(value);
646 0 : if (pid == Pid::OFFSET) {
647 : // Displayed: positive = up, negative = down
648 : // Internal: negative = up, positive = down
649 0 : point = OffsetUtils::flipYSign(point);
650 : }
651 :
652 0 : if (pid == Pid::OFFSET ? element->offsetIsSpatiumDependent() : element->sizeIsSpatiumDependent()) {
653 0 : return point * element->spatium();
654 : } else {
655 0 : return point * mu::engraving::DPMM;
656 : }
657 : }
658 :
659 0 : case P_TYPE::ABSOLUTE:
660 0 : return element->absoluteFromSpatium(Spatium(value.toReal()));
661 :
662 0 : case P_TYPE::SPATIUM:
663 0 : return Spatium(value.toReal());
664 :
665 0 : case P_TYPE::TEMPO:
666 0 : return BeatsPerSecond::fromBPM(BeatsPerMinute(value.toReal()));
667 :
668 0 : case P_TYPE::INT_VEC: {
669 0 : bool ok = true;
670 0 : std::vector<int> res;
671 :
672 0 : for (const QString& str : value.toString().split(',', Qt::SkipEmptyParts)) {
673 0 : if (int i = str.simplified().toInt(&ok); ok) {
674 0 : res.push_back(i);
675 : }
676 0 : }
677 :
678 0 : return res;
679 0 : } break;
680 :
681 0 : case P_TYPE::COLOR:
682 0 : return Color::fromQColor(value.value<QColor>());
683 :
684 0 : default:
685 0 : return PropertyValue::fromQVariant(value, type);
686 : }
687 : }
688 :
689 0 : QVariant PropertiesPanelAbstractModel::valueFromElementUnits(const mu::engraving::Pid& pid, const PropertyValue& value,
690 : const mu::engraving::EngravingItem* element) const
691 : {
692 0 : if (mu::engraving::Pid::VERSE == pid) {
693 0 : return value.toInt() + 1;
694 : }
695 :
696 0 : switch (value.type()) {
697 0 : case P_TYPE::POINT: {
698 0 : PointF point = value.value<PointF>();
699 0 : if (pid == Pid::OFFSET ? element->offsetIsSpatiumDependent() : element->sizeIsSpatiumDependent()) {
700 0 : point = point / element->spatium();
701 : } else {
702 0 : point = point / mu::engraving::DPMM;
703 : }
704 :
705 0 : if (pid == Pid::OFFSET) {
706 : // Displayed: positive = up, negative = down
707 : // Internal: negative = up, positive = down
708 0 : point = OffsetUtils::flipYSign(point);
709 : }
710 :
711 0 : return point.toQPointF();
712 : }
713 :
714 0 : case P_TYPE::ABSOLUTE:
715 0 : return Spatium::fromAbsolute(value.toReal(), element->spatium()).val();
716 :
717 0 : case P_TYPE::SPATIUM:
718 0 : return value.value<Spatium>().val();
719 :
720 0 : case P_TYPE::TEMPO:
721 0 : return value.value<BeatsPerSecond>().toBPM().val;
722 :
723 0 : case P_TYPE::DIRECTION_V:
724 0 : return static_cast<int>(value.value<mu::engraving::DirectionV>());
725 :
726 0 : case P_TYPE::INT_VEC: {
727 0 : QStringList strList;
728 :
729 0 : for (const int i : value.value<std::vector<int> >()) {
730 0 : strList << QString::number(i);
731 0 : }
732 :
733 0 : return strList.join(",");
734 0 : }
735 0 : case P_TYPE::COLOR:
736 0 : return value.value<muse::draw::Color>().toQColor();
737 0 : default:
738 0 : return value.toQVariant();
739 : }
740 : }
741 :
742 0 : void PropertiesPanelAbstractModel::setElementType(mu::engraving::ElementType type)
743 : {
744 0 : m_elementType = type;
745 0 : }
746 :
747 0 : PropertyItem* PropertiesPanelAbstractModel::buildPropertyItem(const mu::engraving::Pid& propertyId,
748 : std::function<void(const mu::engraving::Pid propertyId,
749 : const QVariant& newValue)> onPropertyChangedCallBack,
750 : std::function<void(const mu::engraving::Sid styleId,
751 : const QVariant& newValue)> onStyleChangedCallBack,
752 : std::function<void(const mu::engraving::Pid propertyId)>
753 : onPropertyResetCallBack)
754 : {
755 0 : PropertyItem* newPropertyItem = new PropertyItem(propertyId, this);
756 :
757 0 : initPropertyItem(newPropertyItem, onPropertyChangedCallBack, onStyleChangedCallBack, onPropertyResetCallBack);
758 :
759 0 : return newPropertyItem;
760 : }
761 :
762 0 : PointFPropertyItem* PropertiesPanelAbstractModel::buildPointFPropertyItem(const mu::engraving::Pid& propertyId,
763 : std::function<void(const mu::engraving::Pid propertyId,
764 : const QVariant& newValue)>
765 : onPropertyChangedCallBack,
766 : std::function<void(const mu::engraving::Pid propertyId)>
767 : onPropertyResetCallBack)
768 : {
769 0 : PointFPropertyItem* newPropertyItem = new PointFPropertyItem(propertyId, this);
770 :
771 0 : initPropertyItem(newPropertyItem, onPropertyChangedCallBack, nullptr, onPropertyResetCallBack);
772 :
773 0 : return newPropertyItem;
774 : }
775 :
776 0 : void PropertiesPanelAbstractModel::initPropertyItem(PropertyItem* propertyItem,
777 : std::function<void(const mu::engraving::Pid propertyId,
778 : const QVariant& newValue)> onPropertyChangedCallBack,
779 : std::function<void(const mu::engraving::Sid styleId,
780 : const QVariant& newValue)> onStyleChangedCallBack,
781 : std::function<void(const mu::engraving::Pid propertyId)> onPropertyResetCallBack)
782 : {
783 0 : auto propertyCallback = onPropertyChangedCallBack;
784 0 : if (!propertyCallback) {
785 0 : propertyCallback = [this](const mu::engraving::Pid propertyId, const QVariant& newValue) {
786 0 : onPropertyValueChanged(propertyId, newValue);
787 0 : };
788 : }
789 :
790 0 : auto styleCallback = onStyleChangedCallBack;
791 0 : if (!styleCallback) {
792 0 : styleCallback = [this](const mu::engraving::Sid styleId, const QVariant& newValue) {
793 0 : updateStyleValue(styleId, newValue);
794 :
795 0 : emit requestReloadPropertyItems();
796 0 : };
797 : }
798 :
799 0 : auto resetCallback = onPropertyResetCallBack;
800 0 : if (!resetCallback) {
801 0 : resetCallback = [this](const mu::engraving::Pid propertyId) {
802 0 : onPropertyValueReset(propertyId);
803 0 : };
804 : }
805 :
806 0 : connect(propertyItem, &PropertyItem::propertyModified, this, propertyCallback);
807 0 : connect(propertyItem, &PropertyItem::resetToDefaultRequested, this, resetCallback);
808 0 : connect(propertyItem, &PropertyItem::applyToStyleRequested, this, styleCallback);
809 0 : }
810 :
811 0 : void PropertiesPanelAbstractModel::loadPropertyItem(PropertyItem* propertyItem, ConvertPropertyValueFunc convertElementPropertyValueFunc)
812 : {
813 0 : loadPropertyItem(propertyItem, m_elementList, convertElementPropertyValueFunc);
814 0 : }
815 :
816 0 : void PropertiesPanelAbstractModel::loadPropertyItem(PropertyItem* propertyItem, const QList<EngravingItem*>& elements,
817 : ConvertPropertyValueFunc convertElementPropertyValueFunc)
818 : {
819 0 : if (!propertyItem) {
820 0 : return;
821 : }
822 :
823 0 : if (elements.isEmpty()) {
824 0 : propertyItem->setIsEnabled(false);
825 0 : return;
826 : }
827 :
828 0 : mu::engraving::Pid pid = propertyItem->propertyId();
829 :
830 0 : mu::engraving::Sid styleId = styleIdByPropertyId(pid);
831 0 : propertyItem->setStyleId(styleId);
832 :
833 0 : QVariant propertyValue;
834 :
835 0 : bool isUndefined = false;
836 0 : bool isModified = false;
837 :
838 0 : for (const mu::engraving::EngravingItem* element : elements) {
839 0 : IF_ASSERT_FAILED(element) {
840 0 : continue;
841 : }
842 :
843 0 : QVariant elementCurrentValue = valueFromElementUnits(pid, element->getProperty(pid), element);
844 0 : QVariant elementDefaultValue = valueFromElementUnits(pid, element->propertyDefault(pid), element);
845 :
846 0 : bool isPropertySupportedByElement = elementCurrentValue.isValid();
847 :
848 0 : if (!isPropertySupportedByElement) {
849 0 : continue;
850 : }
851 :
852 0 : if (convertElementPropertyValueFunc) {
853 0 : elementCurrentValue = convertElementPropertyValueFunc(elementCurrentValue);
854 0 : elementDefaultValue = convertElementPropertyValueFunc(elementDefaultValue);
855 : }
856 :
857 0 : if (!propertyValue.isValid()) {
858 0 : propertyValue = elementCurrentValue;
859 : }
860 :
861 0 : if (!isUndefined && propertyValue != elementCurrentValue) {
862 0 : isUndefined = true;
863 : }
864 :
865 0 : if (!isModified) {
866 0 : PropertyFlags f = element->propertyFlags(pid);
867 0 : if (f == PropertyFlags::UNSTYLED || elementCurrentValue != elementDefaultValue) {
868 0 : isModified = true;
869 : }
870 : }
871 :
872 0 : if (isUndefined && isModified) {
873 0 : break;
874 : }
875 0 : }
876 :
877 : //@note Some elements may support the property, some don't. If element doesn't support property it'll return invalid value.
878 : // So we use that knowledge here
879 0 : propertyItem->setIsEnabled(propertyValue.isValid());
880 :
881 0 : if (isUndefined) {
882 0 : propertyValue = QVariant();
883 : }
884 :
885 0 : propertyItem->updateCurrentValue(propertyValue);
886 0 : propertyItem->setIsModified(isModified);
887 0 : }
888 :
889 0 : bool PropertiesPanelAbstractModel::isNotationExisting() const
890 : {
891 0 : return context()->project() != nullptr;
892 : }
893 :
894 0 : INotationStylePtr PropertiesPanelAbstractModel::style() const
895 : {
896 0 : return currentNotation() ? currentNotation()->style() : nullptr;
897 : }
898 :
899 0 : INotationUndoStackPtr PropertiesPanelAbstractModel::undoStack() const
900 : {
901 0 : return currentNotation() ? currentNotation()->undoStack() : nullptr;
902 : }
903 :
904 0 : void PropertiesPanelAbstractModel::beginCommand(const muse::TranslatableString& actionName)
905 : {
906 0 : if (undoStack()) {
907 0 : undoStack()->prepareChanges(actionName);
908 : }
909 :
910 : //! NOTE prevents unnecessary updating of properties
911 : //! after changing their values in the propertiespanel
912 0 : m_shouldUpdateOnScoreChange = false;
913 0 : }
914 :
915 0 : void PropertiesPanelAbstractModel::endCommand()
916 : {
917 0 : if (undoStack()) {
918 0 : undoStack()->commitChanges();
919 : }
920 :
921 0 : m_shouldUpdateOnScoreChange = true;
922 0 : }
923 :
924 0 : INotationPtr PropertiesPanelAbstractModel::currentNotation() const
925 : {
926 0 : return context()->currentNotation();
927 : }
928 :
929 0 : bool PropertiesPanelAbstractModel::isMasterNotation() const
930 : {
931 0 : INotationPtr notation = currentNotation();
932 0 : if (!notation) {
933 0 : return false;
934 : }
935 :
936 0 : return notation->isMaster();
937 0 : }
938 :
939 0 : INotationSelectionPtr PropertiesPanelAbstractModel::selection() const
940 : {
941 0 : INotationPtr notation = currentNotation();
942 0 : return notation ? notation->interaction()->selection() : nullptr;
943 0 : }
944 :
945 0 : void PropertiesPanelAbstractModel::updateNotation()
946 : {
947 0 : if (!currentNotation()) {
948 0 : return;
949 : }
950 :
951 0 : currentNotation()->notationChanged().send(muse::RectF());
952 : }
|