diff --git a/src/framework/global/stringutils.cpp b/src/framework/global/stringutils.cpp index 69dda86c4e..5127016a9e 100644 --- a/src/framework/global/stringutils.cpp +++ b/src/framework/global/stringutils.cpp @@ -46,6 +46,20 @@ void mu::strings::split(const std::string& str, std::vector& out, c out.push_back(str.substr(previous, current - previous)); } +std::string mu::strings::join(const std::vector& strs, const std::string& sep) +{ + std::string str; + bool first = true; + for (const std::string& s : strs) { + if (!first) { + str += sep; + } + first = false; + str += s; + } + return str; +} + void mu::strings::ltrim(std::string& s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { diff --git a/src/framework/global/stringutils.h b/src/framework/global/stringutils.h index 27a21f5d7a..9ead33f30e 100644 --- a/src/framework/global/stringutils.h +++ b/src/framework/global/stringutils.h @@ -29,6 +29,7 @@ namespace mu::strings { bool replace(std::string& source, const std::string& what, const std::string& to); void split(const std::string& str, std::vector& out, const std::string& delim); +std::string join(const std::vector& strs, const std::string& sep = ","); void ltrim(std::string& s); void rtrim(std::string& s); diff --git a/src/framework/shortcuts/internal/shortcutsinstancemodel.cpp b/src/framework/shortcuts/internal/shortcutsinstancemodel.cpp index 73c606a751..b1a1723eda 100644 --- a/src/framework/shortcuts/internal/shortcutsinstancemodel.cpp +++ b/src/framework/shortcuts/internal/shortcutsinstancemodel.cpp @@ -60,12 +60,14 @@ void ShortcutsInstanceModel::loadShortcuts() const ShortcutList& shortcuts = shortcutsRegister()->shortcuts(); for (const Shortcut& sc : shortcuts) { - QString sequence = QString::fromStdString(sc.sequence); + for (const std::string& seq : sc.sequences) { + QString sequence = QString::fromStdString(seq); - //! NOTE There may be several identical shortcuts for different contexts. - //! We only need a list of unique ones. - if (!m_shortcuts.contains(sequence)) { - m_shortcuts << sequence; + //! NOTE There may be several identical shortcuts for different contexts. + //! We only need a list of unique ones. + if (!m_shortcuts.contains(sequence)) { + m_shortcuts << sequence; + } } } diff --git a/src/framework/shortcuts/internal/shortcutsregister.cpp b/src/framework/shortcuts/internal/shortcutsregister.cpp index d5f656b2ac..7a75ecb663 100644 --- a/src/framework/shortcuts/internal/shortcutsregister.cpp +++ b/src/framework/shortcuts/internal/shortcutsregister.cpp @@ -23,12 +23,12 @@ #include -#include "log.h" - #include "global/xmlreader.h" #include "global/xmlwriter.h" #include "multiinstances/resourcelockguard.h" +#include "log.h" + using namespace mu::shortcuts; using namespace mu::framework; using namespace mu::async; @@ -54,6 +54,8 @@ static const Shortcut& findShortcut(const ShortcutList& shortcuts, const std::st void ShortcutsRegister::reload(bool onlyDef) { + TRACEFUNC; + m_shortcuts.clear(); m_defaultShortcuts.clear(); @@ -82,13 +84,15 @@ void ShortcutsRegister::reload(bool onlyDef) if (ok) { expandStandardKeys(m_shortcuts); - + makeUnique(m_shortcuts); m_shortcutsChanged.notify(); } } void ShortcutsRegister::mergeShortcuts(ShortcutList& shortcuts, const ShortcutList& defaultShortcuts) const { + TRACEFUNC; + ShortcutList needadd; for (const Shortcut& defSc : defaultShortcuts) { auto it = std::find_if(shortcuts.begin(), shortcuts.end(), [defSc](const Shortcut& i) { @@ -110,13 +114,42 @@ void ShortcutsRegister::mergeShortcuts(ShortcutList& shortcuts, const ShortcutLi } } +void ShortcutsRegister::makeUnique(ShortcutList& shortcuts) +{ + TRACEFUNC; + + const ShortcutList all = shortcuts; + + shortcuts.clear(); + + for (const Shortcut& sc : all) { + const std::string& action = sc.action; + + auto it = std::find_if(shortcuts.begin(), shortcuts.end(), [action](const Shortcut& s) { + return s.action == action; + }); + + if (it == shortcuts.end()) { + shortcuts.push_back(sc); + continue; + } + + IF_ASSERT_FAILED(it->context == sc.context) { + } + + it->sequences.insert(it->sequences.end(), sc.sequences.begin(), sc.sequences.end()); + } +} + void ShortcutsRegister::expandStandardKeys(ShortcutList& shortcuts) const { + TRACEFUNC; + ShortcutList expanded; ShortcutList notbonded; for (Shortcut& shortcut : shortcuts) { - if (!shortcut.sequence.empty()) { + if (!shortcut.sequences.empty()) { continue; } @@ -128,7 +161,7 @@ void ShortcutsRegister::expandStandardKeys(ShortcutList& shortcuts) const } const QKeySequence& first = kslist.first(); - shortcut.sequence = first.toString().toStdString(); + shortcut.sequences.push_back(first.toString().toStdString()); //LOGD() << "for standard key: " << sc.standardKey << ", sequence: " << sc.sequence; //! NOTE If the keyBindings contains more than one result, @@ -136,7 +169,7 @@ void ShortcutsRegister::expandStandardKeys(ShortcutList& shortcuts) const for (int i = 1; i < kslist.count(); ++i) { const QKeySequence& seq = kslist.at(i); Shortcut esc = shortcut; - esc.sequence = seq.toString().toStdString(); + esc.sequences.push_back(seq.toString().toStdString()); //LOGD() << "for standard key: " << esc.standardKey << ", alternative sequence: " << esc.sequence; expanded.push_back(esc); } @@ -175,15 +208,7 @@ bool ShortcutsRegister::readFromFile(ShortcutList& shortcuts, const io::path& pa Shortcut shortcut = readShortcut(reader); if (shortcut.isValid()) { - if (shortcut.sequence.empty()) { - shortcuts.push_back(shortcut); - } else { - auto seqList = QString::fromStdString(shortcut.sequence).split("\n", Qt::SkipEmptyParts); - for (QString seq : seqList) { - shortcut.sequence = seq.toStdString(); - shortcuts.push_back(shortcut); - } - } + shortcuts.push_back(shortcut); } } @@ -206,7 +231,7 @@ Shortcut ShortcutsRegister::readShortcut(framework::XmlReader& reader) const } else if (tag == STANDARD_KEY_TAG) { shortcut.standardKey = QKeySequence::StandardKey(reader.readInt()); } else if (tag == SEQUENCE_TAG) { - shortcut.sequence += reader.readString() + "\n"; + shortcut.sequences.push_back(reader.readString()); } else if (tag == CONTEXT_TAG) { shortcut.context = reader.readString(); } else { @@ -228,6 +253,8 @@ const ShortcutList& ShortcutsRegister::shortcuts() const mu::Ret ShortcutsRegister::setShortcuts(const ShortcutList& shortcuts) { + TRACEFUNC; + if (shortcuts == m_shortcuts) { return true; } @@ -273,7 +300,10 @@ void ShortcutsRegister::writeShortcut(framework::XmlWriter& writer, const Shortc writer.writeTextElement(STANDARD_KEY_TAG, QString("%1").arg(shortcut.standardKey).toStdString()); } - writer.writeTextElement(SEQUENCE_TAG, shortcut.sequence); + for (const std::string& seq : shortcut.sequences) { + writer.writeTextElement(SEQUENCE_TAG, seq); + } + writer.writeEndElement(); } @@ -295,7 +325,8 @@ const Shortcut& ShortcutsRegister::defaultShortcut(const std::string& actionCode bool ShortcutsRegister::isRegistered(const std::string& sequence) const { for (const Shortcut& sh : m_shortcuts) { - if (sh.sequence == sequence) { + auto it = std::find(sh.sequences.cbegin(), sh.sequences.cend(), sequence); + if (it != sh.sequences.cend()) { return true; } } @@ -306,7 +337,8 @@ ShortcutList ShortcutsRegister::shortcutsForSequence(const std::string& sequence { ShortcutList list; for (const Shortcut& sh : m_shortcuts) { - if (sh.sequence == sequence) { + auto it = std::find(sh.sequences.cbegin(), sh.sequences.cend(), sequence); + if (it != sh.sequences.cend()) { list.push_back(sh); } } diff --git a/src/framework/shortcuts/internal/shortcutsregister.h b/src/framework/shortcuts/internal/shortcutsregister.h index 9048b39f04..3e08c8f0e9 100644 --- a/src/framework/shortcuts/internal/shortcutsregister.h +++ b/src/framework/shortcuts/internal/shortcutsregister.h @@ -72,6 +72,7 @@ private: void writeShortcut(framework::XmlWriter& writer, const Shortcut& shortcut) const; void mergeShortcuts(ShortcutList& shortcuts, const ShortcutList& defaultShortcuts) const; + void makeUnique(ShortcutList& shortcuts); void expandStandardKeys(ShortcutList& shortcuts) const; ShortcutList m_shortcuts; diff --git a/src/framework/shortcuts/shortcutstypes.h b/src/framework/shortcuts/shortcutstypes.h index 18ae8dd365..560f56dbf6 100644 --- a/src/framework/shortcuts/shortcutstypes.h +++ b/src/framework/shortcuts/shortcutstypes.h @@ -26,13 +26,14 @@ #include #include +#include "stringutils.h" #include "midi/midievent.h" namespace mu::shortcuts { struct Shortcut { std::string action; - std::string sequence; + std::vector sequences; std::string context; QKeySequence::StandardKey standardKey = QKeySequence::UnknownKey; @@ -42,12 +43,26 @@ struct Shortcut bool isValid() const { - return !action.empty() && (!sequence.empty() || standardKey != QKeySequence::UnknownKey); + return !action.empty() && (!sequences.empty() || standardKey != QKeySequence::UnknownKey); } bool operator ==(const Shortcut& sc) const { - return action == sc.action && sequence == sc.sequence && standardKey == sc.standardKey; + return action == sc.action && sequences == sc.sequences && standardKey == sc.standardKey; + } + + std::string sequencesAsString() const { return sequencesToString(sequences); } + + static std::string sequencesToString(const std::vector& seqs) + { + return strings::join(seqs, "; "); + } + + static std::vector sequencesFromString(const std::string& str) + { + std::vector seqs; + strings::split(str, seqs, "; "); + return seqs; } }; diff --git a/src/framework/shortcuts/view/editshortcutmodel.cpp b/src/framework/shortcuts/view/editshortcutmodel.cpp index c7833fdc39..1f13107d10 100644 --- a/src/framework/shortcuts/view/editshortcutmodel.cpp +++ b/src/framework/shortcuts/view/editshortcutmodel.cpp @@ -132,5 +132,5 @@ QString EditShortcutModel::unitedSequence() const inputedSequence() }; - return sequences.join(", "); + return sequences.join("; "); } diff --git a/src/framework/shortcuts/view/shortcutsmodel.cpp b/src/framework/shortcuts/view/shortcutsmodel.cpp index ebbb1ddcb7..b7d8a08f57 100644 --- a/src/framework/shortcuts/view/shortcutsmodel.cpp +++ b/src/framework/shortcuts/view/shortcutsmodel.cpp @@ -46,15 +46,12 @@ QVariant ShortcutsModel::data(const QModelIndex& index, int role) const } Shortcut shortcut = m_shortcuts[index.row()]; - QString sequence = QString::fromStdString(shortcut.sequence); - const UiAction& action = this->action(shortcut.action); - QString title = action.title; switch (role) { - case RoleTitle: return title; - case RoleIcon: return static_cast(action.iconCode); - case RoleSequence: return sequence; - case RoleSearchKey: return title + sequence; + case RoleTitle: return this->action(shortcut.action).title; + case RoleIcon: return static_cast(this->action(shortcut.action).iconCode); + case RoleSequence: return QString::fromStdString(shortcut.sequencesAsString()); + case RoleSearchKey: return this->action(shortcut.action).title + QString::fromStdString(shortcut.sequencesAsString()); } return QVariant(); @@ -139,7 +136,7 @@ QString ShortcutsModel::currentSequence() const QModelIndex index = currentShortcutIndex(); if (index.isValid()) { - return QString::fromStdString(m_shortcuts[index.row()].sequence); + return QString::fromStdString(m_shortcuts[index.row()].sequencesAsString()); } return QString(); @@ -201,7 +198,7 @@ void ShortcutsModel::applySequenceToCurrentShortcut(const QString& newSequence) } int row = index.row(); - m_shortcuts[row].sequence = newSequence.toStdString(); + m_shortcuts[row].sequences = Shortcut::sequencesFromString(newSequence.toStdString()); notifyAboutShortcutChanged(index); } @@ -210,7 +207,7 @@ void ShortcutsModel::clearSelectedShortcuts() { for (const QModelIndex& index : m_selection.indexes()) { Shortcut& shortcut = m_shortcuts[index.row()]; - shortcut.sequence.clear(); + shortcut.sequences.clear(); shortcut.standardKey = QKeySequence::StandardKey::UnknownKey; notifyAboutShortcutChanged(index); @@ -239,7 +236,7 @@ QVariantList ShortcutsModel::shortcuts() const for (const Shortcut& shortcut: m_shortcuts) { QVariantMap obj; obj["title"] = actionTitle(shortcut.action); - obj["sequence"] = QString::fromStdString(shortcut.sequence); + obj["sequence"] = QString::fromStdString(shortcut.sequencesAsString()); result << obj; } diff --git a/src/framework/ui/internal/uiactionsregister.cpp b/src/framework/ui/internal/uiactionsregister.cpp index 2929a29031..eb19bfcb40 100644 --- a/src/framework/ui/internal/uiactionsregister.cpp +++ b/src/framework/ui/internal/uiactionsregister.cpp @@ -110,7 +110,7 @@ void UiActionsRegister::updateShortcuts() auto screg = shortcutsRegister(); for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { Info& inf = it->second; - inf.action.shortcut = screg->shortcut(inf.action.code).sequence; + inf.action.shortcuts = screg->shortcut(inf.action.code).sequences; } } diff --git a/src/framework/ui/uitypes.h b/src/framework/ui/uitypes.h index 9e32b85cc0..bbc4778a56 100644 --- a/src/framework/ui/uitypes.h +++ b/src/framework/ui/uitypes.h @@ -175,7 +175,7 @@ struct UiAction QString description; IconCode::Code iconCode = IconCode::Code::NONE; Checkable checkable = Checkable::No; - std::string shortcut; + std::vector shortcuts; UiAction() = default; UiAction(const actions::ActionCode& code, UiContext ctx, Checkable ch = Checkable::No) @@ -280,6 +280,15 @@ struct MenuItem : public UiAction id = QString::fromStdString(a.code); } + QString shortcutsAsString() const + { + QStringList list; + for (const std::string& sc : shortcuts) { + list << QString::fromStdString(sc); + } + return list.join("; "); + } + QVariantMap toMap() const { QVariantList subitemsVariantList; @@ -290,7 +299,7 @@ struct MenuItem : public UiAction return { { "id", id }, { "code", QString::fromStdString(code) }, - { "shortcut", QString::fromStdString(shortcut) }, + { "shortcut", shortcutsAsString() }, { "title", title }, { "description", description }, { "section", section }, diff --git a/src/framework/uicomponents/view/textinputfieldmodel.cpp b/src/framework/uicomponents/view/textinputfieldmodel.cpp index 6aa593b0f3..7b37907d73 100644 --- a/src/framework/uicomponents/view/textinputfieldmodel.cpp +++ b/src/framework/uicomponents/view/textinputfieldmodel.cpp @@ -55,10 +55,11 @@ bool TextInputFieldModel::isShortcutAllowedOverride(int key, Qt::KeyboardModifie QKeySequence keySequence(newModifiers + newKey); for (const Shortcut& shortcut : m_notAllowedForOverrideShortcuts) { - QKeySequence shortcutSequence(QString::fromStdString(shortcut.sequence)); - - if (shortcutSequence == keySequence) { - return false; + for (const std::string& seq : shortcut.sequences) { + QKeySequence shortcutSequence(QString::fromStdString(seq)); + if (shortcutSequence == keySequence) { + return false; + } } } diff --git a/src/notation/internal/notationuiactions.cpp b/src/notation/internal/notationuiactions.cpp index eb0aa39869..835c6392bf 100644 --- a/src/notation/internal/notationuiactions.cpp +++ b/src/notation/internal/notationuiactions.cpp @@ -1179,7 +1179,7 @@ const UiActionList NotationUiActions::m_actions = { ), UiAction("enh-current", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "Change enharmonic spelling (both modes)"), + QT_TRANSLATE_NOOP("action", "Change enharmonic spelling (current mode)"), QT_TRANSLATE_NOOP("action", "Change enharmonic spelling (current mode)"), IconCode::Code::NONE ), @@ -1390,68 +1390,68 @@ const UiActionList NotationUiActions::m_actions = { ), UiAction("pad-note-1-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "Whole note"), - QT_TRANSLATE_NOOP("action", "Note duration: whole note"), + QT_TRANSLATE_NOOP("action", "Whole note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: whole note (TAB)"), IconCode::Code::NOTE_WHOLE ), UiAction("pad-note-2-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "Half note"), - QT_TRANSLATE_NOOP("action", "Note duration: half note"), + QT_TRANSLATE_NOOP("action", "Half note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: half note (TAB)"), IconCode::Code::NOTE_HALF ), UiAction("pad-note-4-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "Quarter note"), - QT_TRANSLATE_NOOP("action", "Note duration: quarter note"), + QT_TRANSLATE_NOOP("action", "Quarter note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: quarter note (TAB)"), IconCode::Code::NOTE_QUARTER ), UiAction("pad-note-8-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "8th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 8th note"), + QT_TRANSLATE_NOOP("action", "8th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 8th note (TAB)"), IconCode::Code::NOTE_8TH ), UiAction("pad-note-16-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "16th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 16th note"), + QT_TRANSLATE_NOOP("action", "16th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 16th note (TAB)"), IconCode::Code::NOTE_16TH ), UiAction("pad-note-32-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "32nd note"), - QT_TRANSLATE_NOOP("action", "Note duration: 32nd note"), + QT_TRANSLATE_NOOP("action", "32nd note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 32nd note (TAB)"), IconCode::Code::NOTE_32ND ), UiAction("pad-note-64-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "64th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 64th note"), + QT_TRANSLATE_NOOP("action", "64th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 64th note (TAB)"), IconCode::Code::NOTE_64TH ), UiAction("pad-note-128-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "128th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 128th note"), + QT_TRANSLATE_NOOP("action", "128th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 128th note (TAB)"), IconCode::Code::NOTE_128TH ), UiAction("pad-note-256-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "256th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 256th note"), + QT_TRANSLATE_NOOP("action", "256th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 256th note (TAB)"), IconCode::Code::NOTE_256TH ), UiAction("pad-note-512-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "512th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 512th note"), + QT_TRANSLATE_NOOP("action", "512th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 512th note (TAB)"), IconCode::Code::NOTE_512TH ), UiAction("pad-note-1024-TAB", mu::context::UiCtxNotationOpened, - QT_TRANSLATE_NOOP("action", "1024th note"), - QT_TRANSLATE_NOOP("action", "Note duration: 1024th note"), + QT_TRANSLATE_NOOP("action", "1024th note (TAB)"), + QT_TRANSLATE_NOOP("action", "Note duration: 1024th note (TAB)"), IconCode::Code::NOTE_1024TH ) }; diff --git a/src/notation/view/notationtoolbarmodel.cpp b/src/notation/view/notationtoolbarmodel.cpp index 8515b2cd90..381597b0fe 100644 --- a/src/notation/view/notationtoolbarmodel.cpp +++ b/src/notation/view/notationtoolbarmodel.cpp @@ -53,7 +53,7 @@ QVariant NotationToolBarModel::data(const QModelIndex& index, int role) const case IconRole: return static_cast(item.iconCode); case EnabledRole: return item.state.enabled; case DescriptionRole: return item.description; - case ShortcutRole: return QString::fromStdString(item.shortcut); + case ShortcutRole: return item.shortcutsAsString(); } return QVariant(); diff --git a/src/project/view/templatepaintview.cpp b/src/project/view/templatepaintview.cpp index 5d5fd048a6..eabf7e0ca8 100644 --- a/src/project/view/templatepaintview.cpp +++ b/src/project/view/templatepaintview.cpp @@ -21,7 +21,11 @@ */ #include "templatepaintview.h" + +#include "stringutils.h" + #include "notation/imasternotation.h" + #include "log.h" using namespace mu::project; @@ -51,13 +55,13 @@ void TemplatePaintView::load(const QString& templatePath) QString TemplatePaintView::zoomInSequence() const { shortcuts::Shortcut shortcut = shortcutsRegister()->shortcut("zoomin"); - return QString::fromStdString(shortcut.sequence); + return QString::fromStdString(shortcut.sequencesAsString()); } QString TemplatePaintView::zoomOutSequence() const { shortcuts::Shortcut shortcut = shortcutsRegister()->shortcut("zoomout"); - return QString::fromStdString(shortcut.sequence); + return QString::fromStdString(shortcut.sequencesAsString()); } void TemplatePaintView::adjustCanvas()