Skip to content
Snippets Groups Projects
tnoteitem.cpp 35.4 KiB
Newer Older
/***************************************************************************
 *   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 "tmeasureobject.h"
#include "tscoreobject.h"
#include "tstaffitem.h"
#include <QtCore/qtimer.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpalette.h>
#include <QtCore/qdebug.h>
 * Width of every accidental for Scorek font of pixel size set to 7.0
 * It was measured by QML and corresponds to QFont size @p QFont::setPointSizeF(5.5) (except of neutral)
 */
// static const qreal accWidthTable[6] = { 2.78125, 1.671875, 0.0, 1.765625, 2.03125, 2.34375 };
/**
 * WARNING This is for comparison purposes only.
 * Do not call any method behind this pointer
 */
TnoteItem *TnoteItem::m_heldNote = nullptr;
QElapsedTimer TnoteItem::m_touchDuration = QElapsedTimer();

QString tieDebug(Trhythm::Etie t)
{
    switch (t) {
    case Trhythm::e_tieEnd:
    case Trhythm::e_tieStart:
    case Trhythm::e_tieCont:
TnoteItem::TnoteItem(TstaffItem *staffObj, TnotePair *wrapper)
    : QQuickItem(staffObj)
    , m_staff(staffObj)
    , m_wrapper(wrapper)
    , m_stemHeight(STEM_HEIGHT)
    setParent(m_staff->score()); // to avoid deleting with parent staff

    m_staff->score()->component()->setData("import QtQuick 2.9; Rectangle {}", QUrl());
    m_stem = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
    m_stem->setParentItem(this);
    m_stem->setWidth(0.3);
    m_stem->setHeight(m_stemHeight);
    m_stem->setVisible(false);

    for (int i = 0; i < 7; ++i) {
        m_upLines << createAddLine();
        m_loLines << createAddLine();
    }
    m_staff->score()->component()->setData("import QtQuick 2.9; Text { font { family: \"Scorek\"; pixelSize: 7 }}", QUrl());
    m_head = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
    m_head->setParentItem(this);
    m_alter = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
    m_alter->setParentItem(m_head);
    connect(m_alter, &QQuickItem::widthChanged, this, &TnoteItem::alterWidthChanged);
    m_flag = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
    m_flag->setParentItem(m_stem);
    m_flag->setX(0.1);
    //   m_staff->score()->component()->setData("import QtQuick 2.9; Text { z: -1; font { pixelSize: 2 }}", QUrl());
    //   m_debug = qobject_cast<QQuickItem*>(m_staff->score()->component()->create());
    //   m_debug->setParentItem(this);
    setColor(qApp->palette().text().color());
    setHeight(staffObj->height());
    setAcceptHoverEvents(true);
    setZ(10);
    setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
    qApp->installEventFilter(this);
TnoteItem::~TnoteItem()
{
    //   qDebug() << debug() << "is going deleted";
int TnoteItem::index() const
{
    return m_wrapper->index();
void TnoteItem::setStaff(TstaffItem *staffObj)
{
    if (staffObj != m_staff) {
        m_staff = staffObj;
        if (m_staff) {
            setParentItem(m_staff);
            if (m_wrapper->beam() && m_wrapper->beam()->last()->item() == this)
                m_wrapper->beam()->changeStaff(m_staff);
        } else {
            setParentItem(nullptr);
        }
        if (m_name)
            m_name->setParentItem(parentItem());
    } else
        qDebug() << debug() << "has staff set already";
void TnoteItem::setMeasure(TmeasureObject *m)
{
    m_measure = m;
void TnoteItem::setStemHeight(qreal sh)
{
    m_stem->setHeight(sh);
void TnoteItem::setColor(const QColor &c)
{
    m_head->setProperty("color", c);
    m_alter->setProperty("color", c);
    m_flag->setProperty("color", c);
    m_stem->setProperty("color", c);
SeeLook's avatar
SeeLook committed
    for (auto line : std::as_const(m_upLines))
        line->setProperty("color", c);
SeeLook's avatar
SeeLook committed
    for (auto line : std::as_const(m_loLines))
        line->setProperty("color", c);
SeeLook's avatar
SeeLook committed
    for (auto line : std::as_const(m_underLoLines))
        line->setProperty("color", c);
    if (m_tie)
        m_tie->setProperty("color", c);
    if (m_name)
        m_name->setProperty("color", c);
    if (m_stringNumber)
        m_stringNumber->setProperty("color", c);
    if (m_bowing)
        m_bowing->setProperty("color", c);
    if (m_fingerNumber)
        m_fingerNumber->setProperty("color", c);
/**
 *                @p pitch/octave
 * - note pos Y
 * - when stem is visible - stem length
 *                @p accidental
 * - entire width
 *                @p rhythm-value (whole, half, etc.)
 * - head text
 * - stem state and flag
 * - width
 *                @p rest
 * - all
 *                @p dot (???)
 * - width maybe
 *                @p stem up/down
 * - stem length
 *                @p beam
 * - stem visibility
 * - width
 */
void TnoteItem::setNote(const Tnote &n)
{
    bool updateHead = n.rhythm() != m_note.rhythm() || n.isRest() != m_note.isRest() || n.hasDot() != m_note.hasDot();
    bool fixBeam = n.isRest() != m_note.isRest();
    bool updateStem = updateHead || fixBeam || ((n.rtm.beam() != Trhythm::e_noBeam) != (m_note.rtm.beam() != Trhythm::e_noBeam))
        || (n.rtm.stemDown() != m_note.rtm.stemDown() || m_stem->height() != m_stemHeight) || n.onUpperStaff() != m_note.onUpperStaff();
    bool updateTie = n.rtm.tie() != m_note.rtm.tie();
        if (m_note.isRest()) {
            if (m_wrapper->beam())
                m_measure->noteGoingRest(m_wrapper);
        } else {
            if (m_note.rhythm() > Trhythm::Quarter)
                m_measure->restGoingNote(m_wrapper);
        }
    if (updateHead)
        updateNoteHead();
    int oldNotePos = static_cast<int>(m_notePosY);
    if (m_note.isRest())
        m_notePosY = staff()->upperLine() + (m_note.onUpperStaff() ? 0.0 : 22.0) + (m_note.rhythm() == Trhythm::Whole ? 2.0 : 4.0);
        if (m_note.isValid()) {
            m_notePosY = getHeadY(n);
        } else {
            if (staff()->score()->singleNote()) {
                m_notePosY = 0.0;
                oldNotePos = -1.0; // cheat the logic to force add lines check
            } else // no clef - rhythm only, note placed at lower staff field
                m_notePosY = staff()->upperLine() + 7.0;
        }
    if (m_notePosY < 2.0 || m_notePosY > height() - 1.0)
        m_notePosY = 0.0;
    if (oldNotePos != static_cast<int>(m_notePosY)) {
        if (m_notePosY) {
            m_head->setVisible(true);
            m_head->setY(m_notePosY - 14.0);
        } else
            m_head->setVisible(false);
        checkAddLinesVisibility();
        updateStem = true;
    }
    if (oldNotePos != static_cast<int>(m_notePosY))
        emit notePosYchanged();
    if (m_bowing && m_wrapper)
        setBowing(static_cast<EbowDirection>(m_wrapper->techicalData().bowing()));
    //   updateDebug();
    //   qDebug() << debug() << "set note" << m_note->toText() << m_note->rtm.string() << "note pos" << m_notePosY << "width:" << width();
quint32 TnoteItem::technical() const
{
    return m_wrapper ? m_wrapper->technical() : 255;
void TnoteItem::setTechnical(quint32 tech)
{
    if (m_wrapper)
        m_wrapper->setTechnical(tech);
void TnoteItem::setX(qreal xx)
{
    if (staff()->score()->singleNote())
        QQuickItem::setX(xx);
    else {
        updateTieScale();
        QQuickItem::setX(xx + (m_alter->width()));
        if (m_wrapper->beam() && m_wrapper->beam()->last()->item() == this)
            m_wrapper->beam()->last()->beam()->drawBeam();
        if (m_name)
            m_name->setX(x() - m_alter->width() + (width() - m_name->width()) / 2.0);
        emit rightXChanged();
    }
Trhythm TnoteItem::rhythm() const
{
    return m_note.rtm;
qreal TnoteItem::rightX() const
{
    return x() + width() + staff()->gapFactor() * rhythmFactor() - m_alter->width();
bool TnoteItem::hasTie() const
{
    return m_note.rtm.tie() > Trhythm::e_tieStart;
void TnoteItem::setHeight(qreal hh)
{
    if (hh != height()) {
        QQuickItem::setHeight(hh);
        for (int l = 0; l < 7; ++l) {
            m_upLines[l]->setY(2 * (l + 1) - 0.1);
            m_loLines[l]->setY(staff()->upperLine() + 10.0 + 2 * l - 0.1);
        if (staff()->isPianoStaff()) {
            if (m_underLoLines.isEmpty()) {
                m_staff->score()->component()->setData("import QtQuick 2.9; Rectangle {}", QUrl());
                for (int l = 0; l < 2; ++l) {
                    auto line = createAddLine();
                    line->setY(m_staff->upperLine() + 32.0 + l * 2.0 - 0.1);
                    line->setProperty("color", m_head->property("color"));
                    m_underLoLines << line;
                }
            }
        }
        checkAddLinesVisibility();
qreal TnoteItem::rhythmFactor() const
{
    if (m_note.rhythm() == Trhythm::NoRhythm)
    /**
     * Static array with space definitions for each rhythm value
     */
    const qreal rtmGapArray[5][3] = {
        //  | bare | dot | triplet |
        {5.0, 6.0, 4.5}, // whole note
        {4.0, 5.0, 3.3}, // half note
        {2.0, 2.5, 1.3}, // quarter note
        {1.0, 1.5, 0.3}, // eighth note
        {0.15, 0.5, 0.0} // sixteenth note
    };
    int add = m_note.hasDot() ? 1 : (m_note.isTriplet() ? 2 : 0);
    return rtmGapArray[static_cast<int>(m_note.rhythm()) - 1][add];
char TnoteItem::debug()
{
    QTextStream o(stdout);
    o << "   \033[01;29m[NOTE " << index() << "]\033[01;00m";
    return 32; // fake
void TnoteItem::shiftHead(qreal shift)
{
    if (shift != m_head->x()) {
        m_head->setX(shift);
        for (int l = 0; l < 7; ++l) {
            m_upLines[l]->setX(-0.5 + shift);
            m_loLines[l]->setX(-0.5 + shift);
        }
        for (int l = 0; l < m_underLoLines.count(); ++l)
            m_underLoLines[l]->setX(-0.5 + shift);
    if (m_tie && (m_note.rtm.tie() == Trhythm::e_noTie || m_note.rtm.tie() == Trhythm::e_tieEnd)) {
    } else if (m_tie == nullptr && (m_note.rtm.tie() == Trhythm::e_tieStart || m_note.rtm.tie() == Trhythm::e_tieCont)) {
        QQmlComponent comp(m_staff->score()->qmlEngine(), QUrl(QStringLiteral("qrc:/score/Tie.qml")));
        m_tie = qobject_cast<QQuickItem *>(comp.create());
        m_tie->setParentItem(m_head);
        m_tie->setProperty("color", qApp->palette().text().color());
        updateTieScale();
        m_tie->setX(m_head->width() - 0.75);
    }
    //   updateDebug();
void TnoteItem::updateDebug()
{
    //   m_debug->setProperty("text", QString("%1 %2").arg(index()).arg(tieDebug(m_note->rtm.tie())));
    //   m_debug->setProperty("text", QString("%1/%2(%3) %4").arg(index()).arg(m_measure->number() +
    //   1).arg(m_wrapper->rhythmGroup()).arg(tieDebug(m_note->rtm.tie()))); m_debug->setProperty("text", QString("%1(%2|%3)
    //   %4").arg(index()).arg(m_note->rtm.string()).arg(m_wrapper->rhythmGroup()).arg(tieDebug(m_note->rtm.tie()))); m_debug->setY(height() - (2 + index() % 2)
    //   * m_debug->height());
qreal TnoteItem::tieWidth()
{
    return qMax(1.5,
                staff()->gapFactor() * rhythmFactor() + (this == m_measure->last()->item() ? 1.5 : 0.0)
                    + (m_note.rtm.stemDown() ? 1.5 : m_flag->width() + 1.3));
    return mapToItem(parentItem(), QPointF(m_stem->x(), m_stem->y() + (m_note.rtm.stemDown() ? m_stem->height() : 0.0)));
/**
 * When set to @p TRUE - creates @p m_name object, which belongs to parent staff (due to issues with blocking hover events over this name text item)
 * @p updateNamePos() method takes care about @p y() coordinate,
 * but @p x() has to be set when this @p TnoteObject changes its X coordinate
 */
void TnoteItem::setNoteNameVisible(bool nameVisible)
{
    if (nameVisible) {
        if (!m_name) {
            m_staff->score()->component()->setData(
                "import QtQuick 2.9; Text { font { pixelSize: 12; family: \"Scorek\" }"
                "transformOrigin: Item.Top; scale: 0.25; textFormat: Text.PlainText; style: Text.Outline }",
                QUrl());
            m_name = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
            m_name->setParentItem(parentItem());
            m_name->setProperty("color", qApp->palette().text().color());
            m_name->setProperty("styleColor", m_measure->score()->nameColor());
            updateNamePos();
        }
    } else {
        if (m_name) {
            delete m_name;
            m_name = nullptr;
        }
    }
/**
 * Used glyphs are:
 * - note heads: @p 0xf4be @p 0xf4bd (half and black over-sized) and @p 0xf486 (whole smaller)
 * - rests: starts form 0xe4e3 (whole)
 */
QString TnoteItem::getHeadText(const Trhythm &r)
{
    if (r.rhythm() == Trhythm::NoRhythm)
        return QStringLiteral("\uf4be"); // just black note-head

    if (r.isRest())
        return QString(QChar(0xe4e2 + static_cast<int>(r.rhythm())));
    else {
        if (r.rhythm() == Trhythm::Whole)
            return QStringLiteral("\uf468");
        else if (r.rhythm() == Trhythm::Half)
            return QStringLiteral("\uf4bd");
        else
            return QStringLiteral("\uf4be");
    }
QString TnoteItem::unicodeGlyphArray(int alter)
{
    static const QString accCharTable[6] = {
        QStringLiteral("\ue264"), // [0] = bb - double flat
        QStringLiteral("\ue260"), // [1] = b - flat
        QString(), // [2] = none
        QStringLiteral("\ue262"), // [3] = # - sharp
        QStringLiteral("\ue263"), // [4] = x - double sharp
        QStringLiteral("\ue261") // [5] = neutral
    };
    return accCharTable[alter + 2];
}

QString TnoteItem::extraAccidString(int alter)
{
    switch (alter) {
    case -2:
        return QStringLiteral("\ue273");
    case -1:
        return QStringLiteral("\ue271");
    case 1:
        return QStringLiteral("\ue270");
    case 2:
        return QStringLiteral("\ue272");
    default:
        return QString();
    }
}
void TnoteItem::setStringNumber(int strNr)
    if (!m_stringNumber && strNr > 0 && strNr < 7) {
        m_staff->score()->component()->setData("import QtQuick 2.9; Text { z: -1; font { pixelSize: 4; family: \"Nootka\" } }", QUrl());
        m_stringNumber = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
        m_stringNumber->setParentItem(this);
    }
    if (strNr > 0 && strNr < 7) {
        m_stringNumber->setProperty("text", QString::number(strNr));
        m_stringNumber->setProperty("color", qApp->palette().text());
        m_stringNumber->setX((width() - m_stringNumber->width()) / 2.0);
        // TODO set Y position
        m_stringNumber->setVisible(true);
    } else {
        if (m_stringNumber)
            m_stringNumber->setVisible(false);
    }
TnoteItem::EbowDirection TnoteItem::bowing() const
{
    return static_cast<EbowDirection>(m_wrapper->techicalData().bowing());
}
void TnoteItem::setBowing(EbowDirection bowDir)
{
    if (!m_bowing && bowDir != BowUndefined) {
        m_staff->score()->component()->setData("import QtQuick 2.9; Text { z: -1; font { pixelSize: 5; family: \"Scorek\" } }", QUrl());
        m_bowing = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
        m_bowing->setParentItem(this);
    }
    if (bowDir != BowUndefined) {
        qreal bowY = 0.0;
        if (m_note.onUpperStaff()) {
            if (m_notePosY < m_staff->upperLine() - 2.0) // high pitch notes, above staff
                bowY = m_staff->upperLine(); // bow below staff
            else if (m_notePosY < m_staff->upperLine() + 1.0) // not that high but still above staff
                bowY = m_notePosY - 12.5; // above note head
            else // on the staff or below
                bowY = m_staff->upperLine() - 12.0; // above staff
        } else {
            if (m_notePosY < m_staff->upperLine() + 24.0)
                bowY = m_staff->upperLine() + 21.0; // below lower staff
            else
                bowY = m_staff->upperLine() + 9.0; // above lower staff
        }
        m_bowing->setProperty("text", QString(QChar(0xe610 + (bowDir == BowDown ? 0 : 2))));
        m_bowing->setX((width() - m_bowing->width()) / 2.0);
        m_bowing->setY(bowY);
        m_bowing->setVisible(true);
        m_bowing->setProperty("color", qApp->palette().text().color());
    } else {
        if (m_bowing)
            m_bowing->setVisible(false);
    }
    m_wrapper->techicalData().setBowing(static_cast<Ttechnical::EbowDirection>(bowDir));
void TnoteItem::setFingerNumber(int fiNr)
{
    Q_UNUSED(fiNr)
}
void TnoteItem::markNoteHead(const QColor &outlineColor)
{
    if (outlineColor.alpha() == 0) {
        m_head->setProperty("style", 0); // In Qt private qquicktext_p.h it is first value of enum TextStyle
        m_head->setProperty("style", 1); // In Qt private qquicktext_p.h it is second value of enum TextStyle
        m_head->setProperty("styleColor", outlineColor);
qreal TnoteItem::getHeadY(const Tnote &n)
{
    qreal yPos = staff()->score()->clefOffset().total() + staff()->upperLine() - (n.octave() * 7 + (n.note() - 1));
    if (staff()->isPianoStaff()) {
        if (n.onUpperStaff()) {
            if (yPos > staff()->upperLine() + 13.0)
                yPos += 10.0;
        } else {
            if (yPos > staff()->upperLine() + 3.0)
                yPos += 10.0;
        }
    }
    return yPos;
}
// #define LINE_WIDTH (0.2)
// void TnoteItem::paint(QPainter* painter) {
//   if (m_note->isValid() && m_notePosY > 0.0) {
//     painter->setPen(QPen(qApp->palette().text().color(), LINE_WIDTH));
//     if (m_notePosY < m_staff->upperLine()) {
//         qreal lY = m_staff->upperLine() - 2.0;
//         while (lY >= m_notePosY) {
//           painter->drawLine(QPointF(-1.5, lY), QPointF(static_cast<qreal>(width()), lY));
//           lY -= 2.0;
//         }
//     } else {
//         if (m_notePosY > m_staff->upperLine() + 9.0) {
//           if (m_staff->score()->isPianoStaff() && m_notePosY < m_staff->upperLine() + 21.0) {
//               if (m_notePosY < m_staff->upperLine() + 14.0) {
//                   qreal lY = m_staff->upperLine() + 10.0;
//                   while (lY <= m_notePosY) {
//                     painter->drawLine(QPointF(-1.5, lY), QPointF(static_cast<qreal>(width()), lY));
//                     lY += 2.0;
//                   }
//               } else {
//                   qreal lY = m_staff->upperLine() + 20.0;
//                   while (lY >= m_notePosY) {
//                     painter->drawLine(QPointF(-1.5, lY), QPointF(static_cast<qreal>(width()), lY));
//                     lY -= 2.0;
//                   }
//               }
//           } else {
//               qreal lY = m_staff->upperLine() + (m_staff->score()->isPianoStaff() ? 30.0 : 10.0);
//               while (lY <= m_notePosY) {
//                 painter->drawLine(QPointF(-1.5, lY), QPointF(static_cast<qreal>(width()), lY));
//                 lY += 2.0;
//               }
//           }
//         }
//     }
//   }
// }

// #################################################################################################
// ###################              PROTECTED           ############################################
// #################################################################################################
QString TnoteItem::getAccidText()
{
    if (!m_note.isValid())
    QString a = unicodeGlyphArray(m_note.alter());
    qint8 accidInKey = m_staff->score()->accidInKey(m_note.note() - 1);
    bool extraAccid = false;
    if (accidInKey) { // key signature has an accidental on this note
        if (m_note.alter() == 0) // show neutral if note has not any accidental
            a = unicodeGlyphArray(3); // neutral
        else {
            if (accidInKey == m_note.alter()) { // accidental in key, do not show
                if (m_staff->score()->showExtraAccids() && accidInKey) { // or user wants it at any cost
                    extraAccid = true;
                    a = extraAccidString(m_note.alter());
    if (m_note.rtm.tie() > Trhythm::e_tieStart) {
        a.clear(); // do not display accidental of first note in measure if it has tie
    } else if (!extraAccid) {
        int id = index() - 1; // check the previous notes for accidentals
        const Tnote *checkNote;
        while (id > -1 && m_staff->score()->noteSegment(id)->item()->measure() == measure()) {
            checkNote = m_staff->score()->noteSegment(id)->note();
            if (checkNote->note() == m_note.note()) {
                if (checkNote->rtm.tie() > Trhythm::e_tieStart && checkNote->alter() == m_note.alter()) {
                    // Ignore notes prolonged with ties - they could be continued from the previous measure
                    // and then, the accidental has to be displayed again in current measure
                    id--;
                    continue;
                }
                if (checkNote->alter() != 0 && m_note.alter() == 0) {
                    if (a.isEmpty())
                        a = unicodeGlyphArray(3); // and add neutral when some of previous notes with the same step had an accidental
                } else if (checkNote->alter() == m_note.alter()) { // do not display it twice
                    if (m_staff->score()->showExtraAccids()) // accidental with parenthesis
                        a = extraAccidString(m_note.alter());
                } else if (accidInKey == m_note.alter() && checkNote->alter() != m_note.alter())
                        m_note.alter()); // There is already accidental in key signature but some of the previous notes had another one, show it again
    }
    // TODO, NOTE: Seems it will be not implemented anytime soon, if any
    //   if (m_staff->score()->remindAccids() && m_measure->number() > 0) {
    //     auto prevMeas = m_staff->score()->measure(m_measure->number() - 1);
    //     auto accidState = prevMeas->accidState(m_note->note - 1);
    //     if (accidState != 100 && accidState != 0 && a.isEmpty() && m_alter == 0) {
    //       a = a = accCharTable[5];
    //       m_alter->setProperty("color", QColor(255, 0, 0));
    //     } else if (accidState == 0 && accidInKey != 0) {
    //       a = accCharTable[m_note->alter + 2];
    //       m_alter->setProperty("color", QColor(255, 0, 0));
    //     }
    //   }
    return a;
}

QString TnoteItem::getHeadText() const
{
    return getHeadText(m_note.rtm);
QString TnoteItem::getFlagText()
{
    if (m_note.rhythm() < Trhythm::Eighth || m_note.isRest() || m_note.rtm.beam() != Trhythm::e_noBeam)
        return QString();
    // In Scorek font, flag glyphs are placed: flag for stem-up, then flag for stem-down, starting from 0xe240
    return QString(QChar(0xe240 + (static_cast<int>(m_note.rhythm()) - 4) * 2 + (m_note.rtm.stemDown() ? 1 : 0)));
void TnoteItem::keySignatureChanged()
{
    updateAlter();
    updateWidth();
void TnoteItem::hoverEnterEvent(QHoverEvent *event)
{
    if (!m_staff->score()->readOnly() && (m_staff->score()->singleNote() || m_staff->score()->editMode())) {
SeeLook's avatar
SeeLook committed
        if (event->position().y() > 2.0 && event->position().y() < height()) {
            m_measure->score()->setHoveredNote(this);
            m_measure->score()->changeActiveNote(this);
        }
void TnoteItem::hoverLeaveEvent(QHoverEvent *)
{
    if (!m_staff->score()->readOnly() && (m_staff->score()->singleNote() || m_staff->score()->editMode())) {
        m_measure->score()->setHoveredNote(nullptr);
        m_measure->score()->changeActiveNote(nullptr);
    }
void TnoteItem::hoverMoveEvent(QHoverEvent *event)
{
    if (!m_staff->score()->readOnly() && (m_staff->score()->singleNote() || m_staff->score()->editMode())) {
        if (m_staff->score()->clefType() == Tclef::NoClef)
            return;
        if (m_measure->score()->hoveredNote() != this) {
            m_measure->score()->setHoveredNote(this);
            m_measure->score()->changeActiveNote(this);
        }
SeeLook's avatar
SeeLook committed
        if (event->position().y() > 2.0 && event->position().y() < height()) {
            if (!m_measure->score()->pressedNote() && m_measure->score()->hoveredNote()
SeeLook's avatar
SeeLook committed
                && static_cast<int>(m_measure->score()->activeYpos()) != static_cast<int>(event->position().y()))
                m_measure->score()->setActiveNotePos(qFloor(event->position().y()));
/**
 * Mouse events are used to handle touch when supported (they are ignored when enter event occurred before invoked by mouse)
 * Press event displays note cursor and locks grabbing the mouse - so moving a finger doesn't scroll entire score
 */
void TnoteItem::mousePressEvent(QMouseEvent *event)
{
    if (!m_staff->score()->readOnly() && (m_staff->score()->singleNote() || m_staff->score()->editMode())) {
        if (event->button() == Qt::LeftButton && event->position().y() > 2.0 && event->position().y() < height()) {
            setKeepMouseGrab(true);
            m_measure->score()->setPressedNote(this);
            if (m_measure->score()->activeNote() != this) {
                m_measure->score()->changeActiveNote(this);
                m_measure->score()->setActiveNotePos(qFloor(event->position().y()));
            } else if (m_staff->score()->singleNote())
                m_measure->score()->setActiveNotePos(qFloor(event->position().y()));

            if (!m_measure->score()->hoveredNote()) {
                m_measure->score()->touchHideTimer()->stop();
                m_touchDuration.restart();
                m_measure->score()->setTouched(true);
            }
        }
    }
}

/**
 * @p touchHideTimer() of score becomes active after first release of a finger,
 * note cursor remains visible for 2.5 seconds.
 * Second touch will set the note and hide cursor.
 *
 * Release event is also used to set (confirm) a note by mouse press
 */
void TnoteItem::mouseReleaseEvent(QMouseEvent *event)
{
    if (!m_staff->score()->readOnly()) {
        if (m_staff->score()->singleNote() || m_staff->score()->editMode()) {
            if (event->button() == Qt::LeftButton) {
                if (keepMouseGrab())
                    setKeepMouseGrab(false);
                if (event->position().y() > 2.0 && event->position().y() < height()) {
                    if (m_measure->score()->hoveredNote()) { // mouse
                        if (m_measure->score()->hoveredNote() == this)
                            m_measure->score()->noteClicked(m_measure->score()->activeYpos());
                        m_measure->score()->setPressedNote(nullptr);
                    } else { // touch
                        if (m_touchDuration.elapsed() < 190) { // confirm note
                            if (m_heldNote == this) {
                                m_measure->score()->noteClicked(m_measure->score()->activeYpos()); // set note only when it was touched second time
                            } else
                                m_measure->score()->setSelectedItem(this);
                            m_heldNote = nullptr;
                            m_measure->score()->setPressedNote(nullptr);
                            m_measure->score()->changeActiveNote(nullptr);
                        } else { // keep cursor visible
                            m_measure->score()->touchHideTimer()->start(2500);
                            m_heldNote = this;
                        }
                        m_measure->score()->setTouched(false);
            } else if (event->button() == Qt::RightButton) {
                m_measure->score()->setSelectedItem(this);
            }
        } else if (!m_staff->score()->singleNote() && !m_staff->score()->editMode()) { // not edit mode but also not read only
            m_measure->score()->setSelectedItem(this); // no matter what button - select the note
        }
    } else {
        if (m_measure->score()->selectInReadOnly())
            emit m_measure->score()->readOnlyNoteClicked(index());
    }
}

void TnoteItem::mouseMoveEvent(QMouseEvent *event)
{
    if (!m_staff->score()->readOnly() && (m_staff->score()->singleNote() || m_staff->score()->editMode())) {
        if (event->pos().y() > 2.0 && event->pos().y() < height()) {
            if (m_measure->score()->pressedNote() && m_touchDuration.elapsed() > 200
                && static_cast<int>(m_measure->score()->activeYpos()) != static_cast<int>(event->pos().y())) {
                m_measure->score()->setActiveNotePos(qFloor(event->pos().y()));
            }
// #################################################################################################
// ###################              PRIVATE             ############################################
// #################################################################################################
QQuickItem *TnoteItem::createAddLine()
{
    auto line = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
    line->setParentItem(this);
    line->setWidth(3.5);
    line->setHeight(m_staff->height() * m_staff->scale() < 200.0 ? 0.3 : 0.2); // for smaller views, keep line thicker to avoid disappearing
    line->setX(m_staff->score()->singleNote() ? 1.0 : -0.5);
    line->setVisible(false);
    line->setProperty("color", qApp->palette().text().color());
    return line;
}

void TnoteItem::updateAlter()
{
    auto accidText = getAccidText();
    m_alter->setProperty("text", accidText);
    if (!accidText.isEmpty())
        m_alter->setX(-m_alter->width() - 0.1);
}

void TnoteItem::updateWidth()
{
    if (m_measure->score()->singleNote())
        setWidth(5.0);
    else {
        qreal w = m_alter->width() + m_head->width();
        if (!m_note.isRest() && !m_note.rtm.stemDown() && m_stem->isVisible() && m_flag->width() > 0.0)
            w += m_flag->width() - 0.5;
        setWidth(w);
        updateTieScale();
void TnoteItem::updateNoteHead()
{
    QString headText = getHeadText();
    if (m_note.hasDot())
        headText.append(QStringLiteral("\ue1e8"));
    m_head->setProperty("text", headText);
}

void TnoteItem::updateTieScale()
{
    if (m_tie) {
        m_tie->setProperty("xScale", tieWidth() / 2.90625);
        m_tie->setProperty("stemDown", m_note.rtm.stemDown());
    if (m_notePosY && !m_note.isRest() && m_note.rhythm() > Trhythm::Whole) {
        if (m_note.rtm.beam() == Trhythm::e_noBeam) {
            m_note.rtm.setStemDown(m_notePosY < staff()->upperLine() + 4.0
                                   || (staff()->isPianoStaff() && m_notePosY > staff()->upperLine() + 13.0 && m_notePosY < staff()->upperLine() + 26.0));
            m_stem->setHeight(
                qMax(STEM_HEIGHT,
                     qAbs(m_notePosY - (staff()->upperLine() + (staff()->isPianoStaff() && m_notePosY > staff()->upperLine() + 13.0 ? 26.0 : 4.0)))));
            QString flagText = getFlagText();
            m_flag->setProperty("text", flagText);
            if (!flagText.isEmpty())
                m_flag->setY((m_note.rtm.stemDown() ? m_stem->height() : 0.0) - 15.0);
        } else {
            if (m_flag->width() > 0.0)
                m_flag->setProperty("text", QString());
        }
        m_stem->setX(m_head->x() + (m_note.rtm.stemDown() ? 0.0 : 2.0));
        m_stem->setY(m_notePosY + (m_note.rtm.stemDown() ? 0.0 : -m_stem->height()));
        m_stem->setVisible(true);
    } else
        m_stem->setVisible(false);
    bool stemHeightChanged = m_stemHeight != m_stem->height();
    m_stemHeight = m_stem->height();
    if (stemHeightChanged)
        updateNamePos();
}

// TODO: name may collide with next/previous note name (16ths), octave mark out-shots too much
void TnoteItem::updateNamePos()
{
    if (m_name) {
        if (m_note.isValid()) {
            m_name->setVisible(true);
            qreal yOff;
            if (m_note.rtm.stemDown())
                yOff = m_notePosY > 6.0 ? (m_bowing && m_note.onUpperStaff() ? m_stemHeight - 4.0 : -9.5) : m_stemHeight - 4.0;
            else
                yOff = m_notePosY > height() - 6.0 && height() - m_stemHeight > 8.0 ? -m_stemHeight - 8.0 : -1.8;
            m_name->setY(m_notePosY + yOff);
            m_name->setProperty("text", m_note.isRest() ? QString() : m_note.styledName());
            m_name->setX(x() - m_alter->width() + (width() - m_name->width()) / 2.0);
        } else {
            m_name->setVisible(false);
        }
    }
}
/**
 * @p m_loLines (lower lines) are displayed always below upper staff (either single one or that upper of grand staff),
 * but in grand staff 1st and 2nd are used for notes of upper staff and the rest for notes of lower staff.
 * This way the same note from range c1 to h1 can be expressed either on lower staff (using those lines) or on upper staff
 * It covers all scale of the bandoneon notation
 * @p m_underLoLines are used only when grand staff
 */
void TnoteItem::checkAddLinesVisibility()
{
    bool v = m_notePosY != 0.0 && !m_note.isRest();
    bool betweenStaves = staff()->isPianoStaff() && m_notePosY >= staff()->upperLine() + 10.0 && m_notePosY < staff()->upperLine() + 21.0;
    for (int i = 0; i < 7; ++i) {
        m_upLines[i]->setVisible(v && m_notePosY > 0.0 && i >= qFloor((m_notePosY - 1.0) / 2.0) && (i != 6 || !staff()->isPianoStaff()));
        qreal upp1 = staff()->upperLine() + 10.0 + i * 2;
        if (staff()->isPianoStaff()) {
            if (m_notePosY < staff()->upperLine() + 14.0)
                m_loLines[i]->setVisible(v && betweenStaves && m_notePosY >= upp1 && m_notePosY < staff()->upperLine() + 14.0);
            else
                m_loLines[i]->setVisible(v && betweenStaves && m_notePosY <= upp1 && m_notePosY <= staff()->upperLine() + 21.0 && i != 6);
        } else
            m_loLines[i]->setVisible(v && m_notePosY >= upp1);
    }
    if (!m_underLoLines.isEmpty()) {
        m_underLoLines[0]->setVisible(v && m_notePosY >= staff()->upperLine() + 32.0);
        m_underLoLines[1]->setVisible(v && m_notePosY >= staff()->upperLine() + 34.0);
    }
#if !defined(Q_OS_ANDROID)
bool TnoteItem::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) {
        setColor(qApp->palette().text().color());
    }
    return QObject::eventFilter(obj, event);
}
#endif