/*************************************************************************** * Copyright (C) 2017-2020 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 "tnootkaqml.h" #include "instruments/tbandoneonbg.h" #include "instruments/tguitarbg.h" #include "instruments/tpianobg.h" #include "instruments/tsaxbg.h" #include "music/timportscore.h" #include "music/tkeysignature.h" #include "music/tnamestylefilter.h" #include "music/ttuneobject.h" #include "nootkaconfig.h" #include "qtr.h" #include "score/taddnoteitem.h" #include "score/tdummychord.h" #include "score/tmelodypreview.h" #include "score/tnoteitem.h" #include "score/tnotepair.h" #include "score/tscoreobject.h" #include "score/tstaffitem.h" #include "score/tstafflines.h" #include "taction.h" #include "tcolor.h" #include "tglobals.h" #include "tmtr.h" #include "tpath.h" #if defined(Q_OS_ANDROID) #include <Android/tandroid.h> #endif #include <QtCore/qbuffer.h> #include <QtCore/qdatetime.h> #include <QtCore/qfileinfo.h> #include <QtCore/qrandom.h> #include <QtCore/qtimer.h> #include <QtGui/qdesktopservices.h> #include <QtGui/qpalette.h> #include <QtQml/qqmlapplicationengine.h> #include <QtWidgets/qapplication.h> #include "Android/tfiledialog.h" #include <QtCore/qdebug.h> TnootkaQML *TnootkaQML::m_instance = nullptr; TnootkaQML::TnootkaQML(QObject *parent) : QObject(parent) { if (m_instance) { qDebug() << "TnootkaQML instance already exists!"; return; } m_instance = this; qRegisterMetaType<Trhythm>(); qRegisterMetaType<Tmeter>(); qRegisterMetaType<Tclef>(); qRegisterMetaType<Ttune>(); qmlRegisterType<TscoreObject>("Score", 1, 0, "TscoreObject"); qmlRegisterType<TstaffItem>("Score", 1, 0, "TstaffItem"); qmlRegisterType<TnoteItem>("Score", 1, 0, "TnoteItem"); qmlRegisterType<TstaffLines>("Score", 1, 0, "TstaffLines"); qmlRegisterType<TaddNoteItem>("Score", 1, 0, "TaddNoteItem"); qmlRegisterType<TmelodyPreview>("Score", 1, 0, "TmelodyPreview"); qmlRegisterUncreatableType<TmelodyPart>("Score", 1, 0, "TmelodyPart", QStringLiteral("You cannot create an instance of the TcommonInstrument.")); qmlRegisterType<TdummyChord>("Score", 1, 0, "TdummyChord"); qmlRegisterUncreatableType<TcommonInstrument>("Nootka", 1, 0, "TcommonInstrument", QStringLiteral("You cannot create an instance of the TcommonInstrument.")); qmlRegisterType<TguitarBg>("Nootka", 1, 0, "TguitarBg"); qmlRegisterType<TpianoBg>("Nootka", 1, 0, "TpianoBg"); qmlRegisterType<TbandoneonBg>("Nootka", 1, 0, "TbandoneonBg"); qmlRegisterType<TsaxBg>("Nootka", 1, 0, "TsaxBg"); qmlRegisterType<Taction>("Nootka", 1, 0, "Taction"); qmlRegisterUncreatableType<TnootkaQML>("Nootka", 1, 0, "Nootka", QStringLiteral("You cannot create an instance of the TnootkaQML.")); qmlRegisterType<TtuneObject>("Nootka", 1, 0, "TtuneObject"); } TnootkaQML::~TnootkaQML() { m_instance = nullptr; } // ################################################################################################# // ################### INVOKABLE METHODS ############################################ // ################################################################################################# QString TnootkaQML::version() { if (qApp->arguments().last().contains(QLatin1String("--no-version"))) return QString(); else return NOOTKA_VERSION; } Tclef TnootkaQML::clef(int type) { return Tclef(static_cast<Tclef::EclefType>(type)); } Tmeter TnootkaQML::meter(int m) { return Tmeter(static_cast<Tmeter::Emeter>(m)); } Tnote TnootkaQML::note(int pitch, int octave, int alter, int rhythm, bool rest, bool dot) { return Tnote(static_cast<char>(pitch), static_cast<char>(octave), static_cast<char>(alter), Trhythm(static_cast<Trhythm::Erhythm>(rhythm), rest, dot, false)); } Tnote TnootkaQML::note(const Tnote &n, int rhythm, bool rest, bool dot) { return Tnote(n, Trhythm(static_cast<Trhythm::Erhythm>(rhythm), rest, dot, false)); } Tnote TnootkaQML::note(const Tnote &n, const Trhythm &r) { return Tnote(n, r); } Tnote TnootkaQML::note(int chroma, bool sharp) { Tnote n(static_cast<short>(chroma)); if (!sharp && (n.alter() != 0 || (n.alter() == 0 && n.note() != 3 && n.note() != 7))) // but skip e and b - otherwise they become fb or cb n = n.showWithFlat(); return n; } Tnote TnootkaQML::setUpperStaff(Tnote n, bool onUpper) { n.setOnUpperStaff(onUpper); return n; } Tnote TnootkaQML::emptyNote() { return Tnote(); } Tnote TnootkaQML::transpose(Tnote n, int semitones) { n.transpose(semitones); return n; } Trhythm TnootkaQML::rhythm(int rtm, bool rest, bool dot, bool triplet) { return Trhythm(static_cast<Trhythm::Erhythm>(rtm), rest, dot, triplet); } /** * It returns glyphs of 'Nootka' font in contrary to TnoteObject::getHeadText() */ QString TnootkaQML::rhythmText(const Trhythm &r) { if (r.rhythm() == Trhythm::NoRhythm) return QStringLiteral("z"); // just black note-head QString out; if (r.isRest()) out = QString(QChar(0xe106 + static_cast<int>(r.rhythm()))); else out = QString(QChar(66 + static_cast<int>(r.rhythm()))); if (r.hasDot()) out += QStringLiteral("."); return out; } QString TnootkaQML::noteName(const Tnote &n, int style, bool showOctave) { // Tnote::toText() method returns only names in user preferred style according to settings // To cheat it and force note name in any given style we are resetting pointer of is7th_B, // then Tnote skips filtering of a style during name generation. auto tmpPtr = TnameStyleFilter::is7th_B(); TnameStyleFilter::setStyleFilter(nullptr, TnameStyleFilter::solfegeStyle()); auto name = n.toText(static_cast<Tnote::EnameStyle>(style), showOctave); TnameStyleFilter::setStyleFilter(tmpPtr, TnameStyleFilter::solfegeStyle()); // restore is7th_B settings return name; } /** * So far this method doesn't cheat @p TnameStyleFilter, so improper style for 7th note will be fixed */ QString TnootkaQML::styledName(const Tnote &n, int style, bool showOctave) { auto tmpStyle = Tnote::defaultStyle; Tnote::defaultStyle = static_cast<Tnote::EnameStyle>(style); auto name = n.styledName(showOctave); Tnote::defaultStyle = tmpStyle; return name; } QString TnootkaQML::majorKeyName(int key) { return TkeySignature(static_cast<char>(key)).getMajorName(); } QString TnootkaQML::minorKeyName(int key) { return TkeySignature(static_cast<char>(key)).getMinorName(); } QString TnootkaQML::majAndMinKeyName(int key) { return majorKeyName(key) + QLatin1String("<br>") + minorKeyName(key); } QStringList TnootkaQML::keyComboModel() { QStringList model; for (int i = -7; i < 8; i++) { TkeySignature k(i); model << QLatin1String("(") + k.accidNumber() + QLatin1String(") ") + k.getMajorName() + QLatin1String(" / ") + k.getMinorName(); } return model; } /** * Returns difference in semitones between keys @p key1 and @p key2 * expressed in values [-7 to 7] [7b to 7#] */ int TnootkaQML::keysDiff(int key1, int key2) { TkeySignature k1(static_cast<char>(key1)); return k1.difference(TkeySignature(static_cast<char>(key2))); } bool TnootkaQML::isAndroid() { #if defined(Q_OS_ANDROID) return true; #else return false; #endif } bool TnootkaQML::isWindows() { #if defined(Q_OS_WIN) return true; #else return false; #endif } bool TnootkaQML::isMac() { #if defined(Q_OS_MACOS) return true; #else return false; #endif } QStringList TnootkaQML::guitarTunings() { QStringList tunList; int start = static_cast<int>(Ttune::Standard_EADGBE); for (int t = start; t < start + 5; ++t) tunList << Ttune::definedName(static_cast<Ttune::Etunings>(t)); tunList << QApplication::translate("InstrumentPage", "Custom tuning"); return tunList; } QStringList TnootkaQML::bassTunings() { QStringList tunList; int start = static_cast<int>(Ttune::Bass4_EADG); for (int t = start; t < start + 4; ++t) tunList << Ttune::definedName(static_cast<Ttune::Etunings>(t)); tunList << QApplication::translate("InstrumentPage", "Custom tuning"); return tunList; } QStringList TnootkaQML::ukuleleTunings() { QStringList tunList; tunList << Ttune::definedName(Ttune::Ukulele_GCEA) << Ttune::definedName(Ttune::Ukulele_Raised); tunList << qTR("InstrumentPage", "Custom tuning"); return tunList; } /** * Tuning names text model - for instruments with tunings */ QStringList TnootkaQML::tuningModel(int instr) { switch (static_cast<Tinstrument::Etype>(instr)) { case Tinstrument::ClassicalGuitar: case Tinstrument::ElectricGuitar: return guitarTunings(); case Tinstrument::BassGuitar: return bassTunings(); case Tinstrument::Ukulele: return ukuleleTunings(); default: return QStringList(); } } Ttune TnootkaQML::tuning(int tuningType) { if (tuningType > -1) { if (tuningType == 0) return Ttune::stdTune; if (tuningType < 5) return Ttune::tunes[tuningType - 1]; if (tuningType > 99 && tuningType < 104) return Ttune::bassTunes[tuningType - 100]; if (tuningType == 110) return Ttune::ukuleleGCEA; if (tuningType == 111) return Ttune::ukuleleRaised; } return Ttune(); } /** * When third note @s3 is valid the tuning represents real guitar tuning * otherwise it is an instrument scale */ Ttune TnootkaQML::tuning(const Tnote &s1, const Tnote &s2, const Tnote &s3, const Tnote &s4, const Tnote &s5, const Tnote &s6) { return Ttune(QApplication::translate("InstrumentPage", "Custom tuning"), s1, s2, s3, s4, s5, s6, s3.isValid() ? Ttune::Custom : Ttune::Scale); } Ttune TnootkaQML::defaultScale(int instr) { switch (static_cast<Tinstrument::Etype>(instr)) { case Tinstrument::ClassicalGuitar: case Tinstrument::ElectricGuitar: return Ttune::stdTune; case Tinstrument::BassGuitar: return Ttune::bassTunes[0]; case Tinstrument::Piano: return tuning(Tnote(-11), Tnote(49), Tnote(), Tnote(), Tnote(), Tnote()); case Tinstrument::Bandoneon: return tuning(Tnote(-11), Tnote(48), Tnote(), Tnote(), Tnote(), Tnote()); case Tinstrument::AltSax: case Tinstrument::TenorSax: return tuning(Tnote(11), Tnote(49), Tnote(), Tnote(), Tnote(), Tnote()); case Tinstrument::Ukulele: return Ttune::ukuleleGCEA; default: // NoInstrument and any unexpected case return tuning(Tnote(10), Tnote(54), Tnote(), Tnote(), Tnote(), Tnote()); } } Tinstrument TnootkaQML::instr(int type) { return Tinstrument(static_cast<Tinstrument::Etype>(type < 0 || type > INSTR_COUNT - 1 ? 0 : type)); } QString TnootkaQML::getXmlToOpen() { QString openFile; #if defined(Q_OS_ANDROID) if (GLOB->lastXmlDir().isEmpty()) GLOB->setLastXmlDir(Tandroid::getExternalPath()); openFile = TfileDialog::getOpenFileName(GLOB->lastXmlDir(), QStringLiteral("xml|musicxml|mxl")); #else openFile = TfileDialog::getOpenFileName(qApp->translate("TmainScoreObject", "Open melody file"), GLOB->lastXmlDir(), qApp->translate("TmainScoreObject", "MusicXML file") + QLatin1String(": *.xml, *.musicxml, *.mxl (*.xml *.musicxml *.mxl);;") + QLatin1String(" *.xml (*.xml);;") + QLatin1String(" *.musicxml (*.musicxml);;") + qApp->translate("TmainScoreObject", "Compressed MusicXML file") + QLatin1String(" *.mxl (*.mxl);;")); #endif if (!openFile.isEmpty()) GLOB->setLastXmlDir(QFileInfo(openFile).absoluteDir().path()); return openFile; } QString TnootkaQML::getXmlToSave(const QString &fileName) { QString saveFile; QString filter; #if defined(Q_OS_ANDROID) if (GLOB->lastXmlDir().isEmpty()) GLOB->setLastXmlDir(Tandroid::getExternalPath()); saveFile = TfileDialog::getSaveFileName(GLOB->lastXmlDir() + QLatin1String("/") + fileName, QStringLiteral("musicxml|mxl")); #else saveFile = TfileDialog::getSaveFileName(qApp->translate("TmainScoreObject", "Save melody as:"), GLOB->lastXmlDir() + QDir::separator() + fileName, qApp->translate("TmainScoreObject", "Compressed MusicXML file") + QLatin1String(" *.mxl (*.mxl);;") + qTR("TmainScoreObject", "MusicXML file") + QLatin1String(" (*.musicxml *.xml)"), &filter); #endif if (!saveFile.isEmpty()) { GLOB->setLastXmlDir(QFileInfo(saveFile).absoluteDir().path()); // if the dialog does not retrieve an extension for the file we deduct it from the filter if (QFileInfo(saveFile).suffix().isEmpty()) { if (filter.endsWith(QLatin1String("(*.mxl)"))) saveFile += QLatin1String(".mxl"); else saveFile += QLatin1String(".musicxml"); } } return saveFile; } QString TnootkaQML::pix(const QString &imageFileName) { return Tpath::pix(imageFileName); } QString TnootkaQML::pix(const char *imageName, int height) { return pixToHtml(QString(imageName), height); } QString TnootkaQML::TR(const QString &context, const QString &text, const QString &disambiguation, int n) { return qTR(qPrintable(context), qPrintable(text), qPrintable(disambiguation), n); } QString TnootkaQML::getOnlineDoc(const QString &post) { return QString("<p align=\"right\"><a href=\"https://nootka.sourceforge.io/index.php/%1/\">").arg(post) + QGuiApplication::translate("ThelpDialogBase", "Open online documentation") + QLatin1String("</a> </p>"); } QColor TnootkaQML::alpha(const QColor &c, int a) { return Tcolor::alpha(c, a); } QColor TnootkaQML::randomColor(int alpha, int level) { return QColor(QRandomGenerator::global()->bounded(level), QRandomGenerator::global()->bounded(level), QRandomGenerator::global()->bounded(level), alpha); } QColor TnootkaQML::invert(const QColor &c) { return Tcolor::invert(c); } qreal TnootkaQML::hue(const QColor &c) const { return c.hueF(); } qreal TnootkaQML::saturation(const QColor &c) const { return c.saturationF(); } qreal TnootkaQML::lightness(const QColor &c) const { return c.saturationF(); } int TnootkaQML::factor() { #if defined(Q_OS_ANDROID) // Set Android font according to screen size/density return qRound(Tmtr::fingerPixels() * 0.35 * GLOB->guiScale()); #else // but use system font size on desktops return qRound(Tmtr::systemFont.pointSize() / 72.0 * qApp->primaryScreen()->logicalDotsPerInch() * GLOB->guiScale()); #endif } QString TnootkaQML::fontFamily() { return Tmtr::systemFont.family(); } int TnootkaQML::fingerPixels() { return Tmtr::fingerPixels(); } int TnootkaQML::shortScreenSide() { return Tmtr::shortScreenSide(); } int TnootkaQML::longScreenSide() { return Tmtr::longScreenSide(); } QString TnootkaQML::pixToHtml(const QString &pixName, int height) { if (height == 0) return QString("<img src=\"%1\">").arg(pixName); QPixmap pix; if (!pix.load(Tpath::img(qPrintable(pixName)))) return QString(); QByteArray byteArray; QBuffer buffer(&byteArray); pix.scaled(qRound(height * (static_cast<qreal>(pix.width()) / static_cast<qreal>(pix.height()))), height, Qt::KeepAspectRatio, Qt::SmoothTransformation) .save(&buffer, "PNG"); return QString("<img src=\"data:image/png;base64,") + byteArray.toBase64() + "\"/>"; } void TnootkaQML::openDocLink(const QString &lnk) { QDesktopServices::openUrl(QUrl(QLatin1String("https://nootka.sourceforge.io/index.php/") + lnk)); } qreal TnootkaQML::bound(qreal min, qreal val, qreal max) { return qBound(min, val, max); } void TnootkaQML::setQmlEngine(QQmlEngine *e) { m_qmlEngine = e; if (GLOB->isFirstRun) // Wizard - actions are not needed yet return; connect(GLOB, &Tglobals::newerVersion, this, &TnootkaQML::warnNewerVersionSlot); if (m_scoreAct) { delete m_scoreAct; delete m_settingsAct; delete m_levelAct; delete m_chartsAct; delete m_melodyAct; delete m_examAct; delete m_aboutAct; } m_settingsAct = new Taction(QApplication::translate("TtoolBar", "Settings"), QStringLiteral("systemsettings"), this); connect(m_settingsAct, &Taction::triggered, this, &TnootkaQML::settingsActTriggered); m_settingsAct->setTip(QApplication::translate("TtoolBar", "Application preferences"), QQuickItem::TopRight); m_levelAct = new Taction(QApplication::translate("TtoolBar", "Level"), QStringLiteral("levelCreator"), this); connect(m_levelAct, &Taction::triggered, this, &TnootkaQML::levelActTriggered); m_levelAct->setTip(QApplication::translate("TtoolBar", "Level creator"), QQuickItem::TopRight); m_chartsAct = new Taction(QApplication::translate("TtoolBar", "Analyze"), QStringLiteral("charts"), this); connect(m_chartsAct, &Taction::triggered, this, &TnootkaQML::chartsActTriggered); m_chartsAct->setTip(tr("Analysis of exam results"), QQuickItem::TopRight); m_scoreAct = new Taction(QApplication::translate("TmainScoreObject", "Score", "it could be 'notation', 'staff' or whatever is associated with that 'place to display musical notes' and " "this the name is quite short and looks well."), QStringLiteral("score"), this); m_scoreAct->setBgColor(qApp->palette().highlight().color()); connect(m_scoreAct, &Taction::triggered, this, &TnootkaQML::scoreActTriggered); m_scoreAct->setTip(QApplication::translate("TmainScoreObject", "Manage and navigate the score."), QQuickItem::TopRight); m_melodyAct = new Taction(QApplication::translate("TmainScoreObject", "Melody"), QStringLiteral("melody"), this); connect(m_melodyAct, &Taction::triggered, this, &TnootkaQML::melodyActTriggered); m_melodyAct->setTip(QApplication::translate("TmainScoreObject", "Open, save, generate and play a melody."), QQuickItem::TopRight); m_examAct = new Taction(QApplication::translate("TtoolBar", "Lessons"), QStringLiteral("startExam"), this); connect(m_examAct, &Taction::triggered, this, &TnootkaQML::examActTriggered); m_examAct->setTip(QApplication::translate("TtoolBar", "Start exercises or an exam"), QQuickItem::TopRight); m_aboutAct = new Taction(this); connect(m_aboutAct, &Taction::triggered, this, &TnootkaQML::aboutActTriggered); m_messageColor = qApp->palette().highlight().color(); qApp->installEventFilter(this); } /** * Opening files from command line argument starts here, but it is a bit clumsy: * - for Music XML we are sending @p wantOpenFile() and main score will handle that, * - but for exam and level files only @p wantOpenFile() signal is emitted * with 700ms delay to give main window time for initialize * - then @p MainWindow.qml handles it and creates @p DialogLoader.qml * - 'dialog loader' invokes @p TdialogLoaderObject::openFile() of its object * - and there the file is distinguished (exam or level) and appropriate signal is emitted * - again, 'dialog loader' handles those signals and creates 'exam executor' or 'level creator' apparently */ void TnootkaQML::openFile(const QString &runArg) { if (GLOB->isExam()) { qDebug() << "--- Exam or exercise is running. File cannot be opened! ---"; return; } if (QFile::exists(runArg)) { QFile file(runArg); auto ext = QFileInfo(file).suffix(); if (ext == QLatin1String("xml") || ext == QLatin1String("musicxml") || ext == QLatin1String("mxl")) { auto fullPath = QDir(file.fileName()).absolutePath(); emit wantOpenXml(fullPath); } else { QTimer::singleShot(700, this, [=] { emit GLOB->wantOpenFile(runArg); }); } } } void TnootkaQML::setMessageColor(const QColor &mc) { if (m_messageColor != mc) { m_messageColor = mc; emit messageColorChanged(); } } void TnootkaQML::setStatusTip(const QString &statusText, int tipPos, bool richText) { if ((GLOB->showHints() && (!m_messageTimer || (m_messageTimer && !m_messageTimer->isActive())))) emit statusTip(statusText, tipPos, richText); } void TnootkaQML::showTimeMessage(const QString &message, int time, int pos, bool richText) { if (!m_messageTimer) { m_messageTimer = new QTimer(this); m_messageTimer->setSingleShot(true); connect(m_messageTimer, &QTimer::timeout, this, [=] { emit statusTip(QString(), m_messagePos, false); QTimer::singleShot(300, this, [=] { setMessageColor(qApp->palette().highlight().color()); }); // restore default status background color }); } m_messagePos = pos; if (m_messageTimer->isActive()) m_messageTimer->stop(); emit statusTip(message, pos, richText); m_messageTimer->start(time); } bool TnootkaQML::messageTimerActive() const { return m_messageTimer ? m_messageTimer->isActive() : false; } QString TnootkaQML::qaTypeText(int qaType) { switch (qaType) { case 0: return QApplication::translate("Texam", "as note on the staff"); case 1: return QApplication::translate("Texam", "as note name"); case 2: return QApplication::translate("Texam", "on instrument"); case 3: return QApplication::translate("Texam", "as played sound"); default: return QString(); } } QString TnootkaQML::note7translated() const { return QApplication::translate("Notation", "b", "Give here a name of 7-th note preferred in your country. But only 'b' or 'h' not 'si' or something worst..."); } QString TnootkaQML::keyNameTranslated() const { return QApplication::translate( "Notation", "letters", "DO NOT TRANSLATE IT DIRECTLY. Put here 'letters' or 'solfege' This is country preferred style of naming key signatures. 'letters' means " "C-major/a-minor names ('major' & 'minor' also are translated by you), 'solfege' means Do-major/La-minor names"); } /** * Call method of @p MainWindow.qml to obtain creted popup reference * then set name of this unsupported file */ void TnootkaQML::warnNewerVersionSlot(const QString &fileName) { QTimer::singleShot(400, this, [=] { if (m_warnNewerPopup) { // when popup already exists just append file name to previous name(s) m_warnNewerPopup->setProperty("fName", m_warnNewerPopup->property("fName").toString() + QLatin1String("<br>") + fileName); } else { auto nootWin = qobject_cast<QQmlApplicationEngine *>(m_qmlEngine)->rootObjects().first(); if (nootWin && QString(nootWin->metaObject()->className()).contains("MainWindow_QMLTYPE")) { QVariant popVar; QMetaObject::invokeMethod(nootWin, "newerVerPop", Q_RETURN_ARG(QVariant, popVar)); m_warnNewerPopup = qvariant_cast<QObject *>(popVar); if (m_warnNewerPopup) { connect(m_warnNewerPopup, &QQuickItem::destroyed, this, [=] { m_warnNewerPopup = nullptr; }); m_warnNewerPopup->setProperty("fName", fileName); } } } }); } // ################################################################################################# // ################### CONNECTIONS NODE ############################################ // ################################################################################################# void TnootkaQML::noteStarted(const Tnote &n) { Tnote note = n; if (m_scoreObject->keySignature() < 0 || (m_scoreObject->keySignature() == 0 && GLOB->GpreferFlats)) note = note.showWithFlat(); m_ignoreScore = true; if (m_scoreObject->singleNote()) { if (!note.isRest()) { note.setRhythm(Trhythm::NoRhythm); m_scoreObject->setNote(0, note); } } else { if (!GLOB->showNotesDiff()) { if (m_scoreObject->selectedItem() == nullptr) { m_scoreObject->addNote(note, true); m_startedNoteId = -1; } else { if (!note.isRest()) { auto r = m_scoreObject->selectedItem()->note()->rtm; r.setRest(false); note.setRhythm(r); m_scoreObject->setNote(m_scoreObject->selectedItem(), note); } m_startedNoteId = selectedNoteId(); } } } m_ignoreScore = false; // Reset the switch in case it is not consumed } void TnootkaQML::noteFinished(const Tnote &n) { Tnote note = n; if (m_instrument) m_instrument->setNote(note); if (m_scoreObject->keySignature() < 0 || (m_scoreObject->keySignature() == 0 && GLOB->GpreferFlats)) note = note.showWithFlat(); m_ignoreScore = true; if (m_scoreObject->singleNote()) { note.setRhythm(Trhythm::NoRhythm); m_scoreObject->setNote(0, note); } else { if (!GLOB->showNotesDiff()) { if (m_scoreObject->selectedItem() == nullptr || m_startedNoteId == -1) { m_scoreObject->setNote(m_scoreObject->lastNote(), note); m_scoreObject->setSelectedItem(nullptr); } else if (m_scoreObject->selectedItem() && !note.isRest()) { auto r = m_scoreObject->selectedItem()->note()->rtm; r.setRest(false); note.setRhythm(r); m_scoreObject->setNote(m_scoreObject->selectedItem(), note); } } } m_ignoreScore = false; // Reset the switch in case it is not consumed // TODO Do treat tied notes as a single one? } void TnootkaQML::setMainScore(QQuickItem *ms) { if (!m_mainScore) { m_mainScore = ms; m_scoreObject = qobject_cast<TscoreObject *>(qvariant_cast<QObject *>(m_mainScore->property("scoreObj"))); connect(m_scoreObject, &TscoreObject::selectedNoteChanged, this, &TnootkaQML::scoreChangedNoteSlot); connect(GLOB, &Tglobals::isExamChanged, this, &TnootkaQML::examStartStop); if (m_scoreObject && !m_nodeConnected) connectInstrument(); } } void TnootkaQML::setInstrument(TcommonInstrument *ci) { if (m_instrument != ci) { if (m_instrument != nullptr) m_nodeConnected = false; // reset connection of instrument signal when instrument type changed m_instrument = ci; if (m_scoreObject && !m_nodeConnected && !GLOB->isExam()) connectInstrument(); } } void TnootkaQML::connectInstrument() { if (m_instrument && !m_nodeConnected) { connect(m_instrument, &TcommonInstrument::noteChanged, this, &TnootkaQML::instrumentChangesNoteSlot); m_nodeConnected = true; } } void TnootkaQML::instrumentChangesNoteSlot() { m_ignoreScore = true; Tnote noteToPlay = m_instrument->note(); noteToPlay.transpose(GLOB->transposition()); emit playNote(noteToPlay); Tnote instrNote = m_instrument->note(); if (m_scoreObject->keySignature() < 0 || (m_scoreObject->keySignature() == 0 && GLOB->GpreferFlats && !(instrNote.alter() == 0 && (instrNote.note() == 3 || instrNote.note() == 7)))) instrNote = instrNote.showWithFlat(); if (m_scoreObject->singleNote()) { m_scoreObject->setNote(0, instrNote); if (GLOB->instrument().bandoneon()) m_scoreObject->setTechnical(0, m_instrument->technical()); } else { if (m_scoreObject->selectedItem() == nullptr) { auto r = m_scoreObject->workRhythm(); r.setRest(false); auto instNoteNr = instrNote.chromatic(); // check is note inside score boundaries if (instNoteNr < m_scoreObject->lowestNote().chromatic() || instNoteNr > m_scoreObject->highestNote().chromatic()) { r.setRest(true); instrNote.setNote(0); // invalidate not - it became rest } instrNote.setRhythm(r); m_scoreObject->addNote(instrNote, true); } else { auto r = m_scoreObject->selectedItem()->note()->rtm; r.setRest(false); instrNote.setRhythm(r); m_scoreObject->setNote(m_scoreObject->selectedItem(), instrNote); } if (GLOB->instrument().bandoneon()) { auto seg = m_scoreObject->selectedItem() ? m_scoreObject->noteSegment(m_scoreObject->selectedItem()->index()) : m_scoreObject->lastSegment(); Ttechnical instrData(m_instrument->technical()); if (seg->index() > 0) { for (int i = seg->index() - 1; i >= 0; --i) { auto searchNoteData = m_scoreObject->noteSegment(i)->techicalData(); if (searchNoteData.bowing()) { // Show bowing but only when it changes comparing to the previously set bow direction if (searchNoteData.bowing() == instrData.bowing()) // if it is the same - just reset bowing on note data from the instrument instrData.setBowing(Ttechnical::BowUndefined); break; } } } seg->setTechnical(instrData.data()); } } m_ignoreScore = false; // Reset the switch in case it is not consumed } void TnootkaQML::examStartStop() { if (GLOB->isExam()) { disconnect(m_instrument, &TcommonInstrument::noteChanged, this, &TnootkaQML::instrumentChangesNoteSlot); disconnect(m_scoreObject, &TscoreObject::selectedNoteChanged, this, &TnootkaQML::scoreChangedNoteSlot); } else { m_nodeConnected = false; connectInstrument(); connect(m_scoreObject, &TscoreObject::selectedNoteChanged, this, &TnootkaQML::scoreChangedNoteSlot); } } void TnootkaQML::scoreChangedNoteSlot() { if (m_ignoreScore) { m_ignoreScore = false; return; } auto scoreNote = m_scoreObject->selectedNote(); if (m_instrument) m_instrument->setNote(scoreNote, getTechicalFromScore()); if (scoreNote.isValid()) scoreNote.transpose(GLOB->transposition()); emit playNote(scoreNote); } int TnootkaQML::selectedNoteId() const { return m_scoreObject->selectedItem() ? m_scoreObject->selectedItem()->index() : -1; } int TnootkaQML::getTechicalFromScore() { quint32 technical = NO_TECHNICALS; // empty by default if (GLOB->instrument().bandoneon() && m_scoreObject->selectedItem()) { auto selectedSegment = m_scoreObject->noteSegment(m_scoreObject->selectedItem()->index()); Ttechnical dataToSet = selectedSegment->technical(); if (!dataToSet.bowing()) { // no bowing, so look up for any previous note with bowing mark for (int i = selectedSegment->index(); i >= 0; --i) { auto searchNoteData = m_scoreObject->noteSegment(i)->techicalData(); if (searchNoteData.bowing()) { dataToSet.setBowing(searchNoteData.bowing()); break; } } } technical = dataToSet.data(); } return technical; } void TnootkaQML::selectPlayingNote(int id) { m_ignoreScore = true; m_scoreObject->setSelectedItem(m_scoreObject->note(id)); if (m_instrument) m_instrument->setNote(m_scoreObject->selectedNote(), getTechicalFromScore()); m_ignoreScore = false; // Reset the switch in case it is not consumed } int TnootkaQML::scoreNotesCount() const { return m_scoreObject->notesCount(); } QList<Tnote> &TnootkaQML::scoreNoteList() const { return m_scoreObject->noteList(); } bool TnootkaQML::eventFilter(QObject *obj, QEvent *event) { if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) { setMessageColor(qApp->palette().highlight().color()); m_scoreAct->setBgColor(qApp->palette().highlight().color()); } return QObject::eventFilter(obj, event); }