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 "tmeasureobject.h"
#include "tnoteitem.h"
#include "tnotepair.h"
#include "tstaffitem.h"
#include <QtCore/qtimer.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpalette.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
SeeLook
committed
SeeLook
committed
#include <QtCore/qdebug.h>
#define WIDTH_CHANGE_DELAY (50) // when score width changes, give 50 ms before staves will be resized
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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);
} 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);
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
*/
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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();
}
}
}
SeeLook
committed
/** @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();
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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);
// 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();
// 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));
} else
note(1)->setVisible(false);
++it;
if (it != enharmList.end()) {
note(2)->setVisible(true);
m_segments[2]->setPairNotes(*(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;
SeeLook
committed
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();
emit clicked();
}
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;
}
}
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
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 ¬e = 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();
SeeLook
committed
}
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));
}
}
SeeLook
committed
// #################################################################################################
// ################### 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);
}
SeeLook
committed
}
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());
}
SeeLook
committed
}
}
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
*/
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
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();
}
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
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);
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());
SeeLook
committed
}
void TscoreObject::setWorkRtmValue(int rtmV)
{
if (static_cast<Trhythm::Erhythm>(rtmV) != m_workRhythm->rhythm()) {
m_workRhythm->setRhythmValue(static_cast<Trhythm::Erhythm>(rtmV));
emit workRhythmChanged();
}
SeeLook
committed
}
bool TscoreObject::workRtmRest() const
{
return m_workRhythm->isRest();
SeeLook
committed
}
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);