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 "notationcontextmenumodel.h"
24 :
25 : #include "types/translatablestring.h"
26 :
27 : #include "ui/view/iconcodes.h"
28 :
29 : #include "engraving/dom/fret.h"
30 : #include "engraving/dom/gradualtempochange.h"
31 : #include "engraving/dom/harmony.h"
32 : #include "engraving/dom/part.h"
33 : #include "engraving/dom/staff.h"
34 :
35 : #include "notation/imasternotation.h"
36 : #include "notation/inotation.h"
37 : #include "notation/inotationselection.h"
38 :
39 : #include "notationscene/notationcommands.h"
40 : #include "palette/palettecommands.h"
41 : #include "instrumentsscene/instrumentscommands.h"
42 :
43 : #include "widgets/editstyleutils.h"
44 :
45 : using namespace mu::notation;
46 : using namespace mu::palette;
47 : using namespace mu::instrumentsscene;
48 : using namespace muse;
49 : using namespace muse::uicomponents;
50 :
51 0 : void NotationContextMenuModel::loadItems(int elementType)
52 : {
53 0 : AbstractMenuModel::load();
54 :
55 0 : MenuItemList items = makeItemsByElementType(static_cast<ElementType>(elementType));
56 :
57 0 : const INotationAutomationPtr automation = this->automation();
58 0 : if (automation && automation->isAutomationModeEnabled()) {
59 0 : items << makeSeparator()
60 0 : << makeMenu(TranslatableString::untranslatable("Automation type"), makeAutomationTypeItems());
61 : }
62 :
63 0 : setItems(items);
64 0 : }
65 :
66 0 : MenuItemList NotationContextMenuModel::makeItemsByElementType(ElementType elementType)
67 : {
68 0 : switch (elementType) {
69 0 : case ElementType::MEASURE:
70 0 : return makeMeasureItems();
71 0 : case ElementType::PAGE:
72 0 : return makePageItems();
73 0 : case ElementType::STAFF_TEXT:
74 0 : return makeStaffTextItems();
75 0 : case ElementType::SYSTEM_TEXT:
76 : case ElementType::TRIPLET_FEEL:
77 0 : return makeSystemTextItems();
78 0 : case ElementType::TIMESIG:
79 0 : return makeTimeSignatureItems();
80 0 : case ElementType::INSTRUMENT_NAME:
81 0 : return makeInstrumentNameItems();
82 0 : case ElementType::HARMONY:
83 0 : return makeHarmonyItems();
84 0 : case ElementType::FRET_DIAGRAM:
85 0 : return makeFretboardDiagramItems();
86 0 : case ElementType::INSTRUMENT_CHANGE:
87 0 : return makeChangeInstrumentItems();
88 0 : case ElementType::VBOX:
89 0 : return makeVerticalBoxItems();
90 0 : case ElementType::HBOX:
91 0 : return makeHorizontalBoxItems();
92 0 : case ElementType::HAIRPIN_SEGMENT:
93 0 : return makeHairpinItems();
94 0 : case ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT:
95 0 : return makeGradualTempoChangeItems();
96 0 : case ElementType::TEXT:
97 0 : return makeTextItems();
98 0 : default:
99 0 : break;
100 : }
101 :
102 0 : return makeElementItems();
103 : }
104 :
105 0 : MenuItemList NotationContextMenuModel::makePageItems()
106 : {
107 : MenuItemList items {
108 0 : makeMenuItem(OPEN_EDIT_STYLE_COMMAND),
109 0 : makeMenuItem(OPEN_PAGE_SETTINGS_COMMAND),
110 0 : makeMenuItem(LOAD_STYLE_COMMAND),
111 0 : };
112 :
113 0 : return items;
114 : }
115 :
116 0 : MenuItemList NotationContextMenuModel::makeDefaultCopyPasteItems()
117 : {
118 : MenuItemList items {
119 0 : makeMenuItem(CUT_COMMAND),
120 0 : makeMenuItem(COPY_COMMAND),
121 0 : makeMenuItem(PASTE_COMMAND),
122 0 : makeMenuItem(COPY_PASTE_SWAP_COMMAND),
123 0 : makeMenuItem(DELETE_COMMAND),
124 0 : };
125 :
126 0 : return items;
127 : }
128 :
129 0 : MenuItemList NotationContextMenuModel::makeMeasureItems()
130 : {
131 : MenuItemList items = {
132 0 : makeMenuItem(CUT_COMMAND),
133 0 : makeMenuItem(COPY_COMMAND),
134 0 : makeMenuItem(PASTE_COMMAND),
135 0 : makeMenuItem(COPY_PASTE_SWAP_COMMAND),
136 0 : };
137 :
138 0 : items << makeSeparator();
139 :
140 0 : MenuItem* clearItem = makeMenuItem(DELETE_COMMAND);
141 0 : clearItem->setTitle(TranslatableString("notation", "Clear measures"));
142 0 : MenuItem* deleteItem = makeMenuItem(REMOVE_SELECTED_RANGE_COMMAND);
143 0 : deleteItem->setTitle(TranslatableString("notation", "Delete measures"));
144 0 : items << clearItem;
145 0 : items << deleteItem;
146 :
147 0 : items << makeSeparator();
148 :
149 0 : if (isDrumsetStaff()) {
150 0 : items << makeMenuItem(OPEN_CUSTOMIZE_KIT_COMMAND);
151 : }
152 :
153 0 : items << makeMenuItem(OPEN_STAFF_PROPERTIES_COMMAND);
154 0 : items << makeSeparator();
155 0 : items << makeMenu(TranslatableString("notation", "Insert measures"), makeInsertMeasuresItems());
156 0 : if (projectSession()->currentNotation()->viewMode() == mu::notation::ViewMode::PAGE) {
157 0 : items << makeMenu(TranslatableString("notation", "Move measures"), makeMoveMeasureItems());
158 : }
159 0 : items << makeMenuItem(MAKE_INTO_SYSTEM_COMMAND);
160 0 : items << makeSeparator();
161 0 : items << makeMenuItem(OPEN_MEASURE_PROPERTIES_COMMAND);
162 :
163 0 : return items;
164 0 : }
165 :
166 0 : MenuItemList NotationContextMenuModel::makeStaffTextItems()
167 : {
168 0 : MenuItemList items = makeElementItems();
169 0 : items << makeSeparator();
170 0 : items << makeMenuItem(OPEN_STAFF_TEXT_PROPERTIES_COMMAND);
171 :
172 0 : return items;
173 0 : }
174 :
175 0 : MenuItemList NotationContextMenuModel::makeSystemTextItems()
176 : {
177 0 : MenuItemList items = makeElementItems();
178 0 : items << makeSeparator();
179 0 : items << makeMenuItem(OPEN_SYSTEM_TEXT_PROPERTIES_COMMAND);
180 :
181 0 : return items;
182 0 : }
183 :
184 0 : MenuItemList NotationContextMenuModel::makeTimeSignatureItems()
185 : {
186 0 : MenuItemList items = makeElementItems();
187 0 : items << makeSeparator();
188 0 : items << makeMenuItem(OPEN_TIME_SIGNATURE_PROPERTIES_COMMAND);
189 :
190 0 : return items;
191 0 : }
192 :
193 0 : MenuItemList NotationContextMenuModel::makeInstrumentNameItems()
194 : {
195 0 : MenuItemList items = makeElementItems();
196 0 : items << makeSeparator();
197 0 : items << makeMenuItem(OPEN_STAFF_PROPERTIES_COMMAND);
198 :
199 0 : return items;
200 0 : }
201 :
202 0 : MenuItemList NotationContextMenuModel::makeHarmonyItems()
203 : {
204 0 : const EngravingItem* element = currentElement();
205 0 : if (element && engraving::toHarmony(element)->isInFretBox()) {
206 0 : return makeElementInFretBoxItems();
207 : }
208 :
209 0 : MenuItemList items = makeElementItems();
210 0 : items << makeSeparator();
211 :
212 0 : if (element) {
213 0 : engraving::EngravingObject* parent = element->isHarmony() ? element->ownershipParent() : nullptr;
214 0 : bool hasLinkedFretboardDiagram = parent && parent->isFretDiagram();
215 0 : if (!hasLinkedFretboardDiagram) {
216 0 : items << makeMenuItem(ADD_FRETBOARD_DIAGRAM_COMMAND);
217 : }
218 : }
219 :
220 0 : items << makeMenuItem(OPEN_REALIZECHORDSYMBOLS_COMMAND);
221 :
222 0 : return items;
223 0 : }
224 :
225 0 : MenuItemList NotationContextMenuModel::makeFretboardDiagramItems()
226 : {
227 0 : const EngravingItem* element = currentElement();
228 0 : if (element && engraving::toFretDiagram(element)->isInFretBox()) {
229 0 : return makeElementInFretBoxItems();
230 : }
231 :
232 0 : MenuItemList items = makeElementItems();
233 :
234 0 : const engraving::FretDiagram* fretDiagram = engraving::toFretDiagram(element);
235 0 : if (!fretDiagram->harmony()) {
236 0 : items << makeSeparator();
237 0 : items << makeMenuItem(ADD_CHORD_TEXT_COMMAND, TranslatableString("notation", "Add c&hord symbol"));
238 : }
239 :
240 0 : return items;
241 0 : }
242 :
243 0 : MenuItemList NotationContextMenuModel::makeElementInFretBoxItems()
244 : {
245 : MenuItemList items {
246 0 : makeMenuItem(COPY_COMMAND)
247 0 : };
248 :
249 0 : MenuItem* hideItem = makeMenuItem(DELETE_COMMAND);
250 0 : hideItem->setTitle(TranslatableString("notation", "Hide"));
251 0 : hideItem->setIcon(ui::IconCode::Code::NONE);
252 :
253 0 : items << hideItem
254 0 : << makeSeparator();
255 :
256 0 : MenuItemList selectItems = makeSelectItems();
257 :
258 0 : if (!selectItems.isEmpty()) {
259 0 : items << makeMenu(TranslatableString("notation", "Select"), selectItems)
260 0 : << makeSeparator();
261 : }
262 :
263 0 : const EngravingItem* element = currentElement();
264 0 : items << makeEditStyle(element);
265 :
266 0 : return items;
267 0 : }
268 :
269 0 : MenuItemList NotationContextMenuModel::makeSelectItems()
270 : {
271 0 : if (isSingleSelection()) {
272 0 : return MenuItemList { makeMenuItem(SELECT_SIMILAR_COMMAND),
273 0 : makeMenuItem(SELECT_SIMILAR_IN_STAFF_COMMAND),
274 0 : makeMenuItem(OPEN_SELECTION_OPTIONS_COMMAND) };
275 0 : } else if (canSelectSimilarInRange()) {
276 0 : return MenuItemList { makeMenuItem(SELECT_SIMILAR_IN_RANGE_COMMAND), makeMenuItem(OPEN_SELECTION_OPTIONS_COMMAND) };
277 0 : } else if (canSelectSimilar()) {
278 0 : return MenuItemList{ makeMenuItem(OPEN_SELECTION_OPTIONS_COMMAND) };
279 : }
280 :
281 0 : return MenuItemList();
282 : }
283 :
284 0 : MenuItemList NotationContextMenuModel::makeElementItems()
285 : {
286 0 : MenuItemList items = makeDefaultCopyPasteItems();
287 :
288 0 : if (interaction()->isTextEditingStarted()) {
289 0 : return items;
290 : }
291 :
292 0 : MenuItemList selectItems = makeSelectItems();
293 :
294 0 : if (!selectItems.isEmpty()) {
295 0 : items << makeMenu(TranslatableString("notation", "Select"), selectItems);
296 : }
297 :
298 0 : const EngravingItem* element = currentElement();
299 :
300 0 : if (element && element->isEditable()) {
301 0 : items << makeSeparator();
302 0 : items << makeMenuItem(EDIT_ELEMENT_COMMAND);
303 : }
304 :
305 0 : items << makeSeparator()
306 0 : << makeEditStyle(element);
307 :
308 0 : return items;
309 0 : }
310 :
311 0 : MenuItemList NotationContextMenuModel::makeInsertMeasuresItems()
312 : {
313 : MenuItemList items {
314 0 : makeMenuItem(INSERT_MEASURES_AFTER_SELECTION_COMMAND, TranslatableString("notation", "After selection…")),
315 0 : makeMenuItem(INSERT_MEASURES_COMMAND, TranslatableString("notation", "Before selection…")),
316 0 : makeSeparator(),
317 0 : makeMenuItem(INSERT_MEASURES_AT_START_OF_SCORE_COMMAND, TranslatableString("notation", "At start of score…")),
318 0 : makeMenuItem(APPEND_MEASURES_COMMAND, TranslatableString("notation", "At end of score…"))
319 0 : };
320 :
321 0 : return items;
322 : }
323 :
324 0 : MenuItemList NotationContextMenuModel::makeMoveMeasureItems()
325 : {
326 : MenuItemList items {
327 0 : makeMenuItem(MOVE_MEASURE_TO_PREV_SYSTEM_COMMAND, TranslatableString("notation", "To previous system")),
328 0 : makeMenuItem(MOVE_MEASURE_TO_NEXT_SYSTEM_COMMAND, TranslatableString("notation", "To next system"))
329 0 : };
330 :
331 0 : return items;
332 : }
333 :
334 0 : MenuItemList NotationContextMenuModel::makeChangeInstrumentItems()
335 : {
336 0 : MenuItemList items = makeElementItems();
337 0 : items << makeSeparator();
338 0 : items << makeMenuItem(INSTRUMENTS_CHANGE_COMMAND);
339 :
340 0 : return items;
341 0 : }
342 :
343 0 : MenuItemList NotationContextMenuModel::makeVerticalBoxItems()
344 : {
345 0 : MenuItemList addMenuItems;
346 0 : addMenuItems << makeMenuItem(ADD_FRAME_TEXT_COMMAND);
347 0 : addMenuItems << makeMenuItem(ADD_TITLE_TEXT_COMMAND);
348 0 : addMenuItems << makeMenuItem(ADD_SUBTITLE_TEXT_COMMAND);
349 0 : addMenuItems << makeMenuItem(ADD_COMPOSER_TEXT_COMMAND);
350 0 : addMenuItems << makeMenuItem(ADD_LYRICIST_TEXT_COMMAND);
351 0 : addMenuItems << makeMenuItem(ADD_PART_TEXT_COMMAND);
352 0 : addMenuItems << makeMenuItem(ADD_IMAGE_COMMAND);
353 :
354 0 : MenuItemList items = makeElementItems();
355 0 : items << makeSeparator();
356 0 : items << makeMenu(TranslatableString("notation", "Add"), addMenuItems);
357 :
358 0 : return items;
359 0 : }
360 :
361 0 : MenuItemList NotationContextMenuModel::makeHorizontalBoxItems()
362 : {
363 0 : MenuItemList addMenuItems;
364 0 : addMenuItems << makeMenuItem(ADD_FRAME_TEXT_COMMAND);
365 0 : addMenuItems << makeMenuItem(ADD_IMAGE_COMMAND);
366 :
367 0 : MenuItemList items = makeElementItems();
368 0 : items << makeSeparator();
369 0 : items << makeMenu(TranslatableString("notation", "Add"), addMenuItems);
370 :
371 0 : return items;
372 0 : }
373 :
374 0 : MenuItemList NotationContextMenuModel::makeHairpinItems()
375 : {
376 0 : MenuItemList items = makeElementItems();
377 :
378 0 : const EngravingItem* element = currentElement();
379 0 : if (!element || !element->isHairpinSegment() || !isSingleSelection()) {
380 0 : return items;
381 : }
382 :
383 0 : items << makeSeparator();
384 :
385 0 : const engraving::Hairpin* h = toHairpinSegment(element)->hairpin();
386 :
387 0 : MenuItem* snapPrev = makeMenuItem(TOGGLE_SNAP_TO_PREV_COMMAND);
388 0 : snapPrev->setChecked(h->snapToItemBefore());
389 0 : items << snapPrev;
390 :
391 0 : MenuItem* snapNext = makeMenuItem(TOGGLE_SNAP_TO_NEXT_COMMAND);
392 0 : snapNext->setChecked(h->snapToItemAfter());
393 0 : items << snapNext;
394 :
395 0 : return items;
396 0 : }
397 :
398 0 : MenuItemList NotationContextMenuModel::makeGradualTempoChangeItems()
399 : {
400 0 : MenuItemList items = makeElementItems();
401 :
402 0 : const EngravingItem* element = currentElement();
403 0 : if (!element || !element->isGradualTempoChangeSegment() || !isSingleSelection()) {
404 0 : return items;
405 : }
406 :
407 0 : items << makeSeparator();
408 :
409 0 : const engraving::GradualTempoChange* gtc = toGradualTempoChangeSegment(element)->tempoChange();
410 :
411 0 : MenuItem* snapNext = makeMenuItem(TOGGLE_SNAP_TO_NEXT_COMMAND);
412 0 : snapNext->setChecked(gtc->snapToItemAfter());
413 0 : items << snapNext;
414 :
415 0 : return items;
416 0 : }
417 :
418 0 : MenuItemList NotationContextMenuModel::makeTextItems()
419 : {
420 0 : const EngravingItem* element = currentElement();
421 0 : if (!(element->ownershipParent() && element->ownershipParent()->isBarLine())) {
422 : // Regular text
423 0 : return makeElementItems();
424 : }
425 :
426 : // Play count text
427 0 : MenuItemList items;
428 :
429 0 : if (interaction()->isTextEditingStarted()) {
430 0 : return items;
431 : }
432 :
433 0 : MenuItemList selectItems = makeSelectItems();
434 :
435 0 : if (!selectItems.isEmpty()) {
436 0 : items << makeMenu(TranslatableString("notation", "Select"), selectItems);
437 : }
438 :
439 0 : items << makeSeparator()
440 0 : << makeEditStyle(element);
441 :
442 0 : return items;
443 0 : }
444 :
445 0 : MenuItem* NotationContextMenuModel::makeEditStyle(const EngravingItem* element)
446 : {
447 0 : MenuItem* item = makeMenuItem(OPEN_EDIT_STYLE_COMMAND);
448 :
449 0 : if (element) {
450 0 : std::string pageCode = EditStyleUtils::pageCodeForElement(element).toStdString();
451 0 : if (!pageCode.empty()) {
452 0 : rcommand::CommandQuery query(OPEN_EDIT_STYLE_COMMAND);
453 0 : query.addParam("page_code", Val(pageCode));
454 :
455 0 : std::string subPageCode = EditStyleUtils::subPageCodeForElement(element).toStdString();
456 0 : if (!subPageCode.empty()) {
457 0 : query.addParam("sub_page_code", Val(subPageCode));
458 : }
459 :
460 0 : item->setCommandQuery(query);
461 0 : }
462 0 : }
463 :
464 0 : return item;
465 : }
466 :
467 0 : bool NotationContextMenuModel::isSingleSelection() const
468 : {
469 0 : INotationSelectionPtr selection = this->selection();
470 0 : return selection ? selection->element() != nullptr : false;
471 0 : }
472 :
473 0 : bool NotationContextMenuModel::canSelectSimilar() const
474 : {
475 0 : return currentElement() != nullptr;
476 : }
477 :
478 0 : bool NotationContextMenuModel::canSelectSimilarInRange() const
479 : {
480 0 : return canSelectSimilar() && selection()->isRange();
481 : }
482 :
483 0 : bool NotationContextMenuModel::isDrumsetStaff() const
484 : {
485 0 : const INotationInteraction::HitElementContext& ctx = hitElementContext();
486 0 : if (!ctx.staff) {
487 0 : return false;
488 : }
489 :
490 0 : Fraction tick = ctx.element ? ctx.element->tick() : Fraction { -1, 1 };
491 0 : return ctx.staff->part()->instrument(tick)->drumset() != nullptr;
492 : }
493 :
494 0 : MenuItemList NotationContextMenuModel::makeAutomationTypeItems()
495 : {
496 : return {
497 0 : makeAutomationTypeItem(AutomationType::Dynamics, "dynamics", TranslatableString::untranslatable("Dynamics")),
498 0 : makeAutomationTypeItem(AutomationType::Tempo, "tempo", TranslatableString::untranslatable("Tempo")),
499 0 : makeAutomationTypeItem(AutomationType::Volume, "volume", TranslatableString::untranslatable("Volume")),
500 0 : makeAutomationTypeItem(AutomationType::Pan, "pan", TranslatableString::untranslatable("Pan")),
501 0 : };
502 : }
503 :
504 0 : MenuItem* NotationContextMenuModel::makeAutomationTypeItem(AutomationType type, const std::string& queryTypeParam,
505 : const TranslatableString& title)
506 : {
507 0 : MenuItem* item = makeMenuItem(SELECT_AUTOMATION_TYPE_COMMAND, title);
508 0 : if (!item) {
509 0 : return item;
510 : }
511 :
512 0 : rcommand::CommandQuery query(SELECT_AUTOMATION_TYPE_COMMAND);
513 0 : query.addParam("type", Val(queryTypeParam));
514 0 : item->setCommandQuery(query);
515 :
516 0 : item->setChecked(notationConfiguration()->currentAutomationType() == type);
517 :
518 0 : return item;
519 0 : }
520 :
521 0 : INotationInteractionPtr NotationContextMenuModel::interaction() const
522 : {
523 0 : INotationPtr notation = projectSession()->currentNotation();
524 0 : return notation ? notation->interaction() : nullptr;
525 0 : }
526 :
527 0 : INotationSelectionPtr NotationContextMenuModel::selection() const
528 : {
529 0 : INotationPtr notation = projectSession()->currentNotation();
530 0 : return notation ? notation->interaction()->selection() : nullptr;
531 0 : }
532 :
533 0 : INotationAutomationPtr NotationContextMenuModel::automation() const
534 : {
535 0 : IMasterNotationPtr masterNotation = projectSession()->masterNotation();
536 0 : return masterNotation ? masterNotation->automation() : nullptr;
537 0 : }
538 :
539 0 : const EngravingItem* NotationContextMenuModel::currentElement() const
540 : {
541 0 : const EngravingItem* element = hitElementContext().element;
542 0 : if (element) {
543 0 : return element;
544 : }
545 :
546 0 : auto selection = this->selection();
547 0 : return selection && selection->element() ? selection->element() : nullptr;
548 0 : }
549 :
550 0 : const INotationInteraction::HitElementContext& NotationContextMenuModel::hitElementContext() const
551 : {
552 0 : if (INotationInteractionPtr interaction = this->interaction()) {
553 0 : return interaction->hitElementContext();
554 0 : }
555 :
556 : static INotationInteraction::HitElementContext dummy;
557 0 : return dummy;
558 : }
|