diff --git a/src/engraving/libmscore/property.cpp b/src/engraving/libmscore/property.cpp index c968f4c085540..01f3ba78231bd 100644 --- a/src/engraving/libmscore/property.cpp +++ b/src/engraving/libmscore/property.cpp @@ -153,6 +153,8 @@ static constexpr PropertyMetaData propertyList[] = { { Pid::SPACE, false, "space", P_TYPE::SP_REAL, DUMMY_QT_TR_NOOP("propertyName", "space") }, { Pid::TEMPO, true, "tempo", P_TYPE::TEMPO, DUMMY_QT_TR_NOOP("propertyName", "tempo") }, { Pid::TEMPO_FOLLOW_TEXT, true, "followText", P_TYPE::BOOL, DUMMY_QT_TR_NOOP("propertyName", "following text") }, + { Pid::TEMPO_EQUATION, true, "tempoEquation", P_TYPE::STRING, DUMMY_QT_TR_NOOP("propertyName", "tempo equation") }, + { Pid::TEMPO_EQUATION_VISIBLE, true, "tempoEquationVisible", P_TYPE::BOOL, DUMMY_QT_TR_NOOP("propertyName", "tempo is equation visible") }, { Pid::ACCIDENTAL_BRACKET, false, "bracket", P_TYPE::INT, DUMMY_QT_TR_NOOP("propertyName", "bracket") }, { Pid::ACCIDENTAL_TYPE, true, "subtype", P_TYPE::INT, DUMMY_QT_TR_NOOP("propertyName", "type") }, { Pid::NUMERATOR_STRING, false, "textN", P_TYPE::STRING, DUMMY_QT_TR_NOOP("propertyName", "numerator string") }, diff --git a/src/engraving/libmscore/property.h b/src/engraving/libmscore/property.h index 6b514082b6b84..67be893743b8c 100644 --- a/src/engraving/libmscore/property.h +++ b/src/engraving/libmscore/property.h @@ -156,6 +156,8 @@ enum class Pid { SPACE, // used for spacer TEMPO, TEMPO_FOLLOW_TEXT, + TEMPO_EQUATION, + TEMPO_EQUATION_VISIBLE, ACCIDENTAL_BRACKET, ACCIDENTAL_TYPE, NUMERATOR_STRING, diff --git a/src/engraving/libmscore/tempotext.cpp b/src/engraving/libmscore/tempotext.cpp index 92a51b33153e9..e79783677a685 100644 --- a/src/engraving/libmscore/tempotext.cpp +++ b/src/engraving/libmscore/tempotext.cpp @@ -33,6 +33,7 @@ #include "tempotext.h" #include "undo.h" #include "xml.h" +#include "textedit.h" using namespace mu; @@ -60,10 +61,13 @@ TempoText::TempoText(Score* s) : TextBase(s, Tid::TEMPO, ElementFlags(ElementFlag::SYSTEM)) { initElementStyle(&tempoStyle); - _tempo = 2.0; // propertyDefault(P_TEMPO).toDouble(); - _followText = false; - _relative = 1.0; + _playbackTempo = 2.0; // propertyDefault(P_TEMPO).toDouble(); + _notatedTempo = 120; + _followText = true; + _relative = 1.0; _isRelative = false; + _equation = "q = 120"; + _isEquationVisible = true; } //--------------------------------------------------------- @@ -73,7 +77,10 @@ TempoText::TempoText(Score* s) void TempoText::write(XmlWriter& xml) const { xml.stag(this); - xml.tag("tempo", _tempo); + xml.tag("playbackTempo", _playbackTempo); + xml.tag("notatedTempo", _notatedTempo); + xml.tag("equation", _equation); + xml.tag("equationVisible", _isEquationVisible); if (_followText) { xml.tag("followText", _followText); } @@ -89,77 +96,85 @@ void TempoText::read(XmlReader& e) { while (e.readNextStartElement()) { const QStringRef& tag(e.name()); - if (tag == "tempo") { + if (tag == "playbackTempo") { setTempo(e.readDouble()); } else if (tag == "followText") { _followText = e.readInt(); + } else if (tag == "equation") { + _equation = e.readElementText(); + } else if (tag == "notatedTempo") { + _notatedTempo = e.readInt(); + } else if (tag == "equationVisible") { + _isEquationVisible = e.readBool(); } else if (!TextBase::readProperties(e)) { e.unknown(); } } // check sanity if (xmlText().isEmpty()) { - setXmlText(QString("metNoteQuarterUp = %1").arg(lrint(60 * _tempo))); + _equation = QString("q = %1").arg(lrint(60 * _playbackTempo)); setVisible(false); } } qreal TempoText::tempoBpm() const { - //! NOTE: find tempo in format " = 180" - QRegularExpression regex("\\s*=\\s*(\\d+[.]{0,1}\\d*)"); - QStringList matches = regex.match(xmlText()).capturedTexts(); - - if (matches.empty() || matches.size() < 1) { - return 0; - } - - qreal tempo = matches[1].toDouble(); - return tempo; + return _notatedTempo; } //--------------------------------------------------------- // TempoPattern //--------------------------------------------------------- -struct TempoPattern { - const char* pattern; - qreal f; - TDuration d; - TempoPattern(const char* s, qreal v, TDuration::DurationType val, int dots = 0) - : pattern(s), f(v), d(val) +struct EquationDescrption +{ + QString letter; + + QString unicode; + QString symbol; + + float relative; + TDuration::DurationType duration; + + EquationDescrption(QString l, float r, QString u, QString s, TDuration::DurationType val) + : letter(l), unicode(u), symbol(s), relative(r), duration(val) { - d.setDots(dots); } }; -// note: findTempoDuration requires the longer patterns to be before the shorter patterns in tp - -static const TempoPattern tp[] = { - TempoPattern("\uECA5\\s*\uECB7\\s*\uECB7", 1.75 / 60.0, TDuration::DurationType::V_QUARTER, 2), // double dotted 1/4 - TempoPattern("\uECA5\\s*\uECB7", 1.5 / 60.0, TDuration::DurationType::V_QUARTER, 1), // dotted 1/4 - TempoPattern("\uECA5", 1.0 / 60.0, TDuration::DurationType::V_QUARTER), // 1/4 - TempoPattern("\uECA3\\s*\uECB7\\s*\uECB7", 1.75 / 30.0, TDuration::DurationType::V_HALF, 2), // double dotted 1/2 - TempoPattern("\uECA3\\s*\uECB7", 1.5 / 30.0, TDuration::DurationType::V_HALF, 1), // dotted 1/2 - TempoPattern("\uECA3", 1.0 / 30.0, TDuration::DurationType::V_HALF), // 1/2 - TempoPattern("\uECA7\\s*\uECB7\\s*\uECB7", 1.75 / 120.0, TDuration::DurationType::V_EIGHTH, 2), // double dotted 1/8 - TempoPattern("\uECA7\\s*\uECB7", 1.5 / 120.0, TDuration::DurationType::V_EIGHTH, 1), // dotted 1/8 - TempoPattern("\uECA7", 1.0 / 120.0, TDuration::DurationType::V_EIGHTH), // 1/8 - TempoPattern("\uECA2\\s*\uECB7", 1.5 / 15.0, TDuration::DurationType::V_WHOLE, 1), // dotted whole - TempoPattern("\uECA2", 1.0 / 15.0, TDuration::DurationType::V_WHOLE), // whole - TempoPattern("\uECA9\\s*\uECB7", 1.5 / 240.0, TDuration::DurationType::V_16TH, 1), // dotted 1/16 - TempoPattern("\uECA9", 1.0 / 240.0, TDuration::DurationType::V_16TH), // 1/16 - TempoPattern("\uECAB\\s*\uECB7", 1.5 / 480.0, TDuration::DurationType::V_32ND, 1), // dotted 1/32 - TempoPattern("\uECAB", 1.0 / 480.0, TDuration::DurationType::V_32ND), // 1/32 - TempoPattern("\uECA1", 1.0 / 7.5, TDuration::DurationType::V_BREVE), // longa - TempoPattern("\uECA0", 1.0 / 7.5, TDuration::DurationType::V_BREVE), // double whole - TempoPattern("\uECAD", 1.0 / 960.0, TDuration::DurationType::V_64TH), // 1/64 - TempoPattern("\uECAF", 1.0 / 1920.0, TDuration::DurationType::V_128TH), // 1/128 - TempoPattern("\uECB1", 1.0 / 3840.0, TDuration::DurationType::V_256TH), // 1/256 - TempoPattern("\uECB3", 1.0 / 7680.0, TDuration::DurationType::V_512TH), // 1/512 - TempoPattern("\uECB5", 1.0 / 15360.0, TDuration::DurationType::V_1024TH), // 1/1024 +static const EquationDescrption durationMap[] = { + { "d", 8.0f, "\uECA0", "metNoteDoubleWhole", TDuration::DurationType::V_BREVE }, + { "w", 4.0f, "\uECA2", "metNoteWhole", TDuration::DurationType::V_WHOLE }, + { "h", 2.0f, "\uECA3", "metNoteHalfUp", TDuration::DurationType::V_HALF }, + { "q", 1.0f, "\uECA5", "metNoteQuarterUp", TDuration::DurationType::V_QUARTER }, + { "e", 0.5f, "\uECA7", "metNote8thUp", TDuration::DurationType::V_EIGHTH }, + { "s", 0.25f, "\uECA9", "metNote16thUp", TDuration::DurationType::V_16TH }, + { "t", 0.125f, "\uECAB", "metNote32ndUp", TDuration::DurationType::V_32ND }, + { ".", 0, "\uECB7", "metAugmentationDot", TDuration::DurationType::V_32ND }, }; +QString TempoText::regexGroup(bool symbol) +{ + static QString unicodeString; + static QString symbolString; + + if (symbol && !symbolString.isEmpty()) { + return symbolString; + } else if (!symbol && !unicodeString.isEmpty()) { + return unicodeString; + } + + for (auto pattern : durationMap) { + unicodeString += pattern.unicode; + symbolString += QString(pattern.symbol).replace("/", "\\/") + "|"; + } + + unicodeString = "[" + unicodeString + "]"; + symbolString = "(" + symbolString.left(symbolString.size() - 1) + ")"; + + return symbol ? symbolString : unicodeString; +} + //--------------------------------------------------------- // findTempoDuration // find the duration part (note + dot) of a tempo text in string s @@ -169,59 +184,41 @@ static const TempoPattern tp[] = { int TempoText::findTempoDuration(const QString& s, int& len, TDuration& dur) { - len = 0; - dur = TDuration(); - for (const auto& i : tp) { - QRegularExpression regex(i.pattern); - QRegularExpressionMatch match = regex.match(s); - if (match.hasMatch()) { - len = match.capturedLength(); - dur = i.d; - return match.capturedStart(); - } + static const QRegularExpression tempoExpression(QString("(?%1+)").arg(TempoText::regexGroup(false))); + QRegularExpressionMatch match = tempoExpression.match(s); + if (match.hasMatch()) { + len = match.capturedLength(); + dur = TempoText::findTempoDuration(match.captured("pattern")); + return match.capturedStart(); } + return -1; } +TDuration TempoText::findTempoDuration(const QString& s) +{ + static const QRegularExpression tempoExpression(QString("(?%1)(?\uECB7*)").arg(TempoText::regexGroup(false))); + QRegularExpressionMatch match = tempoExpression.match(s); + + if (!match.hasMatch()) { + return TDuration(TDuration::DurationType::V_INVALID); + } + + for (auto pattern : durationMap) { + if (pattern.unicode == match.captured("equation")) { + TDuration duration(pattern.duration); + duration.setDots(match.captured("dots").size()); + return duration; + } + } + + return TDuration(TDuration::DurationType::V_INVALID); +} + TDuration TempoText::duration() const { - int dummy = 0; - TDuration result; - - findTempoDuration(xmlText(), dummy, result); - - return result; -} - -static const TempoPattern tpSym[] = { - TempoPattern("metNoteQuarterUp\\s*metAugmentationDot\\s*metAugmentationDot", - 1.75 / 60.0, TDuration::DurationType::V_QUARTER, 2), // double dotted 1/4 - TempoPattern("metNoteQuarterUp\\s*metAugmentationDot", 1.5 / 60.0, TDuration::DurationType::V_QUARTER, - 1), // dotted 1/4 - TempoPattern("metNoteQuarterUp", 1.0 / 60.0, TDuration::DurationType::V_QUARTER), // 1/4 - TempoPattern("metNoteHalfUp\\s*metAugmentationDot\\s*metAugmentationDot", - 1.75 / 30.0, TDuration::DurationType::V_HALF, 2), // double dotted 1/2 - TempoPattern("metNoteHalfUp\\s*metAugmentationDot", 1.5 / 30.0, TDuration::DurationType::V_HALF, 1), // dotted 1/2 - TempoPattern("metNoteHalfUp", 1.0 / 30.0, TDuration::DurationType::V_HALF), // 1/2 - TempoPattern("metNote8thUp\\s*metAugmentationDot\\s*metAugmentationDot", 1.75 / 120.0, - TDuration::DurationType::V_EIGHTH, 2), // double dotted 1/8 - TempoPattern("metNote8thUp\\s*metAugmentationDot", 1.5 / 120.0, TDuration::DurationType::V_EIGHTH, - 1), // dotted 1/8 - TempoPattern("metNote8thUp", 1.0 / 120.0, TDuration::DurationType::V_EIGHTH), // 1/8 - TempoPattern("metNoteWhole\\s*metAugmentationDot", 1.5 / 15.0, TDuration::DurationType::V_WHOLE, 1), // dotted whole - TempoPattern("metNoteWhole", 1.0 / 15.0, TDuration::DurationType::V_WHOLE), // whole - TempoPattern("metNote16thUp\\s*metAugmentationDot", 1.5 / 240.0, TDuration::DurationType::V_16TH, 1), // dotted 1/16 - TempoPattern("metNote16thUp", 1.0 / 240.0, TDuration::DurationType::V_16TH), // 1/16 - TempoPattern("metNote32ndUp\\s*metAugmentationDot", 1.5 / 480.0, TDuration::DurationType::V_32ND, 1), // dotted 1/32 - TempoPattern("metNote32ndUp", 1.0 / 480.0, TDuration::DurationType::V_32ND), // 1/32 - TempoPattern("metNoteDoubleWholeSquare", 1.0 / 7.5, TDuration::DurationType::V_BREVE), // longa - TempoPattern("metNoteDoubleWhole", 1.0 / 7.5, TDuration::DurationType::V_BREVE), // double whole - TempoPattern("metNote64thUp", 1.0 / 960.0, TDuration::DurationType::V_64TH), // 1/64 - TempoPattern("metNote128thUp", 1.0 / 1920.0, TDuration::DurationType::V_128TH), // 1/128 - TempoPattern("metNote256thUp", 1.0 / 3840.0, TDuration::DurationType::V_256TH), // 1/256 - TempoPattern("metNote512thUp", 1.0 / 7680.0, TDuration::DurationType::V_512TH), // 1/512 - TempoPattern("metNote1024thUp", 1.0 / 15360.0, TDuration::DurationType::V_1024TH), // 1/1024 -}; + return findTempoDuration(TempoText::mapEquationToText(_equation, false).split("=")[0]); +} //--------------------------------------------------------- // duration2tempoTextString @@ -230,14 +227,20 @@ static const TempoPattern tpSym[] = { QString TempoText::duration2tempoTextString(const TDuration dur) { - for (const TempoPattern& pa : tpSym) { - if (pa.d == dur) { - QString res = pa.pattern; - res.replace("\\s*", " "); - return res; + QString tempoString = "q"; + + for (auto pattern : durationMap) { + if (pattern.duration == dur.type()) { + tempoString = pattern.letter; + break; } } - return ""; + + for (int i = 0; i < dur.dots(); i++) { + tempoString += "."; + } + + return tempoString; } //--------------------------------------------------------- @@ -247,7 +250,7 @@ QString TempoText::duration2tempoTextString(const TDuration dur) void TempoText::updateScore() { if (segment()) { - score()->setTempo(segment(), _tempo); + score()->setTempo(segment(), _playbackTempo); } score()->fixTicks(); score()->setPlaylistDirty(); @@ -263,6 +266,21 @@ void TempoText::updateRelative() setTempo(tempoBefore * _relative); } +void TempoText::startEdit(EditData& ed) +{ + TextBase::startEdit(ed); + + TextEditData* ted = static_cast(ed.getData(this)); + TextCursor* cursor = ted->cursor(); + + int cursorIndex = textIndexFromCursor(cursor->row(), cursor->column()); + std::pair eqnIndices = equationIndices(); + + if (cursorIndex >= eqnIndices.first && cursorIndex <= eqnIndices.second) { + cursor->movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, cursorIndex - eqnIndices.first); + } +} + //--------------------------------------------------------- // endEdit // text may have changed @@ -286,6 +304,106 @@ void TempoText::endEdit(EditData& ed) } } +int TempoText::textIndexFromCursor(int row, int column) const +{ + int index = 0; + for (int i = 0; i < row; i++) { + index = xmlText().indexOf("\n", index); + } + + index += column; + + return index; +} + +std::pair TempoText::cursorIndexFromTextIndex(int index) const +{ + int row = 0; + int lineIndex = plainText().indexOf("\n"); + + while (lineIndex < index && row < xmlText().count("\n")) { + lineIndex = plainText().indexOf("\n", lineIndex); + row++; + } + + int column = index - lineIndex - 1; + + return std::make_pair(row, column); +} + +std::pair TempoText::equationIndices() const +{ + static const QRegularExpression equationExpression(QString("(?[\\[\\(]?%1+ *= *(%1+|[0-9]+)[\\]\\)]?)").arg(TempoText:: + regexGroup(false))); + QRegularExpressionMatch match = equationExpression.match(plainText()); + + if (!match.hasMatch()) { + return std::make_pair(0, 0); + } + + return std::make_pair(match.capturedStart("equation"), match.capturedEnd("equation")); +} + +bool TempoText::moveCursor(TextCursor* cursor, int key, bool ctrlPressed, QTextCursor::MoveMode moveMode) const +{ + int cursorIndex = textIndexFromCursor(cursor->row(), cursor->column()); + + std::pair eqnIndices = equationIndices(); + + if (eqnIndices.first == 0 && eqnIndices.second == 0) { + return TextBase::moveCursor(cursor, key, ctrlPressed, moveMode); + } + + int length = eqnIndices.second - eqnIndices.first; + + if (eqnIndices.first == cursorIndex && key == Qt::Key_Right) { + return cursor->movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, length); + } else if (eqnIndices.second == cursorIndex && key == Qt::Key_Left) { + return cursor->movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, length); + } else { + if (key == Qt::Key_Left) { + return cursor->movePosition(ctrlPressed ? QTextCursor::WordLeft : QTextCursor::Left, moveMode); + } else if (key == Qt::Key_Right) { + return cursor->movePosition(ctrlPressed ? QTextCursor::WordRight : QTextCursor::Right, moveMode); + } + } + + return false; +} + +void TempoText::dragTo(EditData& ed) +{ + TextEditData* ted = static_cast(ed.getData(this)); + TextCursor* cursor = ted->cursor(); + + cursor->set(ed.pos, QTextCursor::KeepAnchor); + + int cursorEndIndex = textIndexFromCursor(cursor->row(), cursor->column()); + int cursorStartIndex = textIndexFromCursor(cursor->selectLine(), cursor->selectColumn()); + + std::pair eqnIndices = equationIndices(); + + if (!(eqnIndices.first == 0 && eqnIndices.second == 0)) { + int equationStart = eqnIndices.first; + int equationEnd = eqnIndices.second; + + if (cursorStartIndex <= equationStart && cursorEndIndex > equationStart) { + std::pair indices = cursorIndexFromTextIndex(equationStart); + + cursor->setRow(indices.first); + cursor->setColumn(indices.second); + } else if (cursorStartIndex >= equationEnd && cursorEndIndex < equationEnd) { + std::pair indices = cursorIndexFromTextIndex(equationEnd); + + cursor->setRow(indices.first); + cursor->setColumn(indices.second); + } + } + + score()->setUpdateAll(); + score()->update(); +} + //--------------------------------------------------------- // undoChangeProperty //--------------------------------------------------------- @@ -308,51 +426,41 @@ void TempoText::undoChangeProperty(Pid id, const QVariant& v, PropertyFlags ps) void TempoText::updateTempo() { - // cache regexp, they are costly to create - static QHash regexps; - static QHash regexps2; - QString s = plainText(); - s.replace(",", "."); - s.replace("space", " "); - for (const TempoPattern& pa : tp) { - QRegularExpression re; - if (!regexps.contains(pa.pattern)) { - re = QRegularExpression(QString("%1\\s*=\\s*(\\d+[.]{0,1}\\d*)\\s*").arg(pa.pattern)); - regexps[pa.pattern] = re; - } - re = regexps.value(pa.pattern); - QRegularExpressionMatch match = re.match(s); - if (match.hasMatch()) { - QStringList sl = match.capturedTexts(); - if (sl.size() == 2) { - qreal nt = qreal(sl[1].toDouble()) * pa.f; - if (nt != _tempo) { - undoChangeProperty(Pid::TEMPO, QVariant(qreal(sl[1].toDouble()) * pa.f), propertyFlags(Pid::TEMPO)); - _relative = 1.0; - _isRelative = false; - updateScore(); - } - break; - } - } else { - for (const TempoPattern& pa2 : tp) { - QString key = QString("%1_%2").arg(pa.pattern, pa2.pattern); - QRegularExpression re2; - if (!regexps2.contains(key)) { - re2 = QRegularExpression(QString("%1\\s*=\\s*%2\\s*").arg(pa.pattern, pa2.pattern)); - regexps2[key] = re2; - } - re2 = regexps2.value(key); - QRegularExpressionMatch match2 = re2.match(s); - if (match2.hasMatch()) { - _relative = pa2.f / pa.f; - _isRelative = true; - updateRelative(); - updateScore(); - return; - } + static const float quarterNotePlayback = 1.f / 60.f; + + static const QRegularExpression bpmExpression("^[\\[,\\(]?[ ]*(?[dwhqest].*) *= *(?\\d+)[\\],\\)]?$"); + static const QRegularExpression relativeExpression("^[\\[,\\(]?[ ]*(?[dwhqest].*) *= *(?[dwhqest].*)[\\],\\)]?$"); + + QRegularExpressionMatch bpmMatch = bpmExpression.match(_equation); + QRegularExpressionMatch relativeMatch = relativeExpression.match(_equation); + + if (bpmMatch.hasMatch()) { + const float relativeDuration = TempoText::getRelativeDuration(bpmMatch.captured("note")); + const float bpm = bpmMatch.captured("bpm").toFloat(); + + _notatedTempo = bpm; + + qreal playbackTempo(relativeDuration * bpm * quarterNotePlayback); + + if (playbackTempo != _playbackTempo) { + if (segment()) { + undoChangeProperty(Pid::TEMPO, QVariant(playbackTempo), propertyFlags(Pid::TEMPO)); } + + _playbackTempo = playbackTempo; + + _relative = 1.0; + _isRelative = false; + + updateScore(); } + } else if (relativeMatch.hasMatch()) { + _relative = TempoText::getRelativeDuration(relativeMatch.captured("note2")) + / TempoText::getRelativeDuration(relativeMatch.captured("note1")); + _isRelative = true; + + updateRelative(); + updateScore(); } } @@ -367,7 +475,13 @@ void TempoText::setTempo(qreal v) } else if (v > MAX_TEMPO) { v = MAX_TEMPO; } - _tempo = v; + _playbackTempo = v; +} + +void TempoText::setEquationFromTempo(int tempo) +{ + _equation = QString("q = %1").arg(tempo); + parseEquation(); } //--------------------------------------------------------- @@ -396,9 +510,13 @@ QVariant TempoText::getProperty(Pid propertyId) const { switch (propertyId) { case Pid::TEMPO: - return _tempo; + return _playbackTempo; case Pid::TEMPO_FOLLOW_TEXT: return _followText; + case Pid::TEMPO_EQUATION: + return _equation; + case Pid::TEMPO_EQUATION_VISIBLE: + return _isEquationVisible; default: return TextBase::getProperty(propertyId); } @@ -413,12 +531,22 @@ bool TempoText::setProperty(Pid propertyId, const QVariant& v) switch (propertyId) { case Pid::TEMPO: setTempo(v.toDouble()); - score()->setTempo(segment(), _tempo); + score()->setTempo(segment(), _playbackTempo); score()->fixTicks(); break; case Pid::TEMPO_FOLLOW_TEXT: _followText = v.toBool(); break; + case Pid::TEMPO_EQUATION: + if (isEquationValid(v.toString())) { + _equation = v.toString(); + parseEquation(); + } + break; + case Pid::TEMPO_EQUATION_VISIBLE: + _isEquationVisible = v.toBool(); + parseEquation(); + break; default: if (!TextBase::setProperty(propertyId, v)) { return false; @@ -442,6 +570,10 @@ QVariant TempoText::propertyDefault(Pid id) const return 2.0; case Pid::TEMPO_FOLLOW_TEXT: return false; + case Pid::TEMPO_EQUATION: + return "q = 120"; + case Pid::TEMPO_EQUATION_VISIBLE: + return true; default: return TextBase::propertyDefault(id); } @@ -476,6 +608,94 @@ void TempoText::layout() autoplaceSegmentElement(); } +//--------------------------------------------------------- +// parseEquation +//--------------------------------------------------------- + +void TempoText::parseEquation() +{ + static const QString equationBase = "[\\[\\(]?%1+ *= *(%1+|\\d+)[\\]\\)]?"; + static const QRegularExpression equationExpression(equationBase.arg(TempoText::regexGroup())); + static const QRegularExpression unicodeEquationExpression(equationBase.arg(TempoText::regexGroup(false))); + + if (_isEquationVisible) { + QString equation = TempoText::mapEquationToText(_equation); + + if (equationExpression.match(xmlText()).hasMatch()) { + setXmlText(xmlText().replace(equationExpression, equation)); + } else if (unicodeEquationExpression.match(plainText()).hasMatch()) { + auto split = plainText().split(unicodeEquationExpression); + setXmlText(split[0] + equation + split[1]); + } else { + setXmlText(xmlText() + equation); + } + } else { + setXmlText(xmlText().replace(equationExpression, "")); + } + + if (_followText) { + updateTempo(); + } +} + +//--------------------------------------------------------- +// isEquationValid +//--------------------------------------------------------- + +bool TempoText::isEquationValid(const QString equation) const +{ + const QRegularExpression equationExpression("^[\\[,\\(]?[ ]*([dwhqest].*) *= *(\\d+|[dwhqest].*)[\\],\\)]?$"); + QRegularExpressionMatch match = equationExpression.match(equation); + + return match.hasMatch(); +} + +//--------------------------------------------------------- +// mapEquationToText +//--------------------------------------------------------- +QString TempoText::mapEquationToText(const QString equation, bool symbol) +{ + QString mapped; + + for (auto c : equation) { + bool added = false; + + for (auto pattern : durationMap) { + if (pattern.letter == c) { + mapped += (symbol ? pattern.symbol : pattern.unicode); + added = true; + break; + } + } + + if (!added) { + mapped += c; + } + } + + return mapped; +} + +//--------------------------------------------------------- +// getRelativeDuration +//--------------------------------------------------------- + +float TempoText::getRelativeDuration(const QString marking) +{ + float baseDuration = 1.0f; + + for (auto duration : durationMap) { + if (marking[0] == duration.letter) { + baseDuration = duration.relative; + break; + } + } + + const int dots = marking.count("."); + + return baseDuration * std::pow(1.5, float(dots)); +} + //--------------------------------------------------------- // duration2userName //--------------------------------------------------------- diff --git a/src/engraving/libmscore/tempotext.h b/src/engraving/libmscore/tempotext.h index 6c29ef2e08460..48c04e94b06b3 100644 --- a/src/engraving/libmscore/tempotext.h +++ b/src/engraving/libmscore/tempotext.h @@ -37,16 +37,28 @@ namespace Ms { class TempoText final : public TextBase { - qreal _tempo; // beats per second + qreal _playbackTempo; // beats per second + qreal _notatedTempo; // tempo user enters bool _followText; // parse text to determine tempo qreal _relative; bool _isRelative; + QString _equation; + bool _isEquationVisible; void updateScore(); void updateTempo(); + + void startEdit(EditData&) override; void endEdit(EditData&) override; + void undoChangeProperty(Pid id, const QVariant&, PropertyFlags ps) override; + bool isEquationValid(const QString equation) const; + + int textIndexFromCursor(int row, int column) const; + std::pair cursorIndexFromTextIndex(int index) const; + std::pair equationIndices() const; + public: TempoText(Score*); @@ -59,7 +71,7 @@ class TempoText final : public TextBase Segment* segment() const { return toSegment(parent()); } Measure* measure() const { return toMeasure(parent()->parent()); } - qreal tempo() const { return _tempo; } + qreal tempo() const { return _playbackTempo; } qreal tempoBpm() const; void setTempo(qreal v); void undoSetTempo(qreal v); @@ -68,16 +80,32 @@ class TempoText final : public TextBase bool followText() const { return _followText; } void setFollowText(bool v) { _followText = v; } + QString equation() const { return _equation; } + void setEquation(QString equation) { _equation = equation; } + void setEquationVisible(bool equationVisible) { _isEquationVisible = equationVisible; } void undoSetFollowText(bool v); void updateRelative(); + void setEquationFromTempo(int tempo); + + void parseEquation(); + + bool moveCursor(TextCursor* cursor, int key, bool ctrlPressed, QTextCursor::MoveMode moveMode) const override; + + void dragTo(EditData& ed) override; + void layout() override; TDuration duration() const; + static QString regexGroup(bool symbol = true); + static int findTempoDuration(const QString& s, int& len, TDuration& dur); + static TDuration findTempoDuration(const QString& s); static QString duration2tempoTextString(const TDuration dur); static QString duration2userName(const TDuration t); + static float getRelativeDuration(const QString marking); + static QString mapEquationToText(const QString equation, bool symbol = true); QVariant getProperty(Pid propertyId) const override; bool setProperty(Pid propertyId, const QVariant&) override; diff --git a/src/engraving/libmscore/textbase.cpp b/src/engraving/libmscore/textbase.cpp index c0e4ecc2ab2b7..12904e7cbb3b3 100644 --- a/src/engraving/libmscore/textbase.cpp +++ b/src/engraving/libmscore/textbase.cpp @@ -3084,6 +3084,17 @@ Sid TextBase::offsetSid() const return Sid::NOSTYLE; } +bool TextBase::moveCursor(TextCursor* cursor, int key, bool ctrlPressed, QTextCursor::MoveMode moveMode) const +{ + if (key == Qt::Key_Left) { + return cursor->movePosition(ctrlPressed ? QTextCursor::WordLeft : QTextCursor::Left, moveMode); + } else if (key == Qt::Key_Right) { + return cursor->movePosition(ctrlPressed ? QTextCursor::WordRight : QTextCursor::Right, moveMode); + } else { + return false; + } +} + //--------------------------------------------------------- // getHtmlStartTag - helper function for extractText with withFormat = true //--------------------------------------------------------- diff --git a/src/engraving/libmscore/textbase.h b/src/engraving/libmscore/textbase.h index ae930c646ba61..9d7a3ad8e7858 100644 --- a/src/engraving/libmscore/textbase.h +++ b/src/engraving/libmscore/textbase.h @@ -319,6 +319,8 @@ class TextBase : public Element void insertText(EditData&, const QString&); + virtual bool moveCursor(TextCursor* cursor, int key, bool ctrlPressed, QTextCursor::MoveMode moveMode) const; + virtual void layout() override; virtual void layout1(); qreal lineSpacing() const; @@ -364,7 +366,7 @@ class TextBase : public Element mu::RectF pageRectangle() const; - void dragTo(EditData&); + virtual void dragTo(EditData&); QVector dragAnchorLines() const override; diff --git a/src/engraving/libmscore/textedit.cpp b/src/engraving/libmscore/textedit.cpp index bda4c75eef8f4..87cc8de5840d4 100644 --- a/src/engraving/libmscore/textedit.cpp +++ b/src/engraving/libmscore/textedit.cpp @@ -360,21 +360,14 @@ bool TextBase::edit(EditData& ed) return true; case Qt::Key_Left: - if (!_cursor->movePosition(ctrlPressed ? QTextCursor::WordLeft : QTextCursor::Left, - mm) && type() == ElementType::LYRICS) { - return false; - } - s.clear(); - break; - case Qt::Key_Right: - if (!_cursor->movePosition(ctrlPressed ? QTextCursor::NextWord : QTextCursor::Right, - mm) && type() == ElementType::LYRICS) { + if (!toTextBase(ed.element)->moveCursor(_cursor, ed.key, ctrlPressed, mm) && type() == ElementType::LYRICS) { return false; } + s.clear(); - break; + break; case Qt::Key_Up: #if defined(Q_OS_MAC) if (!cursor->movePosition(QTextCursor::Up, mm)) { diff --git a/src/engraving/tests/barline_data/barlinedelete-ref.mscx b/src/engraving/tests/barline_data/barlinedelete-ref.mscx index a8cd45430cf4e..468dda5ea913c 100644 --- a/src/engraving/tests/barline_data/barlinedelete-ref.mscx +++ b/src/engraving/tests/barline_data/barlinedelete-ref.mscx @@ -81,7 +81,10 @@ 4 - 2 + 2 + 120 + q = 120 + 1 1 Tempo text diff --git a/src/engraving/tests/barline_data/barlinedelete.mscx b/src/engraving/tests/barline_data/barlinedelete.mscx index b4643493fa5ba..3d0e8505fdd9b 100644 --- a/src/engraving/tests/barline_data/barlinedelete.mscx +++ b/src/engraving/tests/barline_data/barlinedelete.mscx @@ -80,7 +80,10 @@ 4 - 2 + 2 + 120 + q = 120 + 1 1 Tempo text diff --git a/src/importexport/bww/internal/bww/importbww.cpp b/src/importexport/bww/internal/bww/importbww.cpp index 33ab4a61f5b46..d4f826edd29ac 100644 --- a/src/importexport/bww/internal/bww/importbww.cpp +++ b/src/importexport/bww/internal/bww/importbww.cpp @@ -119,11 +119,9 @@ static void xmlSetPitch(Ms::Note* n, char step, int alter, int octave) static void setTempo(Ms::Score* score, int tempo) { Ms::TempoText* tt = new Ms::TempoText(score); - tt->setTempo(double(tempo) / 60.0); + tt->setEquationFromTempo(tempo); tt->setTrack(0); - QString tempoText = Ms::TempoText::duration2tempoTextString(Ms::TDuration::DurationType::V_QUARTER); - tempoText += QString(" = %1").arg(tempo); - tt->setPlainText(tempoText); + Ms::Measure* measure = score->firstMeasure(); Ms::Segment* segment = measure->getSegment(Ms::SegmentType::ChordRest, Ms::Fraction(0, 1)); segment->add(tt); diff --git a/src/importexport/guitarpro/internal/importgtp.cpp b/src/importexport/guitarpro/internal/importgtp.cpp index f691bc3e11414..d4e35780ebd6e 100644 --- a/src/importexport/guitarpro/internal/importgtp.cpp +++ b/src/importexport/guitarpro/internal/importgtp.cpp @@ -1354,8 +1354,7 @@ void GuitarPro::setTempo(int temp, Measure* measure) } TempoText* tt = new TempoText(score); - tt->setTempo(double(temp) / 60.0); - tt->setXmlText(QString("metNoteQuarterUp = %1").arg(temp)); + tt->setEquationFromTempo(temp); tt->setTrack(0); segment->add(tt); diff --git a/src/importexport/guitarpro/internal/importptb.cpp b/src/importexport/guitarpro/internal/importptb.cpp index 01916c2573caa..60c180e2be9d9 100644 --- a/src/importexport/guitarpro/internal/importptb.cpp +++ b/src/importexport/guitarpro/internal/importptb.cpp @@ -835,8 +835,7 @@ void PowerTab::addToScore(ptSection& sec) } if (sec.tempo) { TempoText* tt = new TempoText(score); - tt->setTempo(double(sec.tempo) / 60.0f); - tt->setXmlText(QString("metNoteQuarterUp = %1").arg(sec.tempo)); + tt->setEquationFromTempo(sec.tempo); tt->setTrack(0); Segment* segment = measure->getSegment(SegmentType::ChordRest, measure->tick()); segment->add(tt); diff --git a/src/importexport/midi/internal/midiimport/importmidi_tempo.cpp b/src/importexport/midi/internal/midiimport/importmidi_tempo.cpp index 51b3e94704ba7..4bc053b0ffc4a 100644 --- a/src/importexport/midi/internal/midiimport/importmidi_tempo.cpp +++ b/src/importexport/midi/internal/midiimport/importmidi_tempo.cpp @@ -76,8 +76,7 @@ void setTempoToScore(Score* score, int tick, double beatsPerSecond) const int tempoInBpm = qRound(beatsPerSecond * 60.0); TempoText* tempoText = new TempoText(score); - tempoText->setTempo(beatsPerSecond); - tempoText->setXmlText(QString("metNoteQuarterUp = %1").arg(tempoInBpm)); + tempoText->setEquationFromTempo(tempoInBpm); tempoText->setTrack(0); Measure* measure = score->tick2measure(Fraction::fromTicks(tick)); diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index 1aad91839366f..f2ef79590b46a 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -4124,6 +4124,12 @@ static bool findMetronome(const QList& list, int rparen = s6.indexOf(")"); hasParen = (lparen == s1.length() - 1 && rparen == 0); + if (!hasParen) { + lparen = s1.indexOf("["); + rparen = s6.indexOf("]"); + hasParen = (lparen == s1.length() - 1 && rparen == 0); + } + metroLeft = s2; metroRight = s5; diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index 330bcbd8de0b5..0c8f45ece23aa 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -2136,10 +2136,8 @@ void MusicXMLParserPass2::measure(const QString& partId, const Fraction time) } else { double tpo = tempo.toDouble() / 60; TempoText* t = new TempoText(_score); - t->setXmlText(QString("%1 = %2").arg(TempoText::duration2tempoTextString(TDuration(TDuration::DurationType::V_QUARTER)), - tempo)); + t->setEquationFromTempo(tempo.toDouble()); t->setVisible(false); - t->setTempo(tpo); t->setFollowText(true); _score->setTempo(tick, tpo); @@ -2631,17 +2629,10 @@ void MusicXMLParserDirection::direction(const QString& partId, if (hasTempoTextAtTick(_score->tempomap(), tick.ticks())) { _logger->logError(QString("duplicate tempo at tick %1").arg(tick.ticks()), &_e); } else { - double tpo = _tpoSound / 60; TempoText* t = new TempoText(_score); - t->setXmlText(QString("%1 = %2").arg(TempoText::duration2tempoTextString(TDuration(TDuration::DurationType::V_QUARTER))).arg( - _tpoSound)); - t->setVisible(false); - t->setTempo(tpo); + t->setEquationFromTempo(_tpoSound); t->setFollowText(true); - // TBD may want ro use tick + _offset if sound is affected - _score->setTempo(tick, tpo); - addElemOffset(t, track, placement, measure, tick + _offset); } } @@ -3366,7 +3357,7 @@ QString MusicXMLParserDirection::metronome(double& r) tempoText += ")"; } - return tempoText; + return TempoText::mapEquationToText(tempoText); } //--------------------------------------------------------- diff --git a/src/importexport/musicxml/tests/data/testExcludeInvisibleElements.mscx b/src/importexport/musicxml/tests/data/testExcludeInvisibleElements.mscx index e3cb8954e46a9..61521e44e689f 100644 --- a/src/importexport/musicxml/tests/data/testExcludeInvisibleElements.mscx +++ b/src/importexport/musicxml/tests/data/testExcludeInvisibleElements.mscx @@ -238,7 +238,10 @@ - 1.3333299999999999 + 1.3333299999999999 + 80 + q = 80 + 1 1 metNoteQuarterUp = 80 @@ -257,7 +260,10 @@ - 1.5 + 1.5 + 90 + q = 90 + 1 1 0 = 90 diff --git a/src/importexport/ove/internal/importove.cpp b/src/importexport/ove/internal/importove.cpp index eb9e8d8b5e186..f271543d393d7 100644 --- a/src/importexport/ove/internal/importove.cpp +++ b/src/importexport/ove/internal/importove.cpp @@ -1038,6 +1038,55 @@ TDuration OveNoteType_To_Duration(ovebase::NoteType noteType) d.setType(TDuration::DurationType::V_256TH); break; } + // case ovebase::NoteType::Note_512: { + // d.setType(TDuration::DurationType::V_512TH); + // break; + // } + // case ovebase::NoteType::Note_1024: { + // d.setType(TDuration::DurationType::V_1024TH); + // break; + // } + default: + d.setType(TDuration::DurationType::V_QUARTER); + break; + } + + return d; +} + +QString OveNoteType_To_EquationString(ovebase::NoteType noteType) +{ + switch (noteType) { + case ovebase::NoteType::Note_DoubleWhole: { + return "d"; + } + case ovebase::NoteType::Note_Whole: { + return "w"; + } + case ovebase::NoteType::Note_Half: { + return "h"; + } + case ovebase::NoteType::Note_Quarter: { + return "q"; + } + case ovebase::NoteType::Note_Eight: { + return "e"; + } + case ovebase::NoteType::Note_Sixteen: { + return "s"; + } + case ovebase::NoteType::Note_32: { + return "t"; + } + //case ovebase::NoteType::Note_64: { + // return "w"; + //} + //case ovebase::NoteType::Note_128: { + // return "w"; + //} + //case ovebase::NoteType::Note_256: { + // return "w"; + //} // case ovebase::NoteType::Note_512: { // d.setType(TDuration::DurationType::V_512TH); // break; @@ -1047,11 +1096,8 @@ TDuration OveNoteType_To_Duration(ovebase::NoteType noteType) // break; // } default: - d.setType(TDuration::DurationType::V_QUARTER); - break; + return "q"; } - - return d; } int accidentalToAlter(ovebase::AccidentalType type) @@ -1362,62 +1408,65 @@ void OveToMScore::convertMeasureMisc(Measure* measure, int part, int staff, int m_score->setTempo(Fraction::fromTicks(absTick), tpo); - t->setTempo(tpo); - QString durationTempoL; - QString durationTempoR; - if (static_cast(tempoPtr->getLeftNoteType())) { - durationTempoL = TempoText::duration2tempoTextString(OveNoteType_To_Duration(tempoPtr->getLeftNoteType())); - } - if (static_cast(tempoPtr->getRightNoteType())) { - durationTempoR = TempoText::duration2tempoTextString(OveNoteType_To_Duration(tempoPtr->getRightNoteType())); - } - QString textTempo; + QString text; + QString equation; + if (tempoPtr->getShowBeforeText()) { - textTempo += (tempoPtr->getLeftText()).toHtmlEscaped(); + text += (tempoPtr->getLeftText()).toHtmlEscaped(); } if (tempoPtr->getShowMark()) { - if (!textTempo.isEmpty()) { - textTempo += " "; + if (!text.isEmpty()) { + text += " "; } if (tempoPtr->getShowParenthesis()) { - textTempo += "("; + equation += "("; } - textTempo += durationTempoL; + + equation += OveNoteType_To_EquationString(tempoPtr->getLeftNoteType()); if (tempoPtr->getLeftNoteDot()) { - textTempo += "spacemetAugmentationDot"; + equation += "."; } - textTempo += " = "; + + equation += " = "; + switch (tempoPtr->getRightSideType()) { case 1: - textTempo += durationTempoR; + equation += OveNoteType_To_EquationString(tempoPtr->getLeftNoteType()); if (tempoPtr->getRightNoteDot()) { - textTempo += "spacemetAugmentationDot"; + equation += "."; } break; case 2: - textTempo += (tempoPtr->getRightText()).toHtmlEscaped(); break; case 3: - textTempo += QString::number(qFloor(tempoPtr->getTypeTempo())); + equation += QString::number(qFloor(tempoPtr->getTypeTempo())); break; case 0: default: - textTempo += QString::number(tempoPtr->getTypeTempo()); + equation += QString::number(tempoPtr->getTypeTempo()); break; } if (tempoPtr->getShowParenthesis()) { - textTempo += ")"; + equation += ")"; } } - if (textTempo.isEmpty()) { - textTempo = durationTempoL; + + if (equation.isEmpty()) { + equation = OveNoteType_To_EquationString(tempoPtr->getLeftNoteType()); if (tempoPtr->getLeftNoteDot()) { - textTempo += "spacemetAugmentationDot"; + equation += "."; } - textTempo += " = " + QString::number(tempoPtr->getTypeTempo()); + + equation += " = " + QString::number(tempoPtr->getTypeTempo()); + t->setVisible(false); } - t->setXmlText(textTempo); + + t->setXmlText(text); + t->setEquation(equation); + t->parseEquation(); + t->setXmlText(t->xmlText() + tempoPtr->getRightText()); + // TODO:ws t->setAbove(true); t->setTrack(track); diff --git a/src/inspector/models/inspectorlistmodel.cpp b/src/inspector/models/inspectorlistmodel.cpp index 6e0f8e73415c9..f60af5e0392ff 100644 --- a/src/inspector/models/inspectorlistmodel.cpp +++ b/src/inspector/models/inspectorlistmodel.cpp @@ -64,7 +64,6 @@ void InspectorListModel::buildModelsForEmptySelection(const QSet persistentSectionList { AbstractInspectorModel::InspectorSectionType::SECTION_SCORE_DISPLAY, AbstractInspectorModel::InspectorSectionType::SECTION_SCORE_APPEARANCE, - AbstractInspectorModel::InspectorSectionType::SECTION_NOTATION, }; removeUnusedModels(selectedElementSet, persistentSectionList); @@ -119,8 +118,10 @@ int InspectorListModel::columnCount(const QModelIndex&) const return 1; } -QVariant InspectorListModel::inspectorModelBySection(const int type) const +QVariant InspectorListModel::inspectorModelBySection(const int type) { + createModelsBySectionType({ static_cast(type) }); + for (AbstractInspectorModel* model : m_modelList) { if (static_cast(model->sectionType()) == type) { QObject* result = qobject_cast(model); diff --git a/src/inspector/models/inspectorlistmodel.h b/src/inspector/models/inspectorlistmodel.h index 758b6e2d69a0f..01d8923cf2fb3 100644 --- a/src/inspector/models/inspectorlistmodel.h +++ b/src/inspector/models/inspectorlistmodel.h @@ -45,7 +45,7 @@ class InspectorListModel : public QAbstractListModel, public mu::async::Asyncabl QHash roleNames() const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; - Q_INVOKABLE QVariant inspectorModelBySection(const int type) const; + Q_INVOKABLE QVariant inspectorModelBySection(const int type); signals: void elementsModified(); diff --git a/src/inspector/models/notation/tempos/temposettingsmodel.cpp b/src/inspector/models/notation/tempos/temposettingsmodel.cpp index 732177df8a059..46a0a46d1c3cf 100644 --- a/src/inspector/models/notation/tempos/temposettingsmodel.cpp +++ b/src/inspector/models/notation/tempos/temposettingsmodel.cpp @@ -44,6 +44,8 @@ void TempoSettingsModel::createProperties() }); m_tempo = buildPropertyItem(Ms::Pid::TEMPO); + m_equation = buildPropertyItem(Ms::Pid::TEMPO_EQUATION); + m_isEquationVisible = buildPropertyItem(Ms::Pid::TEMPO_EQUATION_VISIBLE); } void TempoSettingsModel::requestElements() @@ -57,12 +59,16 @@ void TempoSettingsModel::loadProperties() loadPropertyItem(m_tempo, [](const QVariant& elementPropertyValue) -> QVariant { return DataFormatter::formatDouble(elementPropertyValue.toDouble()); }); + loadPropertyItem(m_equation); + loadPropertyItem(m_isEquationVisible); } void TempoSettingsModel::resetProperties() { m_isDefaultTempoForced->resetToDefault(); m_tempo->resetToDefault(); + m_equation->resetToDefault(); + m_isEquationVisible->resetToDefault(); } PropertyItem* TempoSettingsModel::isDefaultTempoForced() const @@ -74,3 +80,13 @@ PropertyItem* TempoSettingsModel::tempo() const { return m_tempo; } + +PropertyItem* TempoSettingsModel::equation() const +{ + return m_equation; +} + +PropertyItem* TempoSettingsModel::isEquationVisible() const +{ + return m_isEquationVisible; +} diff --git a/src/inspector/models/notation/tempos/temposettingsmodel.h b/src/inspector/models/notation/tempos/temposettingsmodel.h index 684b6a65656a7..b9416de2568f4 100644 --- a/src/inspector/models/notation/tempos/temposettingsmodel.h +++ b/src/inspector/models/notation/tempos/temposettingsmodel.h @@ -31,6 +31,8 @@ class TempoSettingsModel : public AbstractInspectorModel Q_PROPERTY(PropertyItem * isDefaultTempoForced READ isDefaultTempoForced CONSTANT) Q_PROPERTY(PropertyItem * tempo READ tempo CONSTANT) + Q_PROPERTY(PropertyItem * equation READ equation CONSTANT) + Q_PROPERTY(PropertyItem * isEquationVisible READ isEquationVisible CONSTANT) public: explicit TempoSettingsModel(QObject* parent, IElementRepositoryService* repository); @@ -42,10 +44,14 @@ class TempoSettingsModel : public AbstractInspectorModel PropertyItem* isDefaultTempoForced() const; PropertyItem* tempo() const; + PropertyItem* equation() const; + PropertyItem* isEquationVisible() const; private: PropertyItem* m_isDefaultTempoForced = nullptr; PropertyItem* m_tempo = nullptr; + PropertyItem* m_equation = nullptr; + PropertyItem* m_isEquationVisible = nullptr; }; } diff --git a/src/notation/notationscene.qrc b/src/notation/notationscene.qrc index fbdf3f09bc202..fe162cfa0d782 100644 --- a/src/notation/notationscene.qrc +++ b/src/notation/notationscene.qrc @@ -24,5 +24,6 @@ qml/MuseScore/NotationScene/UndoRedoToolBar.qml qml/MuseScore/NotationScene/internal/ElementPopup.qml qml/MuseScore/NotationScene/internal/TempoPopup.qml + qml/MuseScore/NotationScene/internal/DurationPicker.qml diff --git a/src/notation/qml/MuseScore/NotationScene/NotationView.qml b/src/notation/qml/MuseScore/NotationScene/NotationView.qml index 4e6334654c375..36734fd4f0142 100644 --- a/src/notation/qml/MuseScore/NotationScene/NotationView.qml +++ b/src/notation/qml/MuseScore/NotationScene/NotationView.qml @@ -49,11 +49,6 @@ FocusScope { readonly property int scrollbarMargin: 4 } - Component.onCompleted: { - notationView.load() - notationNavigator.load() - } - InspectorListModel { id: inspectorListModel } @@ -126,8 +121,6 @@ FocusScope { ElementPopup { id: elementPopup - - model: inspectorListModel.inspectorModelBySection(Inspector.SECTION_NOTATION) } StyledScrollBar { @@ -267,11 +260,15 @@ FocusScope { function showNotationPopup(type, pos, size) { elementPopup.close(); + elementPopup.model = inspectorListModel.inspectorModelBySection(Inspector.SECTION_NOTATION); + elementPopup.type = type; elementPopup.x = pos.x + size.x / 2 - elementPopup.width / 2; elementPopup.y = pos.y + size.y / 2; + elementPopup.preOpening(); + elementPopup.open(); } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/DurationPicker.qml b/src/notation/qml/MuseScore/NotationScene/internal/DurationPicker.qml new file mode 100644 index 0000000000000..394ee4e5554db --- /dev/null +++ b/src/notation/qml/MuseScore/NotationScene/internal/DurationPicker.qml @@ -0,0 +1,119 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Controls 1.3 + +import MuseScore.NotationScene 1.0 +import MuseScore.UiComponents 1.0 +import MuseScore.Ui 1.0 + +Row { + id: root + + spacing: 4 + + property int buttonWidthBig: 35; + property int buttonWidthSmall: 20; + + property string currentValue: "q"; + + signal valueChanged(var changedValue) + + function setValue(s) { + noteButton.setOptionFromNote(s[0]); + dotList.currentValue = s.substr(1); + + root.currentValue = s; + } + + FlatButton { + id: noteButton + + icon: IconCode.NOTE_QUARTER + + width: buttonWidthBig + height: root.height + + accentButton: true + + property string currentValue: "q" + + function setOptionFromNote(note) { + for(let optionItem in privateProperties.items) { + let option = privateProperties.items[optionItem]; + if(option.value === note) { + noteButton.icon = option.icon; + noteButton.currentValue = option.value; + return; + } + } + } + + QtObject { + id: privateProperties + + readonly property var items: [ + {code: "0", value: "d", icon: IconCode.NOTE_WHOLE_DOUBLE, title: qsTrc("global", "Double Whole")}, + {code: "1", value: "w", icon: IconCode.NOTE_WHOLE, title: qsTrc("global", "Whole")}, + {code: "2", value: "h", icon: IconCode.NOTE_HALF, title: qsTrc("global", "Half")}, + {code: "3", value: "q", icon: IconCode.NOTE_QUARTER, title: qsTrc("global", "Quarter")}, + {code: "4", value: "e", icon: IconCode.NOTE_8TH, title: qsTrc("global", "Eighth")}, + {code: "5", value: "s", icon: IconCode.NOTE_16TH, title: qsTrc("global", "Sixteenth")}, + {code: "6", value: "t", icon: IconCode.NOTE_32TH, title: qsTrc("global", "Thirty-Second")}, + ] + } + + onClicked: { + menuLoader.toggleOpened(privateProperties.items) + } + + StyledMenuLoader { + id: menuLoader + onHandleAction: { + noteButton.icon = privateProperties.items[parseInt(actionCode)]["icon"]; + + noteButton.currentValue = privateProperties.items[parseInt(actionCode)]["value"]; + + root.currentValue = noteButton.currentValue + dotList.currentValue; + valueChanged(root.currentValue); + } + } + + } + + RadioButtonGroup { + id: dotList + + property string currentValue: "" + + width: buttonWidthSmall * 2 + + model: [ + { textRole: "."}, + { textRole: ".."}, + ] + + delegate: FlatRadioButton { + ButtonGroup.group: dotList.radioButtonGroup + + height: root.height + + checked: dotList.currentValue === modelData["textRole"] + + onClicked: { + if (dotList.currentValue !== modelData["textRole"]) + dotList.currentValue = modelData["textRole"] + else + dotList.currentValue = "" + + root.currentValue = noteButton.currentValue + dotList.currentValue; + valueChanged(root.currentValue); + } + + StyledTextLabel { + text: modelData["textRole"] + } + } + } + +} diff --git a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopup.qml index 4bbe3f4c8abcf..d12f13c60e946 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopup.qml @@ -16,17 +16,32 @@ StyledPopup { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - width: 200 - - leftPadding: 0 + leftPadding: 10 + rightPadding: 10 function isVisible(type) { return type === root.type; } + function popupWidth() { + return root.width - root.leftPadding - root.rightPadding; + } + + function preOpening() { + switch(root.type) { + case "Tempo": + tempoPopup.model = root.model.modelByType(Inspector.TYPE_TEMPO); + tempoPopup.parseEquation(); + tempoPopup.setActiveTab(); + break; + default: + break; + } + } + TempoPopup { - model: root.model.modelByType(Inspector.TYPE_TEMPO) - width: root.width + id: tempoPopup + model:null visible: isVisible("Tempo") } } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/TempoPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/TempoPopup.qml index 89095b45c6db5..89f17370e8763 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/TempoPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/TempoPopup.qml @@ -9,31 +9,317 @@ import MuseScore.Ui 1.0 import MuseScore.Inspector 1.0 -TabPanel { - id: tabPanel +Column { + id: root + + property QtObject model; + + property string currentEquation; + property int currentParantheses; + + property int startupTab: -1; - height: 50 width: 200 - property QtObject model; + spacing: 10 + + function setEquation(left, right) { + currentEquation = right ? left + " = " + right : left; + addParantheses(); + } + + function addParantheses() { + switch(paranthesesList.currentValue) { + case 0: + root.model.equation.value = root.currentEquation; + break; + case 1: + root.model.equation.value = "(" + root.currentEquation + ")"; + break; + case 2: + root.model.equation.value = "[" + root.currentEquation + "]"; + break; + } + } + + function parseEquation() { + let eq = root.model.equation.value; + let l = eq.length; + + if(eq[0] === "(" && eq[l - 1] === ")") { + root.currentEquation = eq.substr(1, l - 2); + root.currentParantheses = 1; + } + else if(eq[0] === "[" && eq[l - 1] === "]") { + root.currentEquation = eq.substr(1, l - 2); + root.currentParantheses = 2; + } else { + root.currentEquation = eq; + root.currentParantheses = 0; + } + } + + function setActiveTab() { + root.startupTab = -1; + + if(/[dwhqest].{0,2} = [0-9]+/.test(root.currentEquation)) { + root.startupTab = 0; + } else if(/[dwhqest].{0,2} = [dwhqest].{0,2}/.test(root.currentEquation)) { + root.startupTab = 1; + } else { + root.startupTab = 2; + } + + if(root.startupTab != -1) + tabPanel.currentIndex = root.startupTab; + + tempoTab.startup = true; + equationTab.startup = true; + + } + + TabPanel { + id: tabPanel + + height: 90 + + Tab { + id: tempoTab + + title: "Tempo" + + property bool startup: true; + + Row { + id: tempoRow + + function init() { + if(root.startupTab === 0 && startup) { + tempoDuration.setValue(root.currentEquation.split("=")[0].trim()); + tempoBPM.currentValue = root.currentEquation.split("=")[1].trim(); + } else { + tempoBPM.currentValue = Math.floor(root.model.tempo.value).toString(); + root.setEquation(tempoDuration.currentValue, tempoBPM.currentValue.toString()); + } + + tempoTab.startup = false; + } + + onVisibleChanged: { + if(tabPanel.currentIndex === 0) { + tempoRow.init(); + } + } + + Component.onCompleted: { + tempoRow.init(); + } + + DurationPicker { + id: tempoDuration + + height: 35 + + anchors.verticalCenter: parent.verticalCenter + + onValueChanged: { + root.setEquation(tempoDuration.currentValue, tempoBPM.currentValue.toString()); + } + + } + + StyledTextLabel { + text: "=" + + anchors.verticalCenter: parent.verticalCenter + + width: root.width - tempoDuration.width - tempoBPM.width + + horizontalAlignment: Qt.AlignHCenter + } + + IncrementalPropertyControl { + id: tempoBPM + + iconMode: iconModeEnum.hidden + + anchors.verticalCenter: parent.verticalCenter + + width: tempoDuration.width + + maxValue: 999 + minValue: 0 + step:1 + + decimals: 0 + + onValueEdited: { + currentValue = newValue; + root.setEquation(tempoDuration.currentValue, tempoBPM.currentValue.toString()); + } + } + } - Tab { - id: firstTab + } - title: "Tempo" + Tab { + id: equationTab + + title: "Equation" + + property bool startup: true; + + Row { + id: equationRow + + function init() { + if(root.startupTab === 1 && startup) { + equationLeft.setValue(root.currentEquation.split("=")[0].trim()); + equationRight.setValue(root.currentEquation.split("=")[1].trim()); + } else { + root.setEquation(equationLeft.currentValue, equationRight.currentValue); + } + + equationTab.startup = false; + } + + onVisibleChanged: { + if(tabPanel.currentIndex === 1) { + equationRow.init(); + } + } + + Component.onCompleted: { + equationRow.init(); + } + + DurationPicker { + id: equationLeft + + height: 35 + + anchors.verticalCenter: parent.verticalCenter + + onValueChanged: { + root.setEquation(equationLeft.currentValue, equationRight.currentValue); + } + } + + StyledTextLabel { + text: "=" + + anchors.verticalCenter: parent.verticalCenter + + width: root.width - equationLeft.width - equationRight.width + + horizontalAlignment: Qt.AlignHCenter + } + + DurationPicker { + id: equationRight + + height: 35 + + anchors.verticalCenter: parent.verticalCenter + + onValueChanged: { + root.setEquation(equationLeft.currentValue, equationRight.currentValue); + } + } + + } + + } + + Tab { + id: codeTab + + title: "Code" + + Column { + spacing: 8 + + onVisibleChanged: { + if(tabPanel.currentIndex === 2) { + root.setEquation(codeInput.currentText); + } + } + + Component.onCompleted: { + root.setEquation(codeInput.currentText); + } + + StyledTextLabel { + text: qsTrc("inspector", "Enter Code") + " (eg: e = 100)" + } + + TextInputField { + id: codeInput + + height: 30 + width: root.width + + onCurrentTextEdited: { + root.setEquation(newTextValue); + } + } + } + + } + } + + SeparatorLine { } - Tab { - id: secondTab + StyledTextLabel { + text: qsTrc("Inspector", "Parentheses") + } + + RadioButtonGroup { + id: paranthesesList + + property int currentValue: root.currentParantheses + + height: 30 + width: root.width + + model: [ + { textRole: "None", valueRole: 0 }, + { textRole: "( )", valueRole: 1 }, + { textRole: "[ ]", valueRole: 2 } + ] + + delegate: FlatRadioButton { + ButtonGroup.group: paranthesesList.radioButtonGroup - title: "Equation" + checked: paranthesesList.currentValue === modelData["valueRole"] + onToggled: { + paranthesesList.currentValue = modelData["valueRole"] + root.addParantheses(); + } + + StyledTextLabel { + text: modelData["textRole"] + } + } + } + + SeparatorLine { } - Tab { - id: thirdTab + CheckBox { + id: visibilityToggle - title: "Code" + width: root.width + checked: root.model ? root.model.isEquationVisible.value : false; + + text: qsTrc("inspector", "Visibility") + onClicked: { + checked = !checked + root.model.isEquationVisible.value = checked; + } } + } diff --git a/src/palette/internal/palette/palettecreator.cpp b/src/palette/internal/palette/palettecreator.cpp index 2a4fa768207e2..da0c3ff99a1ba 100644 --- a/src/palette/internal/palette/palettecreator.cpp +++ b/src/palette/internal/palette/palettecreator.cpp @@ -88,6 +88,8 @@ #include "palette/palette.h" #include "translation.h" +#include "log.h" + using namespace mu::actions; namespace Ms { @@ -296,18 +298,16 @@ Palette* PaletteCreator::newLinesPalette() //--------------------------------------------------------- struct TempoPattern { - QString pattern; - const char* name; - double f; - bool relative; - bool italian; - bool followText; - bool basic; - bool masterOnly; - - TempoPattern(const QString& s, const char* n, double v, bool r, bool i, bool f, bool b, bool m) - : pattern(s), - name(n), f(v), relative(r), italian(i), followText(f), basic(b), masterOnly(m) {} + QString text; + QString equation; + QString name; + + TempoPattern(const QString& t, const QString& e, const QString& n = "") + : text(t) + , equation(e) + , name(n) + { + } }; //--------------------------------------------------------- @@ -1525,70 +1525,36 @@ PalettePanel* PaletteCreator::newTempoPalettePanel(bool defaultPalettePanel) } sp->setDrawGrid(true); - static const TempoPattern tps[] = { - TempoPattern("metNoteHalfUp = 80", QT_TRANSLATE_NOOP("palette", - "Half note = 80 BPM"), 80.0 / 30.0, false, false, true, true, - false), // 1/2 - TempoPattern("metNoteQuarterUp = 80", QT_TRANSLATE_NOOP("palette", - "Quarter note = 80 BPM"), 80.0 / 60.0, false, false, true, true, - false), // 1/4 - TempoPattern("metNote8thUp = 80", QT_TRANSLATE_NOOP("palette", - "Eighth note = 80 BPM"), 80.0 / 120.0, false, false, true, true, - false), // 1/8 - TempoPattern("metNoteHalfUpspacemetAugmentationDot = 80", - QT_TRANSLATE_NOOP("palette", - "Dotted half note = 80 BPM"), 120 / 30.0, false, false, true, false, false), // dotted 1/2 - TempoPattern("metNoteQuarterUpspacemetAugmentationDot = 80", - QT_TRANSLATE_NOOP("palette", - "Dotted quarter note = 80 BPM"), 120 / 60.0, false, false, true, true, false), // dotted 1/4 - TempoPattern("metNote8thUpspacemetAugmentationDot = 80", - QT_TRANSLATE_NOOP("palette", - "Dotted eighth note = 80 BPM"), 120 / 120.0, false, false, true, false, - false), // dotted 1/8 - - TempoPattern("Grave", "Grave", 35.0 / 60.0, false, true, false, false, false), - TempoPattern("Largo", "Largo", 50.0 / 60.0, false, true, false, false, false), - TempoPattern("Lento", "Lento", 52.5 / 60.0, false, true, false, false, false), - TempoPattern("Larghetto", "Larghetto", 63.0 / 60.0, false, true, false, false, true), - TempoPattern("Adagio", "Adagio", 71.0 / 60.0, false, true, false, false, false), - TempoPattern("Andante", "Andante", 92.0 / 60.0, false, true, false, false, false), - TempoPattern("Andantino", "Andantino", 94.0 / 60.0, false, true, false, false, true), - TempoPattern("Moderato", "Moderato", 114.0 / 60.0, false, true, false, false, false), - TempoPattern("Allegretto", "Allegretto", 116.0 / 60.0, false, true, false, false, false), - TempoPattern("Allegro moderato", "Allegro moderato", 118.0 / 60.0, false, true, false, false, true), - TempoPattern("Allegro", "Allegro", 144.0 / 60.0, false, true, false, false, false), - TempoPattern("Vivace", "Vivace", 172.0 / 60.0, false, true, false, false, false), - TempoPattern("Presto", "Presto", 187.0 / 60.0, false, true, false, false, false), - TempoPattern("Prestissimo", "Prestissimo", 200.0 / 60.0, false, true, false, false, true), - - TempoPattern( - "metNoteQuarterUp = metNoteQuarterUpspacemetAugmentationDot", QT_TRANSLATE_NOOP( - "palette", - "Quarter note = dotted quarter note metric modulation"), 3.0 / 2.0, true, false, true, false, false), - TempoPattern( - "metNoteQuarterUpspacemetAugmentationDot = metNoteQuarterUp", QT_TRANSLATE_NOOP( - "palette", - "Dotted quarter note = quarter note metric modulation"), 2.0 / 3.0, true, false, true, false, false), - TempoPattern("metNoteHalfUp = metNoteQuarterUp", - QT_TRANSLATE_NOOP("palette", - "Half note = quarter note metric modulation"), 1.0 / 2.0, true, false, true, false, - false), - TempoPattern("metNoteQuarterUp = metNoteHalfUp", - QT_TRANSLATE_NOOP("palette", - "Quarter note = half note metric modulation"), 2.0 / 1.0, true, false, true, false, - false), - TempoPattern("metNote8thUp = metNote8thUp", - QT_TRANSLATE_NOOP("palette", - "Eighth note = eighth note metric modulation"), 1.0 / 1.0, true, false, true, false, - false), - TempoPattern("metNoteQuarterUp = metNoteQuarterUp", - QT_TRANSLATE_NOOP("palette", - "Quarter note = quarter note metric modulation"), 1.0 / 1.0, true, false, true, false, - false), - TempoPattern( - "metNote8thUpspacemetAugmentationDot = metNoteQuarterUp", QT_TRANSLATE_NOOP( - "palette", - "Dotted eighth note = quarter note metric modulation"), 2.0 / 3.0, true, false, true, false, false), + static const TempoPattern tempoPatterns[] = { + TempoPattern("", "h = 80", "Half notes = 80 BPM"), + TempoPattern("", "q = 80", "Quarter notes = 80 BPM"), + TempoPattern("", "e = 80", "Eighth notes = 80 BPM"), + + TempoPattern("", "h. = 80", "Dotted half note = 80 BPM"), + TempoPattern("", "q. = 80", "Dotted quarter note = 80 BPM"), + TempoPattern("", "e. = 80", "Dotted eighth note = 80 BPM"), + + TempoPattern("Grave", "q = 35"), + TempoPattern("Largo", "q = 50"), + TempoPattern("Lento", "q = 52"), + TempoPattern("Larghetto", "q = 63"), + TempoPattern("Adagio", "q = 71"), + TempoPattern("Andante", "q = 92"), + TempoPattern("Andantino", "q = 94"), + TempoPattern("Moderato", "q = 114"), + TempoPattern("Allegretto", "q = 116"), + TempoPattern("Allegro moderato", "q = 118"), + TempoPattern("Allegro", "q = 144"), + TempoPattern("Vivace", "q = 172"), + TempoPattern("Presto", "q = 187"), + TempoPattern("Prestissimo", "q = 200"), + + TempoPattern("", "q = q.", "Quarter note = dotted quarter note metric modulation"), + TempoPattern("", "q. = q", "Dotted quarter note = quarter note metric modulation"), + TempoPattern("", "h = q", "Half note = quarter note metric modulation"), + TempoPattern("", "q = h", "Quarter note = half note metric modulation"), + TempoPattern("", "e = e", "Eighth note = eighth note metric modulation"), + TempoPattern("", "q = q", "Quarter note = quarter note metric modulation") }; auto stxt = makeElement(gscore); @@ -1597,20 +1563,18 @@ PalettePanel* PaletteCreator::newTempoPalettePanel(bool defaultPalettePanel) stxt->setSwing(true); sp->append(stxt, QT_TRANSLATE_NOOP("palette", "Swing"))->setElementTranslated(true); - for (TempoPattern tp : tps) { - auto tt = makeElement(gscore); - tt->setFollowText(tp.followText); - tt->setXmlText(tp.pattern); - if (tp.relative) { - tt->setRelative(tp.f); - sp->append(tt, mu::qtrc("palette", tp.name), 1.5); - } else if (tp.italian) { - tt->setTempo(tp.f); - sp->append(tt, tp.name, 1.3); - } else { - tt->setTempo(tp.f); - sp->append(tt, mu::qtrc("palette", tp.name), 1.5); - } + for (TempoPattern tempoPattern : tempoPatterns) { + auto tempoText = makeElement(gscore); + + tempoText->setFollowText(true); + tempoText->setXmlText(tempoPattern.text + " "); + tempoText->setEquation(tempoPattern.equation); + tempoText->setEquationVisible(tempoPattern.text.isEmpty()); + tempoText->parseEquation(); + + sp->append(tempoText, tempoPattern.name.isEmpty() ? tempoPattern.text : mu::qtrc("palette", + tempoPattern.name.toStdString().c_str()), + tempoPattern.name.isEmpty() ? 1.5 : 1.3); } sp->setMoreElements(false);