diff --git a/share/instruments/CMakeLists.txt b/share/instruments/CMakeLists.txt index bdbb26355e08c..d1c01cc03e413 100644 --- a/share/instruments/CMakeLists.txt +++ b/share/instruments/CMakeLists.txt @@ -21,6 +21,7 @@ install(FILES instruments.xml orders.xml + string_tunings_presets.json DESTINATION ${Mscore_SHARE_NAME}${Mscore_INSTALL_NAME}instruments ) diff --git a/share/instruments/string_tunings_presets.json b/share/instruments/string_tunings_presets.json new file mode 100644 index 0000000000000..1c057f4c1dece --- /dev/null +++ b/share/instruments/string_tunings_presets.json @@ -0,0 +1,308 @@ +[ + { + "familyId": "guitars", + "strings": [ + { + "number": 5, + "presets": [ + { + "name": "Standard", + "value": [40, 45, 50, 55, 59] + },{ + "name": "Hi C", + "value": [40, 45, 50, 55, 60] + },{ + "name": "Baritone", + "value": [36, 43, 50, 57, 64] + },{ + "name": "Open G", + "value": [43, 50, 55, 59, 62] + } + ] + },{ + "number": 6, + "presets": [ + { + "name": "Standard", + "value": [40, 45, 50, 55, 59, 64] + },{ + "name": "Tune down 1/2 step", + "value": [39, 44, 49, 54, 58, 63] + },{ + "name": "Tune down 1 step", + "value": [38, 43, 48, 53, 57, 62] + },{ + "name": "Tune down 2 step", + "value": [36, 41, 46, 51, 55, 60] + },{ + "name": "Dropped D tune down 1/2 step", + "value": [37, 44, 49, 54, 58, 63] + },{ + "name": "Dropped D Variant", + "value": [38, 45, 50, 55, 57, 64] + },{ + "name": "Double Dropped D", + "value": [38, 45, 50, 55, 59, 62] + },{ + "name": "Dropped C", + "value": [36, 43, 48, 53, 57, 62] + },{ + "name": "Dropped E", + "value": [40, 47, 52, 57, 61, 66] + },{ + "name": "Dropped B", + "value": [35, 42, 47, 52, 56, 61] + },{ + "name": "Baritone", + "value": [35, 40, 45, 50, 54, 59] + },{ + "name": "Open C", + "value": [36, 43, 48, 55, 60, 64] + },{ + "name": "Open Cm", + "value": [36, 43, 48, 55, 60, 63] + },{ + "name": "Open C6", + "value": [36, 43, 48, 55, 57, 64] + },{ + "name": "Open CM7", + "value": [36, 43, 52, 55, 59, 64] + },{ + "name": "Open D", + "value": [38, 45, 50, 54, 57, 62] + },{ + "name": "Open Dm", + "value": [38, 45, 50, 53, 57, 62] + },{ + "name": "Open D5", + "value": [38, 45, 50, 50, 57, 62] + },{ + "name": "Open D6", + "value": [38, 45, 50, 54, 59, 62] + },{ + "name": "Open Dsus4", + "value": [38, 45, 50, 55, 57, 62] + },{ + "name": "Open E", + "value": [40, 47, 52, 56, 59, 64] + },{ + "name": "Open Em", + "value": [40, 47, 52, 55, 59, 64] + },{ + "name": "Open Esus11", + "value": [40, 45, 52, 55, 59, 64] + },{ + "name": "Open F", + "value": [41, 45, 48, 53, 60, 65] + },{ + "name": "Open G", + "value": [38, 43, 50, 55, 59, 62] + },{ + "name": "Open Gm", + "value": [38, 43, 50, 55, 58, 62] + },{ + "name": "Open Gsus4", + "value": [38, 43, 50, 55, 60, 62] + },{ + "name": "Open G6", + "value": [38, 43, 50, 55, 59, 64] + },{ + "name": "Open A", + "value": [40, 45, 52, 57, 61, 64] + },{ + "name": "Open Am", + "value": [40, 45, 52, 57, 60, 64] + },{ + "name": "Dobro Open G", + "value": [43, 47, 50, 55, 59, 62] + },{ + "name": "Lute or Vihuela", + "value": [40, 45, 50, 54, 59, 64] + },{ + "name": "Nashville", + "value": [52, 57, 62, 67, 59, 64] + } + ] + },{ + "number": 7, + "presets": [ + { + "name": "Standard", + "value": [35, 40, 45, 50, 55, 59, 64] + },{ + "name": "Drop D", + "value": [33, 38, 45, 50, 55, 59, 64] + },{ + "name": "Tune down 1/2 step", + "value": [34, 39, 44, 49, 54, 58, 63] + },{ + "name": "Tune down 1 step", + "value": [33, 38, 43, 48, 53, 57, 62] + },{ + "name": "Tune down 2 step", + "value": [31, 36, 41, 46, 51, 55, 60] + } + ] + },{ + "number": 8, + "presets": [ + { + "name": "Standard", + "value": [30, 35, 40, 45, 50, 55, 59, 64] + },{ + "name": "Tune down 1/2 step", + "value": [29, 34, 39, 44, 49, 54, 58, 63] + },{ + "name": "Tune down 1 step", + "value": [28, 33, 38, 43, 48, 53, 57, 62] + },{ + "name": "Tune down 2 step", + "value": [27, 32, 36, 41, 46, 51, 55, 60] + } + ] + },{ + "number": 9, + "presets": [ + { + "name": "Standard", + "value": [25, 30, 35, 40, 45, 50, 55, 59, 64] + } + ] + },{ + "number": 10, + "presets": [ + { + "name": "Standard", + "value": [30, 32, 34, 36, 40, 45, 57, 62, 67, 76] + },{ + "name": "Baroque", + "value": [45, 47, 48, 50, 52, 57, 62, 67, 71, 76] + } + ] + } + ] + }, + { + "familyId": "shamisens", + "strings": [ + { + "number": 3, + "presets": [ + { + "name": "Standard", + "value": [57, 64, 69] + } + ] + } + ] + }, + { + "familytId": "banjos", + "strings": [ + { + "number": 4, + "presets": [ + { + "name": "Tenor", + "value": [48, 55, 62, 69] + },{ + "name": "Irish Tenor", + "value": [43, 50, 57, 64] + },{ + "name": "Chicago Tenor", + "value": [50, 55, 59, 64] + } + ] + },{ + "number": 5, + "presets": [ + { + "name": "Open G", + "value": [67, 50, 55, 59, 62] + },{ + "name": "Drop C", + "value": [67, 48, 55, 59, 62] + },{ + "name": "G Modal", + "value": [67, 50, 55, 60, 62] + },{ + "name": "Double C", + "value": [67, 60, 55, 48, 67] + },{ + "name": "Open D", + "value": [66, 50, 54, 57, 62] + } + ] + },{ + "number": 6, + "presets": [ + { + "name": "Standard", + "value": [40, 45, 50, 55, 59, 64] + } + ] + } + ] + },{ + "familyId": "mandolins", + "strings": [ + { + "number": 4, + "presets": [ + { + "name": "Standard", + "value": [55, 62, 69, 76] + } + ] + } + ] + },{ + "familyId": "ukuleles", + "strings": [ + { + "number": 4, + "presets": [ + { + "name": "C Tuning", + "value": [67, 60, 64, 69] + },{ + "name": "D Tuning", + "value": [69, 62, 66, 71] + },{ + "name": "Low G", + "value": [55, 60, 64, 69] + },{ + "name": "Baritone", + "value": [50, 55, 59, 64] + } + ] + } + ] + },{ + "familyId": "orchestral-strings", + "strings": [ + { + "number": 4, + "presets": [ + { + "name": "Standard", + "value": [55, 62, 69, 76] + } + ] + } + ] + },{ + "instrumentId": "contrabass", + "strings": [ + { + "number": 4, + "presets": [ + { + "name": "Standard", + "value": [28, 33, 38, 43] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index ac0df13e3a5e1..7c474fe058f34 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -1599,11 +1599,24 @@ void Chord::cmdUpdateNotes(AccidentalState* as) const Staff* st = staff(); StaffGroup staffGroup = st->staffTypeForElement(this)->group(); if (staffGroup == StaffGroup::TAB) { - const Instrument* instrument = part()->instrument(this->tick()); + Fraction tick = this->tick(); + for (const Staff* _staff : st->staffList()) { + if (!_staff || _staff == st) { + continue; + } + + if (_staff->score() == st->score() && !_staff->isTabStaff(tick)) { + if (!_staff->reflectTranspositionInLinkedTab()) { + return; + } + } + } + + const StringData* stringData = part()->stringData(tick); for (Chord* ch : graceNotes()) { - instrument->stringData()->fretChords(ch); + stringData->fretChords(ch); } - instrument->stringData()->fretChords(this); + stringData->fretChords(this); return; } else { // if not tablature, use instrument->useDrumset to set staffGroup (to allow pitched to unpitched in same staff) diff --git a/src/engraving/dom/chordrest.cpp b/src/engraving/dom/chordrest.cpp index 898a30fcea6f6..354f21ff1c267 100644 --- a/src/engraving/dom/chordrest.cpp +++ b/src/engraving/dom/chordrest.cpp @@ -288,6 +288,7 @@ EngravingItem* ChordRest::drop(EditData& data) case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::STICKING: case ElementType::STAFF_STATE: case ElementType::HARP_DIAGRAM: diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 006fd3f096454..9c09336301051 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -1735,7 +1735,7 @@ void Score::upDown(bool up, UpDownMode mode) break; case StaffGroup::TAB: { - const StringData* stringData = part->instrument(tick)->stringData(); + const StringData* stringData = part->stringData(tick); switch (mode) { case UpDownMode::OCTAVE: // move same note to next string, if possible { @@ -1869,7 +1869,7 @@ void Score::upDown(bool up, UpDownMode mode) refret = true; } if (refret) { - const StringData* stringData = part->instrument(tick)->stringData(); + const StringData* stringData = part->stringData(tick); stringData->fretChords(oNote->chord()); } } @@ -2005,7 +2005,7 @@ static void changeAccidental2(Note* n, int pitch, int tpc) // as pitch has changed, calculate new // string & fret // - const StringData* stringData = n->part()->instrument(n->tick())->stringData(); + const StringData* stringData = n->part()->stringData(n->tick()); if (stringData) { stringData->convertPitch(pitch, st, &string, &fret); } diff --git a/src/engraving/dom/dom.cmake b/src/engraving/dom/dom.cmake index 7db24475e1fae..a26be1fc64276 100644 --- a/src/engraving/dom/dom.cmake +++ b/src/engraving/dom/dom.cmake @@ -302,6 +302,8 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/stretchedbend.h ${CMAKE_CURRENT_LIST_DIR}/stringdata.cpp ${CMAKE_CURRENT_LIST_DIR}/stringdata.h + ${CMAKE_CURRENT_LIST_DIR}/stringtunings.cpp + ${CMAKE_CURRENT_LIST_DIR}/stringtunings.h ${CMAKE_CURRENT_LIST_DIR}/swing.cpp ${CMAKE_CURRENT_LIST_DIR}/swing.h ${CMAKE_CURRENT_LIST_DIR}/symbol.cpp diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index fca51d4d812fa..63ed3a0f5e59f 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -2279,6 +2279,7 @@ void Score::cmdFlip() || e->isInstrumentChange() || e->isPlayTechAnnotation() || e->isCapo() + || e->isStringTunings() || e->isSticking() || e->isFingering() || e->isDynamic() @@ -5956,6 +5957,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool && et != ElementType::TRIPLET_FEEL && et != ElementType::PLAYTECH_ANNOTATION && et != ElementType::CAPO + && et != ElementType::STRING_TUNINGS && et != ElementType::STICKING && et != ElementType::TREMOLO && et != ElementType::ARPEGGIO @@ -6031,6 +6033,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool || element->isStaffText() || element->isPlayTechAnnotation() || element->isCapo() + || element->isStringTunings() || element->isSticking() || element->isFretDiagram() || element->isHarmony() @@ -6059,6 +6062,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::FRET_DIAGRAM: case ElementType::HARMONY: case ElementType::FIGURED_BASS: @@ -6161,6 +6165,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool || element->isStaffText() || element->isPlayTechAnnotation() || element->isCapo() + || element->isStringTunings() || element->isSticking() || element->isFretDiagram() || element->isFermata() @@ -6192,6 +6197,13 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool if (fd->harmony()) { fd->harmony()->setTrack(ntrack); } + } else if (ne->isStringTunings()) { + StringTunings* stringTunings = toStringTunings(ne); + const StringData* stringData = stringTunings->part()->stringData(tick); + int frets = stringData->frets(); + std::vector stringList = stringData->stringList(); + + stringTunings->setStringData(StringData(frets, stringList)); } undo(new AddElement(ne)); diff --git a/src/engraving/dom/engravingobject.cpp b/src/engraving/dom/engravingobject.cpp index 37bef1943eaa8..a79d3d81d77cd 100644 --- a/src/engraving/dom/engravingobject.cpp +++ b/src/engraving/dom/engravingobject.cpp @@ -719,6 +719,7 @@ bool EngravingObject::isTextBase() const || type() == ElementType::TRIPLET_FEEL || type() == ElementType::PLAYTECH_ANNOTATION || type() == ElementType::CAPO + || type() == ElementType::STRING_TUNINGS || type() == ElementType::REHEARSAL_MARK || type() == ElementType::INSTRUMENT_CHANGE || type() == ElementType::FIGURED_BASS diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index a5873b656c69f..9952ae162e85a 100644 --- a/src/engraving/dom/engravingobject.h +++ b/src/engraving/dom/engravingobject.h @@ -153,6 +153,7 @@ class Stem; class StemSlash; class Sticking; class StretchedBend; +class StringTunings; class Symbol; class System; class SystemDivider; @@ -431,6 +432,7 @@ class EngravingObject CONVERT(Sticking, STICKING) CONVERT(GraceNotesGroup, GRACE_NOTES_GROUP) CONVERT(FretCircle, FRET_CIRCLE) + CONVERT(StringTunings, STRING_TUNINGS) #undef CONVERT virtual bool isEngravingItem() const { return false; } // overridden in element.h @@ -510,7 +512,7 @@ class EngravingObject bool isStaffTextBase() const { - return isStaffText() || isSystemText() || isTripletFeel() || isPlayTechAnnotation() || isCapo(); + return isStaffText() || isSystemText() || isTripletFeel() || isPlayTechAnnotation() || isCapo() || isStringTunings(); } bool isArticulationFamily() const @@ -792,6 +794,7 @@ CONVERT(Sticking) CONVERT(GraceNotesGroup) CONVERT(FretCircle) CONVERT(DeadSlapped) +CONVERT(StringTunings) #undef CONVERT } diff --git a/src/engraving/dom/excerpt.cpp b/src/engraving/dom/excerpt.cpp index 97187f6c46339..657896697efc7 100644 --- a/src/engraving/dom/excerpt.cpp +++ b/src/engraving/dom/excerpt.cpp @@ -1211,6 +1211,7 @@ void Excerpt::cloneStaff(Staff* srcStaff, Staff* dstStaff, bool cloneSpanners) case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::FRET_DIAGRAM: case ElementType::HARMONY: case ElementType::FIGURED_BASS: diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index 3ff0c61a0e8d5..bb7b53f0a26f2 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -92,6 +92,7 @@ #include "stemslash.h" #include "sticking.h" #include "stretchedbend.h" +#include "stringtunings.h" #include "system.h" #include "systemdivider.h" #include "systemtext.h" @@ -222,6 +223,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::STICKING: return new Sticking(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::TRIPLET_FEEL: return new TripletFeel(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::FRET_CIRCLE: return new FretCircle(parent->isChord() ? toChord(parent) : dummy->chord()); + case ElementType::STRING_TUNINGS: return new StringTunings(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::LYRICSLINE: case ElementType::TEXTLINE_BASE: @@ -417,6 +419,10 @@ COPY_ITEM_IMPL(Measure) CREATE_ITEM_IMPL(MeasureRepeat, ElementType::MEASURE_REPEAT, Segment, isAccessibleEnabled) COPY_ITEM_IMPL(MeasureRepeat) +CREATE_ITEM_IMPL(StringTunings, ElementType::STRING_TUNINGS, Segment, isAccessibleEnabled) +COPY_ITEM_IMPL(StringTunings) +MAKE_ITEM_IMPL(StringTunings, Segment); + CREATE_ITEM_IMPL(Note, ElementType::NOTE, Chord, isAccessibleEnabled) Note* Factory::copyNote(const Note& src, bool link) { diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 49f4f16b545b6..988868548701c 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -271,6 +271,10 @@ class Factory static Capo* createCapo(Segment* parent, bool isAccessibleEnabled = true); + static StringTunings* createStringTunings(Segment* parent, bool isAccessibleEnabled = true); + static StringTunings* copyStringTunings(const StringTunings& src); + static std::shared_ptr makeStringTunings(Segment* parent); + private: static EngravingItem* doCreateItem(ElementType type, EngravingItem* parent); }; diff --git a/src/engraving/dom/measure.cpp b/src/engraving/dom/measure.cpp index 368019015ed9b..f2e711a3b27c8 100644 --- a/src/engraving/dom/measure.cpp +++ b/src/engraving/dom/measure.cpp @@ -1332,6 +1332,15 @@ bool Measure::acceptDrop(EditData& data) const viewer->setDropRectangle(staffR); return true; + case ElementType::STRING_TUNINGS: { + if (!canAddStringTunings(staffIdx)) { + return false; + } + + viewer->setDropRectangle(staffR); + return true; + } + case ElementType::ACTION_ICON: switch (toActionIcon(e)->actionType()) { case ActionIconType::VFRAME: @@ -1394,6 +1403,7 @@ EngravingItem* Measure::drop(EditData& data) case ElementType::DYNAMIC: case ElementType::EXPRESSION: case ElementType::FRET_DIAGRAM: + case ElementType::STRING_TUNINGS: e->setParent(seg); e->setTrack(staffIdx * VOICES); score()->undoAddElement(e); @@ -3185,6 +3195,36 @@ void Measure::checkTrailer() } } +bool Measure::canAddStringTunings(staff_idx_t staffIdx) const +{ + const Staff* staff = score()->staff(staffIdx); + if (!staff) { + return false; + } + + if (staff->isLinked()) { + return false; + } + + const StringData* stringData = staff->part()->instrument(tick())->stringData(); + if (!stringData || stringData->frettedStrings() == 0) { + return false; + } + + // already a string tunings element in this measure + bool alreadyHasStringTunings = false; + for (const Segment& segment : m_segments) { + for (EngravingItem* element : segment.annotations()) { + if (element && element->isStringTunings() && element->staffIdx() == staffIdx) { + alreadyHasStringTunings = true; + break; + } + } + } + + return !alreadyHasStringTunings; +} + void Measure::stretchToTargetWidth(double targetWidth) { if (targetWidth < width()) { diff --git a/src/engraving/dom/measure.h b/src/engraving/dom/measure.h index 945dcc27c3057..271859ff074d2 100644 --- a/src/engraving/dom/measure.h +++ b/src/engraving/dom/measure.h @@ -352,6 +352,8 @@ class Measure final : public MeasureBase void respaceSegments(); + bool canAddStringTunings(staff_idx_t staffIdx) const; + private: friend class Factory; diff --git a/src/engraving/dom/note.cpp b/src/engraving/dom/note.cpp index 96a17b3e95efb..5a530fb71821a 100644 --- a/src/engraving/dom/note.cpp +++ b/src/engraving/dom/note.cpp @@ -1542,6 +1542,17 @@ bool Note::acceptDrop(EditData& data) const const Staff* st = staff(); bool isTablature = st->isTabStaff(tick()); bool tabFingering = st->staffTypeForElement(this)->showTabFingering(); + + if (type == ElementType::STRING_TUNINGS) { + staff_idx_t staffIdx = 0; + Segment* seg = nullptr; + if (!score()->pos2measure(data.pos, &staffIdx, 0, &seg, 0)) { + return false; + } + + return chord()->measure()->canAddStringTunings(staffIdx); + } + return type == ElementType::ARTICULATION || type == ElementType::ORNAMENT || type == ElementType::FERMATA @@ -1823,6 +1834,9 @@ EngravingItem* Note::drop(EditData& data) toChordLine(e)->setNote(this); return ch->drop(data); + case ElementType::STRING_TUNINGS: + return ch->measure()->drop(data); + default: Spanner* spanner; if (e->isSpanner() && (spanner = toSpanner(e))->anchor() == Spanner::Anchor::NOTE) { @@ -2451,7 +2465,7 @@ void Note::verticalDrag(EditData& ed) int lineOffset = lrint(ed.moveDelta.y() / step); if (tab) { - const StringData* strData = staff()->part()->instrument(_tick)->stringData(); + const StringData* strData = staff()->part()->stringData(_tick); int nString = ned->string + (st->upsideDown() ? -lineOffset : lineOffset); int nFret = strData->fret(m_pitch, nString, staff()); diff --git a/src/engraving/dom/noteentry.cpp b/src/engraving/dom/noteentry.cpp index 77e0f017c0a2d..ff44567ba7624 100644 --- a/src/engraving/dom/noteentry.cpp +++ b/src/engraving/dom/noteentry.cpp @@ -94,7 +94,7 @@ NoteVal Score::noteValForPosition(Position pos, AccidentalType at, bool& error) if (m_is.rest()) { return nval; } - stringData = instr->stringData(); + stringData = st->part()->stringData(s->tick()); line = st->staffType(tick)->visualStringToPhys(line); if (line < 0 || line >= static_cast(stringData->strings())) { error = true; @@ -432,7 +432,7 @@ Ret Score::putNote(const Position& p, bool replace) break; } case StaffGroup::TAB: - stringData = st->part()->instrument(s->tick())->stringData(); + stringData = st->part()->stringData(s->tick()); m_is.setDrumNote(-1); break; case StaffGroup::STANDARD: diff --git a/src/engraving/dom/part.cpp b/src/engraving/dom/part.cpp index ea2ed46118900..5a33a20c331e7 100644 --- a/src/engraving/dom/part.cpp +++ b/src/engraving/dom/part.cpp @@ -31,11 +31,13 @@ #include "fret.h" #include "harppedaldiagram.h" #include "instrtemplate.h" +#include "instrchange.h" #include "linkedobjects.h" #include "masterscore.h" #include "measure.h" #include "score.h" #include "staff.h" +#include "stringtunings.h" #include "log.h" @@ -404,6 +406,48 @@ const InstrumentList& Part::instruments() const return _instruments; } +const StringData* Part::stringData(const Fraction& tick) const +{ + if (!score()) { + return nullptr; + } + + const Instrument* instrument = this->instrument(tick); + if (!instrument) { + return nullptr; + } + + StringTunings* stringTunings = nullptr; + + auto it = findLessOrEqual(m_stringTunings, tick.ticks()); + if (it != m_stringTunings.end()) { + stringTunings = it->second; + } + + if (stringTunings) { + //!NOTE: if there is string tunings element between current instrument and current tick, + //! then return string data from string tunings element + const Instrument* stringTuningsInstrument = this->instrument(stringTunings->tick()); + if (instrument == stringTuningsInstrument) { + return stringTunings->stringData(); + } + } + + return instrument->stringData(); +} + +void Part::addStringTunings(StringTunings* stringTunings) +{ + m_stringTunings[stringTunings->segment()->tick().ticks()] = stringTunings; +} + +void Part::removeStringTunings(StringTunings* stringTunings) +{ + if (m_stringTunings[stringTunings->segment()->tick().ticks()] == stringTunings) { + m_stringTunings.erase(stringTunings->segment()->tick().ticks()); + } +} + //--------------------------------------------------------- // instrumentId //--------------------------------------------------------- diff --git a/src/engraving/dom/part.h b/src/engraving/dom/part.h index ca55bd7bff4d0..58469b949e189 100644 --- a/src/engraving/dom/part.h +++ b/src/engraving/dom/part.h @@ -148,6 +148,10 @@ class Part final : public EngravingObject void removeInstrument(const Fraction&); const InstrumentList& instruments() const; + const StringData* stringData(const Fraction& tick) const; + void addStringTunings(StringTunings* stringTunings); + void removeStringTunings(StringTunings* stringTunings); + void insertTime(const Fraction& tick, const Fraction& len); void addHarpDiagram(HarpPedalDiagram*); @@ -204,6 +208,8 @@ class Part final : public EngravingObject int _color = 0; ///User specified color for helping to label parts PreferSharpFlat _preferSharpFlat = PreferSharpFlat::AUTO; + + std::map m_stringTunings; }; } // namespace mu::engraving #endif diff --git a/src/engraving/dom/property.cpp b/src/engraving/dom/property.cpp index f7c6bf15ebaae..5ba2a30de8054 100644 --- a/src/engraving/dom/property.cpp +++ b/src/engraving/dom/property.cpp @@ -414,6 +414,10 @@ static constexpr PropertyMetaData propertyList[] = { { Pid::TEXT_LINKED_TO_MASTER, false, "textLinkedToMaster", P_TYPE::BOOL, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "text linked to master") }, { Pid::EXCLUDE_FROM_OTHER_PARTS, false, "excludeFromParts", P_TYPE::BOOL, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "exclude from parts") }, + { Pid::STRINGTUNINGS_STRINGS_COUNT, true, "stringsCount", P_TYPE::INT, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "strings count") }, + { Pid::STRINGTUNINGS_PRESET, true, "preset", P_TYPE::STRING, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "strings preset") }, + { Pid::STRINGTUNINGS_VISIBLE_STRINGS, true, "visibleStrings",P_TYPE::INT_VEC, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "visible strings") }, + { Pid::END, false, "++end++", P_TYPE::INT, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "") } }; /* *INDENT-ON* */ diff --git a/src/engraving/dom/property.h b/src/engraving/dom/property.h index 6b14ee36814c0..087376a31c52c 100644 --- a/src/engraving/dom/property.h +++ b/src/engraving/dom/property.h @@ -422,6 +422,10 @@ enum class Pid { TEXT_LINKED_TO_MASTER, EXCLUDE_FROM_OTHER_PARTS, + STRINGTUNINGS_STRINGS_COUNT, + STRINGTUNINGS_PRESET, + STRINGTUNINGS_VISIBLE_STRINGS, + END }; diff --git a/src/engraving/dom/rest.cpp b/src/engraving/dom/rest.cpp index 0eac834bddf3e..159167499bd2f 100644 --- a/src/engraving/dom/rest.cpp +++ b/src/engraving/dom/rest.cpp @@ -206,6 +206,16 @@ bool Rest::acceptDrop(EditData& data) const return true; } + if (type == ElementType::STRING_TUNINGS) { + staff_idx_t staffIdx = 0; + Segment* seg = nullptr; + if (!score()->pos2measure(data.pos, &staffIdx, 0, &seg, 0)) { + return false; + } + + return measure()->canAddStringTunings(staffIdx); + } + // prevent 'hanging' slurs, avoid crash on tie static const std::set ignoredTypes { ElementType::SLUR, @@ -268,6 +278,9 @@ EngravingItem* Rest::drop(EditData& data) score()->undoAddElement(e); return e; + case ElementType::STRING_TUNINGS: + return measure()->drop(data); + default: return ChordRest::drop(data); } diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index e37d25e54474b..ce81811924988 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -822,7 +822,7 @@ void Score::changeEnharmonicSpelling(bool both) } if (staff->isTabStaff(n->tick())) { int string = n->line() + (both ? 1 : -1); - int fret = staff->part()->instrument(n->tick())->stringData()->fret(n->pitch(), string, staff); + int fret = staff->part()->stringData(n->tick())->fret(n->pitch(), string, staff); if (fret != -1) { n->undoChangeProperty(Pid::FRET, fret); n->undoChangeProperty(Pid::STRING, string); @@ -3086,8 +3086,7 @@ void Score::padToggle(Pad p, const EditData& ed) // tab - use fret 0 on current string nval.fret = 0; nval.string = m_is.string(); - const Instrument* instr = s->part()->instrument(tick); - const StringData* stringData = instr->stringData(); + const StringData* stringData = s->part()->stringData(tick); nval.pitch = stringData->getPitch(nval.string, nval.fret, s); } else if (s->isDrumStaff(tick)) { // drum - use selected drum palette note diff --git a/src/engraving/dom/segment.cpp b/src/engraving/dom/segment.cpp index ee379254769d5..af7dad92865a3 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -607,6 +607,12 @@ void Segment::add(EngravingItem* el) _annotations.push_back(el); break; + case ElementType::STRING_TUNINGS: { + _annotations.push_back(el); + el->part()->addStringTunings(toStringTunings(el)); + break; + } + case ElementType::STAFF_STATE: if (toStaffState(el)->staffStateType() == StaffStateType::INSTRUMENT) { StaffState* ss = toStaffState(el); @@ -785,6 +791,11 @@ void Segment::remove(EngravingItem* el) removeAnnotation(el); break; + case ElementType::STRING_TUNINGS: + el->part()->removeStringTunings(toStringTunings(el)); + removeAnnotation(el); + break; + case ElementType::STAFF_STATE: if (toStaffState(el)->staffStateType() == StaffStateType::INSTRUMENT) { Part* part = el->part(); @@ -904,6 +915,7 @@ void Segment::sortStaves(std::vector& dst) ElementType::TRIPLET_FEEL, ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, + ElementType::STRING_TUNINGS, ElementType::JUMP, ElementType::MARKER, ElementType::TEMPO_TEXT, @@ -1793,6 +1805,7 @@ EngravingItem* Segment::nextElement(staff_idx_t activeStaff) case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::REHEARSAL_MARK: case ElementType::MARKER: case ElementType::IMAGE: @@ -1939,6 +1952,7 @@ EngravingItem* Segment::prevElement(staff_idx_t activeStaff) case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::REHEARSAL_MARK: case ElementType::MARKER: case ElementType::IMAGE: @@ -2332,7 +2346,8 @@ void Segment::createShape(staff_idx_t staffIdx) && !e->isStaffText() && !e->isHarpPedalDiagram() && !e->isPlayTechAnnotation() - && !e->isCapo()) { + && !e->isCapo() + && !e->isStringTunings()) { // annotations added here are candidates for collision detection // lyrics, ... s.add(e->shape().translate(e->pos())); diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index e8c60972ab28a..6d9793513a6af 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -60,6 +60,7 @@ #include "stem.h" #include "stemslash.h" #include "sticking.h" +#include "stringtunings.h" #include "tie.h" #include "tremolo.h" #include "tuplet.h" @@ -1019,6 +1020,7 @@ ByteArray Selection::symbolListMimeData() const continue; case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::STAFF_TEXT: seg = toStaffTextBase(e)->segment(); break; diff --git a/src/engraving/dom/staff.cpp b/src/engraving/dom/staff.cpp index 02fef9bc9c89f..9e16c77694c1e 100644 --- a/src/engraving/dom/staff.cpp +++ b/src/engraving/dom/staff.cpp @@ -359,6 +359,16 @@ void Staff::updateVisibilityVoices(const Staff* masterStaff, const TracksMap& tr _visibilityVoices = voices; } +bool Staff::reflectTranspositionInLinkedTab() const +{ + return m_reflectTranspositionInLinkedTab; +} + +void Staff::setReflectTranspositionInLinkedTab(bool reflect) +{ + m_reflectTranspositionInLinkedTab = reflect; +} + //--------------------------------------------------------- // cleanupBrackets //--------------------------------------------------------- diff --git a/src/engraving/dom/staff.h b/src/engraving/dom/staff.h index 1b7ccc73d306b..c5ec4180b1d57 100644 --- a/src/engraving/dom/staff.h +++ b/src/engraving/dom/staff.h @@ -100,6 +100,8 @@ class Staff final : public EngravingItem ChangeMap _velocityMultiplications; ///< cached value PitchList _pitchOffsets; ///< cached value + bool m_reflectTranspositionInLinkedTab = true; + friend class Factory; Staff(Part* parent); Staff(const Staff& staff); @@ -292,6 +294,9 @@ class Staff final : public EngravingItem bool isVoiceVisible(voice_idx_t voice) const; bool canDisableVoice() const; + bool reflectTranspositionInLinkedTab() const; + void setReflectTranspositionInLinkedTab(bool reflect); + #ifndef NDEBUG void dumpClefs(const char* title) const; void dumpKeys(const char* title) const; diff --git a/src/engraving/dom/stringtunings.cpp b/src/engraving/dom/stringtunings.cpp new file mode 100644 index 0000000000000..51d1d7be7eb45 --- /dev/null +++ b/src/engraving/dom/stringtunings.cpp @@ -0,0 +1,302 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2022 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "stringtunings.h" + +#include "types/typesconv.h" +#include "utils.h" + +#include "part.h" +#include "score.h" +#include "segment.h" +#include "undo.h" + +#include "containers.h" +#include "translation.h" + +using namespace mu; +using namespace mu::engraving; + +// STYLE +static const ElementStyle STRING_TUNINGS_STYLE { + { Sid::staffTextPlacement, Pid::PLACEMENT }, + { Sid::staffTextMinDistance, Pid::MIN_DISTANCE }, +}; + +StringTunings::StringTunings(Segment* parent, TextStyleType textStyleType) + : StaffTextBase(ElementType::STRING_TUNINGS, parent, textStyleType, ElementFlag::MOVABLE | ElementFlag::ON_STAFF) +{ + initElementStyle(&STRING_TUNINGS_STYLE); +} + +StringTunings::StringTunings(const StringTunings& s) + : StaffTextBase(s) +{ +} + +StringTunings* StringTunings::clone() const +{ + return new StringTunings(*this); +} + +bool StringTunings::isEditable() const +{ + return false; +} + +PropertyValue StringTunings::getProperty(Pid id) const +{ + if (id == Pid::STRINGTUNINGS_STRINGS_COUNT) { + Fraction tick = this->tick(); + if (staff()->isTabStaff(tick)) { + return staff()->lines(tick); + } else { + for (const Staff* _staff : staff()->staffList()) { + if (_staff == staff()) { + continue; + } + + if (_staff->score() == staff()->score() && _staff->isTabStaff(tick)) { + return _staff->lines(tick); + } + } + } + + if (m_stringsNumber.has_value()) { + return m_stringsNumber.value(); + } + + return stringData()->strings(); + } else if (id == Pid::STRINGTUNINGS_PRESET) { + return m_preset; + } else if (id == Pid::STRINGTUNINGS_VISIBLE_STRINGS) { + std::vector visibleStrings; + for (string_idx_t string : m_visibleStrings) { + visibleStrings.emplace_back(static_cast(string)); + } + + return visibleStrings; + } + + return StaffTextBase::getProperty(id); +} + +PropertyValue StringTunings::propertyDefault(Pid id) const +{ + if (id == Pid::STRINGTUNINGS_STRINGS_COUNT) { + return stringData()->strings(); + } else if (id == Pid::STRINGTUNINGS_PRESET) { + return String(); + } else if (id == Pid::STRINGTUNINGS_VISIBLE_STRINGS) { + return {}; + } + + return StaffTextBase::propertyDefault(id); +} + +bool StringTunings::setProperty(Pid id, const PropertyValue& val) +{ + if (id == Pid::STRINGTUNINGS_STRINGS_COUNT) { + Fraction tick = this->tick(); + if (staff()->isTabStaff(tick)) { + staff()->staffType(tick)->setLines(val.toInt()); + } else { + for (Staff* _staff : staff()->staffList()) { + if (_staff == staff()) { + continue; + } + + if (_staff->score() == staff()->score() && _staff->isTabStaff(tick)) { + _staff->staffType(tick)->setLines(val.toInt()); + } + } + } + + m_stringsNumber = val.toInt(); + } else if (id == Pid::STRINGTUNINGS_PRESET) { + m_preset = val.value(); + } else if (id == Pid::STRINGTUNINGS_VISIBLE_STRINGS) { + m_visibleStrings.clear(); + std::vector ignoredStrings = val.value >(); + for (int string : ignoredStrings) { + m_visibleStrings.emplace_back(static_cast(string)); + } + } else { + return StaffTextBase::setProperty(id, val); + } + + triggerLayout(); + return true; +} + +String StringTunings::accessibleInfo() const +{ + const StringData* stringData = this->stringData(); + if (stringData->isNull()) { + return String(); + } + + String elementName = score() ? score()->getTextStyleUserName(TextStyleType::STRING_TUNINGS).translated() + : TConv::translatedUserName(TextStyleType::STRING_TUNINGS); + String info; + + const std::vector& stringList = stringData->stringList(); + int numOfStrings = static_cast(stringList.size()); + for (int i = 0; i < numOfStrings; ++i) { + string_idx_t index = numOfStrings - i - 1; + if (mu::contains(m_visibleStrings, index)) { + String pitchStr = pitch2string(stringList[index].pitch); + if (pitchStr.empty()) { + LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + continue; + } + + info += mtrc("engraving", "String %1").arg(String::number(i + 1)) + u", " + + mtrc("engraving", "Value %1").arg(pitchStr) + u"; "; + } + } + + return String(u"%1: %2").arg(elementName, info); +} + +String StringTunings::screenReaderInfo() const +{ + return accessibleInfo(); +} + +const StringData* StringTunings::stringData() const +{ + return &m_stringData; +} + +void StringTunings::setStringData(const StringData& stringData) +{ + m_stringData.set(stringData); + triggerLayout(); +} + +void StringTunings::undoStringData(const StringData& stringData) +{ + score()->undo(new ChangeStringData(this, stringData)); + triggerLayout(); +} + +const String& StringTunings::preset() const +{ + return m_preset; +} + +void StringTunings::setPreset(const String& preset) +{ + m_preset = preset; +} + +const std::vector& StringTunings::visibleStrings() const +{ + return m_visibleStrings; +} + +void StringTunings::setVisibleStrings(const std::vector& visibleStrings) +{ + m_visibleStrings = visibleStrings; +} + +void StringTunings::updateText() +{ + String updatedText = generateText(); + undoChangeProperty(Pid::TEXT, updatedText, PropertyFlags::STYLED); + + if (updatedText.empty()) { + m_noStringVisible = true; + } else { + m_noStringVisible = false; + } +} + +bool StringTunings::noStringVisible() const +{ + return m_noStringVisible; +} + +String StringTunings::generateText() const +{ + const StringData* stringData = this->stringData(); + if (!stringData || stringData->isNull()) { + return u""; + } + + auto guitarStringSymbol = [](int i) { return String(u"guitarString") + String::number(i) + u""; }; + + const std::vector& stringList = stringData->stringList(); + std::vector visibleStringList; + int numOfStrings = static_cast(stringList.size()); + for (int i = 0; i < numOfStrings; ++i) { + string_idx_t index = numOfStrings - i - 1; + if (mu::contains(m_visibleStrings, index)) { + String pitchStr = pitch2string(stringList[index].pitch); + if (pitchStr.empty()) { + LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + continue; + } + + Char accidental; + if (pitchStr.size() > 1) { + Char sym(pitchStr[1]); + if (!sym.isDigit()) { + accidental = sym; + } + } + + visibleStringList.emplace_back(String(guitarStringSymbol(i + 1) + u" \u2012 " + + String(pitchStr[0]).toUpper() + accidental) + u" "); + } + } + + if (visibleStringList.empty()) { + return u""; + } + + int columnCount = 0; + int rowCount = 0; + + if (visibleStringList.size() < 4) { + rowCount = visibleStringList.size(); + columnCount = 1; + } else { + rowCount = std::ceil(static_cast(visibleStringList.size()) / 2); + columnCount = 2; + } + + String result; + for (int i = 0; i < rowCount; ++i) { + for (int j = 0; j < columnCount; ++j) { + size_t index = i + j * rowCount; + if (index < visibleStringList.size()) { + result += visibleStringList[index]; + } + result += u"\t"; + } + result += u"\n"; + } + + return result; +} diff --git a/src/engraving/dom/stringtunings.h b/src/engraving/dom/stringtunings.h new file mode 100644 index 0000000000000..66a9e395c6533 --- /dev/null +++ b/src/engraving/dom/stringtunings.h @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2022 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MU_ENGRAVING_STRING_TUNINGS_H +#define MU_ENGRAVING_STRING_TUNINGS_H + +#include "stafftextbase.h" +#include "stringdata.h" + +namespace mu::engraving { +class StringTunings final : public StaffTextBase +{ + OBJECT_ALLOCATOR(engraving, StringTunings) + DECLARE_CLASSOF(ElementType::STRING_TUNINGS) + +public: + explicit StringTunings(Segment* parent, TextStyleType textStyleType = TextStyleType::STAFF); + StringTunings(const StringTunings& s); + + StringTunings* clone() const override; + bool isEditable() const override; + + PropertyValue getProperty(Pid id) const override; + PropertyValue propertyDefault(Pid id) const override; + bool setProperty(Pid id, const PropertyValue& val) override; + + String accessibleInfo() const override; + String screenReaderInfo() const override; + + const StringData* stringData() const; + void setStringData(const StringData& stringData); + void undoStringData(const StringData& stringData); + + const String& preset() const; + void setPreset(const String& preset); + + const std::vector& visibleStrings() const; + void setVisibleStrings(const std::vector& visibleStrings); + + void updateText(); + + bool noStringVisible() const; + +private: + String generateText() const; + + String m_preset; + std::vector m_visibleStrings; + StringData m_stringData; + + bool m_noStringVisible = false; + std::optional m_stringsNumber; +}; +} + +#endif // MU_ENGRAVING_STRING_TUNINGS_H diff --git a/src/engraving/dom/textbase.cpp b/src/engraving/dom/textbase.cpp index ad31417252e4a..1b42f306fac14 100644 --- a/src/engraving/dom/textbase.cpp +++ b/src/engraving/dom/textbase.cpp @@ -860,7 +860,8 @@ mu::draw::Font TextFragment::font(const TextBase* t) const String family; draw::Font::Type fontType = draw::Font::Type::Unknown; if (format.fontFamily() == "ScoreText") { - if (t->isDynamic() || t->textStyleType() == TextStyleType::OTTAVA || t->textStyleType() == TextStyleType::HARP_PEDAL_DIAGRAM) { + if (t->isDynamic() || t->textStyleType() == TextStyleType::OTTAVA || t->textStyleType() == TextStyleType::HARP_PEDAL_DIAGRAM + || t->isStringTunings()) { std::string fontName = engravingFonts()->fontByName(t->style().styleSt(Sid::MusicalSymbolFont).toStdString())->family(); family = String::fromStdString(fontName); fontType = draw::Font::Type::MusicSymbol; @@ -1036,7 +1037,14 @@ void TextBlock::layout(const TextBase* t) } _bbox |= fm.tightBoundingRect(f.text).translated(f.pos); - _lineSpacing = std::max(_lineSpacing, fm.lineSpacing()); + mu::draw::Font font = f.font(t); + if (font.type() == mu::draw::Font::Type::MusicSymbol || font.type() == mu::draw::Font::Type::MusicSymbolText) { + // SEMI-HACK: Music fonts can have huge linespacing because of tall symbols, so instead of using the + // font linespacing value we just use the height of the individual fragment with some added margin + _lineSpacing = std::max(_lineSpacing, 1.25 * _bbox.height()); + } else { + _lineSpacing = std::max(_lineSpacing, fm.lineSpacing()); + } } } diff --git a/src/engraving/dom/undo.cpp b/src/engraving/dom/undo.cpp index f24b0e9646fc0..24a83ce8f51b4 100644 --- a/src/engraving/dom/undo.cpp +++ b/src/engraving/dom/undo.cpp @@ -1715,11 +1715,12 @@ ChangeStaff::ChangeStaff(Staff* _staff) cutaway = staff->cutaway(); hideSystemBarLine = staff->hideSystemBarLine(); mergeMatchingRests = staff->mergeMatchingRests(); + reflectTranspositionInLinkedTab = staff->reflectTranspositionInLinkedTab(); } ChangeStaff::ChangeStaff(Staff* _staff, bool _visible, ClefTypeList _clefType, double _userDist, Staff::HideMode _hideMode, bool _showIfEmpty, bool _cutaway, - bool _hideSystemBarLine, bool _mergeMatchingRests) + bool _hideSystemBarLine, bool _mergeMatchingRests, bool _reflectTranspositionInLinkedTab) { staff = _staff; visible = _visible; @@ -1730,6 +1731,7 @@ ChangeStaff::ChangeStaff(Staff* _staff, bool _visible, ClefTypeList _clefType, cutaway = _cutaway; hideSystemBarLine = _hideSystemBarLine; mergeMatchingRests = _mergeMatchingRests; + reflectTranspositionInLinkedTab = _reflectTranspositionInLinkedTab; } //--------------------------------------------------------- @@ -1746,6 +1748,7 @@ void ChangeStaff::flip(EditData*) bool oldCutaway = staff->cutaway(); bool oldHideSystemBarLine = staff->hideSystemBarLine(); bool oldMergeMatchingRests = staff->mergeMatchingRests(); + bool oldReflectTranspositionInLinkedTab = staff->reflectTranspositionInLinkedTab(); staff->setVisible(visible); staff->setDefaultClefType(clefType); @@ -1755,6 +1758,7 @@ void ChangeStaff::flip(EditData*) staff->setCutaway(cutaway); staff->setHideSystemBarLine(hideSystemBarLine); staff->setMergeMatchingRests(mergeMatchingRests); + staff->setReflectTranspositionInLinkedTab(reflectTranspositionInLinkedTab); visible = oldVisible; clefType = oldClefType; @@ -1764,6 +1768,7 @@ void ChangeStaff::flip(EditData*) cutaway = oldCutaway; hideSystemBarLine = oldHideSystemBarLine; mergeMatchingRests = oldMergeMatchingRests; + reflectTranspositionInLinkedTab = oldReflectTranspositionInLinkedTab; staff->triggerLayout(); staff->masterScore()->rebuildMidiMapping(); @@ -3013,3 +3018,18 @@ void ChangeSingleHarpPedal::flip(EditData*) diagram->triggerLayout(); } } + +void ChangeStringData::flip(EditData*) +{ + const StringData* stringData = m_stringTunings ? m_stringTunings->stringData() : m_instrument->stringData(); + int frets = stringData->frets(); + std::vector stringList = stringData->stringList(); + + if (m_stringTunings) { + m_stringTunings->setStringData(m_stringData); + } else { + m_instrument->setStringData(m_stringData); + } + + m_stringData.set(StringData(frets, stringList)); +} diff --git a/src/engraving/dom/undo.h b/src/engraving/dom/undo.h index 3813b0b38d14c..2845ef4329b8a 100644 --- a/src/engraving/dom/undo.h +++ b/src/engraving/dom/undo.h @@ -58,6 +58,8 @@ #include "spanner.h" #include "staff.h" #include "stafftype.h" +#include "stringdata.h" +#include "stringtunings.h" #include "synthesizerstate.h" #include "text.h" #include "tremolo.h" @@ -696,6 +698,7 @@ class ChangeStaff : public UndoCommand bool cutaway = false; bool hideSystemBarLine = false; bool mergeMatchingRests = false; + bool reflectTranspositionInLinkedTab = false; void flip(EditData*) override; @@ -703,7 +706,7 @@ class ChangeStaff : public UndoCommand ChangeStaff(Staff*); ChangeStaff(Staff*, bool _visible, ClefTypeList _clefType, double userDist, Staff::HideMode _hideMode, bool _showIfEmpty, bool _cutaway, - bool _hideSystemBarLine, bool _mergeRests); + bool _hideSystemBarLine, bool _mergeRests, bool _reflectTranspositionInLinkedTab); UNDO_TYPE(CommandType::ChangeStaff) UNDO_NAME("ChangeStaff") @@ -1603,5 +1606,21 @@ class ChangeSingleHarpPedal : public UndoCommand UNDO_NAME("ChangeSingleHarpPedal") UNDO_CHANGED_OBJECTS({ diagram }); }; + +class ChangeStringData : public UndoCommand +{ + Instrument* m_instrument = nullptr; + StringTunings* m_stringTunings = nullptr; + StringData m_stringData; + +public: + ChangeStringData(StringTunings* stringTunings, const StringData& stringData) + : m_stringTunings(stringTunings), m_stringData(stringData) {} + ChangeStringData(Instrument* instrument, const StringData& stringData) + : m_instrument(instrument), m_stringData(stringData) {} + + void flip(EditData*) override; + UNDO_NAME("ChangeStringData") +}; } // namespace mu::engraving #endif diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 4d4e096107cc8..416b774c05134 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -530,13 +530,53 @@ String pitch2string(int v) if (v < 0 || v > 127) { return String(u"----"); } - int octave = (v / 12) - 1; + int octave = (v / PITCH_DELTA_OCTAVE) - 1; String o; o = String::number(octave); - int i = v % 12; + int i = v % PITCH_DELTA_OCTAVE; return (octave < 0 ? valu[i] : vall[i]) + o; } +/*! + * Returns the pitch number of the given string. + * + * @param s + * The string representation of the note. + * + * @return + * The pitch number of the note. + */ +int string2pitch(const String& s) +{ + if (s == String(u"----")) { + return -1; + } + + String origin = s; + + bool negative = s.contains(u'-'); + int octave = String(s[s.size() - 1]).toInt() * (negative ? -1 : 1); + if (octave < -1 || octave > 9) { + return -1; + } + + origin = origin.mid(0, origin.size() - (negative ? 2 : 1)); + + int pitchIndex = -1; + for (int i = 0; i < PITCH_DELTA_OCTAVE; ++i) { + if (origin.toLower() == String(octave < 0 ? valu[i] : vall[i]).toLower()) { + pitchIndex = i; + break; + } + } + + if (pitchIndex == -1) { + return -1; + } + + return (octave + 1) * PITCH_DELTA_OCTAVE + pitchIndex; +} + /*! * An array of all supported interval sorted by size. * diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index 9dcc1188d7a76..40600cca5b1fd 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -48,6 +48,7 @@ extern int line2pitch(int line, ClefType clef, Key); extern int y2pitch(double y, ClefType clef, double spatium); extern int quantizeLen(int, int); extern String pitch2string(int v); +extern int string2pitch(const String& s); extern void transposeInterval(int pitch, int tpc, int* rpitch, int* rtpc, Interval, bool useDoubleSharpsFlats); extern int transposeTpc(int tpc, Interval interval, bool useDoubleSharpsFlats); diff --git a/src/engraving/engraving.qrc b/src/engraving/engraving.qrc index b42743631a125..730180b2e6329 100644 --- a/src/engraving/engraving.qrc +++ b/src/engraving/engraving.qrc @@ -2,6 +2,7 @@ ../../share/instruments/instruments.xml ../../share/instruments/orders.xml + ../../share/instruments/string_tunings_presets.json data/styles/legacy-style-defaults-v1.mss data/styles/legacy-style-defaults-v2.mss data/styles/legacy-style-defaults-v3.mss diff --git a/src/engraving/rendering/dev/measurelayout.cpp b/src/engraving/rendering/dev/measurelayout.cpp index 562a73da6af4d..323cce4a77c63 100644 --- a/src/engraving/rendering/dev/measurelayout.cpp +++ b/src/engraving/rendering/dev/measurelayout.cpp @@ -135,7 +135,8 @@ static const std::unordered_set BREAK_TYPES { ElementType::TRIPLET_FEEL, ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, - ElementType::INSTRUMENT_CHANGE + ElementType::INSTRUMENT_CHANGE, + ElementType::STRING_TUNINGS }; static const std::unordered_set ALWAYS_BREAK_TYPES { @@ -151,7 +152,8 @@ static const std::unordered_set CONDITIONAL_BREAK_TYPES { ElementType::TRIPLET_FEEL, ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, - ElementType::INSTRUMENT_CHANGE + ElementType::INSTRUMENT_CHANGE, + ElementType::STRING_TUNINGS }; //--------------------------------------------------------- diff --git a/src/engraving/rendering/dev/systemlayout.cpp b/src/engraving/rendering/dev/systemlayout.cpp index cf13941ed8d9b..afd8c4cff4dc2 100644 --- a/src/engraving/rendering/dev/systemlayout.cpp +++ b/src/engraving/rendering/dev/systemlayout.cpp @@ -1148,7 +1148,7 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx) for (const Segment* s : sl) { for (EngravingItem* e : s->annotations()) { - if (e->isPlayTechAnnotation() || e->isCapo() || e->isSystemText() || e->isTripletFeel()) { + if (e->isPlayTechAnnotation() || e->isCapo() || e->isStringTunings() || e->isSystemText() || e->isTripletFeel()) { TLayout::layoutItem(e, ctx); } } diff --git a/src/engraving/rendering/dev/tdraw.cpp b/src/engraving/rendering/dev/tdraw.cpp index 6ac4dbe5a2193..711c27e4a940e 100644 --- a/src/engraving/rendering/dev/tdraw.cpp +++ b/src/engraving/rendering/dev/tdraw.cpp @@ -118,6 +118,7 @@ #include "dom/stem.h" #include "dom/stemslash.h" #include "dom/sticking.h" +#include "dom/stringtunings.h" #include "dom/stretchedbend.h" #include "dom/symbol.h" #include "dom/systemdivider.h" @@ -316,6 +317,8 @@ void TDraw::drawItem(const EngravingItem* item, draw::Painter* painter) break; case ElementType::STICKING: draw(item_cast(item), painter); break; + case ElementType::STRING_TUNINGS: draw(item_cast(item), painter); + break; case ElementType::STRETCHED_BEND: draw(item_cast(item), painter); break; case ElementType::SYMBOL: draw(item_cast(item), painter); @@ -2692,6 +2695,47 @@ void TDraw::draw(const Sticking* item, Painter* painter) drawTextBase(item, painter); } +void TDraw::draw(const StringTunings* item, draw::Painter* painter) +{ + TRACE_DRAW_ITEM; + + if (item->noStringVisible()) { + const TextBase::LayoutData* data = item->ldata(); + + double spatium = item->spatium(); + double lineWidth = spatium * .15; + + Pen pen(item->curColor(), lineWidth, PenStyle::SolidLine, PenCapStyle::RoundCap, PenJoinStyle::RoundJoin); + painter->setPen(pen); + painter->setBrush(Brush(item->curColor())); + + mu::draw::Font f(item->font()); + painter->setFont(f); + + RectF rect = data->bbox(); + + double x = rect.x(); + double y = rect.y(); + double width = rect.width(); + double height = rect.height(); + double topPartHeight = height * .66; + double cornerRadius = 8.0; + + PainterPath path; + path.moveTo(x, y); + path.arcTo(x, y + (topPartHeight - 2 * cornerRadius), 2 * cornerRadius, 2 * cornerRadius, 180.0, 90.0); + path.arcTo(x + width - 2 * cornerRadius, y + (topPartHeight - 2 * cornerRadius), 2 * cornerRadius, 2 * cornerRadius, 270, 90); + path.lineTo(x + width, y); + path.moveTo(x + width / 2, y + topPartHeight); + path.lineTo(x + width / 2, y + height); + + painter->setBrush(BrushStyle::NoBrush); + painter->drawPath(path); + } else { + drawTextBase(item, painter); + } +} + void TDraw::draw(const Symbol* item, Painter* painter) { TRACE_DRAW_ITEM; diff --git a/src/engraving/rendering/dev/tdraw.h b/src/engraving/rendering/dev/tdraw.h index 6a641f9b7a761..65b448839d27f 100644 --- a/src/engraving/rendering/dev/tdraw.h +++ b/src/engraving/rendering/dev/tdraw.h @@ -130,6 +130,7 @@ class StaffTypeChange; class Stem; class StemSlash; class Sticking; +class StringTunings; class StretchedBend; class BSymbol; @@ -263,6 +264,7 @@ class TDraw static void draw(const Stem* item, draw::Painter* painter); static void draw(const StemSlash* item, draw::Painter* painter); static void draw(const Sticking* item, draw::Painter* painter); + static void draw(const StringTunings* item, draw::Painter* painter); static void draw(const StretchedBend* item, draw::Painter* painter); static void draw(const Symbol* item, draw::Painter* painter); static void draw(const FSymbol* item, draw::Painter* painter); diff --git a/src/engraving/rendering/dev/tlayout.cpp b/src/engraving/rendering/dev/tlayout.cpp index 9fdff15350b77..a6107c35e06d5 100644 --- a/src/engraving/rendering/dev/tlayout.cpp +++ b/src/engraving/rendering/dev/tlayout.cpp @@ -123,6 +123,7 @@ #include "dom/stem.h" #include "dom/stemslash.h" #include "dom/sticking.h" +#include "dom/stringtunings.h" #include "dom/stretchedbend.h" #include "dom/bsymbol.h" #include "dom/symbol.h" @@ -367,6 +368,9 @@ void TLayout::layoutItem(EngravingItem* item, LayoutContext& ctx) case ElementType::STICKING: layoutSticking(item_cast(item), static_cast(ldata)); break; + case ElementType::STRING_TUNINGS: + layoutStringTunings(item_cast(item), ctx); + break; case ElementType::SYMBOL: layoutSymbol(item_cast(item), static_cast(ldata), ctx); break; @@ -1709,11 +1713,9 @@ void TLayout::layoutCapo(const Capo* item, Capo::LayoutData* ldata, const Layout //! NOTE Looks like it doesn't belong here if (item->shouldAutomaticallyGenerateText() || item->empty()) { if (const Part* part = item->part()) { - if (const Instrument* instrument = part->instrument(item->tick())) { - if (const StringData* stringData = instrument->stringData()) { - String text = item->generateText(stringData->strings()); - const_cast(item)->setXmlText(text); - } + if (const StringData* stringData = part->stringData(item->tick())) { + String text = item->generateText(stringData->strings()); + const_cast(item)->setXmlText(text); } } } @@ -4971,6 +4973,62 @@ void TLayout::layoutStretched(StretchedBend* item, LayoutContext& ctx) item->setPos(0.0, 0.0); } +void TLayout::layoutStringTunings(StringTunings* item, LayoutContext& ctx) +{ + item->updateText(); + + TLayout::layoutBaseTextBase(item, ctx); + + if (item->noStringVisible()) { + double spatium = item->spatium(); + mu::draw::Font font(item->font()); + + RectF rect; + rect.setTopLeft({ 0, item->ldata()->bbox().y() - font.weight() - spatium * .15 }); + rect.setSize({ font.weight() - spatium, (font.weight() - spatium * .35) * 1.5 }); + + item->setbbox(rect); + } + + for (TextBlock& block : item->mutldata()->blocks) { + for (TextFragment& fragment : block.fragments()) { + mu::draw::Font font = fragment.font(item); + if (font.type() == mu::draw::Font::Type::MusicSymbol) { + // HACK: the music symbol doesn't have a good baseline + // to go with text so we correct it here + const double baselineAdjustment = 0.35 * font.pointSizeF(); + fragment.pos.setY(fragment.pos.y() + baselineAdjustment); + } + } + } + + double secondStringXAlign = 0.0; + for (const TextFragment& fragment : item->fragmentList()) { + if (fragment.font(item).type() == mu::draw::Font::Type::MusicSymbol) { + secondStringXAlign = std::max(secondStringXAlign, fragment.pos.x()); + } + } + + for (TextBlock& block : item->mutldata()->blocks) { + double xMove = 0.0; + for (TextFragment& fragment : block.fragments()) { + if (block.fragments().front() == fragment) { // skip first + continue; + } + + if (fragment.font(item).type() == mu::draw::Font::Type::MusicSymbol) { + xMove = secondStringXAlign - fragment.pos.x(); + } + fragment.pos.setX(fragment.pos.x() + xMove); + } + } + + Segment* parentSegment = item->segment(); + item->move(PointF(-parentSegment->x() + item->spatium(), 0.0)); + + Autoplace::autoplaceSegmentElement(item, item->mutldata()); +} + void TLayout::layoutSymbol(const Symbol* item, Symbol::LayoutData* ldata, const LayoutContext& ctx) { IF_ASSERT_FAILED(item->explicitParent()) { @@ -5070,7 +5128,7 @@ void TLayout::layoutTabDurationSymbol(const TabDurationSymbol* item, TabDuration return; } double spatium = item->spatium(); - double hbb, wbb, xbb, ybb; // bbox sizes + double hbb, wbb, xbb, ybb; // bbox sizes double xpos, ypos; // position coords ldata->beamGrid = TabBeamGrid::NONE; diff --git a/src/engraving/rendering/dev/tlayout.h b/src/engraving/rendering/dev/tlayout.h index 397955489df01..506782b6d26a4 100644 --- a/src/engraving/rendering/dev/tlayout.h +++ b/src/engraving/rendering/dev/tlayout.h @@ -142,6 +142,7 @@ class Spacer; class SpannerSegment; class StaffLines; class StretchedBend; +class StringTunings; class BSymbol; class Symbol; @@ -294,6 +295,7 @@ class TLayout static void layoutSticking(const Sticking* item, Sticking::LayoutData* ldata); static void layoutStretchedBend(StretchedBend* item, LayoutContext& ctx); static void layoutStretched(StretchedBend* item, LayoutContext& ctx); + static void layoutStringTunings(StringTunings* item, LayoutContext& ctx); static void layoutSymbol(const Symbol* item, Symbol::LayoutData* ldata, const LayoutContext& ctx); static void layoutFSymbol(const FSymbol* item, FSymbol::LayoutData* ldata); diff --git a/src/engraving/rendering/single/singledraw.cpp b/src/engraving/rendering/single/singledraw.cpp index cbdffdc223e12..5b707a215bae3 100644 --- a/src/engraving/rendering/single/singledraw.cpp +++ b/src/engraving/rendering/single/singledraw.cpp @@ -106,6 +106,7 @@ #include "dom/stem.h" #include "dom/stemslash.h" #include "dom/sticking.h" +#include "dom/stringtunings.h" #include "dom/stretchedbend.h" #include "dom/symbol.h" #include "dom/systemdivider.h" @@ -280,6 +281,8 @@ void SingleDraw::drawItem(const EngravingItem* item, draw::Painter* painter) break; case ElementType::STICKING: draw(item_cast(item), painter); break; + case ElementType::STRING_TUNINGS: draw(item_cast(item), painter); + break; case ElementType::STRETCHED_BEND: draw(item_cast(item), painter); break; case ElementType::SYMBOL: draw(item_cast(item), painter); @@ -1468,6 +1471,12 @@ void SingleDraw::draw(const Sticking* item, Painter* painter) drawTextBase(item, painter); } +void SingleDraw::draw(const StringTunings* item, Painter* painter) +{ + TRACE_DRAW_ITEM; + drawTextBase(item, painter); +} + void SingleDraw::draw(const StretchedBend* item, Painter* painter) { TRACE_DRAW_ITEM; diff --git a/src/engraving/rendering/single/singledraw.h b/src/engraving/rendering/single/singledraw.h index 4d196d7be592b..5205815225f52 100644 --- a/src/engraving/rendering/single/singledraw.h +++ b/src/engraving/rendering/single/singledraw.h @@ -122,6 +122,7 @@ class StaffText; class StaffTypeChange; class Stem; class StemSlash; +class StringTunings; class StretchedBend; class Sticking; class Symbol; @@ -238,6 +239,7 @@ class SingleDraw static void draw(const Stem* item, draw::Painter* painter); static void draw(const StemSlash* item, draw::Painter* painter); static void draw(const Sticking* item, draw::Painter* painter); + static void draw(const StringTunings* item, draw::Painter* painter); static void draw(const StretchedBend* item, draw::Painter* painter); static void draw(const Symbol* item, draw::Painter* painter); static void draw(const FSymbol* item, draw::Painter* painter); diff --git a/src/engraving/rendering/single/singlelayout.cpp b/src/engraving/rendering/single/singlelayout.cpp index 5687110a27811..9e6c872f265e4 100644 --- a/src/engraving/rendering/single/singlelayout.cpp +++ b/src/engraving/rendering/single/singlelayout.cpp @@ -71,6 +71,7 @@ #include "dom/slur.h" #include "dom/stafftext.h" #include "dom/stafftypechange.h" +#include "dom/stringtunings.h" #include "dom/symbol.h" #include "dom/systemtext.h" #include "dom/tempotext.h" @@ -186,6 +187,8 @@ void SingleLayout::layoutItem(EngravingItem* item) break; case ElementType::STAFFTYPE_CHANGE: layout(toStaffTypeChange(item), ctx); break; + case ElementType::STRING_TUNINGS: layout(toStringTunings(item), ctx); + break; case ElementType::SYMBOL: layout(toSymbol(item), ctx); break; case ElementType::SYSTEM_TEXT: layout(toSystemText(item), ctx); @@ -1392,6 +1395,23 @@ void SingleLayout::layout(StaffTypeChange* item, const Context& ctx) item->setPos(0.0, 0.0); } +void SingleLayout::layout(StringTunings* item, const Context& ctx) +{ + layoutTextBase(item, ctx, item->mutldata()); + + for (TextBlock& block : item->mutldata()->blocks) { + for (TextFragment& fragment : block.fragments()) { + mu::draw::Font font = fragment.font(item); + if (font.type() != mu::draw::Font::Type::MusicSymbol) { + // HACK: the music symbol doesn't have a good baseline + // to go with text so we correct text here + const double baselineAdjustment = font.pointSizeF(); + fragment.pos.setY(fragment.pos.y() - baselineAdjustment); + } + } + } +} + void SingleLayout::layout(Symbol* item, const Context&) { item->setbbox(item->scoreFont() ? item->scoreFont()->bbox(item->sym(), item->magS()) : item->symBbox(item->sym())); diff --git a/src/engraving/rendering/single/singlelayout.h b/src/engraving/rendering/single/singlelayout.h index c59474ca832a4..664951f55d6bc 100644 --- a/src/engraving/rendering/single/singlelayout.h +++ b/src/engraving/rendering/single/singlelayout.h @@ -101,6 +101,7 @@ class Slur; class Spacer; class StaffText; class StaffTypeChange; +class StringTunings; class Symbol; class SystemText; @@ -208,6 +209,7 @@ class SingleLayout static void layout(Spacer* item, const Context&); static void layout(StaffText* item, const Context& ctx); static void layout(StaffTypeChange* item, const Context& ctx); + static void layout(StringTunings* item, const Context& ctx); static void layout(Symbol* item, const Context& ctx); static void layout(SystemText* item, const Context& ctx); diff --git a/src/engraving/rendering/stable/measurelayout.cpp b/src/engraving/rendering/stable/measurelayout.cpp index 2571b580ce212..97f044ca02dd2 100644 --- a/src/engraving/rendering/stable/measurelayout.cpp +++ b/src/engraving/rendering/stable/measurelayout.cpp @@ -129,7 +129,8 @@ static const std::unordered_set BREAK_TYPES { ElementType::TRIPLET_FEEL, ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, - ElementType::INSTRUMENT_CHANGE + ElementType::INSTRUMENT_CHANGE, + ElementType::STRING_TUNINGS }; static const std::unordered_set ALWAYS_BREAK_TYPES { @@ -145,7 +146,8 @@ static const std::unordered_set CONDITIONAL_BREAK_TYPES { ElementType::TRIPLET_FEEL, ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, - ElementType::INSTRUMENT_CHANGE + ElementType::INSTRUMENT_CHANGE, + ElementType::STRING_TUNINGS }; //--------------------------------------------------------- diff --git a/src/engraving/rendering/stable/systemlayout.cpp b/src/engraving/rendering/stable/systemlayout.cpp index a79e9077bceec..8a2c5c240e5b6 100644 --- a/src/engraving/rendering/stable/systemlayout.cpp +++ b/src/engraving/rendering/stable/systemlayout.cpp @@ -1144,7 +1144,7 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx) for (const Segment* s : sl) { for (EngravingItem* e : s->annotations()) { - if (e->isPlayTechAnnotation() || e->isCapo() || e->isSystemText() || e->isTripletFeel()) { + if (e->isPlayTechAnnotation() || e->isCapo() || e->isStringTunings() || e->isSystemText() || e->isTripletFeel()) { TLayout::layoutItem(e, ctx); } } diff --git a/src/engraving/rendering/stable/tdraw.cpp b/src/engraving/rendering/stable/tdraw.cpp index f6bdfe392c811..1e709611380ba 100644 --- a/src/engraving/rendering/stable/tdraw.cpp +++ b/src/engraving/rendering/stable/tdraw.cpp @@ -119,6 +119,7 @@ #include "dom/stemslash.h" #include "dom/sticking.h" #include "dom/stretchedbend.h" +#include "dom/stringtunings.h" #include "dom/symbol.h" #include "dom/systemdivider.h" #include "dom/systemtext.h" @@ -316,6 +317,8 @@ void TDraw::drawItem(const EngravingItem* item, draw::Painter* painter) break; case ElementType::STICKING: draw(item_cast(item), painter); break; + case ElementType::STRING_TUNINGS: draw(item_cast(item), painter); + break; case ElementType::STRETCHED_BEND: draw(item_cast(item), painter); break; case ElementType::SYMBOL: draw(item_cast(item), painter); @@ -2696,6 +2699,47 @@ void TDraw::draw(const Sticking* item, Painter* painter) drawTextBase(item, painter); } +void TDraw::draw(const StringTunings* item, draw::Painter* painter) +{ + TRACE_DRAW_ITEM; + + if (item->noStringVisible()) { + const TextBase::LayoutData* data = item->ldata(); + + double spatium = item->spatium(); + double lineWidth = spatium * .15; + + Pen pen(item->curColor(), lineWidth, PenStyle::SolidLine, PenCapStyle::RoundCap, PenJoinStyle::RoundJoin); + painter->setPen(pen); + painter->setBrush(Brush(item->curColor())); + + mu::draw::Font f(item->font()); + painter->setFont(f); + + RectF rect = data->bbox(); + + double x = rect.x(); + double y = rect.y(); + double width = rect.width(); + double height = rect.height(); + double topPartHeight = height * 2 / 3; + double cornerRadius = 8.0; + + PainterPath path; + path.moveTo(x, y); + path.arcTo(x, y + (topPartHeight - 2 * cornerRadius), 2 * cornerRadius, 2 * cornerRadius, 180.0, 90.0); + path.arcTo(x + width - 2 * cornerRadius, y + (topPartHeight - 2 * cornerRadius), 2 * cornerRadius, 2 * cornerRadius, 270, 90); + path.lineTo(x + width, y); + path.moveTo(x + width / 2, y + topPartHeight); + path.lineTo(x + width / 2, y + height); + + painter->setBrush(BrushStyle::NoBrush); + painter->drawPath(path); + } else { + drawTextBase(item, painter); + } +} + void TDraw::draw(const Symbol* item, Painter* painter) { TRACE_DRAW_ITEM; diff --git a/src/engraving/rendering/stable/tdraw.h b/src/engraving/rendering/stable/tdraw.h index f1aaa244c9407..13f2eaec66780 100644 --- a/src/engraving/rendering/stable/tdraw.h +++ b/src/engraving/rendering/stable/tdraw.h @@ -131,6 +131,7 @@ class Stem; class StemSlash; class Sticking; class StretchedBend; +class StringTunings; class BSymbol; class Symbol; @@ -263,6 +264,7 @@ class TDraw static void draw(const Stem* item, draw::Painter* painter); static void draw(const StemSlash* item, draw::Painter* painter); static void draw(const Sticking* item, draw::Painter* painter); + static void draw(const StringTunings* item, draw::Painter* painter); static void draw(const StretchedBend* item, draw::Painter* painter); static void draw(const Symbol* item, draw::Painter* painter); static void draw(const FSymbol* item, draw::Painter* painter); diff --git a/src/engraving/rendering/stable/tlayout.cpp b/src/engraving/rendering/stable/tlayout.cpp index 5e57c74df6f94..664b4b9aeec7b 100644 --- a/src/engraving/rendering/stable/tlayout.cpp +++ b/src/engraving/rendering/stable/tlayout.cpp @@ -123,6 +123,7 @@ #include "../dom/stem.h" #include "../dom/stemslash.h" #include "../dom/sticking.h" +#include "../dom/stringtunings.h" #include "../dom/stretchedbend.h" #include "../dom/bsymbol.h" #include "../dom/symbol.h" @@ -316,6 +317,8 @@ void TLayout::layoutItem(EngravingItem* item, LayoutContext& ctx) break; case ElementType::STICKING: layout(item_cast(item), ctx); break; + case ElementType::STRING_TUNINGS: layoutStringTunings(item_cast(item), ctx); + break; case ElementType::SYMBOL: layout(item_cast(item), ctx); break; case ElementType::FSYMBOL: layout(item_cast(item), ctx); @@ -1437,11 +1440,9 @@ static void layoutCapo(const Capo* item, const LayoutContext& ctx, Capo::LayoutD //! NOTE Looks like it doesn't belong here if (item->shouldAutomaticallyGenerateText() || item->empty()) { if (const Part* part = item->part()) { - if (const Instrument* instrument = part->instrument(item->tick())) { - if (const StringData* stringData = instrument->stringData()) { - String text = item->generateText(stringData->strings()); - const_cast(item)->setXmlText(text); - } + if (const StringData* stringData = part->stringData(item->tick())) { + String text = item->generateText(stringData->strings()); + const_cast(item)->setXmlText(text); } } } @@ -4505,6 +4506,62 @@ void TLayout::layoutStretched(StretchedBend* item, LayoutContext& ctx) item->setPos(0.0, 0.0); } +void TLayout::layoutStringTunings(StringTunings* item, LayoutContext& ctx) +{ + item->updateText(); + + TLayout::layoutTextBase(item, ctx); + + if (item->noStringVisible()) { + double spatium = item->spatium(); + mu::draw::Font font(item->font()); + + RectF rect; + rect.setTopLeft({ 0, item->ldata()->bbox().y() - font.weight() - spatium * .15 }); + rect.setSize({ font.weight() - spatium, (font.weight() - spatium * .35) * 1.5 }); + + item->setbbox(rect); + } + + for (TextBlock& block : item->mutldata()->blocks) { + for (TextFragment& fragment : block.fragments()) { + mu::draw::Font font = fragment.font(item); + if (font.type() == mu::draw::Font::Type::MusicSymbol) { + // HACK: the music symbol doesn't have a good baseline + // to go with text so we correct it here + const double baselineAdjustment = 0.35 * font.pointSizeF(); + fragment.pos.setY(fragment.pos.y() + baselineAdjustment); + } + } + } + + double secondStringXAlign = 0.0; + for (const TextFragment& fragment : item->fragmentList()) { + if (fragment.font(item).type() == mu::draw::Font::Type::MusicSymbol) { + secondStringXAlign = std::max(secondStringXAlign, fragment.pos.x()); + } + } + + for (TextBlock& block : item->mutldata()->blocks) { + double xMove = 0.0; + for (TextFragment& fragment : block.fragments()) { + if (block.fragments().front() == fragment) { // skip first + continue; + } + + if (fragment.font(item).type() == mu::draw::Font::Type::MusicSymbol) { + xMove = secondStringXAlign - fragment.pos.x(); + } + fragment.pos.setX(fragment.pos.x() + xMove); + } + } + + Segment* parentSegment = item->segment(); + item->move(PointF(-parentSegment->x() + item->spatium(), 0.0)); + + Autoplace::autoplaceSegmentElement(item, item->mutldata()); +} + static void layoutBaseSymbol(const BSymbol* item, const LayoutContext& ctx, BSymbol::LayoutData* ldata) { IF_ASSERT_FAILED(item->explicitParent()) { diff --git a/src/engraving/rendering/stable/tlayout.h b/src/engraving/rendering/stable/tlayout.h index 922b7b846ad31..24e68c047ccc6 100644 --- a/src/engraving/rendering/stable/tlayout.h +++ b/src/engraving/rendering/stable/tlayout.h @@ -135,6 +135,7 @@ class Stem; class StemSlash; class Sticking; class StretchedBend; +class StringTunings; class BSymbol; class Symbol; @@ -289,6 +290,7 @@ class TLayout static void layout(Sticking* item, LayoutContext& ctx); static void layout(StretchedBend* item, LayoutContext& ctx); static void layoutStretched(StretchedBend* item, LayoutContext& ctx); + static void layoutStringTunings(StringTunings* item, LayoutContext& ctx); static void layout(Symbol* item, LayoutContext& ctx); static void layout(FSymbol* item, LayoutContext& ctx); diff --git a/src/engraving/rw/read410/measureread.cpp b/src/engraving/rw/read410/measureread.cpp index 44fcc0eb7cd16..b99618132dc15 100644 --- a/src/engraving/rw/read410/measureread.cpp +++ b/src/engraving/rw/read410/measureread.cpp @@ -521,6 +521,7 @@ void MeasureRead::readVoice(Measure* measure, XmlReader& e, ReadContext& ctx, in || tag == "SystemText" || tag == "PlayTechAnnotation" || tag == "Capo" + || tag == "StringTunings" || tag == "RehearsalMark" || tag == "InstrumentChange" || tag == "StaffState" diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index aa3e39d8d43b5..4a54f63b0845c 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -584,6 +584,7 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract || tag == "StaffText" || tag == "PlayTechAnnotation" || tag == "Capo" + || tag == "StringTunings" || tag == "TempoText" || tag == "FiguredBass" || tag == "Sticking" @@ -888,7 +889,7 @@ void Read410::pasteSymbols(XmlReader& e, ChordRest* dst) score->undoAddElement(el); } } else if (tag == "StaffText" || tag == "PlayTechAnnotation" || tag == "Capo" || tag == "Sticking" - || tag == "HarpPedalDiagram") { + || tag == "HarpPedalDiagram" || tag == "StringTunings") { EngravingItem* el = Factory::createItemByName(tag, score->dummy()); TRead::readItem(el, e, ctx); el->setTrack(destTrack); diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 886c4b37494a9..1811fdcae7776 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -120,6 +120,7 @@ #include "../../dom/spacer.h" #include "../../dom/stafftype.h" #include "../../dom/stafftypechange.h" +#include "../../dom/stringtunings.h" #include "../../dom/system.h" #include "../../dom/textline.h" #include "../../dom/trill.h" @@ -160,7 +161,7 @@ using ReadTypes = rtti::TypeListsetPreset(xml.readText()); + } else if (tag == "StringData") { + StringData sd; + read(&sd, xml); + s->setStringData(sd); + } else if (tag == "visibleStrings") { + s->setVisibleStrings(TConv::fromXml(xml.readText(), std::vector())); + } else if (!readProperties(static_cast(s), xml, ctx)) { + xml.unknown(); + } + } +} + void TRead::read(System* s, XmlReader& e, ReadContext& ctx) { while (e.readNextStartElement()) { diff --git a/src/engraving/rw/read410/tread.h b/src/engraving/rw/read410/tread.h index f63e3606c9137..5fbb0cebbe27e 100644 --- a/src/engraving/rw/read410/tread.h +++ b/src/engraving/rw/read410/tread.h @@ -142,6 +142,7 @@ class StaffTypeChange; class Stem; class StemSlash; class StringData; +class StringTunings; class System; class SystemDivider; class Symbol; @@ -280,6 +281,7 @@ class TRead static void read(Stem* s, XmlReader& xml, ReadContext& ctx); static void read(StemSlash* s, XmlReader& xml, ReadContext& ctx); static void read(StringData* item, XmlReader& xml); + static void read(StringTunings* s, XmlReader& xml, ReadContext& ctx); static void read(System* s, XmlReader& xml, ReadContext& ctx); static void read(SystemDivider* d, XmlReader& xml, ReadContext& ctx); static void read(Symbol* sym, XmlReader& xml, ReadContext& ctx); diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index 45aaa81b52449..606c64dcb2c75 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -128,6 +128,7 @@ #include "../../dom/stemslash.h" #include "../../dom/sticking.h" #include "../../dom/stringdata.h" +#include "../../dom/stringtunings.h" #include "../../dom/symbol.h" #include "../../dom/bsymbol.h" #include "../../dom/system.h" @@ -176,7 +177,7 @@ using WriteTypes = rtti::TypeListfrets()); for (const instrString& strg : item->stringList()) { if (strg.open) { - xml.tag("string open=\"1\"", strg.pitch); + xml.tag("string", { { "open", "1" } }, strg.pitch); } else { xml.tag("string", strg.pitch); } @@ -2563,6 +2564,22 @@ void TWrite::write(const StringData* item, XmlWriter& xml) xml.endElement(); } +void TWrite::write(const StringTunings* item, XmlWriter& xml, WriteContext& ctx) +{ + xml.startElement(item); + + writeProperty(item, xml, Pid::STRINGTUNINGS_PRESET); + + xml.tag("visibleStrings", TConv::toXml(item->visibleStrings())); + + if (!item->stringData()->isNull()) { + write(item->stringData(), xml); + } + + writeProperties(static_cast(item), xml, ctx, true); + xml.endElement(); +} + void TWrite::write(const Symbol* item, XmlWriter& xml, WriteContext& ctx) { xml.startElement(item); @@ -2943,6 +2960,7 @@ void TWrite::writeSegments(XmlWriter& xml, WriteContext& ctx, track_idx_t strack || (et == ElementType::TRIPLET_FEEL) || (et == ElementType::PLAYTECH_ANNOTATION) || (et == ElementType::CAPO) + || (et == ElementType::STRING_TUNINGS) || (et == ElementType::JUMP) || (et == ElementType::MARKER) || (et == ElementType::TEMPO_TEXT) diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index d3c250ec72d30..8ef4fcbc49507 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -142,6 +142,7 @@ class StringData; class Symbol; class BSymbol; class FSymbol; +class StringTunings; class System; class SystemDivider; class SystemText; @@ -273,6 +274,7 @@ class TWrite static void write(const StemSlash* item, XmlWriter& xml, WriteContext& ctx); static void write(const Sticking* item, XmlWriter& xml, WriteContext& ctx); static void write(const StringData* item, XmlWriter& xml); + static void write(const StringTunings* item, XmlWriter& xml, WriteContext& ctx); static void write(const Symbol* item, XmlWriter& xml, WriteContext& ctx); static void write(const FSymbol* item, XmlWriter& xml, WriteContext& ctx); static void write(const System* item, XmlWriter& xml, WriteContext& ctx); diff --git a/src/engraving/style/textstyle.cpp b/src/engraving/style/textstyle.cpp index e05fbe93c21c9..140631aeb137a 100644 --- a/src/engraving/style/textstyle.cpp +++ b/src/engraving/style/textstyle.cpp @@ -1093,6 +1093,7 @@ const TextStyle* textStyle(TextStyleType idx) case TextStyleType::LH_GUITAR_FINGERING: return &lhGuitarFingeringTextStyle; case TextStyleType::RH_GUITAR_FINGERING: return &rhGuitarFingeringTextStyle; case TextStyleType::STRING_NUMBER: return &stringNumberTextStyle; + case TextStyleType::STRING_TUNINGS: return &stringNumberTextStyle; // todo case TextStyleType::HARP_PEDAL_DIAGRAM: return &harpPedalDiagramTextStyle; case TextStyleType::HARP_PEDAL_TEXT_DIAGRAM: return &harpPedalTextDiagramTextStyle; diff --git a/src/engraving/tests/parts_data/part-image-parts.mscx b/src/engraving/tests/parts_data/part-image-parts.mscx index 32f0a0645006a..dad0258711612 100644 --- a/src/engraving/tests/parts_data/part-image-parts.mscx +++ b/src/engraving/tests/parts_data/part-image-parts.mscx @@ -127,7 +127,7 @@ - 8399 + 8499 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png @@ -849,7 +849,7 @@ - 8399 + 8499 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/tests/parts_data/part-image-udel.mscx b/src/engraving/tests/parts_data/part-image-udel.mscx index 544730a35654b..ee9cd6cca7373 100644 --- a/src/engraving/tests/parts_data/part-image-udel.mscx +++ b/src/engraving/tests/parts_data/part-image-udel.mscx @@ -127,7 +127,7 @@ - 8399 + 8499 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png @@ -850,7 +850,7 @@ - 8399 + 8499 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/tests/parts_data/part-image.mscx b/src/engraving/tests/parts_data/part-image.mscx index 1e7d6d3334442..60fb748de97e1 100644 --- a/src/engraving/tests/parts_data/part-image.mscx +++ b/src/engraving/tests/parts_data/part-image.mscx @@ -123,7 +123,7 @@ - 8399 + 8499 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/types/types.h b/src/engraving/types/types.h index 1b41f23a8c4f1..7bd07622ece54 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -116,6 +116,7 @@ enum class ElementType { SYSTEM_TEXT, PLAYTECH_ANNOTATION, CAPO, + STRING_TUNINGS, TRIPLET_FEEL, REHEARSAL_MARK, INSTRUMENT_CHANGE, @@ -733,6 +734,7 @@ enum class TextStyleType { LH_GUITAR_FINGERING, RH_GUITAR_FINGERING, STRING_NUMBER, + STRING_TUNINGS, HARP_PEDAL_DIAGRAM, HARP_PEDAL_TEXT_DIAGRAM, diff --git a/src/engraving/types/typesconv.cpp b/src/engraving/types/typesconv.cpp index 08057994c23ae..0a26e29c70664 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -132,6 +132,33 @@ std::vector TConv::fromXml(const String& tag, const std::vector& def) return list; } +String TConv::toXml(const std::vector& v) +{ + std::vector _v; + for (string_idx_t string : v) { + _v.push_back(static_cast(string)); + } + + return toXml(_v); +} + +std::vector TConv::fromXml(const String& tag, const std::vector& def) +{ + std::vector _def; + for (string_idx_t string : def) { + _def.push_back(static_cast(string)); + } + + std::vector v; + std::vector _v = fromXml(tag, _def); + + for (int string : _v) { + v.push_back(static_cast(string)); + } + + return v; +} + static const std::vector > ELEMENT_TYPES = { { ElementType::INVALID, "invalid", TranslatableString("engraving", "Invalid") }, { ElementType::BRACKET_ITEM, "BracketItem", TranslatableString("engraving", "Bracket") }, @@ -184,6 +211,7 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::SYSTEM_TEXT, "SystemText", TranslatableString("engraving", "System text") }, { ElementType::PLAYTECH_ANNOTATION, "PlayTechAnnotation", TranslatableString("engraving", "Playing technique annotation") }, { ElementType::CAPO, "Capo", TranslatableString("engraving", "Capo") }, + { ElementType::STRING_TUNINGS, "StringTunings", TranslatableString("engraving", "String tunings") }, { ElementType::TRIPLET_FEEL, "TripletFeel", TranslatableString("engraving", "Triplet feel") }, { ElementType::REHEARSAL_MARK, "RehearsalMark", TranslatableString("engraving", "Rehearsal mark") }, { ElementType::INSTRUMENT_CHANGE, "InstrumentChange", TranslatableString("engraving", "Instrument change") }, @@ -1043,6 +1071,7 @@ static const std::vector > TEXTSTYLE_TYPES = { { TextStyleType::LH_GUITAR_FINGERING, "guitar_fingering_lh", TranslatableString("engraving", "LH guitar fingering") }, { TextStyleType::RH_GUITAR_FINGERING, "guitar_fingering_rh", TranslatableString("engraving", "RH guitar fingering") }, { TextStyleType::STRING_NUMBER, "string_number", TranslatableString("engraving", "String number") }, + { TextStyleType::STRING_TUNINGS, "string_tunings", TranslatableString("engraving", "String tunings") }, { TextStyleType::HARP_PEDAL_DIAGRAM, "harp_pedal_diagram", TranslatableString("engraving", "Harp pedal diagram") }, { TextStyleType::HARP_PEDAL_TEXT_DIAGRAM, "harp_pedal_text_diagram", TranslatableString("engraving", "Harp pedal text diagram") }, diff --git a/src/engraving/types/typesconv.h b/src/engraving/types/typesconv.h index 43a7521df7f3a..a27dda33f0c05 100644 --- a/src/engraving/types/typesconv.h +++ b/src/engraving/types/typesconv.h @@ -39,6 +39,9 @@ class TConv static String toXml(const std::vector& v); static std::vector fromXml(const String& tag, const std::vector& def); + static String toXml(const std::vector& v); + static std::vector fromXml(const String& tag, const std::vector& def); + static const TranslatableString& userName(ElementType v); static AsciiStringView toXml(ElementType v); static ElementType fromXml(const AsciiStringView& tag, ElementType def, bool silent = false); diff --git a/src/framework/uicomponents/qml/MuseScore/UiComponents/IncrementalPropertyControl.qml b/src/framework/uicomponents/qml/MuseScore/UiComponents/IncrementalPropertyControl.qml index 2c0a9350eb4ac..0e5f9b6fa74cf 100644 --- a/src/framework/uicomponents/qml/MuseScore/UiComponents/IncrementalPropertyControl.qml +++ b/src/framework/uicomponents/qml/MuseScore/UiComponents/IncrementalPropertyControl.qml @@ -33,17 +33,25 @@ Item { property alias isIndeterminate: textInputField.isIndeterminate property alias currentValue: textInputField.currentText + property real step: 0.5 property int decimals: 2 property real maxValue: 999 property real minValue: -999 property alias validator: textInputField.validator + property alias measureUnitsSymbol: textInputField.measureUnitsSymbol property alias navigation: textInputField.navigation readonly property int spacing: 8 + property bool canIncrease: root.currentValue < root.maxValue + property var onIncrement: null + + property bool canDecrease: root.currentValue > root.minValue + property var onDecrement: null + signal valueEdited(var newValue) signal valueEditingFinished(var newValue) @@ -53,27 +61,39 @@ Item { navigation.name: Boolean(root.objectName) ? root.objectName : "IncrementalControl" function increment() { - var value = root.isIndeterminate ? 0.0 : currentValue - var newValue = Math.min(value + step, root.maxValue) + var newValue + if (Boolean(onIncrement)) { + newValue = onIncrement() + } else { + var value = root.isIndeterminate ? 0.0 : currentValue + newValue = Math.min(value + step, root.maxValue) + + if (newValue === value) { + return + } - if (newValue === value) { - return + newValue = +newValue.toFixed(decimals) } - newValue = +newValue.toFixed(decimals) root.valueEdited(newValue) root.valueEditingFinished(newValue) } function decrement() { - var value = root.isIndeterminate ? 0.0 : currentValue - var newValue = Math.max(value - step, root.minValue) + var newValue + if (Boolean(onDecrement)) { + newValue = onDecrement() + } else { + var value = root.isIndeterminate ? 0.0 : currentValue + newValue = Math.max(value - step, root.minValue) + + if (newValue === value) { + return + } - if (newValue === value) { - return + newValue = +newValue.toFixed(decimals) } - newValue = +newValue.toFixed(decimals) root.valueEdited(newValue) root.valueEditingFinished(newValue) } @@ -84,6 +104,12 @@ Item { Right } + QtObject { + id: prv + + property bool isCustom: Boolean(onIncrement) && Boolean(onDecrement) + } + Rectangle { id: iconBackground @@ -137,7 +163,7 @@ Item { bottom: root.minValue } - validator: root.decimals > 0 ? doubleInputValidator : intInputValidator + validator: !prv.isCustom ? (root.decimals > 0 ? doubleInputValidator : intInputValidator) : null containsMouse: mouseArea.containsMouse || valueAdjustControl.containsMouse @@ -151,8 +177,8 @@ Item { radius: textInputField.background.radius - anchors.margins - canIncrease: root.currentValue < root.maxValue - canDecrease: root.currentValue > root.minValue + canIncrease: root.canIncrease + canDecrease: root.canDecrease onIncreaseButtonClicked: { root.increment() } onDecreaseButtonClicked: { root.decrement() } @@ -194,6 +220,11 @@ Item { } onTextChanged: function(newTextValue) { + if (prv.isCustom) { + root.valueEdited(newTextValue) + return + } + var newVal = parseFloat(newTextValue) if (isNaN(newVal)) { @@ -204,6 +235,11 @@ Item { } onTextEditingFinished: function(newTextValue) { + if (prv.isCustom) { + root.valueEditingFinished(newTextValue) + return + } + var newVal = parseFloat(newTextValue) if (isNaN(newVal)) { diff --git a/src/framework/uicomponents/qml/MuseScore/UiComponents/StyledDropdown.qml b/src/framework/uicomponents/qml/MuseScore/UiComponents/StyledDropdown.qml index ec095d7ff5309..94b6d16fefaa4 100644 --- a/src/framework/uicomponents/qml/MuseScore/UiComponents/StyledDropdown.qml +++ b/src/framework/uicomponents/qml/MuseScore/UiComponents/StyledDropdown.qml @@ -69,6 +69,34 @@ Item { return -1 } + function indexOfText(text) { + if (!root.model) { + return -1 + } + + for (var i = 0; i < root.count; ++i) { + if (Utils.getItemValue(root.model, i, root.textRole) === text) { + return i + } + } + + return -1 + } + + function textOfValue(value) { + if (!root.model) { + return "" + } + + for (var i = 0; i < root.count; ++i) { + if (Utils.getItemValue(root.model, i, root.valueRole) === value) { + return Utils.getItemValue(model, i, textRole, indeterminateText) + } + } + + return "" + } + function ensureActiveFocus() { if (mainItem.navigation) { mainItem.navigation.requestActive() diff --git a/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/DropdownItem.qml b/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/DropdownItem.qml index 7dfea4fa4302e..fff955782dddb 100644 --- a/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/DropdownItem.qml +++ b/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/DropdownItem.qml @@ -83,6 +83,18 @@ Item { anchors.fill: parent hoverEnabled: true onClicked: root.clicked() + + onContainsMouseChanged: { + if (!labelItem.truncated) { + return + } + + if (mouseArea.containsMouse) { + ui.tooltip.show(root, labelItem.text) + } else { + ui.tooltip.hide(root) + } + } } states: [ diff --git a/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/StyledDropdownView.qml b/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/StyledDropdownView.qml index 81c340ec2731f..f6da144131e26 100644 --- a/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/StyledDropdownView.qml +++ b/src/framework/uicomponents/qml/MuseScore/UiComponents/internal/StyledDropdownView.qml @@ -270,6 +270,18 @@ DropdownView { var value = Utils.getItemValue(root.model, model.index, root.valueRole, undefined) root.handleItem(model.index, value) } + + mouseArea.onContainsMouseChanged: { + if (!label.truncated) { + return + } + + if (mouseArea.containsMouse) { + ui.tooltip.show(item, label.text) + } else { + ui.tooltip.hide(item) + } + } } } } diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index f81edf02ae018..8bc00b4ca40f6 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -4316,6 +4316,7 @@ static void directionTag(XmlWriter& xml, Attributes& attr, EngravingItem const* || el->type() == ElementType::STAFF_TEXT || el->type() == ElementType::PLAYTECH_ANNOTATION || el->type() == ElementType::CAPO + || el->type() == ElementType::STRING_TUNINGS || el->type() == ElementType::SYMBOL || el->type() == ElementType::TEXT) { // handle other elements attached (e.g. via Segment / Measure) to a system @@ -5892,7 +5893,8 @@ static bool commonAnnotations(ExportMusicXml* exp, const EngravingItem* e, staff exp->symbol(toSymbol(e), sstaff); } else if (e->isTempoText()) { exp->tempoText(toTempoText(e), sstaff); - } else if (e->isPlayTechAnnotation() || e->isCapo() || e->isStaffText() || e->isSystemText() || e->isTripletFeel() || e->isText() + } else if (e->isPlayTechAnnotation() || e->isCapo() || e->isStringTunings() || e->isStaffText() || e->isSystemText() + || e->isTripletFeel() || e->isText() || e->isExpression() || (e->isInstrumentChange() && e->visible())) { exp->words(toTextBase(e), sstaff); } else if (e->isDynamic()) { diff --git a/src/inspector/CMakeLists.txt b/src/inspector/CMakeLists.txt index 37d68918fa8b9..23ebdd2689ba6 100644 --- a/src/inspector/CMakeLists.txt +++ b/src/inspector/CMakeLists.txt @@ -209,6 +209,8 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/models/notation/dynamics/dynamicsettingsmodel.h ${CMAKE_CURRENT_LIST_DIR}/models/notation/expressions/expressionsettingsmodel.h ${CMAKE_CURRENT_LIST_DIR}/models/notation/expressions/expressionsettingsmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/models/notation/stringtunings/stringtuningssettingsmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/models/notation/stringtunings/stringtuningssettingsmodel.h ${CMAKE_CURRENT_LIST_DIR}/models/inspectorpopupcontroller.cpp ${CMAKE_CURRENT_LIST_DIR}/models/inspectorpopupcontroller.h ${CMAKE_CURRENT_LIST_DIR}/internal/services/elementrepositoryservice.cpp diff --git a/src/inspector/internal/services/elementrepositoryservice.cpp b/src/inspector/internal/services/elementrepositoryservice.cpp index 28a190c5aadf6..1c5b89182b04c 100644 --- a/src/inspector/internal/services/elementrepositoryservice.cpp +++ b/src/inspector/internal/services/elementrepositoryservice.cpp @@ -41,6 +41,7 @@ #include "engraving/dom/tremolo.h" #include "engraving/dom/trill.h" #include "engraving/dom/volta.h" +#include "engraving/dom/note.h" #include "log.h" diff --git a/src/inspector/models/abstractinspectormodel.cpp b/src/inspector/models/abstractinspectormodel.cpp index dbfebe1706757..7534151517236 100644 --- a/src/inspector/models/abstractinspectormodel.cpp +++ b/src/inspector/models/abstractinspectormodel.cpp @@ -97,7 +97,8 @@ static const QMap NOTATION_ELEME { mu::engraving::ElementType::LYRICS, InspectorModelType::TYPE_LYRICS }, { mu::engraving::ElementType::REST, InspectorModelType::TYPE_REST }, { mu::engraving::ElementType::DYNAMIC, InspectorModelType::TYPE_DYNAMIC }, - { mu::engraving::ElementType::EXPRESSION, InspectorModelType::TYPE_EXPRESSION } + { mu::engraving::ElementType::EXPRESSION, InspectorModelType::TYPE_EXPRESSION }, + { mu::engraving::ElementType::STRING_TUNINGS, InspectorModelType::TYPE_STRING_TUNINGS } }; static QMap HAIRPIN_ELEMENT_MODEL_TYPES = { diff --git a/src/inspector/models/abstractinspectormodel.h b/src/inspector/models/abstractinspectormodel.h index 6c14ee9dadacb..a62a261d11b6c 100644 --- a/src/inspector/models/abstractinspectormodel.h +++ b/src/inspector/models/abstractinspectormodel.h @@ -126,6 +126,7 @@ class AbstractInspectorModel : public QObject, public async::Asyncable TYPE_LYRICS, TYPE_REST, TYPE_REST_BEAM, + TYPE_STRING_TUNINGS, }; Q_ENUM(InspectorModelType) diff --git a/src/inspector/models/inspectormodelcreator.cpp b/src/inspector/models/inspectormodelcreator.cpp index 397026942e00d..4e4410329775a 100644 --- a/src/inspector/models/inspectormodelcreator.cpp +++ b/src/inspector/models/inspectormodelcreator.cpp @@ -73,6 +73,7 @@ #include "notation/rests/restsettingsproxymodel.h" #include "notation/dynamics/dynamicsettingsmodel.h" #include "notation/expressions/expressionsettingsmodel.h" +#include "notation/stringtunings/stringtuningssettingsmodel.h" using namespace mu::inspector; @@ -190,6 +191,8 @@ AbstractInspectorModel* InspectorModelCreator::newInspectorModel(InspectorModelT return new DynamicsSettingsModel(parent, repository); case InspectorModelType::TYPE_EXPRESSION: return new ExpressionSettingsModel(parent, repository); + case InspectorModelType::TYPE_STRING_TUNINGS: + return new StringTuningsSettingsModel(parent, repository); case InspectorModelType::TYPE_BREATH: case InspectorModelType::TYPE_ARPEGGIO: case InspectorModelType::TYPE_UNDEFINED: diff --git a/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.cpp b/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.cpp new file mode 100644 index 0000000000000..e283ece2b6995 --- /dev/null +++ b/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.cpp @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "stringtuningssettingsmodel.h" + +#include "translation.h" + +using namespace mu::inspector; +using namespace mu::notation; + +StringTuningsSettingsModel::StringTuningsSettingsModel(QObject* parent, IElementRepositoryService* repository) + : AbstractInspectorModel(parent, repository) +{ + setModelType(InspectorModelType::TYPE_STRING_TUNINGS); + setTitle(qtrc("inspector", "Fretted instruments")); +} + +bool StringTuningsSettingsModel::isEmpty() const +{ + INotationSelectionPtr selection = this->selection(); + return !selection || !selection->isRange(); +} + +void StringTuningsSettingsModel::editStrings() +{ + dispatcher()->dispatch("edit-strings"); +} diff --git a/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.h b/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.h new file mode 100644 index 0000000000000..d2162bfa017a5 --- /dev/null +++ b/src/inspector/models/notation/stringtunings/stringtuningssettingsmodel.h @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef MU_INSPECTOR_STRINGTUNINGSSETTINGSMODEL_H +#define MU_INSPECTOR_STRINGTUNINGSSETTINGSMODEL_H + +#include "models/abstractinspectormodel.h" + +namespace mu::inspector { +class StringTuningsSettingsModel : public AbstractInspectorModel +{ + Q_OBJECT + +public: + explicit StringTuningsSettingsModel(QObject* parent, IElementRepositoryService* repository); + + Q_INVOKABLE void editStrings(); + +private: + void createProperties() override { } + void loadProperties() override { } + void resetProperties() override { } + void requestElements() override { } + + bool isEmpty() const override; +}; +} + +#endif // MU_INSPECTOR_STRINGTUNINGSSETTINGSMODEL_H diff --git a/src/inspector/types/texttypes.h b/src/inspector/types/texttypes.h index 5292d990189be..155e24a13ecd1 100644 --- a/src/inspector/types/texttypes.h +++ b/src/inspector/types/texttypes.h @@ -160,6 +160,7 @@ static const QList TEXT_ELEMENT_TYPES = { mu::engraving::ElementType::TUPLET, mu::engraving::ElementType::PLAYTECH_ANNOTATION, mu::engraving::ElementType::CAPO, + mu::engraving::ElementType::STRING_TUNINGS, mu::engraving::ElementType::HARP_DIAGRAM }; } diff --git a/src/inspector/view/inspector_resources.qrc b/src/inspector/view/inspector_resources.qrc index ca102851de09f..1b09cd1e7faaa 100644 --- a/src/inspector/view/inspector_resources.qrc +++ b/src/inspector/view/inspector_resources.qrc @@ -106,5 +106,6 @@ qml/MuseScore/Inspector/common/PropertyResetButton.qml qml/MuseScore/Inspector/common/PropertyToggle.qml qml/MuseScore/Inspector/parts/PartsSettings.qml + qml/MuseScore/Inspector/notation/stringtunings/StringTuningsSettings.qml diff --git a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml index 06d63feae1590..6a16764302386 100644 --- a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml +++ b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml @@ -60,6 +60,7 @@ import "lyrics" import "rests" import "dynamics" import "expressions" +import "stringtunings" Loader { id: root @@ -133,6 +134,7 @@ Loader { case Inspector.TYPE_REST_BEAM: return restComp case Inspector.TYPE_DYNAMIC: return dynamicComp case Inspector.TYPE_EXPRESSION: return expressionComp + case Inspector.TYPE_STRING_TUNINGS: return stringTuningsComp } return null @@ -336,4 +338,9 @@ Loader { id: expressionComp ExpressionsSettings {} } + + Component { + id: stringTuningsComp + StringTuningsSettings {} + } } diff --git a/src/inspector/view/qml/MuseScore/Inspector/notation/stringtunings/StringTuningsSettings.qml b/src/inspector/view/qml/MuseScore/Inspector/notation/stringtunings/StringTuningsSettings.qml new file mode 100644 index 0000000000000..3441d7cdb0dc7 --- /dev/null +++ b/src/inspector/view/qml/MuseScore/Inspector/notation/stringtunings/StringTuningsSettings.qml @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 + +import MuseScore.Ui 1.0 +import MuseScore.UiComponents 1.0 +import MuseScore.Inspector 1.0 + +import "../../common" + +Column { + id: root + + property QtObject model: null + + property NavigationPanel navigationPanel: null + property int navigationRowStart: 1 + + objectName: "StringTuningsSettings" + + spacing: 12 + + function focusOnFirst() { + editStringsButton.navigation.requestActive() + } + + FlatButton { + id: editStringsButton + + width: parent.width + + text: qsTrc("inspector", "Edit strings") + + navigation.name: "EditStrings" + navigation.panel: root.navigationPanel + navigation.row: root.navigationRowStart + + onClicked: { + if (root.model) { + Qt.callLater(root.model.editStrings) + } + } + } +} diff --git a/src/notation/CMakeLists.txt b/src/notation/CMakeLists.txt index ef328a2b0e456..f1ad68fdc47cb 100644 --- a/src/notation/CMakeLists.txt +++ b/src/notation/CMakeLists.txt @@ -153,6 +153,8 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/view/internal/harppedalpopupmodel.h ${CMAKE_CURRENT_LIST_DIR}/view/internal/caposettingsmodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/internal/caposettingsmodel.h + ${CMAKE_CURRENT_LIST_DIR}/view/internal/stringtuningssettingsmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/internal/stringtuningssettingsmodel.h ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.h ${CMAKE_CURRENT_LIST_DIR}/view/editgridsizedialogmodel.cpp diff --git a/src/notation/iinstrumentsrepository.h b/src/notation/iinstrumentsrepository.h index 10e66e2a785ae..2ad4cc03cc86b 100644 --- a/src/notation/iinstrumentsrepository.h +++ b/src/notation/iinstrumentsrepository.h @@ -42,6 +42,8 @@ class IInstrumentsRepository : MODULE_EXPORT_INTERFACE virtual const InstrumentGenreList& genres() const = 0; virtual const InstrumentGroupList& groups() const = 0; + + virtual const InstrumentStringTuningsMap& stringTuningsPresets() const = 0; }; } diff --git a/src/notation/inotationconfiguration.h b/src/notation/inotationconfiguration.h index 3a9980401b602..4f9858706b4cf 100644 --- a/src/notation/inotationconfiguration.h +++ b/src/notation/inotationconfiguration.h @@ -166,6 +166,8 @@ class INotationConfiguration : MODULE_EXPORT_INTERFACE virtual io::paths_t userScoreOrderListPaths() const = 0; virtual void setUserScoreOrderListPaths(const io::paths_t& paths) = 0; + virtual io::path_t stringTuningsPresetsPath() const = 0; + virtual bool isSnappedToGrid(framework::Orientation gridOrientation) const = 0; virtual void setIsSnappedToGrid(framework::Orientation gridOrientation, bool isSnapped) = 0; diff --git a/src/notation/internal/instrumentsrepository.cpp b/src/notation/internal/instrumentsrepository.cpp index 7868d9a00cb5d..e82bc5a830118 100644 --- a/src/notation/internal/instrumentsrepository.cpp +++ b/src/notation/internal/instrumentsrepository.cpp @@ -21,6 +21,8 @@ */ #include "instrumentsrepository.h" +#include "global/serialization/json.h" + #include "log.h" #include "translation.h" @@ -90,6 +92,11 @@ const InstrumentGroupList& InstrumentsRepository::groups() const return m_groups; } +const InstrumentStringTuningsMap& InstrumentsRepository::stringTuningsPresets() const +{ + return m_stringTuningsPresets; +} + void InstrumentsRepository::load() { TRACEFUNC; @@ -126,4 +133,81 @@ void InstrumentsRepository::load() m_instrumentTemplates << templ; } } + + io::path_t stringTuningsPresetsPath = configuration()->stringTuningsPresetsPath(); + if (!loadStringTuningsPresets(stringTuningsPresetsPath)) { + LOGE() << "Could not load string tunings presets from " << stringTuningsPresetsPath << "!"; + } +} + +bool InstrumentsRepository::loadStringTuningsPresets(const io::path_t& path) +{ + TRACEFUNC; + + Ret ret = fileSystem()->exists(path); + if (!ret) { + LOGE() << ret.toString(); + return false; + } + + RetVal retVal = fileSystem()->readFile(path); + if (!retVal.ret) { + LOGE() << retVal.ret.toString(); + return false; + } + + std::string err; + JsonArray arr = JsonDocument::fromJson(retVal.val, &err).rootArray(); + if (!err.empty()) { + LOGE() << "failed parse string tunings presets, err: " << err; + return false; + } + + for (size_t i = 0; i < arr.size(); ++i) { + const JsonValue& presetInfoVal = arr.at(i); + JsonObject presetInfoObj = presetInfoVal.toObject(); + + std::vector strings; + + JsonArray stringsArr = presetInfoObj.value("strings").toArray(); + for (size_t j = 0; j < stringsArr.size(); ++j) { + StringTuningsInfo info; + + const JsonValue& stringVal = stringsArr.at(j); + JsonObject stringObj = stringVal.toObject(); + + info.number = stringObj.value("number").toInt(); + + JsonArray presetsArr = stringObj.value("presets").toArray(); + for (size_t k = 0; k < presetsArr.size(); ++k) { + const JsonValue& presetVal = presetsArr.at(k); + JsonObject presetObj = presetVal.toObject(); + + StringTuningPreset preset; + preset.name = trc("instruments/stringTunings", presetObj.value("name").toStdString().c_str()); + + JsonArray valuesArr = presetObj.value("value").toArray(); + for (size_t l = 0; l < valuesArr.size(); ++l) { + const JsonValue& valueVal = valuesArr.at(l); + preset.value.push_back(valueVal.toInt()); + } + + if (info.number != static_cast(preset.value.size())) { + LOGE() << "Invalid preset " << preset.name; + continue; + } + + info.presets.emplace_back(std::move(preset)); + } + + strings.emplace_back(std::move(info)); + } + + std::string id = presetInfoObj.contains("familyId") ? presetInfoObj.value("familyId").toStdString() + : presetInfoObj.value("instrumentId").toStdString(); + + m_stringTuningsPresets.emplace(id, strings); + } + + return true; } diff --git a/src/notation/internal/instrumentsrepository.h b/src/notation/internal/instrumentsrepository.h index 55035a3bdb838..ff465980f327f 100644 --- a/src/notation/internal/instrumentsrepository.h +++ b/src/notation/internal/instrumentsrepository.h @@ -27,12 +27,14 @@ #include "async/channel.h" #include "async/asyncable.h" +#include "io/ifilesystem.h" #include "iinstrumentsrepository.h" #include "inotationconfiguration.h" namespace mu::notation { class InstrumentsRepository : public IInstrumentsRepository, public async::Asyncable { + INJECT(io::IFileSystem, fileSystem) INJECT(INotationConfiguration, configuration) public: @@ -47,13 +49,18 @@ class InstrumentsRepository : public IInstrumentsRepository, public async::Async const InstrumentGenreList& genres() const override; const InstrumentGroupList& groups() const override; + const InstrumentStringTuningsMap& stringTuningsPresets() const override; + private: void load(); void clear(); + bool loadStringTuningsPresets(const io::path_t& path); + InstrumentTemplateList m_instrumentTemplates; InstrumentGroupList m_groups; InstrumentGenreList m_genres; + InstrumentStringTuningsMap m_stringTuningsPresets; }; } diff --git a/src/notation/internal/notationactioncontroller.cpp b/src/notation/internal/notationactioncontroller.cpp index bad4c512042f3..91f5e355e8cb7 100644 --- a/src/notation/internal/notationactioncontroller.cpp +++ b/src/notation/internal/notationactioncontroller.cpp @@ -282,6 +282,7 @@ void NotationActionController::init() registerAction("edit-style", &Controller::openEditStyleDialog); registerAction("page-settings", &Controller::openPageSettingsDialog); registerAction("staff-properties", &Controller::openStaffProperties); + registerAction("edit-strings", &Controller::openEditStringsDialog); registerAction("add-remove-breaks", &Controller::openBreaksDialog); registerAction("transpose", &Controller::openTransposeDialog); registerAction("parts", &Controller::openPartsDialog); @@ -472,6 +473,8 @@ void NotationActionController::init() registerTabPadNoteAction("pad-note-1024-TAB", Pad::NOTE1024); registerAction("rest-TAB", &Interaction::putRestToSelection); + registerAction("edit-strings", &Interaction::changeEnharmonicSpelling, true); + for (int i = 0; i < MAX_FRET; ++i) { registerAction("fret-" + std::to_string(i), [i, this]() { addFret(i); }, &Controller::isTablatureStaff); } @@ -1541,6 +1544,11 @@ void NotationActionController::openStaffProperties() interactive()->open("musescore://notation/staffproperties"); } +void NotationActionController::openEditStringsDialog() +{ + interactive()->open("musescore://notation/editstrings"); +} + void NotationActionController::openBreaksDialog() { interactive()->open("musescore://notation/breaks"); diff --git a/src/notation/internal/notationactioncontroller.h b/src/notation/internal/notationactioncontroller.h index 7a7d1e9bade7b..74194dc4ac932 100644 --- a/src/notation/internal/notationactioncontroller.h +++ b/src/notation/internal/notationactioncontroller.h @@ -143,6 +143,7 @@ class NotationActionController : public actions::Actionable, public async::Async void openEditStyleDialog(const actions::ActionData& args); void openPageSettingsDialog(); void openStaffProperties(); + void openEditStringsDialog(); void openBreaksDialog(); void openTransposeDialog(); void openPartsDialog(); diff --git a/src/notation/internal/notationconfiguration.cpp b/src/notation/internal/notationconfiguration.cpp index dc3192431a229..7d2ed5a325de4 100644 --- a/src/notation/internal/notationconfiguration.cpp +++ b/src/notation/internal/notationconfiguration.cpp @@ -738,6 +738,11 @@ void NotationConfiguration::setUserScoreOrderListPaths(const io::paths_t& paths) } } +io::path_t NotationConfiguration::stringTuningsPresetsPath() const +{ + return globalConfiguration()->appDataPath() + "instruments/string_tunings_presets.json"; +} + bool NotationConfiguration::isSnappedToGrid(framework::Orientation gridOrientation) const { switch (gridOrientation) { diff --git a/src/notation/internal/notationconfiguration.h b/src/notation/internal/notationconfiguration.h index 91effd573b4e3..326b0d56aa266 100644 --- a/src/notation/internal/notationconfiguration.h +++ b/src/notation/internal/notationconfiguration.h @@ -169,6 +169,8 @@ class NotationConfiguration : public INotationConfiguration, public async::Async io::paths_t userScoreOrderListPaths() const override; void setUserScoreOrderListPaths(const io::paths_t& paths) override; + io::path_t stringTuningsPresetsPath() const override; + bool isSnappedToGrid(framework::Orientation gridOrientation) const override; void setIsSnappedToGrid(framework::Orientation gridOrientation, bool isSnapped) override; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index b6c5a30714d97..b90c4e6f2a1a4 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -1262,6 +1262,7 @@ bool NotationInteraction::isDropAccepted(const PointF& pos, Qt::KeyboardModifier case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::NOTEHEAD: case ElementType::TREMOLO: case ElementType::LAYOUT_BREAK: @@ -1420,6 +1421,7 @@ bool NotationInteraction::drop(const PointF& pos, Qt::KeyboardModifiers modifier case ElementType::TRIPLET_FEEL: case ElementType::PLAYTECH_ANNOTATION: case ElementType::CAPO: + case ElementType::STRING_TUNINGS: case ElementType::NOTEHEAD: case ElementType::TREMOLO: case ElementType::LAYOUT_BREAK: @@ -2677,7 +2679,7 @@ void NotationInteraction::moveStringSelection(MoveDirection d) { mu::engraving::InputState& is = score()->inputState(); mu::engraving::Staff* staff = score()->staff(is.track() / mu::engraving::VOICES); - int instrStrgs = static_cast(staff->part()->instrument(is.tick())->stringData()->strings()); + int instrStrgs = static_cast(staff->part()->stringData(is.tick())->strings()); int delta = (staff->staffType(is.tick())->upsideDown() ? -1 : 1); if (MoveDirection::Up == d) { diff --git a/src/notation/internal/notationparts.cpp b/src/notation/internal/notationparts.cpp index fb729c3d910ab..e5dbf801482eb 100644 --- a/src/notation/internal/notationparts.cpp +++ b/src/notation/internal/notationparts.cpp @@ -743,7 +743,8 @@ void NotationParts::doSetStaffConfig(Staff* staff, const StaffConfig& config) } score()->undo(new mu::engraving::ChangeStaff(staff, config.visible, config.clefTypeList, config.userDistance, config.hideMode, - config.showIfEmpty, config.cutaway, config.hideSystemBarline, config.mergeMatchingRests)); + config.showIfEmpty, config.cutaway, config.hideSystemBarline, config.mergeMatchingRests, + config.reflectTranspositionInLinkedTab)); score()->undo(new mu::engraving::ChangeStaffType(staff, config.staffType)); } diff --git a/src/notation/internal/notationuiactions.cpp b/src/notation/internal/notationuiactions.cpp index 215e093f3b07f..4f8b2afa0eff4 100644 --- a/src/notation/internal/notationuiactions.cpp +++ b/src/notation/internal/notationuiactions.cpp @@ -2389,6 +2389,10 @@ const UiActionList NotationUiActions::m_engravingDebuggingActions = { mu::context::CTX_NOTATION_OPENED, TranslatableString::untranslatable("Show corrupted measures"), Checkable::Yes + ), + UiAction("edit-strings", + mu::context::UiCtxNotationOpened, + mu::context::CTX_NOTATION_OPENED ) }; diff --git a/src/notation/notationmodule.cpp b/src/notation/notationmodule.cpp index ca36a069423a9..d8c4abd84e92b 100644 --- a/src/notation/notationmodule.cpp +++ b/src/notation/notationmodule.cpp @@ -59,6 +59,7 @@ #include "view/widgets/editstyle.h" #include "view/widgets/measureproperties.h" #include "view/widgets/editstaff.h" +#include "view/widgets/editstringdata.h" #include "view/widgets/breaksdialog.h" #include "view/widgets/pagesettings.h" #include "view/widgets/transposedialog.h" @@ -73,6 +74,7 @@ #include "view/internal/abstractelementpopupmodel.h" #include "view/internal/harppedalpopupmodel.h" #include "view/internal/caposettingsmodel.h" +#include "view/internal/stringtuningssettingsmodel.h" #include "view/styledialog/styleitem.h" #include "view/styledialog/notespagemodel.h" @@ -142,6 +144,9 @@ void NotationModule::resolveImports() ir->registerUri(Uri("musescore://notation/staffproperties"), ContainerMeta(ContainerType::QWidgetDialog, EditStaff::metaTypeId())); + ir->registerUri(Uri("musescore://notation/editstrings"), + ContainerMeta(ContainerType::QWidgetDialog, EditStringData::metaTypeId())); + ir->registerUri(Uri("musescore://notation/transpose"), ContainerMeta(ContainerType::QWidgetDialog, qRegisterMetaType("TransposeDialog"))); @@ -199,6 +204,7 @@ void NotationModule::registerUiTypes() "Not creatable as it is an enum type"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "HarpPedalPopupModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "CapoSettingsModel"); + qmlRegisterType("MuseScore.NotationScene", 1, 0, "StringTuningsSettingsModel"); qmlRegisterUncreatableType("MuseScore.NotationScene", 1, 0, "StyleItem", "Cannot create StyleItem from QML"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "NotesPageModel"); @@ -207,6 +213,7 @@ void NotationModule::registerUiTypes() qRegisterMetaType("EditStyle"); qRegisterMetaType("EditStaff"); + qRegisterMetaType("EditStringData"); qRegisterMetaType("SelectNoteDialog"); qRegisterMetaType("SelectDialog"); qRegisterMetaType("StaffTextPropertiesDialog"); diff --git a/src/notation/notationscene.qrc b/src/notation/notationscene.qrc index 961a592d1f023..4cbd1349394a3 100644 --- a/src/notation/notationscene.qrc +++ b/src/notation/notationscene.qrc @@ -38,5 +38,6 @@ qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml qml/MuseScore/NotationScene/internal/HarpPedalPopup.qml qml/MuseScore/NotationScene/internal/CapoPopup.qml + qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml diff --git a/src/notation/notationtypes.h b/src/notation/notationtypes.h index 75f624bc2c731..513b9d4edbb19 100644 --- a/src/notation/notationtypes.h +++ b/src/notation/notationtypes.h @@ -479,6 +479,7 @@ struct StaffConfig bool showIfEmpty = false; bool hideSystemBarline = false; bool mergeMatchingRests = false; + bool reflectTranspositionInLinkedTab = false; Staff::HideMode hideMode = Staff::HideMode::AUTO; ClefTypeList clefTypeList; engraving::StaffType staffType; @@ -494,6 +495,7 @@ struct StaffConfig equal &= hideMode == conf.hideMode; equal &= clefTypeList == conf.clefTypeList; equal &= staffType == conf.staffType; + equal &= reflectTranspositionInLinkedTab == conf.reflectTranspositionInLinkedTab; return equal; } @@ -676,6 +678,20 @@ inline bool isVerticalBoxTextStyle(TextStyleType type) return mu::contains(types, type); } + +struct StringTuningPreset +{ + std::string name; + std::vector value; +}; + +struct StringTuningsInfo +{ + size_t number = 0; + std::vector presets; +}; + +using InstrumentStringTuningsMap = std::map >; } #endif // MU_NOTATION_NOTATIONTYPES_H diff --git a/src/notation/qml/MuseScore/NotationScene/NotationView.qml b/src/notation/qml/MuseScore/NotationScene/NotationView.qml index afb1f66aaa5bc..366a04e0dac24 100644 --- a/src/notation/qml/MuseScore/NotationScene/NotationView.qml +++ b/src/notation/qml/MuseScore/NotationScene/NotationView.qml @@ -148,7 +148,7 @@ FocusScope { } onHideElementPopupRequested: { - popUpLoader.close() + Qt.callLater(popUpLoader.close) } onViewportChanged: { diff --git a/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml index aa32876be40af..34fb902abf663 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml @@ -44,26 +44,26 @@ StyledPopupView { root.y = elementRect.y - h / 2 } - CapoSettingsModel { - id: capoModel - - onItemRectChanged: function(rect) { - updatePosition(rect) - } - } - - Component.onCompleted: { - capoModel.init() - } - ColumnLayout { id: content + readonly property int columnsSpacing: 6 + width: 294 spacing: 12 - readonly property int columnsSpacing: 6 + CapoSettingsModel { + id: capoModel + + onItemRectChanged: function(rect) { + updatePosition(rect) + } + } + + Component.onCompleted: { + capoModel.init() + } NavigationPanel { id: capoSettingsNavPanel diff --git a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml index 344dcfaa3ee24..d44dc4564bfa4 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml @@ -29,16 +29,11 @@ import MuseScore.NotationScene 1.0 Item { id: container - width: 0 - - height: 0 - anchors.fill: parent - property var openedPopup: null - property bool isPopupOpened: Boolean(openedPopup) && openedPopup.isOpened - signal opened() - signal closed() + property var popup: loader.item + property bool isPopupOpened: Boolean(popup) && popup.isOpened + property NavigationSection notationViewNavigationSection: null property int navigationOrderStart: 0 @@ -46,6 +41,9 @@ Item { ? loader.item.navigationOrderEnd : navigationOrderStart + signal opened() + signal closed() + QtObject { id: prv @@ -53,52 +51,47 @@ Item { switch (type) { case Notation.TYPE_HARP_DIAGRAM: return harpPedalComp case Notation.TYPE_CAPO: return capoComp + case Notation.TYPE_STRING_TUNINGS: return stringTuningsComp } return null } - function openPopup(popup) { - if (Boolean(popup)) { - openedPopup = popup - container.opened() - popup.open() - } + function loadPopup() { + loader.active = true } - function closeOpenedPopup() { - if (isPopupOpened) { - openedPopup.close() - resetOpenedPopup() - } - } + function unloadPopup() { + loader.sourceComponent = undefined + loader.active = false - function resetOpenedPopup() { - container.closed(false) - openedPopup = null + Qt.callLater(container.closed) } } function show(elementType, elementRect) { - if (isPopupOpened) { - prv.closeOpenedPopup() - } - opened() + prv.loadPopup() + var popup = loader.createPopup(prv.componentByType(elementType), elementRect) - prv.openPopup(popup) + popup.open() + + Qt.callLater(container.opened) } function close() { - closed() - prv.closeOpenedPopup() + if (Boolean(container.popup) && container.popup.isOpened) { + container.popup.close() + } } Loader { id: loader + active: false + function createPopup(comp, elementRect) { loader.sourceComponent = comp - loader.item.parent = container + loader.item.parent = loader loader.item.updatePosition(elementRect) //! NOTE: All navigation panels in popups must be in the notation view section. @@ -117,8 +110,7 @@ Item { id: harpPedalComp HarpPedalPopup { onClosed: { - prv.resetOpenedPopup() - loader.sourceComponent = null + prv.unloadPopup() } } } @@ -127,8 +119,16 @@ Item { id: capoComp CapoPopup { onClosed: { - prv.resetOpenedPopup() - loader.sourceComponent = null + prv.unloadPopup() + } + } + } + + Component { + id: stringTuningsComp + StringTuningsPopup { + onClosed: { + prv.unloadPopup() } } } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/HarpPedalPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/HarpPedalPopup.qml index d5064d5b5b67e..e4eadf397b6b7 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/HarpPedalPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/HarpPedalPopup.qml @@ -30,14 +30,6 @@ import MuseScore.NotationScene 1.0 StyledPopupView { id: root - HarpPedalPopupModel { - id: harpModel - - onItemRectChanged: function(rect) { - updatePosition(rect) - } - } - property QtObject model: harpModel property variant pedalState: harpModel.pedalState @@ -105,10 +97,6 @@ StyledPopupView { return noteNames[string][state] } - Component.onCompleted: { - harpModel.init() - } - GridLayout { id: menuItems rows: 5 @@ -116,6 +104,18 @@ StyledPopupView { columnSpacing: 10 rowSpacing: 10 + HarpPedalPopupModel { + id: harpModel + + onItemRectChanged: function(rect) { + updatePosition(rect) + } + } + + Component.onCompleted: { + harpModel.init() + } + NavigationPanel { id: pedalSettingsNavPanel name: "PedalSettings" diff --git a/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml new file mode 100644 index 0000000000000..50ac6cb57e083 --- /dev/null +++ b/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml @@ -0,0 +1,245 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2023 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import MuseScore.Ui 1.0 +import MuseScore.UiComponents 1.0 +import MuseScore.NotationScene 1.0 + +StyledPopupView { + id: root + + property NavigationSection notationViewNavigationSection: null + property int navigationOrderStart: 0 + property int navigationOrderEnd: navPanel.order + + contentWidth: content.width + contentHeight: content.height + + showArrow: false + + function updatePosition(elementRect) { + var h = Math.max(root.contentHeight, 360) + root.x = elementRect.x + elementRect.width + 12 + root.y = elementRect.y - h / 2 + } + + ColumnLayout { + id: content + + width: 272 + + spacing: 12 + + StringTuningsSettingsModel { + id: stringTuningsModel + + onItemRectChanged: function(rect) { + updatePosition(rect) + } + } + + Component.onCompleted: { + stringTuningsModel.init() + } + + NavigationPanel { + id: navPanel + name: "StringTuningsSettings" + direction: NavigationPanel.Vertical + section: root.notationViewNavigationSection + order: root.navigationOrderStart + accessible.name: qsTrc("notation", "String tunings settings") + } + + StyledTextLabel { + id: titleLabel + + text: qsTrc("notation", "Presets") + horizontalAlignment: Text.AlignLeft + } + + RowLayout { + Layout.preferredWidth: parent.width + + spacing: 6 + + StyledDropdown { + id: presetsDropdown + + Layout.fillWidth: true + + navigation.name: "Presets" + navigation.panel: navPanel + navigation.row: 1 + navigation.accessible.name: titleLabel.text + " " + currentText + + model: stringTuningsModel.presets + + currentIndex: indexOfText(stringTuningsModel.currentPreset) + + onActivated: function(index, value) { + stringTuningsModel.currentPreset = textOfValue(value) + } + } + + StyledDropdown { + id: stringNumberDropdown + + Layout.preferredWidth: 92 + + navigation.name: "StringsNumber" + navigation.panel: navPanel + navigation.row: 2 + navigation.accessible.name: qsTrc("notation", "Number of strings:") + " " + currentText + + model: stringTuningsModel.numbersOfStrings + + currentIndex: indexOfValue(stringTuningsModel.currentNumberOfStrings) + + onActivated: function(index, value) { + stringTuningsModel.currentNumberOfStrings = parseInt(textOfValue(value)) + } + } + } + + NavigationPanel { + id: stringsNavPanel + name: "StringTuningsSettings" + direction: NavigationPanel.Vertical + section: root.notationViewNavigationSection + order: navPanel.order + 1 + accessible.name: qsTrc("notation", "Strings") + } + + GridLayout { + id: gridView + + Layout.preferredWidth: parent.width + + readonly property int cellRadius: 2 + + flow: GridLayout.TopToBottom + columns: 2 + rows: Math.ceil(stringTuningsModel.strings.length / 2) + columnSpacing: 10 + rowSpacing: 6 + + Repeater { + id: repeaterStrings + + width: parent.width + + model: stringTuningsModel.strings + + ListItemBlank { + implicitHeight: visibleBox.height + implicitWidth: (content.width / 2 - gridView.columnSpacing / gridView.columns) + + hoverHitColor: "transparent" + background.radius: gridView.cellRadius + + navigation.name: "String" + index + navigation.panel: stringsNavPanel + navigation.row: index + navigation.column: 1 + navigation.accessible.name: visibleBox.navigation.accessible.name + " " + qsTrc("notation", "Value %1").arg(valueControl.currentValue) + + RowLayout { + anchors.fill: parent + + VisibilityBox { + id: visibleBox + + isVisible: modelData["show"] + + navigation.panel: stringsNavPanel + navigation.row: index + navigation.column: 2 + accessibleText: qsTrc("notation", "String %1").arg(numberLabel.text) + + onVisibleToggled: { + stringTuningsModel.toggleString(index) + } + } + + Rectangle { + height: numberLabel.height + 4 + width: height + + color: "transparent" + radius: 180 + border.color: ui.theme.fontPrimaryColor + border.width: 1 + + StyledTextLabel { + id: numberLabel + + anchors.centerIn: parent + + text: modelData["number"] + } + } + + IncrementalPropertyControl { + id: valueControl + + Layout.leftMargin: 6 + + Layout.preferredHeight: parent.height - ui.theme.borderWidth * 2 + Layout.preferredWidth: 64 + + currentValue: modelData["valueStr"] + + minValue: 0 + maxValue: 127 + + navigation.panel: stringsNavPanel + navigation.row: index + navigation.column: 3 + + canIncrease: modelData["value"] < maxValue + onIncrement: function() { + return stringTuningsModel.increaseStringValue(currentValue) + } + + canDecrease: modelData["value"] > minValue + onDecrement: function() { + return stringTuningsModel.decreaseStringValue(currentValue) + } + + onValueEditingFinished: function(newValue) { + var ok = stringTuningsModel.setStringValue(index, newValue) + if (!ok) { + //! NOTE: reset the text entered by the user + currentValue = modelData["valueStr"] + currentValue = Qt.binding( function() { return modelData["valueStr"] } ) + } + } + } + } + } + } + } + } +} diff --git a/src/notation/view/internal/abstractelementpopupmodel.cpp b/src/notation/view/internal/abstractelementpopupmodel.cpp index 899178d7e2c38..0d9e3196ad82f 100644 --- a/src/notation/view/internal/abstractelementpopupmodel.cpp +++ b/src/notation/view/internal/abstractelementpopupmodel.cpp @@ -28,6 +28,7 @@ using namespace mu::notation; static const QMap ELEMENT_POPUP_TYPES = { { mu::engraving::ElementType::HARP_DIAGRAM, PopupModelType::TYPE_HARP_DIAGRAM }, { mu::engraving::ElementType::CAPO, PopupModelType::TYPE_CAPO }, + { mu::engraving::ElementType::STRING_TUNINGS, PopupModelType::TYPE_STRING_TUNINGS }, }; AbstractElementPopupModel::AbstractElementPopupModel(PopupModelType modelType, QObject* parent) @@ -77,6 +78,15 @@ void AbstractElementPopupModel::beginCommand() } } +void AbstractElementPopupModel::beginMultiCommands() +{ + beginCommand(); + + if (undoStack()) { + undoStack()->lock(); + } +} + void AbstractElementPopupModel::endCommand() { if (undoStack()) { @@ -84,6 +94,15 @@ void AbstractElementPopupModel::endCommand() } } +void AbstractElementPopupModel::endMultiCommands() +{ + if (undoStack()) { + undoStack()->unlock(); + } + + endCommand(); +} + void AbstractElementPopupModel::updateNotation() { if (!currentNotation()) { diff --git a/src/notation/view/internal/abstractelementpopupmodel.h b/src/notation/view/internal/abstractelementpopupmodel.h index 00013c47a6867..007d3d4693b12 100644 --- a/src/notation/view/internal/abstractelementpopupmodel.h +++ b/src/notation/view/internal/abstractelementpopupmodel.h @@ -47,6 +47,7 @@ class AbstractElementPopupModel : public QObject, public async::Asyncable, publi TYPE_UNDEFINED = -1, TYPE_HARP_DIAGRAM, TYPE_CAPO, + TYPE_STRING_TUNINGS }; Q_ENUM(PopupModelType) @@ -70,7 +71,9 @@ class AbstractElementPopupModel : public QObject, public async::Asyncable, publi notation::INotationUndoStackPtr undoStack() const; void beginCommand(); + void beginMultiCommands(); void endCommand(); + void endMultiCommands(); void updateNotation(); notation::INotationPtr currentNotation() const; diff --git a/src/notation/view/internal/caposettingsmodel.cpp b/src/notation/view/internal/caposettingsmodel.cpp index ef2b2fe3794e7..9ad2ba40d8674 100644 --- a/src/notation/view/internal/caposettingsmodel.cpp +++ b/src/notation/view/internal/caposettingsmodel.cpp @@ -82,12 +82,7 @@ void CapoSettingsModel::init() return; } - const mu::engraving::Instrument* instrument = part->instrument(m_item->tick()); - IF_ASSERT_FAILED(instrument) { - return; - } - - const mu::engraving::StringData* stringData = instrument->stringData(); + const mu::engraving::StringData* stringData = part->stringData(m_item->tick()); IF_ASSERT_FAILED(stringData) { return; } diff --git a/src/notation/view/internal/stringtuningssettingsmodel.cpp b/src/notation/view/internal/stringtuningssettingsmodel.cpp new file mode 100644 index 0000000000000..4290b1371d32d --- /dev/null +++ b/src/notation/view/internal/stringtuningssettingsmodel.cpp @@ -0,0 +1,484 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2023 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "stringtuningssettingsmodel.h" + +#include "engraving/dom/stringtunings.h" +#include "engraving/dom/utils.h" + +#include "translation.h" + +using namespace mu::notation; + +const QString customPreset() +{ + return qtrc("notation", "Custom"); +} + +StringTuningsSettingsModel::StringTuningsSettingsModel(QObject* parent) + : AbstractElementPopupModel(PopupModelType::TYPE_STRING_TUNINGS, parent) +{ +} + +void StringTuningsSettingsModel::init() +{ + TRACEFUNC; + + m_strings.clear(); + + AbstractElementPopupModel::init(); + + IF_ASSERT_FAILED(m_item || m_item->isStringTunings()) { + return; + } + + engraving::StringTunings* stringTunings = engraving::toStringTunings(m_item); + const mu::engraving::StringData* stringData = stringTunings->stringData(); + IF_ASSERT_FAILED(stringData) { + return; + } + + const mu::engraving::Part* part = m_item->part(); + IF_ASSERT_FAILED(part) { + return; + } + + const mu::engraving::Instrument* instrument = part->instrument(m_item->tick()); + IF_ASSERT_FAILED(instrument) { + return; + } + + const InstrumentStringTuningsMap& stringTuningsPresets = instrumentsRepository()->stringTuningsPresets(); + + if (contains(stringTuningsPresets, instrument->id().toStdString())) { + m_itemId = instrument->id().toStdString(); + } else if (contains(stringTuningsPresets, instrument->family().toStdString())) { + m_itemId = instrument->family().toStdString(); + } + + const std::vector& stringList = stringData->stringList(); + const std::vector& visibleStrings = stringTunings->visibleStrings(); + int numOfStrings = static_cast(stringList.size()); + for (int i = 0; i < numOfStrings; ++i) { + engraving::string_idx_t instrStringIndex = numOfStrings - i - 1; + const engraving::instrString& string = stringList[instrStringIndex]; + StringTuningsItem* item = new StringTuningsItem(this); + + item->blockSignals(true); + item->setShow(contains(visibleStrings, instrStringIndex)); + item->setNumber(QString::number(i + 1)); + item->setValue(string.pitch); + item->blockSignals(false); + + m_strings.push_back(item); + } + + emit numbersOfStringsChanged(); + emit currentNumberOfStringsChanged(); + emit presetsChanged(); + emit currentPresetChanged(); + emit stringsChanged(); +} + +QString StringTuningsSettingsModel::pitchToString(int pitch) +{ + return engraving::pitch2string(pitch); +} + +void StringTuningsSettingsModel::toggleString(int stringIndex) +{ + if (stringIndex >= m_strings.size()) { + return; + } + + StringTuningsItem* item = m_strings.at(stringIndex); + item->setShow(!item->show()); + + saveStringsVisibleState(); +} + +bool StringTuningsSettingsModel::setStringValue(int stringIndex, const QString& stringValue) +{ + if (stringIndex >= m_strings.size()) { + return false; + } + + StringTuningsItem* item = m_strings.at(stringIndex); + + QString _stringValue = convertToUnicode(stringValue); + int value = engraving::string2pitch(_stringValue); + if (value == -1) { + item->valueChanged(); + return false; + } + + item->setValue(value); + + beginMultiCommands(); + + updateCurrentPreset(); + saveStrings(); + + endMultiCommands(); + + return true; +} + +bool StringTuningsSettingsModel::canIncreaseStringValue(const QString& stringValue) const +{ + QString value = convertToUnicode(stringValue); + return engraving::string2pitch(value) != -1; +} + +QString StringTuningsSettingsModel::increaseStringValue(const QString& stringValue) +{ + QString value = convertToUnicode(stringValue); + return engraving::pitch2string(engraving::string2pitch(value) + 1); +} + +bool StringTuningsSettingsModel::canDecreaseStringValue(const QString& stringValue) const +{ + QString value = convertToUnicode(stringValue); + return engraving::string2pitch(value) != -1; +} + +QString StringTuningsSettingsModel::decreaseStringValue(const QString& stringValue) +{ + QString value = convertToUnicode(stringValue); + return engraving::pitch2string(engraving::string2pitch(value) - 1); +} + +QVariantList StringTuningsSettingsModel::presets(bool withCustom) const +{ + const InstrumentStringTuningsMap& stringTunings = instrumentsRepository()->stringTuningsPresets(); + + QVariantList presetsList; + + if (!contains(stringTunings, m_itemId)) { + return presetsList; + } + + QString custom = customPreset(); + if (withCustom && (currentPreset().isEmpty() || currentPreset() == custom)) { + QVariantMap customMap; + customMap.insert("text", custom); + + QVariantList valueList; + for (const StringTuningsItem* item : m_strings) { + valueList << item->value(); + } + + customMap.insert("value", valueList); + + presetsList << customMap; + } + + size_t currentStringNumber = this->currentNumberOfStrings(); + + for (const StringTuningsInfo& stringTuning : stringTunings.at(m_itemId)) { + if (stringTuning.number != currentStringNumber) { + continue; + } + + for (const StringTuningPreset& preset : stringTuning.presets) { + QVariantMap presetMap; + presetMap.insert("text", QString::fromStdString(preset.name)); + + QVariantList valueList; + for (int value : preset.value) { + valueList << value; + } + + presetMap.insert("value", valueList); + + presetsList.push_back(presetMap); + } + } + + return presetsList; +} + +QString StringTuningsSettingsModel::currentPreset() const +{ + QString preset + = m_item ? engraving::toStringTunings(m_item)->getProperty(engraving::Pid::STRINGTUNINGS_PRESET).value().toQString() : ""; + + if (preset.isEmpty()) { + preset = customPreset(); + } + + return preset; +} + +void StringTuningsSettingsModel::setCurrentPreset(const QString& preset) +{ + bool isCurrentCustom = currentPreset() == customPreset(); + + beginMultiCommands(); + + changeItemProperty(mu::engraving::Pid::STRINGTUNINGS_PRESET, String::fromQString(preset)); + emit currentPresetChanged(); + + if (isCurrentCustom) { + //! NOTE: if current preset was custom then we should update presets. + //! Custom preset will no longer be available + emit presetsChanged(); + } + + updateStrings(); + saveStrings(); + saveStringsVisibleState(); + + endMultiCommands(); +} + +QVariantList StringTuningsSettingsModel::numbersOfStrings() const +{ + const InstrumentStringTuningsMap& stringTunings = instrumentsRepository()->stringTuningsPresets(); + + QVariantList numbersList; + + if (!contains(stringTunings, m_itemId)) { + return numbersList; + } + + for (const StringTuningsInfo& stringTuning : stringTunings.at(m_itemId)) { + QVariantMap stringNumberMap; + stringNumberMap.insert("text", QString::number(stringTuning.number) + " " + tr("strings")); + stringNumberMap.insert("value", static_cast(stringTuning.number)); + numbersList << stringNumberMap; + } + + return numbersList; +} + +size_t StringTuningsSettingsModel::currentNumberOfStrings() const +{ + return m_item ? engraving::toStringTunings(m_item)->getProperty(engraving::Pid::STRINGTUNINGS_STRINGS_COUNT).toInt() : 0; +} + +void StringTuningsSettingsModel::setCurrentNumberOfStrings(int number) +{ + int currentNumber = static_cast(currentNumberOfStrings()); + if (currentNumber == number) { + return; + } + + beginMultiCommands(); + + changeItemProperty(mu::engraving::Pid::STRINGTUNINGS_STRINGS_COUNT, number); + + emit currentNumberOfStringsChanged(); + emit presetsChanged(); + + setCurrentPreset(presets(false /*withCustom*/).first().toMap()["text"].toString()); + + emit stringsChanged(); + + endMultiCommands(); +} + +QList StringTuningsSettingsModel::strings() const +{ + return m_strings; +} + +void StringTuningsSettingsModel::updateStrings() +{ + const QVariantList presets = this->presets(); + QString currentPreset = this->currentPreset(); + + m_strings.clear(); + + for (const QVariant& _preset : presets) { + if (_preset.toMap()["text"].toString() == currentPreset) { + QVariantList valueList = _preset.toMap()["value"].toList(); + int numOfStrings = valueList.size(); + for (int i = 0; i < numOfStrings; ++i) { + int valueIndex = numOfStrings - i - 1; + StringTuningsItem* item = new StringTuningsItem(this); + + item->blockSignals(true); + item->setShow(true); + item->setNumber(QString::number(i + 1)); + item->setValue(valueList[valueIndex].toInt()); + item->blockSignals(false); + + m_strings.push_back(item); + } + + break; + } + } + + emit stringsChanged(); +} + +void StringTuningsSettingsModel::saveStrings() +{ + engraving::StringTunings* stringTunings = engraving::toStringTunings(m_item); + IF_ASSERT_FAILED(stringTunings) { + return; + } + + const mu::engraving::StringData* originStringData = stringTunings->stringData(); + + std::vector stringList = originStringData->stringList(); + stringList.resize(m_strings.size()); + + int numOfStrings = m_strings.size(); + for (int i = 0; i < numOfStrings; ++i) { + stringList[i].pitch = m_strings[numOfStrings - i - 1]->value(); + } + + beginCommand(); + + StringData newStringData(originStringData->frets(), stringList); + stringTunings->undoStringData(newStringData); + + endCommand(); + updateNotation(); +} + +void StringTuningsSettingsModel::saveStringsVisibleState() +{ + std::vector visibleStrings; + int numOfStrings = static_cast(m_strings.size()); + for (int i = 0; i < numOfStrings; ++i) { + const StringTuningsItem* item = m_strings.at(i); + + if (item->show()) { + visibleStrings.push_back(numOfStrings - i - 1); + } + } + + changeItemProperty(mu::engraving::Pid::STRINGTUNINGS_VISIBLE_STRINGS, visibleStrings); +} + +void StringTuningsSettingsModel::updateCurrentPreset() +{ + QVariantList currentValueList; + for (int i = 0; i < m_strings.size(); ++i) { + currentValueList << m_strings[i]->value(); + } + + const QVariantList presets = this->presets(false /*withCustom*/); + QString currentPreset = this->currentPreset(); + QString newPreset; + for (const QVariant& _preset : presets) { + if (currentValueList == _preset.toMap()["value"].toList()) { + newPreset = _preset.toMap()["text"].toString(); + break; + } + } + + if (currentPreset != newPreset) { + if (newPreset.isEmpty()) { + newPreset = customPreset(); + } + + doSetCurrentPreset(newPreset); + emit presetsChanged(); + } +} + +void StringTuningsSettingsModel::doSetCurrentPreset(const QString& preset) +{ + changeItemProperty(mu::engraving::Pid::STRINGTUNINGS_PRESET, String::fromQString(preset)); + emit currentPresetChanged(); +} + +QString StringTuningsSettingsModel::convertToUnicode(const QString& stringValue) const +{ + if (stringValue.isEmpty()) { + return QString(); + } + + QString value = stringValue[0]; + for (int i = 1; i < stringValue.size(); ++i) { + QChar symbol = stringValue[i].toLower(); + if (symbol == "b") { + value.append("♭"); + } else if (symbol == "#") { + value.append("♯"); + } else { + value.append(symbol); + } + } + + return value; +} + +StringTuningsItem::StringTuningsItem(QObject* parent) + : QObject(parent) +{ +} + +bool StringTuningsItem::show() const +{ + return m_show; +} + +void StringTuningsItem::setShow(bool show) +{ + if (m_show == show) { + return; + } + + m_show = show; + emit showChanged(); +} + +QString StringTuningsItem::number() const +{ + return m_number; +} + +void StringTuningsItem::setNumber(const QString& number) +{ + if (m_number == number) { + return; + } + + m_number = number; + emit numberChanged(); +} + +int StringTuningsItem::value() const +{ + return m_value; +} + +QString StringTuningsItem::valueStr() const +{ + return engraving::pitch2string(m_value).toUpper(); +} + +void StringTuningsItem::setValue(int value) +{ + if (m_value == value) { + return; + } + + m_value = value; + emit valueChanged(); +} diff --git a/src/notation/view/internal/stringtuningssettingsmodel.h b/src/notation/view/internal/stringtuningssettingsmodel.h new file mode 100644 index 0000000000000..2a300b95205d5 --- /dev/null +++ b/src/notation/view/internal/stringtuningssettingsmodel.h @@ -0,0 +1,136 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2023 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MU_NOTATION_STRINGTUNINGSSETTINGSMODEL_H +#define MU_NOTATION_STRINGTUNINGSSETTINGSMODEL_H + +#include "abstractelementpopupmodel.h" + +#include "modularity/ioc.h" +#include "iinstrumentsrepository.h" + +#include + +namespace mu::engraving { +class StringTunings; +} + +namespace mu::notation { +class StringTuningsItem; +class StringTuningsSettingsModel : public AbstractElementPopupModel +{ + Q_OBJECT + + INJECT(IInstrumentsRepository, instrumentsRepository) + + Q_PROPERTY(QVariantList presets READ presets NOTIFY presetsChanged) + Q_PROPERTY(QString currentPreset READ currentPreset WRITE setCurrentPreset NOTIFY currentPresetChanged) + + Q_PROPERTY(QVariantList numbersOfStrings READ numbersOfStrings NOTIFY numbersOfStringsChanged) + Q_PROPERTY(int currentNumberOfStrings READ currentNumberOfStrings WRITE setCurrentNumberOfStrings NOTIFY currentNumberOfStringsChanged) + + Q_PROPERTY(QList strings READ strings NOTIFY stringsChanged) + +public: + explicit StringTuningsSettingsModel(QObject* parent = nullptr); + + Q_INVOKABLE void init() override; + Q_INVOKABLE QString pitchToString(int pitch); + + Q_INVOKABLE void toggleString(int stringIndex); + Q_INVOKABLE bool setStringValue(int stringIndex, const QString& stringValue); + + Q_INVOKABLE bool canIncreaseStringValue(const QString& stringValue) const; + Q_INVOKABLE QString increaseStringValue(const QString& stringValue); + + Q_INVOKABLE bool canDecreaseStringValue(const QString& stringValue) const; + Q_INVOKABLE QString decreaseStringValue(const QString& stringValue); + + QVariantList presets(bool withCustom = true) const; + + QString currentPreset() const; + void setCurrentPreset(const QString& preset); + + QVariantList numbersOfStrings() const; + + size_t currentNumberOfStrings() const; + void setCurrentNumberOfStrings(int number); + + QList strings() const; + +signals: + void presetsChanged(); + void currentPresetChanged(); + void numbersOfStringsChanged(); + void currentNumberOfStringsChanged(); + void stringsChanged(); + +private: + void updateStrings(); + void saveStrings(); + void saveStringsVisibleState(); + void updateCurrentPreset(); + + void doSetCurrentPreset(const QString& preset); + + QString convertToUnicode(const QString& stringValue) const; + + QList m_strings; + + std::string m_itemId; +}; + +class StringTuningsItem : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool show READ show NOTIFY showChanged) + Q_PROPERTY(QString number READ number NOTIFY numberChanged) + Q_PROPERTY(int value READ value NOTIFY valueChanged) + Q_PROPERTY(QString valueStr READ valueStr NOTIFY valueChanged) + +public: + explicit StringTuningsItem(QObject* parent = nullptr); + + bool show() const; + void setShow(bool show); + + QString number() const; + void setNumber(const QString& number); + + int value() const; + QString valueStr() const; + void setValue(int value); + +signals: + void showChanged(); + void numberChanged(); + void valueChanged(); + +private: + bool m_show = false; + QString m_number; + int m_value = 0; +}; +} //namespace mu::notation + +#endif // MU_NOTATION_STRINGTUNINGSSETTINGSMODEL_H diff --git a/src/notation/view/widgets/editstaff.cpp b/src/notation/view/widgets/editstaff.cpp index a6472c9dda3c4..9fcb624982714 100644 --- a/src/notation/view/widgets/editstaff.cpp +++ b/src/notation/view/widgets/editstaff.cpp @@ -156,6 +156,7 @@ void EditStaff::setStaff(Staff* s, const Fraction& tick) m_staff->setShowIfEmpty(m_orgStaff->showIfEmpty()); m_staff->setHideSystemBarLine(m_orgStaff->hideSystemBarLine()); m_staff->setMergeMatchingRests(m_orgStaff->mergeMatchingRests()); + m_staff->setReflectTranspositionInLinkedTab(m_orgStaff->reflectTranspositionInLinkedTab()); // get tick range for instrument auto i = part->instruments().upper_bound(tick.ticks()); @@ -183,6 +184,7 @@ void EditStaff::setStaff(Staff* s, const Fraction& tick) showIfEmpty->setChecked(m_staff->showIfEmpty()); hideSystemBarLine->setChecked(m_staff->hideSystemBarLine()); mergeMatchingRests->setChecked(m_staff->mergeMatchingRests()); + noReflectTranspositionInLinkedTab->setChecked(!m_staff->reflectTranspositionInLinkedTab()); updateStaffType(*stt); updateInstrument(); @@ -506,6 +508,7 @@ void EditStaff::applyStaffProperties() config.hideMode = Staff::HideMode(hideMode->currentIndex()); config.clefTypeList = m_instrument.clefType(m_orgStaff->rstaff()); config.staffType = *m_staff->staffType(mu::engraving::Fraction(0, 1)); + config.reflectTranspositionInLinkedTab = !noReflectTranspositionInLinkedTab->isChecked(); notationParts()->setStaffConfig(m_orgStaff->id(), config); } @@ -589,9 +592,12 @@ void EditStaff::editStringDataClicked() int frets = m_instrument.stringData()->frets(); std::vector stringList = m_instrument.stringData()->stringList(); - EditStringData* esd = new EditStringData(this, &stringList, &frets); - esd->setWindowModality(Qt::WindowModal); + EditStringData* esd = new EditStringData(this, stringList, frets); + if (esd->exec()) { + frets = esd->frets(); + stringList = esd->strings(); + mu::engraving::StringData stringData(frets, stringList); // update instrument pitch ranges as necessary diff --git a/src/notation/view/widgets/editstaff.ui b/src/notation/view/widgets/editstaff.ui index b64a98ace1698..2bed098769bbd 100644 --- a/src/notation/view/widgets/editstaff.ui +++ b/src/notation/view/widgets/editstaff.ui @@ -6,8 +6,8 @@ 0 0 - 751 - 641 + 788 + 659 @@ -962,6 +962,16 @@ + + + + Don’t reflect transposition in linked tablature staves + + + true + + + diff --git a/src/notation/view/widgets/editstringdata.cpp b/src/notation/view/widgets/editstringdata.cpp index a5583be43b6e9..073a527a9f8b2 100644 --- a/src/notation/view/widgets/editstringdata.cpp +++ b/src/notation/view/widgets/editstringdata.cpp @@ -26,9 +26,15 @@ #include "translation.h" #include "global/utils.h" + +#include "editpitch.h" + +#include "dom/stringdata.h" +#include "dom/stringtunings.h" +#include "dom/undo.h" + #include "ui/view/widgetstatestore.h" #include "ui/view/widgetnavigationfix.h" -#include "editpitch.h" static const int OPEN_ACCESSIBLE_TITLE_ROLE = Qt::UserRole + 1; @@ -40,66 +46,22 @@ using namespace mu::ui; // To edit the string data (tuning and number of frets) for an instrument //--------------------------------------------------------- -EditStringData::EditStringData(QWidget* parent, std::vector* strings, int* frets) +EditStringData::EditStringData(QWidget* parent, const std::vector& strings, int frets) : QDialog(parent) { setObjectName("EditStringData"); setupUi(this); setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); - _strings = strings; - stringList->setHorizontalHeaderLabels({ qtrc("notation/editstringdata", "Always open"), - qtrc("notation/editstringdata", "Pitch") }); - QString toolTip = qtrc("notation/editstringdata", - "Always open
On tablature staves, fret positions other than ‘0’ cannot be entered on strings marked ‘always open’. Useful for instruments with strings that are not on the fretboard, such as the theorbo."); - stringList->horizontalHeaderItem(0)->setToolTip(toolTip); - int numOfStrings = static_cast(_strings->size()); - stringList->setRowCount(numOfStrings); - // if any string, insert into string list control and select the first one - - if (numOfStrings > 0) { - mu::engraving::instrString strg; - // insert into local working copy and into string list dlg control - // IN REVERSED ORDER - for (int i = 0; i < numOfStrings; i++) { - strg = (*_strings)[numOfStrings - i - 1]; - _stringsLoc.push_back(strg); - QTableWidgetItem* newCheck = new QTableWidgetItem(); - newCheck->setFlags(Qt::ItemFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled)); - newCheck->setCheckState(strg.open ? Qt::Checked : Qt::Unchecked); - - newCheck->setData(OPEN_ACCESSIBLE_TITLE_ROLE, stringList->horizontalHeaderItem(0)->text()); - newCheck->setToolTip(toolTip); - newCheck->setData(Qt::AccessibleTextRole, openColumnAccessibleText(newCheck)); - - stringList->setItem(i, 0, newCheck); - QTableWidgetItem* newPitch = new QTableWidgetItem(midiCodeToStr(strg.pitch)); - stringList->setItem(i, 1, newPitch); - } - stringList->setCurrentCell(0, 1); - } - // if no string yet, disable buttons acting on individual string - else { - editString->setEnabled(false); - deleteString->setEnabled(false); - } - - connect(stringList, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item){ - if (item->column() == 0) { - item->setData(Qt::AccessibleTextRole, openColumnAccessibleText(item)); - } - }); + setWindowModality(Qt::WindowModal); + _strings = strings; _frets = frets; - numOfFrets->setValue(*_frets); - - connect(deleteString, &QPushButton::clicked, this, &EditStringData::deleteStringClicked); - connect(editString, &QPushButton::clicked, this, &EditStringData::editStringClicked); - connect(newString, &QPushButton::clicked, this, &EditStringData::newStringClicked); - connect(stringList, &QTableWidget::itemClicked, this, &EditStringData::listItemClicked); - connect(stringList, &QTableWidget::itemDoubleClicked, this, &EditStringData::editStringClicked); + if (_strings.empty()) { + initStringsData(); + } - _modified = false; + init(); WidgetStateStore::restoreGeometry(this); @@ -109,10 +71,30 @@ EditStringData::EditStringData(QWidget* parent, std::vectorinstallEventFilter(this); } +EditStringData::EditStringData(const EditStringData& other) + : QDialog(other.parentWidget()) +{ +} + EditStringData::~EditStringData() { } +int EditStringData::metaTypeId() +{ + return QMetaType::type("EditStringData"); +} + +std::vector EditStringData::strings() const +{ + return _strings; +} + +int EditStringData::frets() const +{ + return _frets; +} + //--------------------------------------------------------- // hideEvent //--------------------------------------------------------- @@ -144,6 +126,13 @@ QString EditStringData::openColumnAccessibleText(const QTableWidgetItem* item) c + (item->checkState() == Qt::Checked ? qtrc("ui", "checked", "checkstate") : qtrc("ui", "unchecked", "checkstate")); } +INotationSelectionPtr EditStringData::currentNotationSelection() const +{ + auto currentNotation = globalContext()->currentNotation(); + auto interaction = currentNotation ? currentNotation->interaction() : nullptr; + return interaction ? interaction->selection() : nullptr; +} + //--------------------------------------------------------- // deleteStringClicked //--------------------------------------------------------- @@ -239,6 +228,90 @@ void EditStringData::newStringClicked() } } +void EditStringData::init() +{ + numOfFrets->setValue(_frets); + + stringList->setHorizontalHeaderLabels({ qtrc("notation/editstringdata", "Always open"), + qtrc("notation/editstringdata", "Pitch") }); + QString toolTip = qtrc("notation/editstringdata", + "Always open
On tablature staves, fret positions other than ‘0’ cannot be entered on strings marked ‘always open’. Useful for instruments with strings that are not on the fretboard, such as the theorbo."); + stringList->horizontalHeaderItem(0)->setToolTip(toolTip); + int numOfStrings = static_cast(_strings.size()); + stringList->setRowCount(numOfStrings); + // if any string, insert into string list control and select the first one + + if (numOfStrings > 0) { + mu::engraving::instrString strg; + // insert into local working copy and into string list dlg control + // IN REVERSED ORDER + for (int i = 0; i < numOfStrings; i++) { + strg = _strings[numOfStrings - i - 1]; + _stringsLoc.push_back(strg); + QTableWidgetItem* newCheck = new QTableWidgetItem(); + newCheck->setFlags(Qt::ItemFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled)); + newCheck->setCheckState(strg.open ? Qt::Checked : Qt::Unchecked); + + newCheck->setData(OPEN_ACCESSIBLE_TITLE_ROLE, stringList->horizontalHeaderItem(0)->text()); + newCheck->setToolTip(toolTip); + newCheck->setData(Qt::AccessibleTextRole, openColumnAccessibleText(newCheck)); + + stringList->setItem(i, 0, newCheck); + QTableWidgetItem* newPitch = new QTableWidgetItem(midiCodeToStr(strg.pitch)); + stringList->setItem(i, 1, newPitch); + } + stringList->setCurrentCell(0, 1); + } + // if no string yet, disable buttons acting on individual string + else { + editString->setEnabled(false); + deleteString->setEnabled(false); + } + + connect(stringList, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item){ + if (item->column() == 0) { + item->setData(Qt::AccessibleTextRole, openColumnAccessibleText(item)); + } + }); + + connect(deleteString, &QPushButton::clicked, this, &EditStringData::deleteStringClicked); + connect(editString, &QPushButton::clicked, this, &EditStringData::editStringClicked); + connect(newString, &QPushButton::clicked, this, &EditStringData::newStringClicked); + + connect(stringList, &QTableWidget::itemClicked, this, &EditStringData::listItemClicked); + connect(stringList, &QTableWidget::itemDoubleClicked, this, &EditStringData::editStringClicked); + + _modified = false; +} + +void EditStringData::initStringsData() +{ + engraving::StringTunings* stringTunings = nullptr; + for (EngravingItem* element : currentNotationSelection()->elements()) { + if (element && element->isStringTunings()) { + stringTunings = toStringTunings(element); + break; + } + } + + if (!stringTunings) { + return; + } + + Part* part = stringTunings->staff()->part(); + + auto it = mu::findLessOrEqual(part->instruments(), stringTunings->tick().ticks()); + if (it == part->instruments().cend()) { + return; + } + + m_instrument = it->second; + _strings = m_instrument->stringData()->stringList(); + _frets = m_instrument->stringData()->frets(); + + m_updateOnExit = true; +} + //--------------------------------------------------------- // accept //--------------------------------------------------------- @@ -248,17 +321,25 @@ void EditStringData::accept() // store data back into original variables // string tunings are copied in reversed order (from lowest to highest) if (_modified) { - _strings->clear(); + _strings.clear(); for (int i = static_cast(_stringsLoc.size()) - 1; i >= 0; i--) { - _strings->push_back(_stringsLoc[i]); + _strings.push_back(_stringsLoc[i]); } } - if (*_frets != numOfFrets->value()) { - *_frets = numOfFrets->value(); + if (_frets != numOfFrets->value()) { + _frets = numOfFrets->value(); _modified = true; } if (_modified) { + if (m_updateOnExit) { + StringData newStringData(_frets, _strings); + mu::engraving::EngravingItem* lastHit = currentNotationSelection()->lastElementHit(); + if (lastHit) { + lastHit->score()->undo(new ChangeStringData(m_instrument, newStringData)); + } + } + QDialog::accept(); } else { QDialog::reject(); // if no data change, no need to trigger changes downward the caller chain diff --git a/src/notation/view/widgets/editstringdata.h b/src/notation/view/widgets/editstringdata.h index b29eca6aafe40..b0731c8b71773 100644 --- a/src/notation/view/widgets/editstringdata.h +++ b/src/notation/view/widgets/editstringdata.h @@ -22,9 +22,14 @@ #ifndef MU_NOTATION_EDITSTRINGDATA_H #define MU_NOTATION_EDITSTRINGDATA_H +#include + #include "ui_editstringdata.h" #include "engraving/dom/stringdata.h" +#include "modularity/ioc.h" +#include "context/iglobalcontext.h" + namespace mu::notation { //--------------------------------------------------------- // EditStringData @@ -34,10 +39,18 @@ class EditStringData : public QDialog, private Ui::EditStringDataBase { Q_OBJECT + INJECT(context::IGlobalContext, globalContext) + public: - EditStringData(QWidget* parent, std::vector* strings, int* frets); + EditStringData(QWidget* parent = nullptr, const std::vector& strings = {}, int frets = 0); + EditStringData(const EditStringData&); ~EditStringData(); + static int metaTypeId(); + + std::vector strings() const; + int frets() const; + protected: QString midiCodeToStr(int midiCode); @@ -49,16 +62,26 @@ private slots: void newStringClicked(); private: + void init(); + void initStringsData(); + virtual void hideEvent(QHideEvent*) override; bool eventFilter(QObject* obj, QEvent* event) override; QString openColumnAccessibleText(const QTableWidgetItem* item) const; - int* _frets = nullptr; + INotationSelectionPtr currentNotationSelection() const; + + int _frets = -1; bool _modified = false; - std::vector* _strings; // pointer to original string list + std::vector _strings; // pointer to original string list std::vector _stringsLoc; // local working copy of string list + + bool m_updateOnExit = false; + Instrument* m_instrument = nullptr; }; } +Q_DECLARE_METATYPE(mu::notation::EditStringData) + #endif // MU_NOTATION_EDITSTRINGDATA_H diff --git a/src/notation/view/widgets/editstringdata.ui b/src/notation/view/widgets/editstringdata.ui index b2bd88221598e..c37626d9ebfcd 100644 --- a/src/notation/view/widgets/editstringdata.ui +++ b/src/notation/view/widgets/editstringdata.ui @@ -11,7 +11,7 @@ - Strings tuning: + Default strings tuning: diff --git a/src/palette/internal/palette.cpp b/src/palette/internal/palette.cpp index 73be49984a8e5..32af6eb686b28 100644 --- a/src/palette/internal/palette.cpp +++ b/src/palette/internal/palette.cpp @@ -585,6 +585,8 @@ Palette::Type Palette::guessType() const return Type::Accordion; case ElementType::HARP_DIAGRAM: return Type::Harp; + case ElementType::STRING_TUNINGS: + return Type::StringTunings; case ElementType::ACTION_ICON: { const ActionIcon* action = toActionIcon(e); QString actionCode = QString::fromStdString(action->actionCode()); diff --git a/src/palette/internal/palette.h b/src/palette/internal/palette.h index 2e915143394e6..f9a335ebeec9f 100644 --- a/src/palette/internal/palette.h +++ b/src/palette/internal/palette.h @@ -86,6 +86,7 @@ class Palette : public QObject Keyboard, Pitch, Harp, + StringTunings, Custom }; Q_ENUM(Type) diff --git a/src/palette/internal/palettecompat.cpp b/src/palette/internal/palettecompat.cpp index 9a51b89bd25ec..382ade67c13cc 100644 --- a/src/palette/internal/palettecompat.cpp +++ b/src/palette/internal/palettecompat.cpp @@ -34,6 +34,7 @@ #include "engraving/dom/ornament.h" #include "engraving/dom/score.h" #include "engraving/dom/stafftext.h" +#include "engraving/dom/stringtunings.h" #include "engraving/dom/capo.h" #include "engraving/types/symid.h" @@ -84,11 +85,14 @@ void PaletteCompat::addNewItemsIfNeeded(Palette& palette, Score* paletteScore) void PaletteCompat::addNewGuitarItems(Palette& guitarPalette, Score* paletteScore) { bool containsCapo = false; + bool containsStringTunings = false; for (const PaletteCellPtr& cell : guitarPalette.cells()) { if (cell->element && cell->element->isCapo()) { containsCapo = true; - break; + } + if (cell->element && cell->element->isStringTunings()) { + containsStringTunings = true; } } @@ -98,4 +102,13 @@ void PaletteCompat::addNewGuitarItems(Palette& guitarPalette, Score* paletteScor int defaultPosition = std::min(7, guitarPalette.cellsCount()); guitarPalette.insertElement(defaultPosition, capo, QT_TRANSLATE_NOOP("palette", "Capo"))->setElementTranslated(true); } + + if (!containsStringTunings) { + auto stringTunings = std::make_shared(paletteScore->dummy()->segment()); + stringTunings->setXmlText(u"guitarString6 - D"); + stringTunings->initTextStyleType(TextStyleType::STAFF); + int defaultPosition = std::min(8, guitarPalette.cellsCount()); + guitarPalette.insertElement(defaultPosition, stringTunings, QT_TRANSLATE_NOOP("palette", "String tunings"))->setElementTranslated( + true); + } } diff --git a/src/palette/internal/palettecreator.cpp b/src/palette/internal/palettecreator.cpp index f5d2a352522ce..223db7396e995 100644 --- a/src/palette/internal/palettecreator.cpp +++ b/src/palette/internal/palettecreator.cpp @@ -78,6 +78,7 @@ #include "engraving/dom/stafftext.h" #include "engraving/dom/playtechannotation.h" #include "engraving/dom/stafftypechange.h" +#include "engraving/dom/stringtunings.h" #include "engraving/dom/systemtext.h" #include "engraving/dom/tempo.h" #include "engraving/dom/tempotext.h" @@ -111,6 +112,7 @@ MAKE_ELEMENT(StaffText, score->dummy()->segment()) MAKE_ELEMENT(Expression, score->dummy()->segment()) MAKE_ELEMENT(PlayTechAnnotation, score->dummy()->segment()) MAKE_ELEMENT(Capo, score->dummy()->segment()) +MAKE_ELEMENT(StringTunings, score->dummy()->segment()) MAKE_ELEMENT(RehearsalMark, score->dummy()->segment()) MAKE_ELEMENT(Jump, score->dummy()->measure()) @@ -1865,6 +1867,11 @@ PalettePtr PaletteCreator::newGuitarPalette(bool defaultPalette) capo->setXmlText(String::fromAscii(QT_TRANSLATE_NOOP("palette", "Capo"))); sp->appendElement(capo, QT_TRANSLATE_NOOP("palette", "Capo"))->setElementTranslated(true); + auto stringTunings = makeElement(gpaletteScore); + stringTunings->setXmlText(u"guitarString6 - D"); + stringTunings->initTextStyleType(TextStyleType::STAFF); + sp->appendElement(stringTunings, QT_TRANSLATE_NOOP("palette", "String tunings"))->setElementTranslated(true); + const char* finger = "pimac"; for (unsigned i = 0; i < strlen(finger); ++i) { auto f = makeElement(gpaletteScore);