Skip to content
Snippets Groups Projects
tnootkaqml.cpp 28.2 KiB
Newer Older
/***************************************************************************
SeeLook's avatar
SeeLook committed
 *   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 "score/taddnoteitem.h"
#include "score/tmelodypreview.h"
#include "instruments/tguitarbg.h"
#include "instruments/tpianobg.h"
#include "instruments/tbandoneonbg.h"
#if defined (Q_OS_ANDROID)
  #include <Android/tandroid.h>
#endif

#include <QtCore/qbuffer.h>
#include <QtWidgets/qapplication.h>
#include <QtGui/qdesktopservices.h>
#include <QtGui/qpalette.h>
TnootkaQML* TnootkaQML::m_instance = nullptr;
TnootkaQML::TnootkaQML(QObject* parent) :
  QObject(parent)
  if (m_instance) {
    qDebug() << "TnootkaQML instance already exists!";
    return;
  }
  m_instance = this;
  qsrand(QDateTime::currentDateTimeUtc().toTime_t());
  qmlRegisterUncreatableType<Tclef>("Score", 1, 0, "Tclef", QStringLiteral("You cannot create an instance of the Tclef."));
  qRegisterMetaType<Tmeter>();
  qmlRegisterUncreatableType<Tmeter>("Score", 1, 0, "Tmeter", QStringLiteral("You cannot create an instance of the Tmeter."));
  qmlRegisterUncreatableType<Trhythm>("Score", 1, 0, "Trhythm", QStringLiteral("You cannot create an instance of the Trhythm."));

  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<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."));
  qRegisterMetaType<Tinstrument>();
  qmlRegisterUncreatableType<Tinstrument>("Nootka", 1, 0, "Tinstrument", QStringLiteral("You cannot create an instance of the Tinstrument."));
  qRegisterMetaType<Ttune>();
  qmlRegisterUncreatableType<Ttune>("Nootka", 1, 0, "Ttune", QStringLiteral("You cannot create an instance of the Ttune."));
  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::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 doesnt 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);
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");
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];
  }
  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());
    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 openFile;
  if (GLOB->lastXmlDir().isEmpty())
    GLOB->setLastXmlDir(Tandroid::getExternalPath());
  openFile = TfileDialog::getOpenFileName(GLOB->lastXmlDir(), QStringLiteral("xml|musicxml"));
  openFile = TfileDialog::getOpenFileName(qApp->translate("TmainScoreObject", "Open melody file"), GLOB->lastXmlDir(),
                                          qApp->translate("TmainScoreObject", "MusicXML file") + QLatin1String(" (*.xml *.musicxml *.mxl)"));
  if (!openFile.isEmpty())
    GLOB->setLastXmlDir(QFileInfo(openFile).absoluteDir().path());
  return openFile;
QString TnootkaQML::getXmlToSave(const QString& fileName) {
  QString saveFile;
  if (GLOB->lastXmlDir().isEmpty())
    GLOB->setLastXmlDir(Tandroid::getExternalPath());
  saveFile = TfileDialog::getSaveFileName(GLOB->lastXmlDir() + QLatin1String("/") + fileName,
SeeLook's avatar
SeeLook committed
                                          QStringLiteral("musicxml|xml"));
  saveFile = TfileDialog::getSaveFileName(qApp->translate("TmainScoreObject", "Save melody as:"), GLOB->lastXmlDir() + QDir::separator() + fileName,
                                          qTR("TmainScoreObject", "MusicXML file") + QLatin1String(" (*.musicxml *.xml)"), &filter);
  if (!saveFile.isEmpty())
    GLOB->setLastXmlDir(QFileInfo(saveFile).absoluteDir().path());
  return saveFile;
SeeLook's avatar
SeeLook committed


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::onlineDocP(const QString& hash) {
  return QString("<p align=\"right\"><a href=\"https://nootka.sourceforge.io/index.php?C=doc#%1\">").arg(hash)
  + QGuiApplication::translate("ThelpDialogBase", "Open online documentation") + QLatin1String("</a> </p>");
}


QColor TnootkaQML::alpha(const QColor& c, int a) {
}


QColor TnootkaQML::randomColor(int alpha, int level) {
  return QColor(qrand() % level, qrand() % level, qrand() % 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() {
  // Set Android font according to screen size/density
  return qRound(Tmtr::fingerPixels() * 0.35 * GLOB->guiScale());
  // but use system font size on desktops
  return qRound(Tmtr::systemFont.pointSize() / 72.0 * qApp->primaryScreen()->logicalDotsPerInch() * GLOB->guiScale());
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::openHelpLink(const QString& hash) {
  QDesktopServices::openUrl(QUrl(QString("https://nootka.sourceforge.io/index.php?L=%1&C=doc#" + hash).
    arg(QString(qgetenv("LANG")).left(2).toLower()), QUrl::TolerantMode));
}


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;

  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();

  connect(qApp, &QGuiApplication::paletteChanged, this, [=]{
    setMessageColor(qApp->palette().highlight().color());
    m_scoreAct->setBgColor(qApp->palette().highlight().color());
  });
/**
 * Opening files from command line argument starts here, but it is a bit clumsy:
 * - for Music XML is fine, we have @p TscoreObject here so just call @p openMusicXml()
 * - 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")) {
        auto fullPath = QDir(file.fileName()).absolutePath();
        m_scoreObject->openMusicXml(fullPath);
    } else {
        QTimer::singleShot(700, this, [=]{ emit GLOB->wantOpenFile(runArg); });
void TnootkaQML::setMessageColor(const QColor& mc) {
  if (m_messageColor != mc) {
    m_messageColor = mc;
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
    });
  }
  if (m_messageTimer->isActive())
    m_messageTimer->stop();

  emit statusTip(message, pos, richText);
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...");
  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");
//#################################################################################################
//###################     CONNECTIONS  NODE            ############################################
//#################################################################################################
SeeLook's avatar
SeeLook committed

void TnootkaQML::noteStarted(const Tnote& n) {
  Tnote note = n;
  if (m_scoreObject->keySignature() < 0 || (m_scoreObject->keySignature() == 0 && GLOB->GpreferFlats))
    note = note.showWithFlat();
  if (m_scoreObject->singleNote()) {
      if (!note.isRest()) {
        note.setRhythm(Trhythm::NoRhythm);
        m_scoreObject->setNote(0, note);
  } else {
      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;
    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 (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)
  }
}


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())
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
          }
          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();
        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
  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;
        }
      }
    }
}


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();
}