Skip to content
Snippets Groups Projects
tscoreobject.cpp 60.7 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 "tscoreobject.h"
#include "music/tmelody.h"
#include "music/tmeter.h"
#include "taction.h"
#include "tbeamobject.h"
#include <QtCore/qtimer.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpalette.h>
#include <QtQml/qqmlengine.h>
#define WIDTH_CHANGE_DELAY (50) // when score width changes, give 50 ms before staves will be resized
TscoreObject::TscoreObject(QObject *parent)
    : QObject(parent)
    , m_keySignEnabled(false)
    , m_showExtraAccids(false)
    , m_remindAccids(false)
    , m_enableDoubleAccids(false)
    , m_showNoteNames(false)
    , m_clefOffset(TclefOffset(3, 2))
    , m_width(0.0)
    , m_adjustInProgress(false)
    , m_nameStyle(static_cast<int>(Tnote::defaultStyle))
    , m_workRhythm(new Trhythm()) // quarter by default
{
    m_meter = new Tmeter();
    setMeter(4); // Tmeter::Meter_4_4
    m_measures << getMeasure(0); // new TmeasureObject(0, this);

    m_widthTimer = new QTimer(this);
    m_widthTimer->setSingleShot(true);
    connect(m_widthTimer, &QTimer::timeout, this, &TscoreObject::adjustScoreWidthSlot);

    for (int i = 0; i < 7; i++) // reset accidentals array
        m_accidInKeyArray[i] = 0;

    m_touchHideTimer = new QTimer(this);
    connect(m_touchHideTimer, &QTimer::timeout, this, [=] {
        changeActiveNote(nullptr);
        setPressedNote(nullptr);
        m_touchHideTimer->stop();
    });
    m_enterTimer = new QTimer(this);
    m_enterTimer->setSingleShot(true);
    connect(m_enterTimer, &QTimer::timeout, this, &TscoreObject::enterTimeElapsed);
    m_leaveTimer = new QTimer(this);
    m_leaveTimer->setSingleShot(true);
    connect(m_leaveTimer, &QTimer::timeout, this, &TscoreObject::leaveTimeElapsed);
    m_bgColor = qApp->palette().base().color();
}
TscoreObject::~TscoreObject()
{
    delete m_meter;
    delete m_qmlComponent;
    delete m_workRhythm;
    qDeleteAll(m_segments);
    qDeleteAll(m_spareSegments);
// #################################################################################################
// ###################          Musical parameters      ############################################
// #################################################################################################
void TscoreObject::setClefType(Tclef::EclefType ct)
    if (m_clefType != ct) {
        auto oldClef = m_clefType;
        m_clefType = ct;
        updateClefOffset();
        emit clefTypeChanged();

        if (notesCount() > 0) {
            bool pianoChanged = (oldClef == Tclef::PianoStaffClefs && m_clefType != Tclef::PianoStaffClefs)
                || (oldClef != Tclef::PianoStaffClefs && m_clefType == Tclef::PianoStaffClefs);
            bool fixBeam = false;
            int rtmGrToCheck = 0;
            for (int n = 0; n < notesCount(); ++n) {
                auto noteSeg = m_segments[n];
                if (pianoChanged)
                    noteSeg->item()->setHeight(m_clefType == Tclef::PianoStaffClefs ? 49.0 : 38.0);
                if (m_clefType == Tclef::NoClef) {
                    Tnote newNote(Tnote(), noteSeg->note()->rtm);
                    newNote.rtm.setStemDown(false);
                    noteSeg->item()->setStemHeight(STEM_HEIGHT);
                    noteSeg->setPairNotes(newNote);
                    updateNoteInList(noteSeg, newNote);
                } else {
                    Tnote newNote(*noteSeg->note());
                    if (oldClef == Tclef::NoClef) {
                        int globalNr = m_clefOffset.octave * 7 - (7 - m_clefOffset.note);
                        newNote.setNote(static_cast<char>(56 + globalNr) % 7 + 1);
                        newNote.setOctave(static_cast<char>(56 + globalNr) / 7 - 8);
                    } else
                        fitToRange(newNote);

                    if (m_clefType == Tclef::PianoStaffClefs
                        && ((newNote.chromatic() < 8 && newNote.onUpperStaff())
                            || !newNote.onUpperStaff())) { // find when to fix beaming due to note went to/from grand staff
                        newNote.setOnUpperStaff(false);
                        if (newNote.rhythm() > Trhythm::Quarter)
                            fixBeam = true;
                    } else if (pianoChanged && m_clefType != Tclef::PianoStaffClefs) {
                        // or merge beams when note from the same rhythmic group where on different staves
                        if (!newNote.onUpperStaff() && newNote.rhythm() > Trhythm::Quarter)
                            fixBeam = true;
                    }

                    noteSeg->setPairNotes(newNote);
                    updateNoteInList(noteSeg, newNote);

                    if (pianoChanged) {
                        int nextRtmGr = (n == notesCount() - 1 ? -1 : m_segments[n + 1]->rhythmGroup());
                        bool lastInBar = (noteSeg == noteSeg->item()->measure()->last());
                        if (fixBeam && (nextRtmGr != rtmGrToCheck || lastInBar)) // summarize beaming at the end of group or measure
                            noteSeg->item()->measure()->resolveBeaming(rtmGrToCheck, rtmGrToCheck);
                        if (nextRtmGr != rtmGrToCheck || lastInBar) {
                            fixBeam = false; // reset beam fix for next group checking
                            rtmGrToCheck = nextRtmGr;
                        }
                    }
            }
            for (int m = 0; m < m_measures.count(); ++m)
                m_measures[m]->refresh();
            if (!pianoChanged) // otherwise adjustScoreWidth() will be called due to score scale changes
                adjustScoreWidth();
/**
 * When meter is changed and there are notes on the score, all segments are flushed and stored
 * then measures and staves are deleted,
 * then all notes are added from scratch, but stored segments are reused to improve single segment creation time
 */
void TscoreObject::setMeter(int m)
{
    Tmeter::Emeter newMeter = static_cast<Tmeter::Emeter>(m);
    Tmeter::Emeter prevMeter = m_meter->meter();
    if (m_meter->meter() != newMeter) {
        // set notes grouping
        m_meter->setMeter(newMeter);
        m_meter->fillMeterGroups(m_meterGroups);
        if (measuresCount())
            firstMeasure()->meterChanged();
        emit meterChanged();
        setWorkRhythm(Trhythm(newMeter == Tmeter::NoMeter ? Trhythm::NoRhythm : (newMeter <= Tmeter::Meter_7_4 ? Trhythm::Quarter : Trhythm::Eighth)));

        if (!m_singleNote && measuresCount() && firstMeasure()->noteCount() > 0) {
            clearScorePrivate();
            QList<Tnote> oldList = m_notes;
            m_notes.clear();
            for (int n = 0; n < oldList.size(); ++n) {
                // TODO fold (merge) all ties
                if (m_meter->meter() == Tmeter::NoMeter) // remove rhythm then
                    oldList[n].setRhythm(Trhythm(Trhythm::NoRhythm));
                if (prevMeter == Tmeter::NoMeter) // initialize empty rhythm
                    oldList[n].setRhythm(Trhythm()); // quarter by default
                addNote(oldList[n]);
            }
            m_activeBarNr = 0;
            adjustScoreWidth();
        }
        emitLastNote();
int TscoreObject::meterToInt() const
{
    return static_cast<int>(m_meter->meter());
}
void TscoreObject::setKeySignature(int k)
{
    qint8 key = static_cast<qint8>(k);
    if (m_keySignEnabled && key != m_keySignature) {
        if (key != m_keySignature) {
            m_keySignature = key;
            for (int i = 1; i < 8; i++) {
                qint8 sign = k < 0 ? -1 : 1;
                int startVal = k < 0 ? 38 : 48;
                if (i <= qAbs(k))
                    m_accidInKeyArray[(startVal + sign * (i * 4)) % 7] = sign;
                else
                    m_accidInKeyArray[(startVal + sign * (i * 4)) % 7] = 0;
            }
            m_keyChanged = true;
            for (TmeasureObject *m : std::as_const(m_measures))
                m->keySignatureChanged();
            if (notesCount() > 0)
                adjustScoreWidth();
            emit keySignatureChanged();
        }
/** @p static
 * This method creates @p outList of notes that have pitch of @p n note
 * but splits @p dur duration into possible rhythmic values.
 */
void solveList(const Tnote &n, int dur, QList<Tnote> &outList)
{
    Trhythm rtm(dur); // try to obtain rhythm value
    if (!rtm.isValid()) { // if it is not possible to express the duration in single rhythm - resolve it in multiple values
        TrhythmList solvList;
        Trhythm::resolve(dur, solvList);
        for (int r = 0; r < solvList.count(); ++r) {
            outList << Tnote(n, Trhythm(solvList[r].rhythm(), n.isRest(), solvList[r].hasDot(), solvList[r].isTriplet()));
    } else { // just single note in the list
        rtm.setRest(n.isRest());
        outList << Tnote(n, rtm);
void TscoreObject::addNote(const Tnote &newNote, bool fromQML)
{
    if (m_singleNote) {
        qDebug() << "[TscoreObject]" << "FIXME! Trying to add note in single mode";
    auto lastMeasure = m_measures.last();
    if (lastMeasure->free() == 0) { // new measure is needed
        lastMeasure = getMeasure(m_measures.count());
        m_measures << lastMeasure;
        lastStaff()->appendMeasure(lastMeasure);
    }
    Tnote n = newNote;
    fitToRange(n);
    int noteDur = n.rhythm() == Trhythm::NoRhythm || m_meter->meter() == Tmeter::NoMeter ? 1 : n.duration();
    if (noteDur > lastMeasure->free()) { // split note that is adding
        int leftDuration = noteDur - lastMeasure->free();
        int lastNoteId = m_segments.count();

        QList<Tnote> notesToCurrent;
        solveList(n, lastMeasure->free(), notesToCurrent); // solve free duration in current measure
        if (notesToCurrent.isEmpty())
            qDebug() << "[TscoreObject]" << "can't resolve duration of" << lastMeasure->free();
        else {
            if (!n.isRest()) {
                notesToCurrent.first().rtm.setTie(newNote.rtm.tie() > Trhythm::e_tieStart ? Trhythm::e_tieCont : Trhythm::e_tieStart);
                if (notesToCurrent.count() == 2)
                    notesToCurrent.last().rtm.setTie(Trhythm::e_tieCont);
            }
            appendToNoteList(notesToCurrent);
            lastMeasure->appendNewNotes(lastNoteId, notesToCurrent.count());
        }

        QList<Tnote> notesToNext;
        solveList(n, leftDuration, notesToNext); // solve remaining duration for the next measure
        lastNoteId = m_segments.count(); // update id of the last note segment
        if (notesToNext.isEmpty())
            qDebug() << "[TscoreObject] can't resolve duration" << leftDuration;
        else {
            if (!n.isRest()) {
                if (notesToNext.count() == 1)
                    notesToNext.first().rtm.setTie(Trhythm::e_tieEnd);
                else {
                    notesToNext.first().rtm.setTie(Trhythm::e_tieCont);
                    notesToNext.last().rtm.setTie(Trhythm::e_tieEnd);
                }
            }
            appendToNoteList(notesToNext);
            auto newLastMeasure = getMeasure(m_measures.count()); // new TmeasureObject(m_measures.count(), this); // add a new measure
            m_measures << newLastMeasure;
            lastStaff()->appendMeasure(newLastMeasure);
            newLastMeasure->appendNewNotes(lastNoteId, notesToNext.count());
        }
    } else { // just add new note to the last measure
        m_notes << n;
        const int lastNoteId = m_segments.count();
        m_segments << getSegment(lastNoteId, n);
        lastMeasure->appendNewNotes(lastNoteId, 1);
    }
    emitLastNote();
    if (fromQML) {
        emit noteWasAdded();
    }
void TscoreObject::setNote(TnoteItem *no, const Tnote &n)
{
    if (!no)
        return;

    if (m_allowAdding && m_meter->meter() != Tmeter::NoMeter && no == lastNote() && no->note()->rtm != n.rtm) {
        deleteLastNote();
        addNote(n);
        emitLastNote();
        return;
    }
    int durDiff = no->note()->duration() - n.duration();
    auto oldNote = *no->wrapper()->note();
    auto newNote = n;
    if (!durDiff) {
        newNote.rtm.setBeam(oldNote.rtm.beam());
        newNote.rtm.setTie(oldNote.rtm.tie());
    }
    fitToRange(newNote);
    bool fitStaff = false;
    // disconnect tie (if any) if note pitch changed
    QPoint notesForAlterCheck; // x is first note and y is the last note to check
    if (oldNote.rtm.tie() && newNote.chromatic() != oldNote.chromatic()) {
        // alters of notes has to be checked due to note changed
        // and all measures contained note with the tie are affected. Find them then
        notesForAlterCheck = tieRange(no);
        notesForAlterCheck.setX(m_segments[notesForAlterCheck.x()]->item()->measure()->firstNoteId());
        notesForAlterCheck.setY(m_segments[notesForAlterCheck.y()]->item()->measure()->lastNoteId());
        if (oldNote.rtm.tie() == Trhythm::e_tieStart) {
            m_segments[no->index() + 1]->disconnectTie(TnotePair::e_untieNext);
        } else { // tie continue or end
            if (oldNote.rtm.tie() == Trhythm::e_tieCont)
                m_segments[no->index() + 1]->disconnectTie(TnotePair::e_untieNext);
            m_segments[no->index() - 1]->disconnectTie(TnotePair::e_untiePrev);
        newNote.rtm.setTie(Trhythm::e_noTie);
        if (no->wrapper() == no->staff()->firstNote())
            no->staff()->deleteExtraTie();
        fitStaff = true;
    }
    if (durDiff) {
        no->measure()->changeNoteDuration(no->wrapper(), newNote);
        if (lastMeasure()->isEmpty())
            removeLastMeasure();
        adjustScoreWidth();
    } else {
        no->wrapper()->setPairNotes(newNote);
        updateNoteInList(no->wrapper(), newNote);
        // When note or alter are different - check accidentals in whole measure and fit staff if necessary
        if (!notesForAlterCheck.isNull() || oldNote.note() != newNote.note() || oldNote.alter() != newNote.alter()) {
            if (notesForAlterCheck.isNull())
                notesForAlterCheck = QPoint(no->measure()->firstNoteId(), no->measure()->lastNoteId());
            auto measureToRefresh = m_segments[notesForAlterCheck.x()]->item()->measure();
            for (int n = notesForAlterCheck.x(); n <= notesForAlterCheck.y(); ++n) {
                if (m_segments[n]->note()->note() == oldNote.note() || m_segments[n]->note()->note() == newNote.note()) {
                    fitStaff = true;
                    m_segments[n]->item()->updateAlter();
                }
                // Update measure sums (notes width)
                if (m_segments[n]->item()->measure() != measureToRefresh) {
                    measureToRefresh->refresh();
                    measureToRefresh = m_segments[n]->item()->measure();
            measureToRefresh->refresh();
        // update note range on current staff
        if (oldNote.note() != newNote.note() || oldNote.octave() != newNote.octave())
            no->staff()->checkNotesRange();
        // If there is a beam - prepare it again then draw
        if (no->wrapper()->beam()) {
            no->wrapper()->beam()->prepareBeam();
            if (!fitStaff)
                no->wrapper()->beam()->drawBeam();
        }
        if (fitStaff) {
            m_segments[notesForAlterCheck.x()]->item()->staff()->fit();
            if (m_segments[notesForAlterCheck.y()]->item()->staff() != m_segments[notesForAlterCheck.x()]->item()->staff())
                m_segments[notesForAlterCheck.y()]->item()->staff()->fit();

    if (no == m_selectedItem)
        emit selectedNoteChanged();
    if (m_singleNote && m_enharmNotesEnabled && n.isValid()) {
        TnotesList enharmList = newNote.getTheSameNotes(m_enableDoubleAccids);
        TnotesList::iterator it = enharmList.begin();
        ++it;
        if (it != enharmList.end()) {
            note(1)->setVisible(true);
            m_segments[1]->setPairNotes(*(it));
            updateNoteInList(m_segments[1], *(it));
        } else
            note(1)->setVisible(false);
        ++it;
        if (it != enharmList.end()) {
            note(2)->setVisible(true);
            m_segments[2]->setPairNotes(*(it));
            updateNoteInList(m_segments[2], *(it));
        } else
            note(2)->setVisible(false);
    }
void TscoreObject::setNote(int noteNr, const Tnote &n)
{
    if (noteNr >= 0 && noteNr < notesCount())
        setNote(note(noteNr), n);
    else
        qDebug() << "[TscoreObject FIXME] Trying to set note of item that doesn't exist!" << noteNr;
void TscoreObject::setTechnical(int noteId, quint32 tech)
{
    if (noteId >= 0 && noteId < notesCount())
        noteSegment(noteId)->setTechnical(tech);
}
TnoteItem *TscoreObject::note(int noteId)
{
    return noteId > -1 && noteId < notesCount() ? m_segments[noteId]->item() : nullptr;
QQuickItem *TscoreObject::noteHead(int noteId)
{
    return noteId > -1 && noteId < notesCount() ? m_segments[noteId]->item()->head() : nullptr;
}
Tnote TscoreObject::noteOfItem(TnoteItem *item) const
{
    return item ? *item->note() : Tnote();
Tnote TscoreObject::noteAt(int index) const
{
    return index >= 0 && index < m_notes.count() ? m_notes[index] : Tnote();
}
void TscoreObject::noteClicked(qreal yPos)
{
    if (!activeNote())
        return;
    Trhythm newRhythm = m_meter->meter() == Tmeter::NoMeter ? Trhythm(Trhythm::NoRhythm) : *m_workRhythm;
    int globalNr = globalNoteNr(yPos);
    Tnote newNote(static_cast<char>(56 + globalNr) % 7 + 1, static_cast<char>(56 + globalNr) / 7 - 8, static_cast<char>(m_cursorAlter), newRhythm);
    newNote.setOnUpperStaff(!(isPianoStaff() && yPos > upperLine() + 13.0));
    if (m_workRhythm->isRest() || m_clefType == Tclef::NoClef)
        newNote.setNote(0);
    // when note changed staff upper/lower but rhythm is the same
    // we have to resolve beaming and fit staff here, setNote() will not preform that
    bool fixBeam = isPianoStaff() && newNote.rhythm() > Trhythm::Quarter && m_activeNote && m_activeNote->note()->onUpperStaff() != newNote.onUpperStaff()
        && newNote.isValid() && m_activeNote->note()->rtm == newRhythm;
    bool selectLastNoteAgain = m_activeNote == lastNote() && m_activeNote->note()->rtm != newRhythm;
    setNote(m_activeNote, newNote);
    setSelectedItem(m_activeNote);
    if (fixBeam) {
        m_activeNote->measure()->resolveBeaming(m_activeNote->wrapper()->rhythmGroup(), m_activeNote->wrapper()->rhythmGroup());
        m_activeNote->staff()->fit();
    }
    if (selectLastNoteAgain) { // clicked note is the last one and changed its rhythm, so it was deleted and added again
        m_activeNote = lastNote();
        setSelectedItem(lastNote());
        emit activeNoteChanged();
void TscoreObject::setCursorAlter(int curAlt)
{
    curAlt = qBound(m_enableDoubleAccids ? -2 : -1, curAlt, m_enableDoubleAccids ? 2 : 1);
    if (curAlt != m_cursorAlter) {
        m_cursorAlter = curAlt;
        emit cursorAlterChanged();
    }
QString TscoreObject::alterText()
{
    static const QString accids = QStringLiteral("\ue264\ue260 \ue262\ue263");
    return accids.mid(m_cursorAlter + 2, 1);
}
void TscoreObject::openMusicXml(const QString &musicFile, Tmelody *melody, bool ignoreTechnical)
{
    if (!musicFile.isEmpty()) {
        bool melodyCreated = false;
        if (!melody) {
            melody = new Tmelody();
            melodyCreated = true;
        }
        if (melody->grabFromMusicXml(musicFile))
            setMelody(melody, ignoreTechnical);
        if (melodyCreated)
            delete melody;
    }
void TscoreObject::saveMusicXml(const QString &musicFile, const QString &title, const QString &composer, int transposition)
{
    if (!musicFile.isEmpty()) {
        QString fileName = musicFile;
        if (musicFile.right(4) != QLatin1String(".xml") && musicFile.right(9) != QLatin1String(".musicxml") && musicFile.right(4) != QLatin1String(".mxl"))
            fileName += QLatin1String(".musicxml"); // prefer musicxml extension
        auto melody = new Tmelody(title, TkeySignature(static_cast<char>(keySignature())));
        getMelody(melody);
        melody->setComposer(composer);
        melody->saveToMusicXml(fileName, transposition);
        delete melody;
    }
}
void TscoreObject::setMelody(Tmelody *melody, bool ignoreTechnical, int notesAmount, int transposition)
{
    clearScorePrivate();
    m_notes.clear();
    setMeter(static_cast<int>(melody->meter()->meter()));
    setClefType(melody->clef());
    int newKey = static_cast<int>(melody->key().value());
    if (newKey != keySignature()) {
        if (!m_keySignEnabled && qAbs(newKey) != 0)
            setKeySignatureEnabled(true);
        setKeySignature(newKey);
    }
    int notesToCopy = notesAmount == 0 ? melody->length() : qMin(notesAmount, melody->length());
    for (int n = 0; n < notesToCopy; ++n) {
        Tnote &note = melody->note(n)->p();
        // when note is lower than 7  - 'g small' (the lowest supported note on the upper staff)
        // set it to lower staff (upper to false) to beam notes correctly
        if (melody->clef() == Tclef::PianoStaffClefs && note.chromatic() < 8 && !note.isRest() && note.onUpperStaff())
            note.setOnUpperStaff(false);
        if (transposition) {
            Tnote nn = note;
            nn.transpose(transposition);
            if (m_keySignature < 0 && nn.alter() == Tnote::e_Sharp)
                nn = nn.showWithFlat();
            addNote(nn);
        } else
            addNote(note);
        if (!ignoreTechnical)
            lastSegment()->setTechnical(melody->note(n)->technical());
    }
    adjustScoreWidth();
    emitLastNote();
void TscoreObject::getMelody(Tmelody *melody)
{
    melody->setClef(clefType());
    melody->setMeter(m_meter->meter());
    if (m_keySignEnabled)
        melody->setKey(TkeySignature(static_cast<char>(m_keySignature)));
    for (int n = 0; n < notesCount(); ++n) {
        Ttechnical technical(noteSegment(n)->techicalData());
        melody->addNote(Tchunk(m_notes[n], technical));
    }
}
// #################################################################################################
// ###################         Score switches           ############################################
// #################################################################################################
void TscoreObject::setKeySignatureEnabled(bool enKey)
{
    if (enKey != m_keySignEnabled) {
        if (!enKey)
            m_keySignature = 0;
        m_keySignEnabled = enKey;
        emit keySignatureEnabledChanged();
        if (notesCount() > 0)
            adjustScoreWidth();
void TscoreObject::setKeyReadOnly(bool ro)
{
    if (m_keySignEnabled) {
        if (ro != m_keyReadOnly) {
            m_keyReadOnly = ro;
            emit keyReadOnlyChanged();
        }
    }
}
void TscoreObject::setShowExtraAccids(bool accShow)
{
    if (m_showExtraAccids != accShow) {
        m_showExtraAccids = accShow;
        if (notesCount()) {
            for (int n = 0; n < notesCount(); ++n)
                m_segments[n]->item()->keySignatureChanged(); // HACK: it will force refresh accidental symbol
            adjustScoreWidth();
        }
void TscoreObject::setEnableDoubleAccids(bool dblEnabled)
{
    if (m_enableDoubleAccids != dblEnabled) {
        m_enableDoubleAccids = dblEnabled;
        if (!m_enableDoubleAccids) {
            // TODO: convert notes with double accidentals into single-ones
        }
    }
}
/**
 * When @p m_showNoteNames is set to @p TRUE:
 * @p TmeasureObject during adding a note item calls @p TnoteItem::setNoteNameVisible() to create note name
 * This method iterates all notes and switches its state of displaying note name
 */
void TscoreObject::setShowNoteNames(bool showNames)
{
    if (m_showNoteNames != showNames) {
        m_showNoteNames = showNames;
        if (notesCount()) {
            for (int n = 0; n < notesCount(); ++n)
                m_segments[n]->item()->setNoteNameVisible(m_showNoteNames && m_clefType != Tclef::NoClef && !m_singleNote);
        }
void TscoreObject::setNameColor(const QColor &nameC)
{
    if (m_nameColor != nameC) {
        m_nameColor = nameC;
        if (m_showNoteNames) {
            if (notesCount()) {
                for (int n = 0; n < notesCount(); ++n) // with hope that all items have name item created
                    m_segments[n]->item()->nameItem()->setProperty("styleColor", m_nameColor);
            }
        }
void TscoreObject::setNameStyle(int nameS)
{
    m_nameStyle = nameS;
    if (m_showNoteNames) {
        if (notesCount()) {
            for (int n = 0; n < notesCount(); ++n) // with hope that all items have name item created
                m_segments[n]->item()->nameItem()->setProperty("text", m_notes[n].styledName());
        }
void TscoreObject::setReadOnly(bool ro)
{
    if (m_readOnly != ro) {
        m_readOnly = ro;
        emit readOnlyChanged();
        if (m_deleteNoteAct && !m_singleNote) {
            m_deleteNoteAct->setEnabled(!ro);
            m_clearScoreAct->setEnabled(!ro);
            m_editModeAct->setEnabled(!ro);
            m_insertNoteAct->setEnabled(!ro);
        }
        setKeyReadOnly(ro);
        if (!m_readOnly) {
            setEditMode(true);
        }
void TscoreObject::setEditMode(bool isEdit)
{
    if (isEdit != m_editMode) {
        m_editMode = isEdit;
        if (m_editModeAct)
            m_editModeAct->setChecked(m_editMode);
        emit editModeChanged();
        if (!m_editMode && m_activeNote) {
            m_activeNote = nullptr;
            emit activeNoteChanged();
            setActiveNotePos(0.0);
            setHoveredNote(nullptr);
        }
/**
 * MainScore.qml also handles single note mode change,
 * but this method is (AND HAS TO BE) invoked first
 */
void TscoreObject::setSingleNote(bool singleN)
{
    if (singleN != m_singleNote) {
        clearScore(); // In single note mode this call is ignored
        if (singleN) {
            addNote(Tnote()); // it is avoided in single note mode
            addNote(Tnote());
            addNote(Tnote());
            setShowNoteNames(false);
            m_singleNote = true;
            setNote(0, Tnote()); // reset it (hide) because addNote was performed above in multi notes mode
            setNote(1, Tnote());
            setNote(2, Tnote());
            note(0)->shiftHead(1.5);
            note(1)->shiftHead(1.5);
            note(2)->shiftHead(1.5);
            note(1)->setEnabled(false);
            note(2)->setEnabled(false);
            m_selectedItem = note(0);
        } else {
            m_singleNote = false;
            resetNoteItem(note(0));
            resetNoteItem(note(1));
            resetNoteItem(note(2));
            clearScore(); // call it again when transitioning from single note mode
        }
        emit singleNoteChanged();
void TscoreObject::setScaleFactor(qreal factor)
{
    if (factor != m_scaleFactor) {
        m_scaleFactor = factor;
        emit scaleFactorChanged();
    }
void TscoreObject::setEnableTechnical(bool enTech)
{
    if (enTech != m_enableTechnControl) {
        m_enableTechnControl = enTech;
        emit enableTechnicalChanged();
    }
void TscoreObject::transpose(int semis, bool outScaleToRest, const Tnote &loNote, const Tnote &hiNote)
{
    if (semis == 0 || notesCount() == 0)
        return; // nothing to transpose

    auto lo = loNote.isValid() ? loNote.chromatic() : lowestNote().chromatic();
    auto hi = hiNote.isValid() ? hiNote.chromatic() : highestNote().chromatic();
    int rtmGrToCheck = 0;
    bool fixBeam = false;
    for (int n = 0; n < notesCount(); ++n) {
        auto noteSeg = m_segments[n];
        int transOff = 0;
        Trhythm transRtm(noteSeg->note()->rtm);
        auto transChrom = noteSeg->note()->chromatic() + semis;
        if (outScaleToRest) {
            if (transChrom > hi || transChrom < lo) {
                transRtm.setRest(true);
                transRtm.setTie(Trhythm::e_noTie);
            }
        } else {
            if (transChrom > hi)
                transOff = -12; // when too high drop octave down
            else if (transChrom < lo)
                transOff = 12; // when too low raise octave up
        }
        Tnote transposed(*noteSeg->note(), transRtm);
        if (transRtm.isRest())
            transposed.setNote(0);
        else
            transposed.transpose(semis + transOff);
        if (m_keySignEnabled) {
            auto inKeyNote = TkeySignature(static_cast<char>(m_keySignature)).inKey(transposed);
            if (inKeyNote.isValid()) {
                transposed.setNote(inKeyNote.note());
                transposed.setOctave(inKeyNote.octave());
                transposed.setAlter(inKeyNote.alter());
            }
        noteSeg->setPairNotes(transposed);
        updateNoteInList(noteSeg, transposed);
        if (noteSeg->beam() && !transRtm.isRest())
            fixBeam = true;
        if (fixBeam) {
            int nextRtmGr = (n == notesCount() - 1 ? -1 : m_segments[n + 1]->rhythmGroup());
            bool lastInBar = (noteSeg == noteSeg->item()->measure()->last());
            if (nextRtmGr != rtmGrToCheck || lastInBar) { // summarize beaming at the end of group or measure
                noteSeg->item()->measure()->resolveBeaming(rtmGrToCheck, rtmGrToCheck);
                fixBeam = false; // reset beam fix for next group checking
                rtmGrToCheck = nextRtmGr;
            }
        }
    for (int m = 0; m < m_measures.count(); ++m)
        m_measures[m]->refresh();
    adjustScoreWidth();
qreal TscoreObject::stavesHeight()
{
    if (m_staves.isEmpty())
        return 0.0;
    auto last = lastStaff();
    return last->y() + last->height() * last->scale();
void TscoreObject::changeActiveNote(TnoteItem *aNote)
{
    if (aNote != m_activeNote) {
        if (m_activeNote && m_activeNote->staff())
            m_activeNote->staff()->setZ(0); // reset staff z order (set it down)
        auto prevActive = m_activeNote;
        m_activeNote = aNote;
        if (m_activeNote == nullptr) {
            m_leaveTimer->start(600);
        } else {
            if (prevActive == nullptr)
                m_enterTimer->start(300);
            else {
                enterTimeElapsed();
                emit activeYposChanged();
            }
            if (m_activeNote->staff()) // raise staff to overlap other staves and keep note cursor stick to this staff
                m_activeNote->staff()->setZ(1);
void TscoreObject::setActiveNotePos(qreal yPos)
{
    if (!m_enterTimer->isActive() && yPos != m_activeYpos) {
        m_activeYpos = yPos;
        emit activeYposChanged();
    }
/**
 * Returns X coordinate of the first note in active measure (if any).
 *  or (if score is empty yet) - returns staff indent (position after meter)
 * So far it is used for positioning accidental pane on the active measure left.
qreal TscoreObject::xFirstInActivBar()
{
    if (m_activeBarNr < 0)
        return (firstStaff()->notesIndent() - 2.0) * firstStaff()->scale();
    else
        return (m_measures[m_activeBarNr]->first()->item()->x() - m_measures[m_activeBarNr]->first()->item()->alterWidth() - 1.0) * firstStaff()->scale();
}

/**
 * Returns X coordinate of the last note in active measure (if any),
 * but if that note is too far on the right (near a staff end), coordinate of first note is returned,
 * subtracted with 11.2 units of score scale which is width of the rhythm pane - to keep it visible.
 * So far it is used for positioning rhythm pane on the active measure right
 */
qreal TscoreObject::xLastInActivBar()
{
    if (m_activeBarNr > -1) {
        qreal xx = m_measures[m_activeBarNr]->last()->item()->rightX();
        if (xx > m_width - 12.0)
            return xFirstInActivBar() - 11.2 * firstStaff()->scale();
        else
            return (xx + 7.0) * firstStaff()->scale();
    }
    return (firstStaff()->notesIndent() + 7.0) * firstStaff()->scale();
Trhythm TscoreObject::workRhythm() const
{
    return *m_workRhythm;
void TscoreObject::setWorkRhythm(const Trhythm &r)
{
    if (r != *m_workRhythm) {
        m_workRhythm->setRhythm(r);
        emit workRhythmChanged();
    }
QString TscoreObject::workRtmText() const
{
    return TnoteItem::getHeadText(workRhythm());
int TscoreObject::workRtmValue() const
{
    return static_cast<int>(m_workRhythm->rhythm());
void TscoreObject::setWorkRtmValue(int rtmV)
{
    if (static_cast<Trhythm::Erhythm>(rtmV) != m_workRhythm->rhythm()) {
        m_workRhythm->setRhythmValue(static_cast<Trhythm::Erhythm>(rtmV));
        emit workRhythmChanged();
    }
bool TscoreObject::workRtmRest() const
{
    return m_workRhythm->isRest();
void TscoreObject::setWorkRtmRest(bool hasRest)
{
    if (hasRest != m_workRhythm->isRest()) {
        m_workRhythm->setRest(hasRest);
        emit workRhythmChanged();
    }
bool TscoreObject::workRtmDot() const
{
    return m_workRhythm->hasDot();
void TscoreObject::setWorkRtmDot(bool hasDot)
{
    if (hasDot != m_workRhythm->hasDot()) {
        m_workRhythm->setDot(hasDot);
        emit workRhythmChanged();
    }
QString TscoreObject::activeRtmText()
{
    if (m_activeNote)
        return TnoteItem::getHeadText(
            Trhythm(m_activeNote == lastSegment()->item() ? m_workRhythm->rhythm() : m_activeNote->note()->rhythm(), m_workRhythm->isRest()));
    return QString();
Tnote TscoreObject::posToNote(qreal yPos)
{
    int globalNr = globalNoteNr(yPos);
    return Tnote(m_workRhythm->isRest() || m_clefType == Tclef::NoClef ? 0 : static_cast<char>(56 + globalNr) % 7 + 1,
                 static_cast<char>(56 + globalNr) / 7 - 8,
                 static_cast<char>(m_cursorAlter),
                 workRhythm());
}
void TscoreObject::setAllowAdding(bool allow)
{
    if (m_singleNote)
        allow = false;
    if (allow != m_allowAdding) {
        m_allowAdding = allow;
        adjustScoreWidth();
        emit allowAddingChanged();
    }
TnoteItem *TscoreObject::lastNote()
{
    return m_segments.isEmpty() ? nullptr : lastSegment()->item();
}
qreal TscoreObject::midLine(TnoteItem *actNote)
{
    if (stavesCount() == 0)
        return 0.0;
    if (actNote && m_activeNote)
        return activeNote()->y() + (upperLine() + 4.0) * lastStaff()->scale();
        return lastStaff()->y() + (upperLine() + 4.0) * lastStaff()->scale();
void TscoreObject::deleteNote(TnoteItem *n)
{
    if (n == lastNote()) {
        deleteLastNote();
        return;
    }
    if (n) {
        int rmId = n->index();
        if (n->note()->rtm.tie()) {
            if (n->note()->rtm.tie() == Trhythm::e_tieStart) {
                m_segments[rmId + 1]->disconnectTie(TnotePair::e_untieNext);
            } else { // tie continue or end
                if (n->note()->rtm.tie() == Trhythm::e_tieCont)
                    m_segments[rmId + 1]->disconnectTie(TnotePair::e_untieNext);
                m_segments[rmId - 1]->disconnectTie(TnotePair::e_untiePrev);
            }
        }
        auto segToRemove = m_segments.takeAt(rmId);
        auto m = n->measure();
        int staffNr = m->staff()->number();
        segToRemove->flush();
        m_spareSegments << segToRemove;
        m_notes.removeAt(rmId);