/*************************************************************************** * Copyright (C) 2017-2021 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * 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 <http://www.gnu.org/licenses/>. * ***************************************************************************/ #include "tmainscoreobject.h" #include <music/timportscore.h> #include <music/tkeysignature.h> #include <music/tmelody.h> #include <music/ttechnical.h> #include <qtr.h> #include <score/tmeasureobject.h> #include <score/tnoteitem.h> #include <score/tscoreobject.h> #include <score/tstaffitem.h> #include <score/tstafflines.h> #include <taction.h> #include <tcolor.h> #include <tglobals.h> #include <tnootkaqml.h> #include <tsound.h> #include <QtCore/qsettings.h> #include <QtCore/qtimer.h> #include <QtGui/qguiapplication.h> #include <QtGui/qpalette.h> #include <QtQml/qqmlapplicationengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlengine.h> #include <QtQuick/qquickitem.h> #include <string> #include <QtCore/qdebug.h> TmainScoreObject *TmainScoreObject::m_instance = nullptr; TmainScoreObject::TmainScoreObject(QObject *parent) : QObject(parent) { if (m_instance) { qDebug() << "TmainScoreObject instance already exists!"; return; } m_instance = this; m_goodNote = new Tnote(); m_showNamesAct = new Taction(tr("Show note names"), QString(), this); m_showNamesAct->setCheckable(true); m_showNamesAct->setChecked(GLOB->namesOnScore()); m_showNamesAct->setTip(tr("Shows names of all notes on the staff.")); m_extraAccidsAct = new Taction(tr("Additional accidentals"), QString(), this); m_extraAccidsAct->setCheckable(true); m_extraAccidsAct->setTip(tr( "Shows accidentals from the key signature also next to a note. <b>WARING! It never occurs in real scores - use it only for theoretical purposes.</b>")); m_extraAccidsAct->setChecked(false); // never stored - theoretical purposes m_zoomOutAct = new Taction(tr("Zoom score out"), QStringLiteral("zoom-out"), this); m_zoomInAct = new Taction(tr("Zoom score in"), QStringLiteral("zoom-in"), this); m_transposeAct = new Taction(qTR("Transpose", "Transpose"), QStringLiteral("transpose"), this); m_playAct = new Taction(qTR("TtoolBar", "Play"), QStringLiteral("playMelody"), this); m_playAct->setBgColor(QColor(0, 255, 0)); m_openXmlAct = new Taction(qTR("QShortcut", "Open"), QStringLiteral("open"), this); connect(m_openXmlAct, &Taction::triggered, this, &TmainScoreObject::openXmlActSlot); m_saveXmlAct = new Taction(qTR("QShortcut", "Save"), QStringLiteral("save"), this); connect(m_saveXmlAct, &Taction::triggered, this, &TmainScoreObject::getMelodyNameSlot); m_randMelodyAct = new Taction(tr("Generate melody"), QStringLiteral("melody"), this); m_randMelodyAct->setBgColor(qApp->palette().highlight().color()); connect(m_randMelodyAct, &Taction::triggered, this, &TmainScoreObject::randMelodySlot); m_randMelodyAct->setTip(tr("Generate a melody with random notes.")); m_melodyActions << m_playAct << m_openXmlAct << m_saveXmlAct << m_randMelodyAct; m_nextNoteAct = new Taction(tr("Next note"), QString(), this); m_prevNoteAct = new Taction(tr("Previous note"), QString(), this); m_notesMenuAct = new Taction(tr("notes", "musical notes of course") + QStringLiteral(" ⋮"), QStringLiteral("score"), this); m_reviewModeAct = new Taction(tr("Review mode"), QString(), this); m_reviewModeAct->setCheckable(true); m_reviewModeAct->setChecked(GLOB->showNotesDiff()); m_reviewModeAct->setTip(tr("Do not change score when playing on instrument and display bar to see selected and played notes.")); connect(m_reviewModeAct, &Taction::triggered, this, [=] { m_reviewModeAct->setChecked(!m_reviewModeAct->checked()); GLOB->setShowNotesDiff(m_reviewModeAct->checked()); }); QQmlComponent actionsComp(NOO->qmlEngine(), this); m_openXmlAct->createQmlShortcut(&actionsComp, "StandardKey.Open; enabled: !GLOB.singleNoteMode && !GLOB.isExam"); m_saveXmlAct->createQmlShortcut(&actionsComp, "StandardKey.Save; enabled: !GLOB.singleNoteMode && !GLOB.isExam"); m_zoomOutAct->createQmlShortcut(&actionsComp, "StandardKey.ZoomOut; enabled: !GLOB.singleNoteMode"); m_zoomInAct->createShortcutSequence(&actionsComp, "StandardKey.ZoomIn", "!GLOB.singleNoteMode"); // HACK: Create m_playAct action shortcut only when enabled. m_randMelodyAct->createQmlShortcut(&actionsComp, "\"Ctrl+M\"; enabled: !GLOB.singleNoteMode && !GLOB.isExam"); m_nextNoteAct->createQmlShortcut(&actionsComp, "StandardKey.MoveToNextChar"); m_prevNoteAct->createQmlShortcut(&actionsComp, "StandardKey.MoveToPreviousChar"); connect(SOUND, &Tsound::playingChanged, this, [=] { m_playAct->setIconTag(SOUND->playing() ? QLatin1String("stopMelody") : QLatin1String("playMelody")); m_playAct->setText(SOUND->playing() ? qTR("QShortcut", "Stop") : qTR("TtoolBar", "Play")); }); isExamChangedSlot(); connect(NOO, &TnootkaQML::wantOpenXml, this, &TmainScoreObject::openXmlFileSlot); qApp->installEventFilter(this); } TmainScoreObject::~TmainScoreObject() { delete m_goodNote; m_instance = nullptr; } void TmainScoreObject::setScoreObject(TscoreObject *scoreObj) { if (m_scoreObj) { qDebug() << "[TmainScoreObject] score object was already set. FIX IT!"; return; } m_scoreObj = scoreObj; m_scoreObj->enableActions(); connect(m_scoreObj, &TscoreObject::clicked, this, &TmainScoreObject::clicked); connect(m_scoreObj, &TscoreObject::readOnlyNoteClicked, this, &TmainScoreObject::readOnlyNoteClicked); connect(m_showNamesAct, &Taction::triggered, this, [=] { m_showNamesAct->setChecked(!m_showNamesAct->checked()); m_scoreObj->setShowNoteNames(m_showNamesAct->checked()); }); connect(m_extraAccidsAct, &Taction::triggered, this, [=] { m_extraAccidsAct->setChecked(!m_extraAccidsAct->checked()); m_scoreObj->setShowExtraAccids(m_extraAccidsAct->checked()); }); connect(m_playAct, &Taction::triggered, this, &TmainScoreObject::playScoreSlot); connect(m_zoomOutAct, &Taction::triggered, this, [=] { m_scoreObj->setScaleFactor(qMax(0.4, m_scoreObj->scaleFactor() - 0.2)); }); connect(m_zoomInAct, &Taction::triggered, this, [=] { m_scoreObj->setScaleFactor(qMin(m_scoreObj->scaleFactor() + 0.2, 1.4)); }); connect(GLOB, &Tglobals::isExamChanged, this, &TmainScoreObject::isExamChangedSlot); connect(m_scoreObj, &TscoreObject::singleNoteChanged, this, &TmainScoreObject::singleModeSlot); connect(GLOB, &Tglobals::showEnharmNotesChanged, this, &TmainScoreObject::checkSingleNoteVisibility); connect(GLOB, &Tglobals::enableDoubleAccidsChanged, this, &TmainScoreObject::checkSingleNoteVisibility); connect(m_scoreObj, &TscoreObject::keySignatureChanged, this, [=] { if (GLOB->keySignatureEnabled() && GLOB->showKeyName() && !GLOB->isExam()) emit keyNameTextChanged(); }); connect(GLOB, &Tglobals::keyNameChanged, this, [=] { if (GLOB->keySignatureEnabled() && GLOB->showKeyName() && !GLOB->isExam()) emit keyNameTextChanged(); }); connect(m_scoreObj->clearScoreAct(), &Taction::triggered, this, [=] { if (!m_scoreObj->singleNote() && !GLOB->isExam()) { SOUND->stopPlaying(); m_title.clear(); m_composer.clear(); emit titleChanged(); } }); m_scoreObj->clearScoreAct()->setBgColor(QColor(255, 140, 0)); // orange #if !defined(Q_OS_ANDROID) m_scoreActions.prepend(m_reviewModeAct); m_scoreActions.prepend(m_scoreObj->editModeAct()); m_scoreActions << m_scoreObj->insertNoteAct() << m_scoreObj->deleteNoteAct() << m_scoreObj->clearScoreAct() << m_transposeAct << m_notesMenuAct; #else m_scoreActions << m_randMelodyAct << m_openXmlAct << m_saveXmlAct; #endif m_noteActions << m_scoreObj->riseAct() << m_scoreObj->lowerAct() << m_scoreObj->wholeNoteAct() << m_scoreObj->halfNoteAct() << m_scoreObj->quarterNoteAct() << m_scoreObj->eighthNoteAct() << m_scoreObj->sixteenthNoteAct() << m_scoreObj->restNoteAct() << m_scoreObj->dotNoteAct() << m_scoreObj->tieAct(); connect(m_nextNoteAct, &Taction::triggered, this, [=] { if (!GLOB->isSingleNote()) { auto noteItem = m_scoreObj->selectedItem() ? m_scoreObj->getNext(m_scoreObj->selectedItem()) : m_scoreObj->note(0); if (m_scoreObj->readOnly()) { if (noteItem) emit m_scoreObj->readOnlyNoteClicked(noteItem->index()); } else m_scoreObj->setSelectedItem(noteItem); } }); connect(m_prevNoteAct, &Taction::triggered, this, [=] { if (!GLOB->isSingleNote()) { auto noteItem = m_scoreObj->selectedItem() ? m_scoreObj->getPrev(m_scoreObj->selectedItem()) : m_scoreObj->note(m_scoreObj->notesCount() - 1); if (m_scoreObj->readOnly()) { if (noteItem) emit m_scoreObj->readOnlyNoteClicked(noteItem->index()); } else m_scoreObj->setSelectedItem(noteItem); } }); connect(m_scoreObj, &TscoreObject::stavesHeightChanged, this, &TmainScoreObject::checkExtraStaves); connect(m_scoreObj, &TscoreObject::meterChanged, this, [=] { SOUND->setCurrentMeter(m_scoreObj->meterToInt()); }); SOUND->setCurrentMeter(m_scoreObj->meterToInt()); if (!GLOB->isSingleNote() && GLOB->gotIt(QStringLiteral("noteSelected"), true)) connect(m_scoreObj, &TscoreObject::selectedItemChanged, this, &TmainScoreObject::gotItNoteSelectedSlot); if (!m_playAct->shortcut() && !GLOB->isSingleNote()) { // HACK: Create play action shortcut only when enabled, otherwise it locks next question shortcut QQmlComponent actionsComp(NOO->qmlEngine(), this); m_playAct->createQmlShortcut(&actionsComp, "\" \"; enabled: !GLOB.singleNoteMode && !GLOB.isExam"); } } QString TmainScoreObject::keyNameText() const { return m_scoreObj ? NOO->majAndMinKeyName(m_scoreObj->keySignature()) : QString(); } void TmainScoreObject::setMainScoreItem(QQuickItem *msItem) { m_mainScoreItem = msItem; connect(m_mainScoreItem, &QQuickItem::heightChanged, this, &TmainScoreObject::checkExtraStaves); } int TmainScoreObject::notesCount() const { return m_scoreObj->notesCount(); } void TmainScoreObject::setReadOnly(bool ro) { if (ro != m_scoreObj->readOnly()) { m_scoreObj->setReadOnly(ro); m_scoreObj->setAllowAdding(!ro); m_notesMenuAct->setEnabled(!ro && m_scoreObj->meter()->meter() != Tmeter::NoMeter); } } void TmainScoreObject::clearScore() { m_scoreObj->clearScore(); if (m_questionKey) { delete m_questionKey; m_questionKey = nullptr; } m_questionMark->setVisible(false); m_scoreObj->setBgColor(qApp->palette().base().color()); if (m_scoreObj->singleNote()) { m_scoreObj->note(1)->setTechnical(NO_TECHNICALS); m_scoreObj->note(0)->markNoteHead(Qt::transparent); m_scoreObj->note(1)->markNoteHead(Qt::transparent); } showNoteNames(false); } void TmainScoreObject::setKeySignatureEnabled(bool enableKey) { m_scoreObj->setKeySignatureEnabled(enableKey); } void TmainScoreObject::setKeySignature(const TkeySignature &key) { m_scoreObj->setKeySignature(static_cast<int>(key.value())); } char TmainScoreObject::keySignatureValue() { return static_cast<char>(m_scoreObj->keySignature()); } int TmainScoreObject::clefType() const { return static_cast<int>(m_scoreObj->clefType()); } void TmainScoreObject::setClef(int clefType) { m_scoreObj->setClefType(static_cast<Tclef::EclefType>(clefType)); } int TmainScoreObject::meter() const { return m_scoreObj->meterToInt(); } void TmainScoreObject::setMeter(int meterType) { m_scoreObj->setMeter(meterType); } Tnote TmainScoreObject::getNote(int id) { return m_scoreObj->noteAt(id); } int TmainScoreObject::setSelectedItem(int id) { auto n = m_scoreObj->note(id); int notesSpan = 0; m_scoreObj->setSelectedItem(n); if (n) { notesSpan = 1; if (n->note()->isRest()) { id++; while (id < m_scoreObj->notesCount() && m_scoreObj->noteList().at(id).isRest()) { notesSpan++; id++; } } else if (n->note()->rtm.tie() == Trhythm::e_tieStart) { id++; while (id < m_scoreObj->notesCount() && m_scoreObj->noteList().at(id).rtm.tie() && m_scoreObj->noteList().at(id).rtm.tie() != Trhythm::e_tieStart) { notesSpan++; id++; } } } return notesSpan; } void TmainScoreObject::setTechnical(int noteId, quint32 tech) { m_scoreObj->setTechnical(noteId, tech); } bool TmainScoreObject::selectInReadOnly() const { return m_scoreObj->selectInReadOnly(); } void TmainScoreObject::setSelectInReadOnly(bool sel) { m_scoreObj->setSelectInReadOnly(sel); } quint32 TmainScoreObject::technical(int noteId) { if (noteId >= 0 && noteId < m_scoreObj->notesCount()) return m_scoreObj->note(noteId)->technical(); return 255; } void TmainScoreObject::setMelody(Tmelody *mel) { m_scoreObj->setMelody(mel); } void TmainScoreObject::getMelody(Tmelody *melody) { m_scoreObj->getMelody(melody); } Taction *TmainScoreObject::clearScoreAct() { return m_scoreObj ? m_scoreObj->clearScoreAct() : nullptr; } Taction *TmainScoreObject::scoreMenuAct() { return NOO->scoreAct(); } void TmainScoreObject::askQuestion(Tmelody *mel, bool ignoreTechnical, const TkeySignature &melodyKey) { m_scoreObj->setBgColor(scoreBackgroundColor(GLOB->EquestionColor, 20)); int transposition = 0; auto tempKey = mel->key(); if (mel->key() != melodyKey) { transposition = mel->key().difference(melodyKey); mel->setKey(melodyKey); } m_scoreObj->setMelody(mel, ignoreTechnical, 0, transposition); mel->setKey(tempKey); m_scoreObj->setReadOnly(true); m_questionMark->setVisible(true); } /** * We are sure that this kind of questions occurs only in single note mode */ void TmainScoreObject::askQuestion(const Tnote ¬e, quint32 technicalData) { m_scoreObj->setBgColor(scoreBackgroundColor(GLOB->EquestionColor, 20)); m_scoreObj->setNote(m_scoreObj->note(1), note); m_questionMark->setVisible(true); m_scoreObj->note(1)->setTechnical(technicalData); } void TmainScoreObject::askQuestion(const Tnote ¬e, const TkeySignature &key, quint32 technicalData) { setKeySignature(key); askQuestion(note, technicalData); } void TmainScoreObject::prepareKeyToAnswer(const TkeySignature &fakeKey, const QString &expectKeyName) { setKeySignature(fakeKey); if (!m_questionKey) { auto p = qobject_cast<QQuickItem *>(parent()); // parent: MainScore.qml auto nameItem = qvariant_cast<QQuickItem *>(p->property("keyName")); m_scoreObj->component()->setData( "import QtQuick 2.9; Text { horizontalAlignment: Text.AlignHCenter;" "font.pixelSize: 12; transformOrigin: Item.TopLeft; scale: 0.17 }", QUrl()); m_questionKey = qobject_cast<QQuickItem *>(m_scoreObj->component()->create()); if (m_questionKey && nameItem) { m_questionKey->setParentItem(nameItem->parentItem()); m_questionKey->setProperty("text", expectKeyName + QLatin1String("<br>?")); m_questionKey->setProperty("color", GLOB->EquestionColor); m_questionKey->setX(nameItem->x()); m_questionKey->setY(nameItem->y()); } } } void TmainScoreObject::showNoteNames(bool showName) { if (m_scoreObj->singleNote()) { m_scoreObj->note(0)->setNoteNameVisible(showName); // m_scoreObj->note(1)->setNoteNameVisible(showName); } else { m_scoreObj->setShowNoteNames(showName); } } void TmainScoreObject::showNoteName(int noteNr, bool showName) { auto note = m_scoreObj->note(noteNr); if (note) note->setNoteNameVisible(showName); } void TmainScoreObject::forceAccidental(int accid) { m_scoreObj->setCursorAlter(accid); } void TmainScoreObject::unLockScore() { m_scoreObj->setBgColor(scoreBackgroundColor(GLOB->EanswerColor, 20)); setReadOnly(false); } void TmainScoreObject::lockKeySignature(bool lock) { m_scoreObj->setKeyReadOnly(lock); } int TmainScoreObject::markNoteHead(const QColor &outColor, int noteNr) { int markedCount = 0; auto noteItem = m_scoreObj->note(noteNr); if (noteItem) { noteItem->markNoteHead(outColor); markedCount = 1; if (noteItem->note()->rtm.tie()) { noteNr++; while (noteNr < m_scoreObj->notesCount() && m_scoreObj->noteList()[noteNr].rtm.tie() && m_scoreObj->noteList()[noteNr].rtm.tie() != Trhythm::e_tieStart) { auto nextNote = m_scoreObj->note(noteNr); if (nextNote) { nextNote->markNoteHead(outColor); noteNr++; markedCount++; } else break; } } else if (noteItem->note()->isRest()) { noteNr++; while (noteNr < m_scoreObj->notesCount() && m_scoreObj->noteList()[noteNr].isRest()) { auto nextNote = m_scoreObj->note(noteNr); if (nextNote) { nextNote->markNoteHead(outColor); noteNr++; markedCount++; } else break; } } } return markedCount; } void TmainScoreObject::correctNote(const Tnote &goodNote, bool corrAccid) { if (corrAccid) { // FIXME probably it is not necessary - animation is universal for any kind of score mistake } m_correctNoteId = m_scoreObj->singleNote() ? 0 : (m_scoreObj->selectedItem() ? m_scoreObj->selectedItem()->index() : -1); auto noteItem = m_scoreObj->note(m_correctNoteId); if (noteItem) { if (!m_animationObj) { QQmlComponent comp(m_scoreObj->qmlEngine(), QUrl(QStringLiteral("qrc:/exam/CorrectNoteAnim.qml"))); m_animationObj = qobject_cast<QObject *>(comp.create()); m_animationObj->setParent(this); connect(m_animationObj, SIGNAL(finished()), this, SLOT(correctionFinishedSlot())); connect(m_animationObj, SIGNAL(applyCorrect()), this, SLOT(applyCorrectSlot())); } m_animationObj->setProperty("noteHead", QVariant::fromValue(m_scoreObj->noteHead(m_correctNoteId))); m_animationObj->setProperty("endY", noteItem->getHeadY(goodNote) - 15.0); m_animationObj->setProperty("running", true); *m_goodNote = goodNote; } } void TmainScoreObject::correctKeySignature(const TkeySignature &keySign) { if (m_questionKey) { m_questionKey->setProperty("text", keySign.getName() + QLatin1String("<br>!")); m_questionKey->setProperty("color", GLOB->EanswerColor); } if (keySign != keySignatureValue()) m_scoreObj->setKeySignature(keySign.value()); } void TmainScoreObject::saveMusicXml(const QString &fileName, const QString &title, const QString &composer) { QString fn = fileName; if (fileName.isEmpty()) // ask for file name if this parameter was empty fn = NOO->getXmlToSave(composer + QLatin1String(" - ") + title); if (!fn.isEmpty()) { m_scoreObj->saveMusicXml(fn, title, composer, GLOB->transposition()); if (!title.isEmpty() && title != qTR("MelodyNameDialog", "Nootka melody")) { auto lt = GLOB->config->value(QLatin1String("Melody/recentTitles"), QStringList()).toStringList(); lt.prepend(title); lt.removeDuplicates(); while (lt.size() > 10) lt.removeLast(); GLOB->config->setValue(QLatin1String("Melody/recentTitles"), lt); } if (!composer.isEmpty() && composer != QLatin1String("Nootka The Composer")) { auto lc = GLOB->config->value(QLatin1String("Melody/recentComposers"), QStringList()).toStringList(); lc.prepend(composer); lc.removeDuplicates(); while (lc.size() > 10) lc.removeLast(); GLOB->config->setValue(QLatin1String("Melody/recentComposers"), lc); } m_title = title; m_composer = composer; emit titleChanged(); } } QStringList TmainScoreObject::recentTitles() const { auto lt = GLOB->config->value(QLatin1String("Melody/recentTitles"), QStringList()).toStringList(); lt.prepend(QGuiApplication::translate("MelodyNameDialog", "Nootka melody")); return lt; } QStringList TmainScoreObject::recentComposers() const { auto lc = GLOB->config->value(QLatin1String("Melody/recentComposers"), QStringList()).toStringList(); lc.prepend(QLatin1String("Nootka The Composer")); return lc; } // ################################################################################################# // ################### PROTECTED ############################################ // ################################################################################################# void TmainScoreObject::openXmlActSlot() { SOUND->stopListen(); auto xmlFile = NOO->getXmlToOpen(); openXmlFileSlot(xmlFile); SOUND->startListen(); } void TmainScoreObject::getMelodyNameSlot() { emit melodyNameDialog(); } void TmainScoreObject::randMelodySlot() { emit melodyGenerate(); } void TmainScoreObject::isExamChangedSlot() { m_scoreActions.clear(); if (GLOB->isExam()) { m_scoreActions << m_zoomOutAct << m_zoomInAct; if (!m_questionMark) { m_scoreObj->component()->setData( "import QtQuick 2.9; Text { anchors.centerIn: parent ? parent : undefined; scale: parent ? parent.height / height : 1; text: \"?\"; font { " "family: \"Nootka\"; pixelSize: 20 }}", QUrl()); m_questionMark = qobject_cast<QQuickItem *>(m_scoreObj->component()->create()); if (m_questionMark) { m_questionMark->setParentItem(qvariant_cast<QQuickItem *>(qobject_cast<QQuickItem *>(m_scoreObj->parent())->property("bgRect"))); m_questionMark->setVisible(false); if (m_questionMark) m_questionMark->setProperty("color", scoreBackgroundColor(GLOB->EquestionColor, 40)); } } singleModeSlot(); } else { m_scoreActions << m_showNamesAct << m_extraAccidsAct << m_zoomOutAct << m_zoomInAct; if (m_questionMark) { delete m_questionMark; m_questionMark = nullptr; } if (!GLOB->isSingleNote()) { m_openXmlAct->setEnabled(true); m_saveXmlAct->setEnabled(true); m_randMelodyAct->setEnabled(true); } } if (m_scoreObj) { #if !defined(Q_OS_ANDROID) m_scoreActions.prepend(m_reviewModeAct); m_scoreActions.prepend(m_scoreObj->editModeAct()); m_scoreActions << m_scoreObj->insertNoteAct() << m_scoreObj->deleteNoteAct() << m_scoreObj->clearScoreAct() << m_transposeAct << m_notesMenuAct; #else if (!GLOB->isExam()) m_scoreActions << m_randMelodyAct << m_openXmlAct << m_saveXmlAct; #endif } emit scoreActionsChanged(); } void TmainScoreObject::singleModeSlot() { if (GLOB->isSingleNote()) { m_openXmlAct->setEnabled(false); m_saveXmlAct->setEnabled(false); m_randMelodyAct->setEnabled(false); if (GLOB->isExam()) { m_scoreObj->note(1)->setColor(qApp->palette().text().color()); m_scoreObj->note(2)->setColor(qApp->palette().text().color()); } else { m_scoreObj->note(1)->setColor(GLOB->getEnharmNoteColor()); m_scoreObj->note(2)->setColor(GLOB->getEnharmNoteColor()); } checkSingleNoteVisibility(); } else { if (!GLOB->isExam()) { m_openXmlAct->setEnabled(true); m_saveXmlAct->setEnabled(true); m_randMelodyAct->setEnabled(true); if (!m_playAct->shortcut()) { // HACK: Create play action shortcut only when enabled, otherwise it locks next question shortcut QQmlComponent actionsComp(NOO->qmlEngine(), this); m_playAct->createQmlShortcut(&actionsComp, "\" \"; enabled: !GLOB.singleNoteMode && !GLOB.isExam"); } } } } void TmainScoreObject::applyCorrectSlot() { markNoteHead(GLOB->correctColor(), m_correctNoteId); } void TmainScoreObject::correctionFinishedSlot() { m_scoreObj->setNote(m_correctNoteId, *m_goodNote); m_correctNoteId = -1; emit correctionFinished(); } void TmainScoreObject::playScoreSlot() { int countDownDur = 0; if (SOUND->tickDuringPlay()) { // play from selected note but tick from current bar beginning, so calculate initial duration if (m_scoreObj->selectedItem() && m_scoreObj->selectedItem()->index() > 0) countDownDur = m_scoreObj->selectedItem()->measure()->durationBefore(m_scoreObj->selectedItem()); } SOUND->playNoteList(m_scoreObj->noteList(), m_scoreObj->selectedItem() ? m_scoreObj->selectedItem()->index() : 0, countDownDur); } void TmainScoreObject::gotItNoteSelectedSlot() { if (m_scoreObj->selectedItem()) { disconnect(m_scoreObj, &TscoreObject::selectedItemChanged, this, &TmainScoreObject::gotItNoteSelectedSlot); if (!GLOB->isExam()) emit wantSelectGotIt(); } } void TmainScoreObject::openXmlFileSlot(const QString &xmlFile) { if (!xmlFile.isEmpty()) { auto melImport = new TimportScore(xmlFile); connect(melImport, &TimportScore::wantDialog, this, [=] { auto nootWin = qobject_cast<QQmlApplicationEngine *>(NOO->qmlEngine())->rootObjects().first(); if (nootWin && QString(nootWin->metaObject()->className()).contains("MainWindow_QMLTYPE")) { QMetaObject::invokeMethod(nootWin, "showDialog", Q_ARG(QVariant, TnootkaQML::ScoreImport)); connect(melImport, &TimportScore::importReady, this, &TmainScoreObject::melodyImportSlot); } }); connect(melImport, &TimportScore::xmlWasRead, this, [=] { if (!IMPORT_SCORE->hasMoreParts()) { NOO->scoreObj()->setMelody(IMPORT_SCORE->mainMelody(), !GLOB->instrument().bandoneon() && !GLOB->instrument().isGuitar()); // NOTE use here NOO->scoreObj() because m_scoreObj may be yet unset SOUND->setMetronome(IMPORT_SCORE->mainMelody()->tempo(), IMPORT_SCORE->mainMelody()->beat()); m_title = IMPORT_SCORE->mainMelody()->title(); m_composer = IMPORT_SCORE->mainMelody()->composer(); emit titleChanged(); IMPORT_SCORE->deleteLater(); } }); melImport->runXmlThread(); } } void TmainScoreObject::melodyImportSlot() { for (auto mi : IMPORT_SCORE->model()) { auto voicePart = qobject_cast<TmelodyPart *>(mi); if (voicePart && !voicePart->parts.isEmpty()) { for (auto snip : voicePart->parts) { if (snip->selected()) { NOO->scoreObj()->setMelody(snip->melody(), GLOB->instrument().type() != Tinstrument::Bandoneon && !GLOB->instrument().isGuitar()); // NOTE use here NOO->scoreObj() because m_scoreObj may be yet unset SOUND->setMetronome(snip->melody()->tempo(), snip->melody()->beat()); m_title = snip->melody()->title(); m_composer = snip->melody()->composer(); emit titleChanged(); break; } } } } } QColor TmainScoreObject::scoreBackgroundColor(const QColor &c, int alpha) { return Tcolor::merge(NOO->alpha(c, alpha), qApp->palette().base().color()); } void TmainScoreObject::checkSingleNoteVisibility() { if (m_scoreObj && m_scoreObj->singleNote()) { m_scoreObj->note(1)->setVisible(GLOB->showEnharmNotes() || GLOB->isExam()); m_scoreObj->note(2)->setVisible(!GLOB->isExam() && GLOB->showEnharmNotes() && GLOB->enableDoubleAccids()); m_scoreObj->setNote(0, m_scoreObj->noteAt(0)); // refresh } } void TmainScoreObject::checkExtraStaves() { if (m_scoreObj == nullptr || m_mainScoreItem == nullptr) return; auto staff = m_scoreObj->lastStaff(); int emptyStavesCount = qMax(0, static_cast<int>((m_mainScoreItem->height() - (staff->y() + staff->height() * staff->scale())) / (staff->scale() * 16.0))); if (GLOB->isSingleNote()) emptyStavesCount = 0; if (m_emptyStaves.count() != emptyStavesCount) { if (m_emptyStaves.count() > emptyStavesCount) { // remove some staff lines int toRemove = m_emptyStaves.count() - emptyStavesCount; for (int s = 0; s < toRemove; ++s) delete m_emptyStaves.takeLast(); } else { // add empty staff lines for (int s = m_emptyStaves.count(); s < emptyStavesCount; ++s) { auto newEmpty = new TstaffLines(m_mainScoreItem); m_emptyStaves << newEmpty; newEmpty->setTransformOrigin(QQuickItem::Left); newEmpty->setEnabled(false); } } } if (emptyStavesCount) { for (int s = 0; s < emptyStavesCount; ++s) { auto emptyStaff = m_emptyStaves[s]; emptyStaff->setScale(staff->scale()); emptyStaff->setX((m_scoreObj->clefType() == Tclef::PianoStaffClefs ? 3 : 0.5) * staff->scale()); emptyStaff->setWidth(staff->width() - (m_scoreObj->clefType() == Tclef::PianoStaffClefs ? 3.5 : 1.0)); emptyStaff->setStaffScale(staff->scale()); emptyStaff->setY(staff->y() + (10.0 + staff->height() + s * 16.0) * staff->scale()); } } } #if !defined(Q_OS_ANDROID) bool TmainScoreObject::eventFilter(QObject *obj, QEvent *event) { if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) { if (m_questionMark) m_questionMark->setProperty("color", scoreBackgroundColor(GLOB->EquestionColor, 40)); } return QObject::eventFilter(obj, event); } #endif