Skip to content
Snippets Groups Projects
tscoreobject.cpp 60.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    
     *   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);