diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 086cf75cc1df9cba18b19bc81c41cf870f91cd64..be4ab2aad2aa98226b7bb446c2916d5a120efa02 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,7 +59,7 @@ endif (APPLE) # add_subdirectory( libs/scorek ) add_subdirectory( libs/core ) # libNootkaCore -# add_subdirectory( libs/score ) # libNootkaScore +add_subdirectory( libs/score ) # libNootkaScore # add_subdirectory( libs/widgets ) # libNootkaWidgets # add_subdirectory( libs/sound ) # libNootkaSound # add_subdirectory( libs/misc ) # libNootkaMisc @@ -88,6 +88,7 @@ qt5_add_resources(NOOTKA_SRC nootka.qrc) add_executable(nootka WIN32 ${NOOTKA_SRC} ${NOOTKA_EXE_ICON}) target_link_libraries(nootka NootkaCore + NootkaScore Qt5::Core Qt5::Widgets Qt5::Qml diff --git a/src/libs/core/music/tmeter.cpp b/src/libs/core/music/tmeter.cpp index c40b4bf346e8df6718e3e4813fd2a76c53114758..f96429e09424b9fee95f08f194551c5523c8b919 100644 --- a/src/libs/core/music/tmeter.cpp +++ b/src/libs/core/music/tmeter.cpp @@ -17,7 +17,7 @@ ***************************************************************************/ #include "tmeter.h" -// #include "tnoofont.h" +#include "tnoofont.h" #include "trhythm.h" #include <QtGui/qfontmetrics.h> #include <QtGui/qpainter.h> @@ -69,20 +69,19 @@ QPixmap Tmeter::pixmap(int fontSize, const QColor& c) { if (meter() == e_none) return QPixmap(); -// TnooFont font(fontSize); -// QFontMetrics fm(font); -// QString upperDigit = TnooFont::digit(upper()); -// QPixmap pix(QSize(fm.boundingRect(upperDigit).width() + 4, fontSize + 12)); // upper digit is usually wider -// /*/*pix.fill(Qt::transparent); -// QPainter p(&pix); -// p.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing, true); -// p.setFont(font); -// p.setPen(c == -1 ? qApp->palette().text().color() : c); -// p.setBrush(Qt::NoBrush); -// p.drawText(QRect(0, 0, pix.width(), fontSize / 2 + 8), Qt::AlignCenter, upperDigit); -// p.drawText(QRect(0, fontSize / 2 - 1, pix.width(), fontSize / 2 + 8), Qt::AlignCenter, TnooFont::digit(lower()));*/*/ -// return pix; - return QPixmap(); + TnooFont font(fontSize); + QFontMetrics fm(font); + QString upperDigit = TnooFont::digit(upper()); + QPixmap pix(QSize(fm.boundingRect(upperDigit).width() + 4, fontSize + 12)); // upper digit is usually wider + pix.fill(Qt::transparent); + QPainter p(&pix); + p.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing, true); + p.setFont(font); + p.setPen(c == -1 ? qApp->palette().text().color() : c); + p.setBrush(Qt::NoBrush); + p.drawText(QRect(0, 0, pix.width(), fontSize / 2 + 8), Qt::AlignCenter, upperDigit); + p.drawText(QRect(0, fontSize / 2 - 1, pix.width(), fontSize / 2 + 8), Qt::AlignCenter, TnooFont::digit(lower())); + return pix; } diff --git a/src/libs/core/tinitcorelib.cpp b/src/libs/core/tinitcorelib.cpp index 72347bf78c3e7dc035ee548ca4957b6863e56b45..2a9fdf2d22ff97f84d6d5b508516d55a4f6772b5 100644 --- a/src/libs/core/tinitcorelib.cpp +++ b/src/libs/core/tinitcorelib.cpp @@ -116,7 +116,8 @@ void prepareTranslations(QApplication* a, QTranslator& qt, QTranslator& noo) { bool loadNootkaFont(QApplication* a) { QFontDatabase fd; int fid = fd.addApplicationFont(Tpath::main + QLatin1String("fonts/nootka.ttf")); - if (fid == -1) { + int fid2 = fd.addApplicationFont(Tpath::main + QLatin1String("fonts/Scorek.otf")); + if (fid == -1 || fid2 == -1) { QMessageBox::critical(0, QString(), a->translate("main", "<center>Can not load a font.<br>Try to install nootka.ttf manually.</center>")); return false; } diff --git a/src/libs/core/tnoofont.cpp b/src/libs/core/tnoofont.cpp index bc3ca8b50f779c713e930e69400a9229c5a2f684..dbe961b2905156f6aa52905718c4ad27e359b4b1 100644 --- a/src/libs/core/tnoofont.cpp +++ b/src/libs/core/tnoofont.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2014-2015 by Tomasz Bojczuk * + * Copyright (C) 2014-2016 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -16,18 +16,58 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ +/** Glyphs (some) + * Rests: + * 0xe102 - whole rest + * 0xe103 - half rest + * 0xe108 - quarter rest + * 0xe10A - eight rest + * 0xe10B - sixteen rest + * + * Flags + * 0xe21C - eight Up + * 0xe21D - sixteen Up + * 0xe221 - eight Down + * 0xe221 - sixteen Down + * + * Note heads: + * 0xe191 - whole (empty but big) + * 0xe192 - half (empty) + * 0xe193 - quarter and above (full) + * Note symbols (head, stem and flag) + * 0x0043 - 'C' - whole - head only + * 0x0044 - 'D' - half (stem up) + * ... and so on till + * 0x0047 - 'G' - sixteen + * stems down starts from + * 0x004A - 'J' - half - (stem down) + * ... till 0x004D - 'M' - sixteen + * + * Accidentals (all are centered in glyph height but those available by letters: B b # x are placed at a bottom) + * 0xe123 - double flat (also B) + * 0xe11a - flat (also b) + * 0xe10e - sharp (also #) + * 0xe125 - double sharp (also x) + * 0xe116 + * + * digits [0-9] starts from 0x0180 + */ #include "tnoofont.h" +#include <math.h> + TnooFont::TnooFont(int pointSize) : - QFont("nootka", pointSize) + QFont(QStringLiteral("nootka"), pointSize) { setPixelSize(pointSize); setBold(false); setWeight(50); // Normal } - +//################################################################################################# +//################### STATIC ############################################ +//################################################################################################# QString TnooFont::tag(const QString& tag, const QString& text, int fontSize, const QString& extraStyle) { QString fSize; if (fontSize) @@ -40,3 +80,20 @@ QString TnooFont::tag(const QString& tag, const QString& text, int fontSize, con } +quint16 TnooFont::getCharFromRhythm(quint16 rhythm, bool stemUp, bool rest) { + int baseChar = 67, stemGap = 0; + if (rest) + baseChar = 0xe107; + else if (!stemUp && rhythm > 1) // stem down only if no rest and half note at least + stemGap = 6; + if (rhythm) + return baseChar + (int)std::log2(rhythm) + stemGap; + else + return 0xe193; +} + + + + + + diff --git a/src/libs/core/tnoofont.h b/src/libs/core/tnoofont.h index 567c0f54d1e5bdbd2468828eac87eb1fcbb11792..d2bf3eed8f121b9aa3b6ff4d3ebca3806ef49fde 100644 --- a/src/libs/core/tnoofont.h +++ b/src/libs/core/tnoofont.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2014 by Tomasz Bojczuk * + * Copyright (C) 2014-2016 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -44,10 +44,20 @@ public: /** tag() method with span tag */ static QString span(const QString& text, int fontSize = 0, const QString& extraStyle = QString()) { return tag("span", text, fontSize, extraStyle); } - - /** Overloaded method with current font size */ -// QString span(const QString& text, const QString& extraStyle = "") { return span(text, pointSize(), extraStyle); } + /** Bare digits starts from UNI-0x180 in nootka.ttf + * due to glyphs of ordinary numbers are used for strings (circled). + * This method returns proper string of given digit + */ + static QString digit(quint8 d) { return (d / 10 ? QString(QChar(0x0180 + d / 10)) : QString()) + QString(QChar(0x0180 + d % 10)); } + + /** Converts rhythm value (0, 1, 2, 4 - 16) into uni-code char number in Nootka font + * 0 (no rhythm returns full note head symbol) + * set @p stemUp to false to get symbols with stem down. + * Returned characters are optimized for score (staff lines height), + * to get just note symbol use 'n' and 'N' (stem down) instead + */ + static quint16 getCharFromRhythm(quint16 rhythm, bool stemUp = true, bool rest = false); }; diff --git a/src/libs/score/CMakeLists.txt b/src/libs/score/CMakeLists.txt index d99e28c3a8091c37684f123c30e75825a1091653..8325c354fd2a0fe257aaef4d65af45e454799a27 100644 --- a/src/libs/score/CMakeLists.txt +++ b/src/libs/score/CMakeLists.txt @@ -7,34 +7,42 @@ include_directories( . ../core ) add_definitions(-DNOOTKACORE_LIBRARY) set(LIB_NOOTKASCORE_SRC - graphics/tnotepixmap.cpp - - widgets/tselectclef.cpp - - tscoreitem.cpp - tscoreclef.cpp - tscorekeysignature.cpp - tscorenote.cpp - tscorescene.cpp - tscorescordature.cpp - tscorestaff.cpp - tscore5lines.cpp - tnotecontrol.cpp - tpaneitem.cpp - tscorelines.cpp - tsimplescore.cpp - tmultiscore.cpp +# tscoreplugin.cpp +# graphics/tnotepixmap.cpp + + declarative/descore.cpp + declarative/denote.cpp + + tscoreitem.cpp + tscoreclef.cpp + tscorekeysignature.cpp + tscorenote.cpp + tnoteitem.cpp + tscorescene.cpp + tscorescordature.cpp + tscorestaff.cpp + tscoremeasure.cpp + tscorebeam.cpp + tscoretie.cpp + tscore5lines.cpp + tscorelines.cpp + tscoremeter.cpp ) + +# qt5_add_resources(LIB_NOOTKASCORE_SRC score.qrc) + + add_library(NootkaScore SHARED ${LIB_NOOTKASCORE_SRC} ) target_link_libraries(NootkaScore NootkaCore Qt5::Widgets) -if(UNIX AND NOT APPLE) # Linux path for Nootka library + +if(UNIX AND NOT APPLE) # Linux path for Nootka library install(TARGETS NootkaScore DESTINATION lib/nootka) else(UNIX AND NOT APPLE) - if(WIN32) # Windows + if(WIN32) # Windows install(TARGETS NootkaScore DESTINATION .) - else(WIN32) # MacOs + else(WIN32) # MacOs install(TARGETS NootkaScore DESTINATION "${CMAKE_INSTALL_PREFIX}/nootka.app/Contents/Frameworks") endif(WIN32) endif(UNIX AND NOT APPLE) diff --git a/src/libs/score/tnoteitem.cpp b/src/libs/score/tnoteitem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a1789ef017ba8a26f1916c556913278d8e1d0c9 --- /dev/null +++ b/src/libs/score/tnoteitem.cpp @@ -0,0 +1,167 @@ +/*************************************************************************** + * Copyright (C) 2016 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 "tnoteitem.h" +#include <music/trhythm.h> +#include <QtWidgets/qapplication.h> +#include <QtGui/qpalette.h> +#include <QtGui/qpainter.h> + +#include <QtCore/qdebug.h> + + +#define DOT (0xe1e7) +#define WHOLE_REST (0xe4e2) +#define BASE_HEAD (0xe0a1) + +#define STEM_HEIGHT (5.8) +#define HALF_STEM (0.1) +#define HEAD_WIDTH (2.35938) // width of black note head glyph for default font size of 6 +#define FONT_SIZE (6) + +#define STEM_DOWN_Y (1.2) +#define STEM_UP_Y (0.79) +#define MID_LINE (20.0) +#define FLAG_GLYPH (0xe238) + + +TnoteItem::TnoteItem(TscoreScene* scene, const Trhythm& r) : + TscoreItem(scene) +{ + m_rhythm = new Trhythm(r); + m_stem = new QGraphicsLineItem(this); + m_stem->hide(); + m_flag = new QGraphicsSimpleTextItem(this); + m_flag->setFont(QFont(QStringLiteral("Scorek"), 5)); + m_flag->hide(); + setColor(qApp->palette().text().color()); + setAcceptHoverEvents(false); +} + + +TnoteItem::~TnoteItem() +{ + delete m_rhythm; +} + + +void TnoteItem::setItAsCursor() { + delete m_flag; + m_flag = nullptr; + delete m_stem; + m_stem = nullptr; +} + + +/** + * Except setting Y coordinate, + * this method takes care about stem length when note is much above or below staff. + * Stem is prolonged until middle staff line (as professional scores have) + * + * TODO: WHAT ABOUT PIANO STAFF WHERE ARE TWO MIDDLE LINES + */ +void TnoteItem::setY(qreal yPos) { + QGraphicsItem::setY(yPos); + if (m_stem) { + if (m_stem->isVisible()) { + if (m_rhythm->stemDown()) + m_stem->setLine(HALF_STEM, STEM_DOWN_Y, HALF_STEM, qMax(STEM_HEIGHT + STEM_DOWN_Y, MID_LINE - yPos)); + else + m_stem->setLine(HEAD_WIDTH - HALF_STEM, STEM_UP_Y, HEAD_WIDTH - HALF_STEM, + qMin(-(STEM_HEIGHT + (m_rhythm->rhythm() > Trhythm::e_quarter && m_rhythm->hasDot() ? 1.0 : 0.0) - STEM_UP_Y), MID_LINE - yPos)); + } + if (m_flag->isVisible()) { + if (m_rhythm->stemDown()) + m_flag->setPos(HALF_STEM, m_stem->line().length() - STEM_DOWN_Y - 12.5); + else + m_flag->setPos(HEAD_WIDTH, STEM_UP_Y - m_stem->line().length() - 15.0); + } + } +} + + +/** + * It occurs only when stem is managed by @p TscoreBeam class, so there is no flag + * and no need to set flag position again + */ +void TnoteItem::setStemLength(qreal len) { + qreal y2 = m_stem->line().y1() + len; + if (m_rhythm->stemDown()) + y2 = qMax(y2, MID_LINE - y()); + else + y2 = qMin(y2, MID_LINE - y()); + m_stem->setLine(m_stem->line().x1(), m_stem->line().y1(), m_stem->line().x2(), y2); +} + + + +void TnoteItem::setColor(const QColor& c) { + m_color = c; + if (m_stem) { + m_stem->setPen(QPen(m_color, 2 * HALF_STEM)); + m_flag->setBrush(m_color); + } +} + + +void TnoteItem::setRhythm(const Trhythm& r) { + if (r != *m_rhythm || r.stemDown() != m_rhythm->stemDown() || r.beam() != m_rhythm->beam()) { + *m_rhythm = r; + int v = BASE_HEAD + qMin(static_cast<int>(m_rhythm->rhythm()), 3); + if (m_stem) { + if (!m_rhythm->isRest() && m_rhythm->rhythm() > Trhythm::e_whole) { + m_stem->show(); + if (m_rhythm->beam() == Trhythm::e_noBeam && m_rhythm->rhythm() > Trhythm::e_quarter) { + m_flag->setText(QString(QChar(FLAG_GLYPH + m_rhythm->rhythm() * 2 + (m_rhythm->stemDown() ? 1 : 0)))); + m_flag->show(); + } else + m_flag->hide(); + setY(y()); // updates positions of stem and flag + } else { + m_stem->hide(); + m_flag->hide(); + } + } + if (m_rhythm->isRest()) + v = WHOLE_REST + m_rhythm->rhythm(); + m_noteLetter = QString(QChar(v)); + } +} + + +void TnoteItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + Q_UNUSED(option) + Q_UNUSED(widget) + +// paintBackground(painter, Qt::blue); + painter->setPen(m_color); + + painter->setFont(QFont(QStringLiteral("Scorek"), FONT_SIZE)); + painter->drawText(QPointF(0.0, 1.0), m_noteLetter); + if (m_rhythm->hasDot()) { + painter->drawText(QPointF(m_rhythm->rhythm() == Trhythm::e_whole ? 3.5 : 2.9, 1.0 - static_cast<qreal>(static_cast<int>(y()) % 2)), + QString(QChar(DOT))); + } +} + + +QRectF TnoteItem::boundingRect() const { + return QRectF(0.0, -2.5, 3.0, 8.0); // it covers note heads and all rests +} + + diff --git a/src/libs/score/tnoteitem.h b/src/libs/score/tnoteitem.h new file mode 100644 index 0000000000000000000000000000000000000000..9b4dfd03cd74ad672561bb7d74a59d23ec44bb11 --- /dev/null +++ b/src/libs/score/tnoteitem.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2016 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/>. * + ***************************************************************************/ + +#ifndef TNOTEITEM_H +#define TNOTEITEM_H + + +#include <nootkacoreglobal.h> +#include "tscoreitem.h" + + +class Trhythm; + + +/** + * Paints note head on the @class TscoreNote + * and rhythm when it is enabled. + * It paints steam and flag of whole and quarter notes + * but if rhythm has any beam, + * notes above quarter have hidden flags + * + * It can be just a cursor without stem and flag when @p setItAsCursor() is called + */ +class NOOTKACORE_EXPORT TnoteItem : public TscoreItem +{ + +public: + TnoteItem(TscoreScene* scene, const Trhythm& r); + virtual ~TnoteItem(); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual QRectF boundingRect() const; + + void setColor(const QColor& c); + QColor color() { return m_color; } + + /** In cursor mode there are no steam and flag - only note head or a rest */ + void setItAsCursor(); + + + void setRhythm(const Trhythm& r); + Trhythm* rhythm() const { return m_rhythm; } /**< Actual rhythm of a note */ + + QGraphicsLineItem* stem() { return m_stem; } + + /** Sets stem length - adds @p len value to y1 of stem line */ + void setStemLength(qreal len); + + /** End point (p2) of stem line in parent note coordinates */ + QPointF stemEndPoint() { return pos() + m_stem->line().p2(); } + + void setY(qreal yPos); + + +private: + QColor m_color; + Trhythm *m_rhythm; /**< This is pointer of @p Trhythm - Note head and flags are determined by it */ + QString m_noteLetter; /**< single letter representing a note symbol in Nootka font */ + QGraphicsLineItem *m_stem; + QGraphicsSimpleTextItem *m_flag; +}; + +#endif // TNOTEITEM_H diff --git a/src/libs/score/tscorebeam.cpp b/src/libs/score/tscorebeam.cpp new file mode 100644 index 0000000000000000000000000000000000000000..acfb5c62951e90bc6f908ecddae702a966b93501 --- /dev/null +++ b/src/libs/score/tscorebeam.cpp @@ -0,0 +1,276 @@ +/*************************************************************************** + * Copyright (C) 2016 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 "tscorebeam.h" +#include "tscorenote.h" +#include "tscorestaff.h" +#include "tnoteitem.h" +#include "tscoremeasure.h" +#include <music/tnote.h> +#include <QtWidgets/qgraphicsscene.h> +#include <QtCore/qdebug.h> + + +/** + * Simple structure to describe second beam line (sixteenth) + * which can be chunked when there are rests or eights in the group + */ +class T16beam { +public: + T16beam(int firstStemNr = 0, QGraphicsPolygonItem* beamPoly = nullptr) : startStem(firstStemNr), b(beamPoly) {} + ~T16beam() { delete b; } + int startStem = -1; /**< Undefined by default */ + int endStem = -1; /**< When remains undefined - beam is partial */ + + /** @p TRUE when beam is not connected to another stem */ + bool isHalf() { return endStem == -1; } + QGraphicsPolygonItem* b = nullptr; /**< beam item */ +}; + + +#define MIN_STEM_HEIGHT (4) // minimal stem height (distance of note head to staff boundary) +#define STEM_HEIGHT (5.8) +#define SLOPER (1.5) // common value to change first and last stems length when there are more than two notes in a beam +#define HALF_STEM (0.1) // half of stem line width - this is also distance that stem line takes above stem points +#define BEAM_THICK (0.8) // thickness of a beam + + +TscoreBeam::TscoreBeam(TscoreNote* sn, TscoreMeasure* m) : + QObject(m), + m_measure(m) +{ + sn->note()->rtm.setBeam(Trhythm::e_beamStart); + addNote(sn); + qDebug() << " [BEAM] created for note id" << sn->index(); +} + + +TscoreBeam::~TscoreBeam() +{ + qDebug() << " [BEAM] deleted of id" << first()->index(); + for (TscoreNote* note : m_notes) { + note->note()->rtm.setBeam(Trhythm::e_noBeam); // restore beams + note->setBeam(nullptr); + } + qDeleteAll(m_16_beams); + delete m_8_beam; +} + + +void TscoreBeam::addNote(TscoreNote* sn) { + if (m_notes.isEmpty() && sn->note()->rtm.beam() != Trhythm::e_beamStart) // TODO: delete it when no errors + qDebug() << " [BEAM] new beam starts but not proper flag is set!"; + else if (!m_notes.isEmpty() && last()->note()->rtm.beam() == Trhythm::e_beamEnd) + qDebug() << " [BEAM] Adding note to beam group that already ended!"; + + if (m_notes.isEmpty()) + m_summaryPos = 0; + m_summaryPos += sn->newNotePos(); + + m_notes << sn; + sn->setBeam(this); +// if (sn->newRhythm()->beam() == Trhythm::e_beamEnd) + connect(sn, &TscoreNote::noteWasClicked, this, &TscoreBeam::performBeaming); + +// 'main' beam of eights - always exists + if (sn->note()->rtm.beam() == Trhythm::e_beamStart) { // resume grouping, prepare for painting + m_8_beam = createBeam(sn); + } + +// beam of 16th - it may be chunked when rest or eight occurs + if (sn->note()->rhythm() == Trhythm::e_sixteenth) { + if (sn->note()->isRest()) { + + } else { + if (m_16_beams.isEmpty() || m_notes.at(m_notes.count() - 2)->note()->rhythm() != Trhythm::e_sixteenth + || (m_notes.at(m_notes.count() - 2)->note()->rhythm() == Trhythm::e_sixteenth && m_notes.at(m_notes.count() - 2)->note()->isRest())) { + // is first in beam or previous note was not a sixteenth or it was sixteenth but rest + m_16_beams << new T16beam(m_notes.count() - 1, createBeam(sn)); // then create new beam segment + } else if (!m_16_beams.isEmpty() && m_notes.at(m_notes.count() - 2)->note()->rhythm() == Trhythm::e_sixteenth + && !m_notes.at(m_notes.count() - 2)->note()->isRest()) { + // there is 16 beam and previous note was 16th and not a rest + m_16_beams.last()->endStem = m_notes.count() - 1; // then set end stem for it + } + } + } + +} + + +void TscoreBeam::closeBeam() { + last()->note()->rtm.setBeam(Trhythm::e_beamEnd); + connect(m_measure, &TscoreMeasure::updateBeams, this, &TscoreBeam::beamsUpdateSlot); + + if (first()->note()->rtm.beam() == Trhythm::e_beamStart && last()->note()->rtm.beam() == Trhythm::e_beamEnd) + qDebug() << " [BEAM] closed correctly with" << count() << "notes"; + else + qDebug() << " [BEAM] is corrupted!!!!"; +} + +//################################################################################################# +//################### PROTECTED ############################################ +//################################################################################################# +void TscoreBeam::beamsUpdateSlot(TscoreNote* sn) { + if (!sn || (sn->index() >= first()->index() && sn->index() <= last()->index())) + performBeaming(); + else + qDebug() << " [BEAM] ignored beaming of" << sn->index(); +} + + +/** + * This method (slot) is invoked after TscoreNote is updated and placed apparently to the rhythm in measure (staff). + * TscoreNote::noteWasClicked() signal invokes it actually + * + * With @p stemDirStrength (strength of stem direction) we trying to determine preferred common direction of stems in the group. + * More far from middle of the staff (18) note is placed - stronger its direction has to be preserved + * to keep beaming more natural and looking good + * + * @p stemsUpPossible keep true when there is enough space between note head and staff border for whole stem + * @p firstStemOff and @p lastStemOff are calculated to make appropriate stems longer + * and keep beam slope nice and avoid collisions with notes in between first and last ones. + * + * Then @p drawBeam() is called. + * + * TODO: WHAT ABOUT PIANO STAFF WHERE ARE TWO MIDDLE LINES +*/ +void TscoreBeam::performBeaming() { +// find common stem direction for beaming + int stemDirStrength = 0; + bool stemsUpPossible = true; + qint16 hiNote = 99, loNote = 0; + for (TscoreNote* sn : m_notes) { + stemDirStrength += sn->notePos() - 18; + if (sn->notePos() < MIN_STEM_HEIGHT) + stemsUpPossible = false; + hiNote = qMin(hiNote, sn->notePos()); + loNote = qMax(loNote, sn->notePos()); + } + + if (last()->note()->rtm.beam() != Trhythm::e_beamEnd) + qDebug() << " [BEAM] was not closed!!" << m_notes.size(); + + bool allStemsDown = !stemsUpPossible; + if (stemDirStrength < 0) + allStemsDown = true; // stems down are always possible + + qreal firstStemOff = 0.0, lastStemOff = 0.0; + // pick up or pull down fist and/or last stems to keep beam above all notes and avoid collision of middle note(s) and beam + if (allStemsDown) { + firstStemOff = qMax(qreal(loNote - first()->notePos()) - SLOPER, 0.0); + lastStemOff = qMax(qreal(loNote - last()->notePos()) - SLOPER, 0.0); + } else { + firstStemOff = qMax(qreal(first()->notePos() - hiNote) - SLOPER, 0.0); + lastStemOff = qMax(qreal(last()->notePos() - hiNote) - SLOPER, 0.0); + } + +// initial setting stems directions and stem lines + for (int i = 0; i < count(); ++i) { + auto n = m_notes[i]; // cache pointer for multiple reuse + Trhythm r(n->note()->rtm); + r.setStemDown(allStemsDown); + n->setRhythm(r); + // set only first and last stems - the inner ones later - adjusted to leading beam line + if (n == last() || n == first()) { + if (n->note()->rtm.stemDown()) + n->mainNote()->setStemLength((n->notePos() < n->staff()->height() - STEM_HEIGHT ? STEM_HEIGHT : 3.5) + + (n == first() ? firstStemOff : 0.0) + + (n == last() ? lastStemOff : 0.0)); + else + n->mainNote()->setStemLength(-((n->notePos() < STEM_HEIGHT ? 3.5 : STEM_HEIGHT) - 0.85) + - (n == first() ? firstStemOff : 0.0) + - (n == last() ? lastStemOff : 0.0)); + } + } + + qDebug() << " [BEAM" << first()->index() << "]" << "beaming was done" << stemDirStrength << m_16_beams.count(); + + drawBeam(); +} + +/** + * @p beamLine is calculated as a base line, inner stems fitted to it. + * If there are some sixteenths - @p m_16_beams are painted + */ +void TscoreBeam::drawBeam() { + QPointF stemOff(0.0, last()->note()->rtm.stemDown() ? HALF_STEM : -HALF_STEM); // offset to cover stem line thickness + QLineF beamLine(first()->pos() + first()->mainNote()->stemEndPoint() + stemOff, last()->pos() + last()->mainNote()->stemEndPoint() + stemOff); +// adjust stem lines length to leading beam line for notes in between of the beam group + for (int i = 1; i < m_notes.count() - 1; ++i) { + QPointF stemEnd; + auto s = m_notes[i]->stem(); + auto nPos = m_notes[i]->pos() + m_notes[i]->mainNote()->pos(); + beamLine.intersect(QLineF(nPos + s->line().p1(), nPos + s->line().p2()), &stemEnd); + s->setLine(QLineF(s->line().p1(), stemEnd - nPos - 2 * stemOff)); + } + QPolygonF poly; + poly << beamLine.p1() << beamLine.p2(); + /** @p beamOff the lower point on a stem for bottom beam outline (it depends on beam line angel) */ + QPointF beamOff(0.0, (last()->note()->rtm.stemDown() ? -BEAM_THICK : BEAM_THICK) / qCos(qDegreesToRadians(beamLine.angle()))); + applyBeam(poly, beamOff, m_8_beam); + + if (!m_16_beams.isEmpty()) { + QPointF beam16LineOff = beamOff * 1.5; + beamLine.translate(beam16LineOff); + for (T16beam* b16 : m_16_beams) { + poly.clear(); + poly << m_notes[b16->startStem]->pos() + m_notes[b16->startStem]->mainNote()->stemEndPoint() + stemOff + beam16LineOff; + if (b16->isHalf()) { // 16th beam of fist stem is right-sided others are left-sided + QPointF halfPoint; + qreal halfX = poly.last().x() + BEAM_THICK * (b16->startStem == 0 ? 2 : -2); + // intersection point with fake stem line for chunked beam + beamLine.intersect(QLineF(halfX, poly.last().y(), halfX, poly.last().y() - 6.0), &halfPoint); + poly << halfPoint; + } else + poly << m_notes[b16->endStem]->pos() + m_notes[b16->endStem]->mainNote()->stemEndPoint() + stemOff + beam16LineOff; + applyBeam(poly, beamOff, b16->b); + } + } + qDebug() << " [BEAM" << first()->index() << "]" << "DRAW BEAM"; +} + + +void TscoreBeam::changeStaff(TscoreStaff* st) { + for (T16beam* b16 : m_16_beams) + b16->b->setParentItem(st); + m_8_beam->setParentItem(st); +} + + +//################################################################################################# +//################### PRIVATE ############################################ +//################################################################################################# + +void TscoreBeam::applyBeam(QPolygonF& poly, const QPointF& offset, QGraphicsPolygonItem* polyItem) { + poly << poly.last() + offset; + poly << poly.first() + offset; + poly << poly.first(); + polyItem->setPolygon(poly); +} + + +QGraphicsPolygonItem* TscoreBeam::createBeam(TscoreNote* sn) { + QGraphicsPolygonItem* b = new QGraphicsPolygonItem(sn->staff()); + b->setPen(Qt::NoPen); + b->setBrush(sn->mainNote()->color()); + b->setZValue(55); + return b; +} + + + diff --git a/src/libs/score/tscorebeam.h b/src/libs/score/tscorebeam.h new file mode 100644 index 0000000000000000000000000000000000000000..784e9de7ed9b68e271e638bccbfe4daafcf11e48 --- /dev/null +++ b/src/libs/score/tscorebeam.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2016 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/>. * + ***************************************************************************/ + +#ifndef TSCOREBEAM_H +#define TSCOREBEAM_H + + +#include <nootkacoreglobal.h> +#include <QtCore/qobject.h> + + +class TscoreNote; +class QGraphicsLineItem; +class QGraphicsPolygonItem; +class TscoreMeasure; +class TscoreStaff; +class T16beam; + + +/** + * This class manages displaying beams of note rhythmic groups + * also it paints stems of them + */ +class NOOTKACORE_EXPORT TscoreBeam : public QObject +{ + + friend class TscoreMeasure; + friend class TscoreStaff; + friend class TscoreNote; + + Q_OBJECT + +public: + explicit TscoreBeam(TscoreNote* sn, TscoreMeasure* m); + virtual ~TscoreBeam(); + + /** Adds @p TscoreNote to beam group. + * note has to have properly set beam state in TscoreNote::newRhythm() + * It changes stem direction of note(s) when necessary. + * It does not perform painting yet + */ + void addNote(TscoreNote* sn); + + /** Returns note @p id in beam group */ + TscoreNote* note(int id) { return m_notes[id]; } + + /** First note in the beam group */ + TscoreNote* first() { return m_notes.first(); } + + /** Last note in the beam group */ + TscoreNote* last() { return m_notes.last(); } + + /** Number of notes in the beam group */ + int count() { return m_notes.count(); } + + /** Sets beam flag of the last note to 'beam end' */ + void closeBeam(); + +protected: + /** calls @p performBeaming when note belongs to beam group */ + void beamsUpdateSlot(TscoreNote* sn); + void performBeaming(); + void drawBeam(); + + /** + * Because beams are parented with staff it is important + * to change their staff when measure is shifted between staves + */ + void changeStaff(TscoreStaff* st); + +private: + /** + * Given polygon has to have two points (upper beam boundary line). + * This method adds bottom boundary, closes polygon + * and applying it into @c QGraphicsPolygonItem. + */ + void applyBeam(QPolygonF& poly, const QPointF& offset, QGraphicsPolygonItem* polyItem); + + /** Initial routines of beam (polygon) - color of note, staff as the parent */ + QGraphicsPolygonItem* createBeam(TscoreNote* sn); + +private: + TscoreMeasure *m_measure; + QList<TscoreNote*> m_notes; + QGraphicsPolygonItem *m_8_beam; /**< line of main beam of eight */ + QList<T16beam*> m_16_beams; /**< list of lines of sixteenths */ + int m_summaryPos; +}; + +#endif // TSCOREBEAM_H diff --git a/src/libs/score/tscoreclef.cpp b/src/libs/score/tscoreclef.cpp index 7975b75460b9d6ee73b0e335637383bfa7436dce..9693c3d995c2b83bf5678ceda5d8836f095ea429 100644 --- a/src/libs/score/tscoreclef.cpp +++ b/src/libs/score/tscoreclef.cpp @@ -20,7 +20,6 @@ #include "tscorescene.h" #include "tscorestaff.h" #include <music/tclef.h> -#include "widgets/tselectclef.h" #include <tnoofont.h> #if defined (Q_OS_ANDROID) #include <touch/ttouchproxy.h> @@ -32,9 +31,8 @@ #include <QtWidgets/qdesktopwidget.h> #include <QtGui/qpalette.h> #include <QtWidgets/qgraphicsview.h> -#include <QtCore/qtimer.h> -#include <QtCore/qdebug.h> +#include <QDebug> /*static*/ QChar TscoreClef::clefToChar(Tclef clef) { @@ -60,21 +58,19 @@ QChar TscoreClef::clefToChar(Tclef clef) { return ch; } - QList<Tclef::Etype> TscoreClef::m_typesList = QList<Tclef::Etype>(); + TscoreClef::TscoreClef(TscoreScene* scene, TscoreStaff* staff, Tclef clef) : TscoreItem(scene), m_clef(Tclef(Tclef::e_none)), - m_lowerClef(0), m_textClef(0), m_readOnly(false) { - m_fakeMouseEvent = new QGraphicsSceneMouseEvent(QEvent::MouseButtonPress); - m_fakeMouseEvent->setButton(Qt::LeftButton); setStaff(staff); setParentItem(staff); + setFlag(QGraphicsItem::ItemHasNoContents); if (m_typesList.size() == 0) // initialize types list m_typesList << Tclef::e_treble_G << Tclef::e_bass_F << Tclef::e_bass_F_8down << Tclef::e_alto_C << Tclef::e_tenor_C << Tclef::e_treble_G_8down; @@ -85,54 +81,48 @@ TscoreClef::TscoreClef(TscoreScene* scene, TscoreStaff* staff, Tclef clef) : m_textClef->setFont(TnooFont(18)); setClef(clef); - m_tapTimer = new QTimer(this); - connect(m_tapTimer, &QTimer::timeout, [=]{ m_textClef->setBrush(qApp->palette().highlight().color()); }); } TscoreClef::~TscoreClef() { - if (m_clefMenu) - delete m_clefMenu; - delete m_fakeMouseEvent; +// if (m_clefMenu) +// delete m_clefMenu; } void TscoreClef::setClef(Tclef clef) { if (clef.type() == Tclef::e_pianoStaff) { if (!m_lowerClef) { - m_lowerClef = new TscoreClef(scoreScene(), staff(), Tclef(Tclef::e_bass_F)); - m_lowerClef->setPos(0.5, getYclefPos(m_lowerClef->clef()) - (16.0 - staff()->lowerLinePos())); - connect(m_lowerClef, SIGNAL(clefChanged(Tclef)), this, SLOT(lowerClefChanged(Tclef))); + m_lowerClef = new TscoreClef(scoreScene(), staff(), Tclef(Tclef::e_bass_F)); + m_lowerClef->setPos(0.5, getYclefPos(m_lowerClef->clef()) - (16.0 - staff()->lowerLinePos()) + 0.1); + connect(m_lowerClef, SIGNAL(clefChanged(Tclef)), this, SLOT(lowerClefChanged(Tclef))); } else // clefs already set to piano mode return; clef.setClef(Tclef::e_treble_G); } else { - if (m_lowerClef) { - m_lowerClef->deleteLater(); - m_lowerClef = 0; - } + if (m_lowerClef) + delete m_lowerClef; } - m_clef = clef; - m_currClefInList = getClefPosInList(m_clef); - m_textClef->setText(QString(clefToChar(m_clef.type()))); - qreal fineOff = 0.1; - if (clef.type() == Tclef::e_bass_F || clef.type() == Tclef::e_bass_F_8down) - fineOff = 0.0; - setPos(0.5, getYclefPos(m_clef) - (16.0 - staff()->upperLinePos()) + fineOff); - getStatusTip(); + m_clef = clef; + m_currClefInList = getClefPosInList(m_clef); + m_textClef->setText(QString(clefToChar(m_clef.type()))); + qreal fineOff = 0.1; + if (clef.type() == Tclef::e_bass_F || clef.type() == Tclef::e_bass_F_8down) + fineOff = 0.0; + setPos(0.5, getYclefPos(m_clef) - (16.0 - staff()->upperLinePos()) + fineOff); + getStatusTip(); } QRectF TscoreClef::boundingRect() const { - return QRectF(0, 0, 6, 18); // optimal height for all clef types + return QRectF(0, 0, 6, 18); // optimal height for all clef types } - void TscoreClef::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget) -// paintBackground(painter, Qt::magenta); +// paintBackground(painter, Qt::magenta); } //################################################################################################# @@ -141,10 +131,9 @@ void TscoreClef::paint(QPainter* painter, const QStyleOptionGraphicsItem* option void TscoreClef::touched(const QPointF& scenePos) { Q_UNUSED(scenePos) - if (!readOnly()) - m_tapTimer->start(300); + m_tapTimer.start(); #if defined (Q_OS_ANDROID) - if (!readOnly() && !TtouchParams::i()->clefWasTouched && tMessage && !tMessage->isVisible() && tMessage->mainWindowOnTop()) { + if (!TtouchParams::i()->clefWasTouched && !tMessage->isVisible() && tMessage->mainWindowOnTop()) { tMessage->setMessage(TtouchProxy::touchClefHelp(), 0); TtouchParams::i()->clefWasTouched = true; } @@ -153,22 +142,46 @@ void TscoreClef::touched(const QPointF& scenePos) { void TscoreClef::untouched(const QPointF& scenePos) { - m_tapTimer->stop(); - if (!readOnly() && !scenePos.isNull() && m_textClef->brush().color() == qApp->palette().highlight().color()) { - m_textClef->setBrush(qApp->palette().text().color()); - m_fakeMouseEvent->setPos(mapFromScene(scenePos)); - QTimer::singleShot(5, [=]{ showMenu(); }); + if (!scenePos.isNull() && m_tapTimer.hasExpired(300)) { + QGraphicsSceneMouseEvent me(QEvent::MouseButtonPress); + me.setPos(mapFromScene(scenePos)); + me.setButton(Qt::LeftButton); + mousePressEvent(&me); } } void TscoreClef::mousePressEvent(QGraphicsSceneMouseEvent* event) { - if (m_readOnly) +/* + if (m_readOnly) { TscoreItem::mousePressEvent(event); -#if !defined (Q_OS_ANDROID) - else // FIXME: but it will not work on Android with mouse - showMenu(); -#endif + } else { + if (!m_menu) { + m_menu = new QMenu(); + if (!m_clefMenu) { + m_clefMenu = new TclefMenu(m_menu); + connect(m_clefMenu, SIGNAL(statusTipRequired(QString)), this, SLOT(clefMenuStatusTip(QString))); + } else + m_clefMenu->setMenu(m_menu); + Tclef curClef = m_clef; + if (staff()->isPianoStaff()) + curClef = Tclef(Tclef::e_pianoStaff); + m_clefMenu->selectClef(curClef); + #if defined (Q_OS_ANDROID) + Tclef cl = m_clefMenu->exec(QPoint(qApp->desktop()->availableGeometry().width() / 8, 10)); + #else + Tclef cl = m_clefMenu->exec(QCursor::pos()); + #endif + m_clefMenu->setMenu(0); + delete m_menu; + if (cl.type() == Tclef::e_none) + return; + if (curClef.type() != cl.type()) { + emit clefChanged(cl); + } + } + } + */ } @@ -182,9 +195,9 @@ void TscoreClef::lowerClefChanged(Tclef clef) { //########################################################################################################## void TscoreClef::getStatusTip() { - QString tip = QLatin1String("<b>") + m_clef.name() + QLatin1String("</b> (") + m_clef.desc() + QLatin1String(")"); + QString tip = "<b>" + m_clef.name() + "</b> (" + m_clef.desc() + ")"; if (!readOnly()) - tip += QLatin1String("<br>") + tr("Click to select another clef."); + tip += "<br>" + tr("Click to select another clef."); setStatusTip(tip); } @@ -214,36 +227,5 @@ int TscoreClef::getClefPosInList(Tclef clef) { } -void TscoreClef::showMenu() { - if (!m_menu) { - m_menu = new QMenu(); - if (!m_clefMenu) { - m_clefMenu = new TclefMenu(m_menu); - #if !defined (Q_OS_ANDROID) - connect(m_clefMenu, SIGNAL(statusTipRequired(QString)), this, SLOT(clefMenuStatusTip(QString))); - #endif - } else - m_clefMenu->setMenu(m_menu); - Tclef curClef = m_clef; - if (staff()->isPianoStaff()) - curClef = Tclef(Tclef::e_pianoStaff); - m_clefMenu->selectClef(curClef); - #if defined (Q_OS_ANDROID) - Tclef cl = m_clefMenu->exec(QPoint(qApp->desktop()->availableGeometry().width() / 8, 10)); - #else - Tclef cl = m_clefMenu->exec(QCursor::pos()); - #endif - if (cl.type() != Tclef::e_none) - m_clef = cl; - m_clefMenu->setMenu(0); - delete m_menu; - if (cl.type() == Tclef::e_none) - return; - - if (curClef.type() != cl.type()) - QTimer::singleShot(5, [=]{ emit clefChanged(m_clef); }); - } -} - diff --git a/src/libs/score/tscoreclef.h b/src/libs/score/tscoreclef.h index a9e8c83a815897f75b62ceb677cae007ef0ba400..f75d94a6c46c915cdbcefeec117bdb05fa3d1917 100644 --- a/src/libs/score/tscoreclef.h +++ b/src/libs/score/tscoreclef.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2016 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -22,10 +22,11 @@ #include <nootkacoreglobal.h> #include "tscoreitem.h" #include <music/tclef.h> -#include <QtCore/qpointer.h> +#include <QPointer> +#include <QElapsedTimer> -class TclefMenu; +// class TclefMenu; /** @@ -72,22 +73,22 @@ protected slots: private: int getYclefPos(Tclef clef); int getClefPosInList(Tclef clef); - void getStatusTip(); /**< Generates and refresh status tip depends on readOnly() and isClickable() state. */ - void showMenu(); + /** Generates and refresh status tip depends on readOnly() and isClickable() state. */ + void getStatusTip(); private: Tclef m_clef; - TscoreClef *m_lowerClef; + QPointer<TscoreClef> m_lowerClef; QGraphicsSimpleTextItem *m_textClef; - QPointer<TclefMenu> m_clefMenu; - QPointer<QMenu> m_menu; +// QPointer<TclefMenu> m_clefMenu; +// QPointer<QMenu> m_menu; int m_currClefInList; - /** List of all clef types except empty (none clef) and piano staff. */ + /** List of all clef types exept empty (none clef) and piano staff. */ static QList<Tclef::Etype> m_typesList; bool m_readOnly; // when TRUE clef is locked - QTimer *m_tapTimer; - QGraphicsSceneMouseEvent *m_fakeMouseEvent; + QElapsedTimer m_tapTimer; + }; #endif // TSCORECLEF_H diff --git a/src/libs/score/tscorekeysignature.cpp b/src/libs/score/tscorekeysignature.cpp index 43a2e9ff749ce4becec680ba4998f76efffc8416..fe0e55ae16f38977f8c3b8c4bc86333bbc4b81b3 100644 --- a/src/libs/score/tscorekeysignature.cpp +++ b/src/libs/score/tscorekeysignature.cpp @@ -31,10 +31,10 @@ const qreal TscoreKeySignature::relatedLine = 3.0; void TscoreKeySignature::setKeyNameScale(QGraphicsTextItem* keyNameItem) { - qreal factor = (KEY_WIDTH + 5.0) / (keyNameItem->boundingRect().width()); - if (keyNameItem->boundingRect().height() * factor > 8.0) // 8.0 is a measure of height - about 4 staff lines. - factor = (8.0 / keyNameItem->boundingRect().height()); - keyNameItem->setScale(factor); + qreal factor = (KEY_WIDTH + 5.0) / (keyNameItem->boundingRect().width()); + if (keyNameItem->boundingRect().height() * factor > 8.0) // 8.0 is a measure of height - about 4 staff lines. + factor = (8.0 / keyNameItem->boundingRect().height()); + keyNameItem->setScale(factor); } @@ -52,15 +52,15 @@ qint8 TscoreKeySignature::m_posOfAccidFlats[7] = { 4, 1, 5, 2, 6, 3, 7 }; int nOff(Tclef::Etype c) { - if (c == Tclef::e_treble_G || c == Tclef::e_treble_G_8down) - return 3; - if (c == Tclef::e_bass_F || c == Tclef::e_bass_F_8down) - return 5; - if (c == Tclef::e_alto_C) - return 2; - if (c == Tclef::e_tenor_C) - return 4; - return 3; + if (c == Tclef::e_treble_G || c == Tclef::e_treble_G_8down) + return 3; + if (c == Tclef::e_bass_F || c == Tclef::e_bass_F_8down) + return 5; + if (c == Tclef::e_alto_C) + return 2; + if (c == Tclef::e_tenor_C) + return 4; + return 3; } /*end static*/ @@ -75,19 +75,19 @@ TscoreKeySignature::TscoreKeySignature(TscoreScene* scene, TscoreStaff* staff, q m_maxKey(7), m_minKey(-7) { setStaff(staff); - setParentItem(staff); + setParentItem(staff); TnooFont font(5); for (int i = 0; i < 7; i++) { - m_accidentals[i] = new QGraphicsSimpleTextItem(); - registryItem(m_accidentals[i]); - m_accidentals[i]->setBrush(qApp->palette().text().color()); - m_accidentals[i]->setFont(font); - m_accidentals[i]->setScale(scoreScene()->accidScale()); - m_accidentals[i]->hide(); - } + m_accidentals[i] = new QGraphicsSimpleTextItem(); + registryItem(m_accidentals[i]); + m_accidentals[i]->setBrush(qApp->palette().text().color()); + m_accidentals[i]->setFont(font); + m_accidentals[i]->setScale(scoreScene()->accidScale()); + m_accidentals[i]->hide(); + } - setStatusTip(tr("Key signature - to change it, click above or below the staff or use mouse wheel.")); + setStatusTip(tr("Key signature - to change it, click above or below the staff or use mouse wheel.")); } @@ -118,9 +118,9 @@ void TscoreKeySignature::setKeySignature(qint8 keySign) { // (int)staff()->accidInKeyArray[2] << (int)staff()->accidInKeyArray[3] << // (int)staff()->accidInKeyArray[4] << (int)staff()->accidInKeyArray[5] << (int)staff()->accidInKeyArray[6]; m_keySignature = keySign; - updateKeyName(); - if (m_lowKey && m_keySignature != m_lowKey->keySignature()) - m_lowKey->setKeySignature(m_keySignature); + updateKeyName(); + if (m_lowKey && m_keySignature != m_lowKey->keySignature()) + m_lowKey->setKeySignature(m_keySignature); emit keySignatureChanged(); } @@ -139,66 +139,66 @@ qint8 TscoreKeySignature::getPosOfAccid(int noteNr, bool flatKey) { QPointF TscoreKeySignature::accidTextPos(int noteNr) { - if (noteNr >= 0 && noteNr < 7) - return mapToScene(m_accidentals[noteNr]->pos()); - else - return QPointF(); + if (noteNr >= 0 && noteNr < 7) + return mapToScene(m_accidentals[noteNr]->pos()); + else + return QPointF(); } void TscoreKeySignature::setClef(Tclef clef) { - if (clef.type() == Tclef::e_pianoStaff) { - m_clef = Tclef(Tclef::e_treble_G); - if (!m_lowKey) { - m_lowKey = new TscoreKeySignature(scoreScene(), staff()); - m_lowKey->setParentItem(this); -// m_lowKey->setPos(0.0, staff()->lowerLinePos() - 2.0); - m_lowKey->setPos(0.0, 14.0); - m_lowKey->setClef(Tclef(Tclef::e_bass_F)); - m_lowKey->setZValue(30); -// m_lowKey->setRelatedLine(2); -// setRelatedLine(staff()->upperLinePos()); - m_lowKey->setKeySignature(keySignature()); - connect(m_lowKey, SIGNAL(keySignatureChanged()), this, SLOT(onLowKeyChanged())); - } - } else { - m_clef = clef; -// setRelatedLine(staff()->upperLinePos()); - if (m_lowKey) { - delete m_lowKey; - } - } - m_clefOffset = nOff(m_clef.type()); + if (clef.type() == Tclef::e_pianoStaff) { + m_clef = Tclef(Tclef::e_treble_G); + if (!m_lowKey) { + m_lowKey = new TscoreKeySignature(scoreScene(), staff()); + m_lowKey->setParentItem(this); +// m_lowKey->setPos(0.0, staff()->lowerLinePos() - 2.0); + m_lowKey->setPos(0.0, 14.0); + m_lowKey->setClef(Tclef(Tclef::e_bass_F)); + m_lowKey->setZValue(30); +// m_lowKey->setRelatedLine(2); +// setRelatedLine(staff()->upperLinePos()); + m_lowKey->setKeySignature(keySignature()); + connect(m_lowKey, SIGNAL(keySignatureChanged()), this, SLOT(onLowKeyChanged())); + } + } else { + m_clef = clef; +// setRelatedLine(staff()->upperLinePos()); + if (m_lowKey) { + delete m_lowKey; + } + } + m_clefOffset = nOff(m_clef.type()); setKeySignature(keySignature()); } void TscoreKeySignature::showKeyName(bool showIt) { - if (showIt) { - if (!m_keyNameText) { - m_keyNameText = new QGraphicsTextItem(); - registryItem(m_keyNameText); - m_keyNameText->setZValue(7); - setKeySignature(keySignature()); - } - } else { - delete m_keyNameText; - m_keyNameText = 0; - } + if (showIt) { + if (!m_keyNameText) { + m_keyNameText = new QGraphicsTextItem(); + registryItem(m_keyNameText); + m_keyNameText->setZValue(7); + setKeySignature(keySignature()); + } + } else { + delete m_keyNameText; + m_keyNameText = 0; + } } QRectF TscoreKeySignature::boundingRect() const{ - return QRectF(0, 0, KEY_WIDTH + 1.0, KEY_HEIGHT); + return QRectF(0, 0, KEY_WIDTH + 1.0, KEY_HEIGHT); } void TscoreKeySignature::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - Q_UNUSED(option) - Q_UNUSED(widget) - if (m_bgColor != -1) { - paintBackground(painter, m_bgColor); - } + Q_UNUSED(option) + Q_UNUSED(widget) + if (m_bgColor != -1) { + paintBackground(painter, m_bgColor); + } } @@ -225,42 +225,10 @@ void TscoreKeySignature::setMinKey(int mk) { //########################################################################################################## void TscoreKeySignature::onLowKeyChanged() { - setKeySignature(m_lowKey->keySignature()); + setKeySignature(m_lowKey->keySignature()); } -void TscoreKeySignature::mousePressEvent(QGraphicsSceneMouseEvent* event) { - if (!m_readOnly && event->button() == Qt::LeftButton) { - if (event->pos().y() > relatedLine + 4.0) - increaseKey(-1); - else if (event->pos().y() > 0.0) // ignore clicking on key name item - increaseKey(1); - } -} - - -void TscoreKeySignature::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { - scoreScene()->mouseEntersOnKey(true); - TscoreItem::hoverEnterEvent(event); -} - - -void TscoreKeySignature::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { - scoreScene()->mouseEntersOnKey(false); - TscoreItem::hoverLeaveEvent(event); -} - - -void TscoreKeySignature::untouched(const QPointF& scenePos) { - if (!m_readOnly && !scenePos.isNull()) { - QPointF fingerPos = mapFromScene(scenePos); - if (fingerPos.y() > relatedLine + 4.0) - increaseKey(-1); - else if (fingerPos.y() > 0.0) // ignore clicking on key name item - increaseKey(1); - } -} - void TscoreKeySignature::increaseKey(int step) { qint8 prevKey = m_keySignature; @@ -277,13 +245,13 @@ void TscoreKeySignature::increaseKey(int step) { void TscoreKeySignature::updateKeyName() { - if (m_keyNameText) { + if (m_keyNameText) { m_keyNameText->setHtml(TkeySignature::getMajorName(m_keySignature) + QLatin1String("<br>") + TkeySignature::getMinorName(m_keySignature)); - setKeyNameScale(m_keyNameText); - m_keyNameText->setPos((boundingRect().width() - m_keyNameText->boundingRect().width() * m_keyNameText->scale()) / 2 - 2.5, - -2.0 - m_keyNameText->boundingRect().height() * m_keyNameText->scale()); - } + setKeyNameScale(m_keyNameText); + m_keyNameText->setPos((boundingRect().width() - m_keyNameText->boundingRect().width() * m_keyNameText->scale()) / 2 - 2.5, + -2.0 - m_keyNameText->boundingRect().height() * m_keyNameText->scale()); + } } diff --git a/src/libs/score/tscorekeysignature.h b/src/libs/score/tscorekeysignature.h index 1025d62873e71ee97e613b4a8b0f681511e3a21b..796be55eb6b7db94a85ffcf99404ef2aaebb05a0 100644 --- a/src/libs/score/tscorekeysignature.h +++ b/src/libs/score/tscorekeysignature.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2016 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -37,83 +37,80 @@ */ class NOOTKACORE_EXPORT TscoreKeySignature : public TscoreItem { + Q_OBJECT - + public: - TscoreKeySignature(TscoreScene *scene, TscoreStaff *staff, qint8 keySign = 0); - - /** This methods get and set the key signature, and are called - * only from their parent @p TscoreWidgetSimple as continuation - * his public methods */ - void setKeySignature(qint8 keySign); - qint8 keySignature() { return m_keySignature; } - void setClef(Tclef clef); - - /** Returns y coefficient of given note (0 - 7, 0 is c, 1 is d...). - * It depends on Tclef value*/ - qint8 getPosOfAccid(int noteNr, bool flatKey = false); - - /** Returns position point of accidental text in staff coordinates. @p noteNr is [0-7] range */ - QPointF accidTextPos(int noteNr); - - void showKeyName(bool showIt); - - void setReadOnly(bool readOnly) { m_readOnly = readOnly; if (m_lowKey) m_lowKey->setReadOnly(readOnly); } - bool readOnly() { return m_readOnly; } - - /** It sets background of the note segment. When sets to -1 means transparent - no background. */ - void setBackgroundColor(QColor bg) { m_bgColor = bg; update(); } - - /** Static method that calculates scale factor of key signature name appropriate for available space above clef. */ - static void setKeyNameScale(QGraphicsTextItem *keyNameItem); - static const qreal relatedLine; /** Y position of upper staff line in item coordinates */ - - /** Maximal key value possible to set, by default it is 7 */ - qint8 maxKey() { return m_maxKey; } - void setMaxKey(int mk); - - /** Minimal key value possible to set, by default it is -7 */ - qint8 minKey() { return m_minKey; } - void setMinKey(int mk); - - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); - virtual QRectF boundingRect() const; - + TscoreKeySignature(TscoreScene *scene, TscoreStaff *staff, qint8 keySign = 0); + + /** This methods get and set the key signature, and are called + * only from their parent @p TscoreWidgetSimple as continuation + * his public methods */ + void setKeySignature(qint8 keySign); + qint8 keySignature() { return m_keySignature; } + void setClef(Tclef clef); + + /** Returns y coefficient of given note (0 - 7, 0 is c, 1 is d...). + * It depends on Tclef value*/ + qint8 getPosOfAccid(int noteNr, bool flatKey = false); + + /** Returns position point of accidental text in staff coordinates. @p noteNr is [0-7] range */ + QPointF accidTextPos(int noteNr); + + void showKeyName(bool showIt); + + void setReadOnly(bool readOnly) { m_readOnly = readOnly; if (m_lowKey) m_lowKey->setReadOnly(readOnly); } + bool readOnly() { return m_readOnly; } + + /** It sets background of the note segment. When sets to -1 means transparent - no background. */ + void setBackgroundColor(QColor bg) { m_bgColor = bg; update(); } + + /** Static method that calculates scale factor of key signature name appropriate for available space above clef. */ + static void setKeyNameScale(QGraphicsTextItem *keyNameItem); + static const qreal relatedLine; /** Y position of upper staff line in item coordinates */ + + /** Maximal key value possible to set, by default it is 7 */ + qint8 maxKey() { return m_maxKey; } + void setMaxKey(int mk); + + /** Minimal key value possible to set, by default it is -7 */ + qint8 minKey() { return m_minKey; } + void setMinKey(int mk); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual QRectF boundingRect() const; + signals: - void keySignatureChanged(); + void keySignatureChanged(); protected: - virtual void untouched(const QPointF& scenePos); - virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); - virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); - virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); - - /** Adds @p step to key value. Only 1 or -1 values are accepted. */ - void increaseKey(int step); - void updateKeyName(); - + + /** Adds @p step to key value. Only 1 or -1 values are accepted. */ + void increaseKey(int step); + void updateKeyName(); + protected slots: - void onLowKeyChanged(); + void onLowKeyChanged(); private: - /** Array of text items with # or b signs*/ - QGraphicsSimpleTextItem *m_accidentals[7]; - QPointer<QGraphicsTextItem> m_keyNameText; - qint8 m_keySignature; - QPointer<TscoreKeySignature> m_lowKey; - - /** It keeps array of accidental symbol (# or b) positions - * (in PosY coordinates from TnoteView) - * @li [0] is position for f# and fb - * @li [1] c# and - * @li etc.... */ - static qint8 m_posOfAccid[7]; - static qint8 m_posOfAccidFlats[7]; - Tclef m_clef; - bool m_readOnly; - QColor m_bgColor; - int m_clefOffset; /**< accidental distance from upper staff line depends on clef */ - qint8 m_maxKey, m_minKey; + /** Array of text items with # or b signs*/ + QGraphicsSimpleTextItem *m_accidentals[7]; + QPointer<QGraphicsTextItem> m_keyNameText; + qint8 m_keySignature; + QPointer<TscoreKeySignature> m_lowKey; + + /** It keeps array of accidental symbol (# or b) positions + * (in PosY coordinates from TnoteView) + * @li [0] is position for f# and fb + * @li [1] c# and + * @li etc.... */ + static qint8 m_posOfAccid[7]; + static qint8 m_posOfAccidFlats[7]; + Tclef m_clef; + bool m_readOnly; + QColor m_bgColor; + int m_clefOffset; /**< accidental distance from upper staff line depends on clef */ + qint8 m_maxKey, m_minKey; }; #endif // TSCOREKEYSIGNATURE_H diff --git a/src/libs/score/tscorelines.cpp b/src/libs/score/tscorelines.cpp index 59abbb0add86e8e3914524ac41351298d0e05ce5..7f1ec60720bdb50dc11d023a4d05d1e0ca389b8e 100644 --- a/src/libs/score/tscorelines.cpp +++ b/src/libs/score/tscorelines.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2014-2015 by Tomasz Bojczuk * + * Copyright (C) 2014-2016 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -19,68 +19,63 @@ #include "tscorelines.h" #include "tscorenote.h" #include "tscorestaff.h" -#include <QPen> +#include <QtGui/qpen.h> TscoreLines::TscoreLines(TscoreNote* note) : - m_parentNote(note) + QGraphicsItemGroup(note), + m_parentNote(note) { - createLines(); + createLines(); } void TscoreLines::checkLines(int curPos) { - for (int i = 0; i < m_upper.size(); i++) { - if (curPos < m_upper[i]->line().y1()) - m_upper[i]->show(); - else - m_upper[i]->hide(); - } - if (m_parentNote && m_parentNote->staff()->isPianoStaff()) { - if (curPos == m_middle[0]->line().y1() - 1) - m_middle[0]->show(); - else - m_middle[0]->hide(); - if (curPos == m_middle[1]->line().y1() - 1) - m_middle[1]->show(); - else - m_middle[1]->hide(); - } - for (int i = 0; i < m_lower.size(); i++) { - if (curPos > m_lower[i]->line().y1() - 2) - m_lower[i]->show(); - else - m_lower[i]->hide(); - } + for (int i = 0; i < m_upper.size(); i++) { + if (curPos < m_upper[i]->line().y1()) + m_upper[i]->show(); + else + m_upper[i]->hide(); + } + if (m_parentNote && m_parentNote->staff()->isPianoStaff()) { + if (curPos == m_middle[0]->line().y1() - 1) + m_middle[0]->show(); + else + m_middle[0]->hide(); + if (curPos == m_middle[1]->line().y1() - 1) + m_middle[1]->show(); + else + m_middle[1]->hide(); + } + for (int i = 0; i < m_lower.size(); i++) { + if (curPos > m_lower[i]->line().y1() - 2) + m_lower[i]->show(); + else + m_lower[i]->hide(); + } } void TscoreLines::hideAllLines() { - hideLines(m_upper); - hideLines(m_lower); - if (m_parentNote && m_parentNote->staff()->isPianoStaff()) - hideLines(m_middle); + hideLines(m_upper); + hideLines(m_lower); + if (m_parentNote && m_parentNote->staff()->isPianoStaff()) + hideLines(m_middle); } void TscoreLines::setParent(TscoreNote* sn) { m_parentNote = sn; - for (int i = 0; i < m_upper.size(); i++) - m_upper[i]->setParentItem(sn); - for (int i = 0; i < m_middle.size(); i++) - m_middle[i]->setParentItem(sn); - for (int i = 0; i < m_lower.size(); i++) - m_lower[i]->setParentItem(sn); + setParentItem(sn); } - void TscoreLines::setColor(const QColor& lineColor) { - for (int i = 0; i < m_upper.size(); i++) - m_upper[i]->setPen(QPen(lineColor, 0.2)); - for (int i = 0; i < m_lower.size(); i++) - m_lower[i]->setPen(QPen(lineColor, 0.2)); - for (int i = 0; i < m_middle.size(); i++) - m_middle[i]->setPen(QPen(lineColor, 0.2)); + for (int i = 0; i < m_upper.size(); i++) + m_upper[i]->setPen(QPen(lineColor, 0.2, Qt::SolidLine, Qt::RoundCap)); + for (int i = 0; i < m_lower.size(); i++) + m_lower[i]->setPen(QPen(lineColor, 0.2, Qt::SolidLine, Qt::RoundCap)); + for (int i = 0; i < m_middle.size(); i++) + m_middle[i]->setPen(QPen(lineColor, 0.2, Qt::SolidLine, Qt::RoundCap)); } //########################################################################################## @@ -88,47 +83,46 @@ void TscoreLines::setColor(const QColor& lineColor) { //########################################################################################## QGraphicsLineItem* TscoreLines::createNoteLine(int yPos) { - QGraphicsLineItem *line = new QGraphicsLineItem(); - line->hide(); - line->setParentItem(m_parentNote); - line->setZValue(7); - line->setLine(2.5, yPos, 7.0, yPos); - return line; + QGraphicsLineItem *line = new QGraphicsLineItem(this); + line->hide(); + line->setZValue(7); + line->setLine(0.0, yPos, 3.3, yPos); + return line; } void TscoreLines::createLines() { - deleteLines(m_upper); - deleteLines(m_middle); - deleteLines(m_lower); + deleteLines(m_upper); + deleteLines(m_middle); + deleteLines(m_lower); int i = m_parentNote->staff()->upperLinePos() - 2; while (i > 0) { // upper lines - m_upper << createNoteLine(i); + m_upper << createNoteLine(i); i -= 2; } i = m_parentNote->staff()->upperLinePos() + 10.0; // distance between upper and lower (or lower grand staff) staff line if (m_parentNote->staff()->isPianoStaff()) { - i = m_parentNote->staff()->lowerLinePos() + 10.0; - m_middle << createNoteLine(m_parentNote->staff()->upperLinePos() + 10); - m_middle << createNoteLine(m_parentNote->staff()->lowerLinePos() - 2); - } + i = m_parentNote->staff()->lowerLinePos() + 10.0; + m_middle << createNoteLine(m_parentNote->staff()->upperLinePos() + 10); + m_middle << createNoteLine(m_parentNote->staff()->lowerLinePos() - 2); + } while (i < m_parentNote->boundingRect().height()) { - m_lower << createNoteLine(i); + m_lower << createNoteLine(i); i += 2; } } void TscoreLines::deleteLines(TaddLines& linesList) { - for (int i = 0; i < linesList.size(); i++) - delete linesList[i]; - linesList.clear(); + for (int i = 0; i < linesList.size(); i++) + delete linesList[i]; + linesList.clear(); } void TscoreLines::hideLines(TaddLines& linesList) { - for (int i=0; i < linesList.size(); i++) - linesList[i]->hide(); + for (int i=0; i < linesList.size(); i++) + linesList[i]->hide(); } diff --git a/src/libs/score/tscorelines.h b/src/libs/score/tscorelines.h index 5850a9739ad4dd133efe3dc97933f25903609787..5b62fccb82e4c2a46e7d9fe51ae97621da09fc7c 100644 --- a/src/libs/score/tscorelines.h +++ b/src/libs/score/tscorelines.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2014-2015 by Tomasz Bojczuk * + * Copyright (C) 2014-2016 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -20,7 +20,7 @@ #define TSCORELINES_H #include <nootkacoreglobal.h> -#include <QGraphicsObject> +#include <QtWidgets/qgraphicsitem.h> class TscoreItem; class TscoreNote; @@ -28,47 +28,47 @@ class TscoreNote; typedef QList<QGraphicsLineItem*> TaddLines; /** List of graphics lines */ -/** +/** * There are additional lines appearing above or below a staff (or between piano staves) * Every @class TscoreNote has it and note cursor as well. * It has method to determine which lines has to be shown depending on note position: * @p checkLines() */ -class NOOTKACORE_EXPORT TscoreLines +class NOOTKACORE_EXPORT TscoreLines : public QGraphicsItemGroup { public: - TscoreLines(TscoreNote* note); - - TaddLines& up() { return m_upper; } - TaddLines& mid() { return m_middle; } - TaddLines& lo() { return m_lower; } - - void setColor(const QColor& lineColor); - - /** Recreates lines adjusting them to new staff (clef) */ - void adjustLines(TscoreNote* scoreNote) { m_parentNote = scoreNote; createLines(); } - - void hideAllLines(); - - void checkLines(int curPos); /** Checks whose lines show and hide. @p curPos is current position of note those lines belong to. */ - - void setParent(TscoreNote* sn); - + TscoreLines(TscoreNote* note); + + TaddLines& up() { return m_upper; } + TaddLines& mid() { return m_middle; } + TaddLines& lo() { return m_lower; } + + void setColor(const QColor& lineColor); + + /** Recreates lines adjusting them to new staff (clef) */ + void adjustLines(TscoreNote* scoreNote) { m_parentNote = scoreNote; createLines(); } + + void hideAllLines(); + + void checkLines(int curPos); /** Checks whose lines show and hide. @p curPos is current position of note those lines belong to. */ + + void setParent(TscoreNote* sn); + private: - QGraphicsLineItem* createNoteLine(int yPos); - - /** Common method creating upper and lower staff lines. - * It appends new lines to list - * so do not forget to clear list before every next call. */ - void createLines(); - void hideLines(TaddLines &linesList); - void deleteLines(TaddLines &linesList); /** Deletes lines in the list and clears the list */ - + QGraphicsLineItem* createNoteLine(int yPos); + + /** Common method creating upper and lower staff lines. + * It appends new lines to list + * so do not forget to clear list before every next call. */ + void createLines(); + void hideLines(TaddLines &linesList); + void deleteLines(TaddLines &linesList); /** Deletes lines in the list and clears the list */ + private: - TaddLines m_upper, m_middle, m_lower; - TscoreNote *m_parentNote; - + TaddLines m_upper, m_middle, m_lower; + TscoreNote *m_parentNote; + }; #endif // TSCORELINES_H diff --git a/src/libs/score/tscoremeasure.cpp b/src/libs/score/tscoremeasure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8eae9472c40d013ca30a8f4829477546927ef134 --- /dev/null +++ b/src/libs/score/tscoremeasure.cpp @@ -0,0 +1,733 @@ +/*************************************************************************** + * Copyright (C) 2016 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 "tscoremeasure.h" +#include "tscorenote.h" +#include "tscorestaff.h" +#include "tscoremeter.h" +#include "tscorebeam.h" +#include "tscoretie.h" +#include "tscorescene.h" +#include <music/tmeter.h> +#include <music/tnote.h> +#include <QtWidgets/qapplication.h> +#include <QtCore/qdebug.h> +#include <QtGui/qpen.h> +#include <QtGui/qpalette.h> + + +/** For debug purposes: displays content of given measure */ +void content (TscoreMeasure* m) { + QString c; + if (m->isEmpty()) + c = QStringLiteral("is empty"); + else + for (TscoreNote* sn : m->notes()) { + c += QStringLiteral("<"); + if (sn->note()->isValid() && !sn->note()->isRest()) + c += sn->note()->toText(); + else + c += QLatin1String("r"); + c += QLatin1String(">"); + c += QString("%1%2").arg(sn->note()->weight()).arg(sn->note()->hasDot() ? QStringLiteral(".") : QString()); + c += QString("(%1) ").arg(sn->rhythmGroup()); + } + qDebug() << m->debug() << c; +} + + +/*static*/ +int TscoreMeasure::groupDuration(const QList<TscoreNote*>& notes) { + int total = 0; + for (TscoreNote* sn : notes) + total += sn->note()->duration(); + return total; +} + +//################################################################################################# +//################### TscoreMeasure ############################################ +//################################################################################################# +/** + * This class is grouping notes (TscoreNote) into measure. + * Notes are added through: + * - @p insertNote() - adds single note + * - @p prependNotes() - adds QList<TscoreNote*> at the measure beginning + * - @p noteChangedSlot() - connected to every note when it changes the rhythm + * + * In @p updateRhythmicGroups() each note gets rhythmic group number + * and array m_firstInGr keeps indexes of first note in each group. + * (groups are defined in TscoreMeter class and depend on meter) + * @p resolveBeaming() sets beams (creates TscoreBeam if necessary) + * @p checkBarLine() manages measure bar line + * + * @p releaseAtEnd() takes a given duration of notes from the measure end + * then @p shiftReleased() moves them to the next measure + */ +TscoreMeasure::TscoreMeasure(TscoreStaff* staff, int nr) : + QObject(staff), + m_id(nr), + m_staff(staff), + m_firstInGr(new qint8[1]) +{ + m_duration = m_staff->scoreScene()->scoreMeter() ? m_staff->scoreScene()->scoreMeter()->meter()->duration() : 0; + m_free = duration(); + m_barLine = new QGraphicsLineItem; + m_barLine->hide(); + m_barLine->setPen(QPen(qApp->palette().text().color(), 0.2)); + m_barLine->setZValue(50); +} + + +TscoreMeasure::~TscoreMeasure() +{ + delete[] m_firstInGr; + delete m_barLine; +} + + +Tmeter* TscoreMeasure::meter() const { + return m_staff->scoreScene()->scoreMeter()->meter(); +} + + +TscoreMeter* TscoreMeasure::scoreMeter() const { + return m_staff->scoreScene()->scoreMeter(); +} + + +void TscoreMeasure::setStaff(TscoreStaff* st) { + m_staff = st; + setParent(st); + for (TscoreBeam* b : m_beams) + b->changeStaff(st); + + if (st->firstMeasure() == this) { // this measure was the last and went at the beginning of the next staff + auto first = firstNote(); + if (first->note()->rtm.tie() > Trhythm::e_tieStart) { // tie continues or ends on first note + auto prev = first->prevNote(); + if (prev->tie()) + prev->tie()->checkStaves(); + + else // TODO: delete if not occurs + qDebug() << debug() << "first note has tie flag set but tie doesn't exists"; + } + } else if (st->lastMeasure() == this) { // this measure was the first and went at the end of previous staff + auto last = lastNote(); + if (last->tie()) + last->tie()->checkStaves(); + } +} + + +void TscoreMeasure::changeMeter(const Tmeter& m) { + m_duration = m.duration(); + m_free = duration(); + // TODO: recalculate measure, move notes if not fit to the meter to next measure or ask for notes from next + if (m.meter() == Tmeter::e_none) { + for (TscoreNote* sn : m_notes) { + sn->setRhythm(Trhythm(Trhythm::e_none)); + } + } else { + for (TscoreNote* sn : m_notes) { + sn->setRhythm(Trhythm(m.lower() == 4 ? Trhythm::e_quarter : Trhythm::e_eighth, !sn->note()->isValid())); + m_free -= sn->note()->duration(); + } + } +// qDebug() << debug() << "duration" << duration() << "remained" << m_free; + // TODO: recalculate or remove beams when no meter +} + + +void TscoreMeasure::prependNotes(QList<TscoreNote*>& nl) { + QList<TscoreNote*> notesToOut; + Tnote newNote; // new note that has to be created when rhythm duration is split + int notesDur = groupDuration(nl); + + if (nl.first()->note()->rhythm() != Trhythm::e_none && m_free - notesDur < 0) // move notes to the next measure if not enough space + releaseAtEnd(notesDur, notesToOut, newNote); + + for (int i = 0; i < nl.size(); ++i) { + m_notes.insert(i, nl[i]); + connect(nl[i], &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + } + + updateRhythmicGroups(); + resolveBeaming(m_notes.first()->rhythmGroup()); + checkBarLine(); + + for (TscoreBeam* b : m_beams) + b->performBeaming(); + + shiftReleased(notesToOut, newNote); + + content(this); +} + + +void TscoreMeasure::appendNotes(QList<TscoreNote*>& nl) { + if (groupDuration(nl) > m_free) { // TODO: It should never occur - delete checking after all + qDebug() << debug() << "MEASURE HAS NO FREE SPACE FOR SO MANY NOTES. appendNotes skipped"; + return; + } + + int lastIndex = lastNoteId() - firstNoteId(); + for (int i = 0; i < nl.size(); ++i) { + m_notes.append(nl[i]); + connect(nl[i], &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + } + + updateRhythmicGroups(); + resolveBeaming(m_notes[lastIndex]->rhythmGroup()); + checkBarLine(); +} + + +void TscoreMeasure::insertNote(int id, TscoreNote* sn) { +// qDebug() << debug() << sn->note()->rtm.xmlType() << "inserting id" << id << "count" << m_notes.count() << "free" << m_free; + int noteDur = sn->note()->duration(); + int resolveAfter = 0; // un-resolvable duration of new note + + QList<TscoreNote*> notesToOut; + Tnote newNote; // new note that has to be created when rhythm duration is split + + if (sn->note()->rhythm() != Trhythm::e_none && m_free - noteDur < 0) { // move notes to the next measure if not enough space + int toRelease = releaseAtEnd(noteDur - m_free, notesToOut, newNote, id); + if (toRelease > 0) { // There is still not enough space + if (m_free > 0) { // measure has some free space for the new note but not for entire + if (Trhythm(m_free).rhythm() == Trhythm::e_none) { + Trhythm subRhythm(noteDur - m_free, sn->note()->isRest()); + subRhythm.setTie(sn->note()->rtm.tie()); + subRhythm.setStemDown(sn->note()->rtm.stemDown()); + if (subRhythm.rhythm() == Trhythm::e_none) + qDebug() << debug() << "can not shrink note to duration" << noteDur - m_free; + sn->setRhythm(subRhythm); + splitThenInsert(m_free, id, *sn->note(), sn->isReadOnly()); + insertNote(lastNoteId() + 1, sn); + if (!sn->note()->isRest()) + lastNote()->tieWithNext(); + return; + } + Trhythm oldRhythm(sn->note()->rtm); + sn->setRhythm(Trhythm(m_free, sn->note()->isRest())); + copyRhythmParams(sn, oldRhythm); +// sn->note()->rtm.setTie(oldRhythm.tie()); +// sn->note()->rtm.setStemDown(oldRhythm.stemDown()); + if (newNote.rhythm() != Trhythm::e_none) // TODO: remove when tested + qDebug() << "NEW NOTE is already created!!!"; + Trhythm newRtm(noteDur - m_free, sn->note()->isRest()); + if (newRtm.rhythm() == Trhythm::e_none) // two notes have to be added + resolveAfter = noteDur - m_free; // do it after + else + newNote = Tnote(*sn->note(), newRtm); + noteDur = m_free; + } else { // measure has not at all space + qDebug() << debug() << "move entire note" << sn->note()->toText(); + notesToOut << sn; + m_staff->shiftToMeasure(m_staff->measures().indexOf(this) + 1, notesToOut); + return; + } + } + } + + m_free -= noteDur; + m_notes.insert(id, sn); + + qDebug() << debug() << "measure got a note" << sn->note()->rtm.xmlType() << "FREE" << m_free << "out" << notesToOut.count(); + connect(sn, &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + + updateRhythmicGroups(); + resolveBeaming(sn->rhythmGroup()); + checkBarLine(); + + shiftReleased(notesToOut, newNote); + if (resolveAfter) { + auto lastN = lastNote(); + splitThenInsert(resolveAfter, lastN->index() + 1, *lastN->note(), lastN->isReadOnly()); + } + + content(this); +} + + +void TscoreMeasure::removeNote(int noteToRemove) { + if (noteToRemove >= 0 && noteToRemove < m_notes.count()) { + int rmDur = m_notes[noteToRemove]->note()->duration(); + disconnect(m_notes[noteToRemove], &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + if (m_barLine->parentItem() == m_notes[noteToRemove]) + m_barLine->setParentItem(nullptr); // reset bar-line parent - otherwise it will be deleted with the note + m_notes.removeAt(noteToRemove); + + if (rmDur == duration()) { + qDebug() << "A special case when removing note is as long as the measure - remove whole measure then"; + } + m_free += rmDur; + fill(); + } else + qDebug() << debug() << "Tried to remove note out of range"; +} + + +qreal TscoreMeasure::notesWidth() { + if (!isEmpty()) + return lastNote()->rightX() - firstNote()->x(); + else + return 0.0; +} + + +int TscoreMeasure::freeAt(int noteId) const { + int total = 0; + for (int i = noteId; i < m_notes.size(); ++i) + total += m_notes[i]->note()->duration(); + return total; +} + + +int TscoreMeasure::durationAt(int noteId) const { + int total = 0; + int end = qMin<int>(noteId, m_notes.size()); + for (int i = 0; i < end; ++i) + total += m_notes[i]->note()->duration(); + return total; +} + + +int TscoreMeasure::firstNoteId() const { + return m_notes.count() ? m_notes.first()->index() : 0; +} + + +int TscoreMeasure::lastNoteId() const { + return m_notes.count() ? m_notes.last()->index() : 0; +} + + +char TscoreMeasure::debug() { + QTextStream o(stdout); + o << " \033[01;33m[" << QString("%1/%2").arg(id()).arg(m_staff->number()) << " MEASURE]\033[01;00m"; + return 32; // fake +} + + +//################################################################################################# +//################### PROTECTED ############################################ +//################################################################################################# +void TscoreMeasure::noteChangedSlot(TscoreNote* sn) { + Tnote newNote; // new note that has to be created when rhythm duration is split + QList<TscoreNote*> notesToOut; + + if (sn->rhythmChanged()) { + // TODO: if new duration of changed note is longer than whole measure duration - split it here and create new measure with single note + // - still we don't know new pitch + int prevDur = sn->rhythm()->duration(); + int newDur = sn->note()->duration(); + int nextMeasDur = 0; + qDebug() << debug() << "rhythm changed from" << sn->rhythm()->string() << "to" << sn->note()->rtm.string() << "free" << m_free; + if (m_free - (newDur - prevDur) < 0) { // There is not enough space for this note with longer duration + /** 1. Try to release measure (move notes after this @p sn one to the next measure) */ + int leftDur = releaseAtEnd(newDur - prevDur - m_free, notesToOut, newNote, sn->index() - firstNoteId() + 1); + if (leftDur) { + sn->moveNote(sn->note()->isRest() ? 0 : sn->newNotePos()); // update position of note head + sn->staff()->updatePitch(sn->index()); // update Tnote according to new head position + /** 2. There is still not enough space for new duration - splitting duration of this @p sn note to fill free space in this measure + * and create part of that duration in the next one */ + nextMeasDur = newDur - (m_free + prevDur); + Trhythm oldRhythm(sn->note()->rtm); + Trhythm newRtmOfChanged(m_free + prevDur); + if (newRtmOfChanged.rhythm() == Trhythm::e_none) { + /** 3. Unfortunately remained free space in the measure can't be filled with single rhythm value */ + qDebug() << debug() << "To fill measure with remaining duration need to split note of" << m_free + prevDur; + TrhythmList solvedList; + Trhythm::resolve(m_free + prevDur, solvedList); + if (solvedList.size() == 2) { + solvedList.first().setRest(oldRhythm.isRest()); + solvedList.last().setRest(oldRhythm.isRest()); + sn->note()->rtm.setRhythm(solvedList.first().duration()); + copyRhythmParams(sn, oldRhythm); + Tnote n2(*sn->note(), solvedList.last()); + // TODO: common code with @p split + auto inserted = m_staff->insertNote(n2, sn->index() + 1, sn->isReadOnly()); + fixStemDirection(inserted); + m_notes.insert(inserted->index() - firstNoteId(), inserted); + connect(inserted, &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); +// inserted->setGroup(sn->group()); + m_staff->updateNotesPos(inserted->index()); + if (!sn->note()->isRest()) + restoreTie(oldRhythm.tie(), sn); + } else + qDebug() << debug() << "Can not resolve duration of" << m_free + prevDur; + } else { + sn->note()->rtm.setRhythm(newRtmOfChanged); + copyRhythmParams(sn, oldRhythm); + } + } + qDebug() << debug() << "RECALCULATED, remained" << nextMeasDur; + updateRhythmicGroups(); + resolveBeaming(sn->rhythmGroup()); + checkBarLine(); + if (nextMeasDur) { + /** 4. At the beginning of the next staff, create new note of the same pitch with remaining duration. */ + Trhythm nextMeasRtm(nextMeasDur); + if (nextMeasRtm.rhythm() == Trhythm::e_none) { + splitThenInsert(nextMeasDur, lastNoteId() + 1, *sn->note(), sn->isReadOnly()); + } else { + auto nextMeasNote = m_staff->insertNote(lastNoteId() + 1, Tnote(*sn->note(), nextMeasRtm), sn->isReadOnly()); + fixStemDirection(nextMeasNote); + } + if (!sn->note()->isRest()) + lastNote()->tieWithNext(); + } + } else if (newDur == prevDur) { + if (sn->note()->isRest() != sn->rhythm()->isRest()) + qDebug() << debug() << "note" << sn->index() << "changed to/from rest"; + resolveBeaming(sn->rhythmGroup(), sn->rhythmGroup()); + checkBarLine(); + } else { // measure duration is less than meter - take notes from the next measure + m_free += prevDur - newDur; + qDebug() << debug() << "needs duration" << m_free; + fill(); + } + } + + shiftReleased(notesToOut, newNote); +} + + +void TscoreMeasure::updateRhythmicGroups() { + if (duration() == 0) + return; + + qDebug() << debug() << "Updating rhythmic groups"; + + int notePos = 0, grNr = 0, currGr = 0; + delete[] m_firstInGr; + m_firstInGr = new qint8[scoreMeter()->groupCount()]; + m_firstInGr[0] = 0; // first note in measure also begins first rhythmic group + for (int i = 0; i < m_notes.size(); ++i) { + if (currGr != grNr) { + m_firstInGr[grNr] = i; + currGr = grNr; + } + m_notes[i]->setRhythmGroup(grNr); + notePos += m_notes[i]->note()->duration(); + while (grNr < scoreMeter()->groupCount() && notePos >= scoreMeter()->groupPos(grNr)) + grNr++; + } + if (currGr < scoreMeter()->groupCount() - 1) { // fill the rest of the array + for (int gr = currGr + 1; gr < scoreMeter()->groupCount(); ++gr) + m_firstInGr[gr] = -1; // with '-1' - means no notes yet + } + m_free = m_duration - notePos; +} + + +void TscoreMeasure::resolveBeaming(int firstGroup, int endGroup) { + if (m_notes.count() < 2) + return; + + if (endGroup == -1) + endGroup = scoreMeter()->groupCount() - 1; + + // delete beams in groups affected by rhythm change + if (!m_beams.isEmpty()) { + QMutableListIterator<TscoreBeam*> i(m_beams); + while (i.hasNext()) { + qint8 rg = i.next()->first()->rhythmGroup(); + if (rg >= firstGroup && rg <= endGroup) { + delete i.value(); + i.remove(); + } + } + } + + // create beams + while (firstGroup <= endGroup && m_firstInGr[firstGroup] > -1) { + int noteNr = qMax(1, int(m_firstInGr[firstGroup]) + 1); + while (noteNr < m_notes.count() && m_notes[noteNr]->rhythmGroup() == firstGroup) { + auto noteSeg = m_notes[noteNr]; // cache pointer to TscoreNote for multiple reuse + auto prevSeg = m_notes[noteNr - 1]; + if (!noteSeg->note()->isRest() && !prevSeg->note()->isRest() // not a rest + && noteSeg->note()->rhythm() > Trhythm::e_quarter // sixteenth or eighth + && prevSeg->note()->rhythm() > Trhythm::e_quarter) + { + if (prevSeg->note()->rtm.beam() == Trhythm::e_noBeam) // start beam group + m_beams << new TscoreBeam(prevSeg, this); + if (!m_beams.isEmpty()) { + noteSeg->note()->rtm.setBeam(Trhythm::e_beamCont); + m_beams.last()->addNote(noteSeg); + } + } + noteNr++; + } + if (!m_beams.isEmpty() && m_beams.last()->last()->note()->rtm.beam() == Trhythm::e_beamCont) + m_beams.last()->closeBeam(); + firstGroup++; + } +} + + +/** + * iterate through notes in backward order (right to left), take note by note to release required duration + * create a list from taken notes to send it to the next measure + * split the latest note (the most right one in the measure) if necessary + * half of the duration remains in current measure at the end tied with + * a new note that has to be created and push to the beginning of the next measure + */ +int TscoreMeasure::releaseAtEnd(int dur, QList<TscoreNote*>& notesToOut, Tnote& newNote, int endNote) { + int noteNr = m_notes.count() - 1; + while (noteNr >= endNote && dur > 0) { + auto lastN = lastNote(); + int lastDur = lastN->note()->duration(); + if (lastDur > dur) { // last note is longer than required space - split it then create and move the rest of its duration to the next measure + if (Trhythm(lastDur - dur).rhythm() == Trhythm::e_none) { // subtracting can't be solved by single note + split(lastN); // then split on two notes + continue; // and call while loop again + } + Trhythm oldRhythm(lastN->note()->rtm); // preserve tie and stem direction + lastN->setRhythm(Trhythm(lastDur - dur, lastN->note()->isRest())); + copyRhythmParams(lastN, oldRhythm); +// lastN->note()->rtm.setStemDown(oldRhythm.stemDown()); +// if (oldRhythm.tie()) +// lastN->note()->rtm.setTie(Trhythm::e_tieCont); + newNote = Tnote(*lastN->note(), Trhythm(dur, lastN->note()->isRest())); +// newNote.rtm.setStemDown(oldRhythm.stemDown()); + lastDur = dur; + } else { // last note is the same long or smaller than required space - so move it to the next measure + notesToOut << m_notes.takeLast(); + disconnect(notesToOut.last(), &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + auto b = notesToOut.last()->beam(); + if (b) { + m_beams.removeOne(b); + delete b; + } + } + dur -= lastDur; + m_free += lastDur; // note was taken out so there is more free space in the measure + } + + return dur; +} + + +int TscoreMeasure::takeAtStart(int dur, QList<TscoreNote*>& notesToOut) { + qDebug() << debug() << "taking" << dur << "from the beginning"; + content(this); + int noteNr = 0; + int retDur = 0; + while (noteNr < m_notes.count() && dur > 0) { + int firstDur = firstNote()->note()->duration(); + if (firstDur > dur) { // first measure note is longer than required duration - shrink it and create new one + auto first = firstNote(); + Trhythm oldRhythm(first->note()->rtm); + if (Trhythm(firstDur - dur).rhythm() == Trhythm::e_none) { // subtracting can't be solved by single note + split(first); // then split on two notes + continue; // and call while loop again + } + firstNote()->note()->setRhythm(Trhythm(firstDur - dur, first->note()->isRest())); + copyRhythmParams(first, oldRhythm); + firstDur = dur; + retDur = dur; + dur = 0; + } else { // first note is the same long or smaller than required space - so move it to the next measure + notesToOut << m_notes.takeFirst(); + disconnect(notesToOut.last(), &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + dur -= firstDur; + auto b = notesToOut.last()->beam(); + if (b) { + m_beams.removeOne(b); + delete b; + } + } + m_free += firstDur; + noteNr++; + } + + qDebug() << debug() << notesToOut.count() << "taken at start." << "Now measure has" << m_notes.count() << "notes"; + content(this); + + if (m_free) + fill(); + + return retDur; +} + + +void TscoreMeasure::checkBarLine() { + if (duration() && m_free == 0) { // update bar line + if (m_barLine->parentItem() != lastNote()->parentItem()) { + m_barLine->setParentItem(lastNote()); + QPointF barLinePos = lastNote()->mapFromParent(QPointF(lastNote()->rightX() - 0.2, 0.0)); + m_barLine->setLine(barLinePos.x(), lastNote()->staff()->upperLinePos(), barLinePos.x(), lastNote()->staff()->upperLinePos() + 8.0); + m_barLine->show(); + } + } else if (m_free > 0) { + m_barLine->hide(); + } else // TODO: It should never occur - delete it when tested + qDebug() << debug() << "ARE YOU MAD? Measure has more notes than the meter allows!"; +} + + +//################################################################################################# +//################### PRIVATE ############################################ +//################################################################################################# + +void TscoreMeasure::addNewNote(Tnote& newNote) { +// prepare tie, if any to restore it after new note will be created + auto lastN = lastNote(); + auto lastTie = lastN->note()->rtm.tie(); // backup tie state to revert it after new note will be added + +// create a new note on the staff + auto inserted = m_staff->insertNote(lastN->index() + 1, newNote); + fixStemDirection(lastNote()); + qDebug() << debug() << "creating new note after" << lastN->index() << lastN->note()->toText() << newNote.rtm.string(); + + if (!newNote.isRest()) { + lastN->tieWithNext(); + if (lastTie == Trhythm::e_tieCont || lastTie == Trhythm::e_tieStart) + inserted->tieWithNext(); + } +} + + +void TscoreMeasure::shiftReleased(QList<TscoreNote*>& notesToOut, Tnote& newNote) { + if (!notesToOut.isEmpty()) + m_staff->shiftToMeasure(id() + 1, notesToOut); + + if (newNote.rhythm() != Trhythm::e_none) + addNewNote(newNote); +} + + +void TscoreMeasure::fill() { + QList<TscoreNote*> notesToShift; + int remainDur = m_staff->shiftFromMeasure(id() + 1, m_free, notesToShift); + qDebug() << debug() << "fill, remain" << remainDur << "to shift:" << notesToShift.count(); + for (int i = 0; i < notesToShift.size(); ++i) { + m_notes.append(notesToShift[i]); + connect(notesToShift[i], &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + } + if (remainDur) { // next measure has a part of a new note + qDebug() << debug() << remainDur << "remained in the next measure"; + auto firstInNext = m_staff->measures()[id() + 1]->firstNote()->note(); + Tnote newNote(*firstInNext, Trhythm(remainDur, firstInNext->isRest())); + auto inserted = m_staff->insertNote(newNote, lastNoteId() + 1, lastNote()->isReadOnly()); +// copyRhythmParams(inserted, firstInNext->rtm); + fixStemDirection(inserted); + insertNote(inserted->index() - firstNoteId(), inserted); + m_staff->updateNotesPos(); + if (!inserted->note()->isRest()) // add a tie + inserted->tieWithNext(); + } else { + updateRhythmicGroups(); + resolveBeaming(0); + checkBarLine(); + } + + content(this); +} + + +/** + * This method is called when subtracting from rhythm value can't be solved in single note. + * So given 'long' note is split, then calling method tries to subtract from smaller rhythm. + * Because adding new, split note doesn't change measure duration, + * this method simplifies process of adding new note to staff and measure. + * It ties notes as well. + */ +void TscoreMeasure::split(TscoreNote* sn) { + auto tieCopy = sn->note()->rtm.tie(); +// if (!sn->note()->isRest()) +// preserveTie(tieCopy, sn); + + TrhythmList splitList; + sn->note()->rtm.split(splitList); + sn->note()->rtm.setRhythm(splitList.first()); + Tnote n2(*sn->note(), splitList.last()); + // TODO: common code like in @p noteChangedSlot() - do method of it + auto inserted = m_staff->insertNote(n2, sn->index() + 1, sn->isReadOnly()); + fixStemDirection(inserted); + m_notes.insert(inserted->index() - firstNoteId(), inserted); + connect(inserted, &TscoreNote::noteGoingToChange, this, &TscoreMeasure::noteChangedSlot); + inserted->setGroup(sn->group()); + m_staff->updateNotesPos(inserted->index()); + // ------------ + + if (!sn->note()->isRest()) + restoreTie(tieCopy, sn); +} + + +/** + * Looks for smallest rhythm that given duration divides on, + * then tries to assembly that duration for two notes which are multiplicity of obtained smallest value. + * Inserts that notes to staff using given @p id index + */ +void TscoreMeasure::splitThenInsert(int dur, int id, const Tnote& note, bool readOnly) { + qDebug() << debug() << "splitThenInsert" << dur << note.toText() << note.rtm.string(); + + TrhythmList twoSplit; + Trhythm::resolve(dur, twoSplit); + if (twoSplit.size() == 2) { + Trhythm& r1 = twoSplit.first(), r2 = twoSplit.last(); + if (r1.rhythm() != Trhythm::e_none && r2.rhythm() != Trhythm::e_none) { + r1.setRest(note.isRest()); + r2.setRest(note.isRest()); + // We found it! + auto note1 = m_staff->insertNote(id, Tnote(note, r2), readOnly); + fixStemDirection(note1); + auto note2 = m_staff->insertNote(note1->index() + 1, Tnote(note, r1), readOnly); + fixStemDirection(note2); + if (!note1->note()->isRest()) + note1->tieWithNext(); + } else + qDebug() << debug() << "Can't resolve duration of " << dur; + } else + qDebug() << debug() << "Can't resolve duration" << dur << "and split it!"; +} + + +void TscoreMeasure::fixStemDirection(TscoreNote* sn) { + sn->note()->rtm.setStemDown(sn->notePos() <= 18); +} + + +void TscoreMeasure::copyRhythmParams(TscoreNote* sn, const Trhythm& r) { + sn->note()->rtm.setTie(r.tie()); + sn->note()->rtm.setStemDown(r.stemDown()); +} + + +/** + * Restores ties. + * It always ties @p thisNote with the next one (if the same) + * and depends on @p tieCopy, adds ties to previous note and to the next one + */ +void TscoreMeasure::restoreTie(quint8 tieCopy, TscoreNote* thisNote) { + qDebug() << debug() << "restoring tie of" << thisNote->note()->toText() << thisNote->note()->rtm.string(); + thisNote->tieWithNext(); + auto prevNote = thisNote->prevNote(); + auto nextNote = thisNote->nextNote(); + if (tieCopy == Trhythm::e_tieCont) { + prevNote->tieWithNext(); + nextNote->tieWithNext(); + } else if (tieCopy == Trhythm::e_tieEnd) + prevNote->tieWithNext(); + else // tie started + nextNote->tieWithNext(); +} + diff --git a/src/libs/score/tscoremeasure.h b/src/libs/score/tscoremeasure.h new file mode 100644 index 0000000000000000000000000000000000000000..b5d456adfd48e729f20b37bc1b9d0bb43fa21cd4 --- /dev/null +++ b/src/libs/score/tscoremeasure.h @@ -0,0 +1,234 @@ +/*************************************************************************** + * Copyright (C) 2016 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/>. * + ***************************************************************************/ + +#ifndef TSCOREMEASURE_H +#define TSCOREMEASURE_H + +#include <nootkacoreglobal.h> +#include <QtCore/qobject.h> + + +class TscoreMeter; +class TscoreStaff; +class TscoreNote; +class TscoreBeam; +class QGraphicsLineItem; +class Tmeter; +class Tnote; +class Trhythm; + + +/** + * Implementation of the measure + */ +class NOOTKACORE_EXPORT TscoreMeasure : public QObject +{ + + friend class TscoreStaff; + + Q_OBJECT + +public: + explicit TscoreMeasure(TscoreStaff* staff, int nr); + virtual ~TscoreMeasure(); + + void changeMeter(const Tmeter& m); + + /** + * Sets parent staff and parent object as well + */ + void setStaff(TscoreStaff* st); + + void insertNote(int id, TscoreNote* sn); + + /** + * Inserts list of notes at the beginning of this measure. + * IT IS WASTING ENERGY TO ADDING WHOLE MEASURE THIS WAY! + * Use this method only for lists of notes shorter than measure duration + */ + void prependNotes(QList<TscoreNote*>& nl); + + /** + * Adds list of notes @p nl at the measure end. + * Measure has to have already space for them + */ + void appendNotes(QList<TscoreNote*>& nl); + + /** + * Removes note with index @p noteToRemove, + * Asks the staff for notes from the next measure + * to fulfill free space + */ + void removeNote(int noteToRemove); + + QList<TscoreNote*>& notes() { return m_notes; } + + bool isEmpty() { return m_notes.isEmpty(); } + + TscoreNote* firstNote() { return m_notes.first(); } + + TscoreNote* lastNote() { return m_notes.last(); } + + /** Staff index of the first measure note */ + int firstNoteId() const; + + /** Staff index of the last measure note */ + int lastNoteId() const; + + /** @p TRUE when there is no more space in the measure */ + bool isFull() const { return m_free == 0; } + + /** Free 'rhythm space' remained in the measure */ + int free() const { return m_free; } + + /** Summary of notes duration starting from @p noteId to the end of measure */ + int freeAt(int noteId) const; + + /** Duration of the measure */ + int duration() const { return m_duration; } + + /** Duration of notes starting from measure begin until @p noteId */ + int durationAt(int noteId) const; + + Tmeter* meter() const; + TscoreMeter* scoreMeter() const; + + /** + * Measure index - number in current staff + * Starts from 0 in every staff. + */ + int id() const { return m_id; } + + /** + * Width of all notes in this measure. + * Difference between last note position with its rhythm gap + * and X coordinate of the first note + */ + qreal notesWidth(); + + /** Prints debug message with [nr MEASURE] */ + char debug(); + + /** + * Returns summary duration of notes in the list. + */ + static int groupDuration(const QList<TscoreNote*>& notes); + + +signals: + + /** + * Emitted when measure was changed and re-beamed. + * TscoreNote parameter is a note that invoked change (or 0 if new one was added) + * so all other beam groups that don't contain this note has to be updated + */ + void updateBeams(TscoreNote*); + + +protected: + + /** + * Returns first @c TscoreNote in given rhythmic group @p grNr or null pointer if none + */ + TscoreNote* firstInGroup(int grNr) { return m_firstInGr[grNr] >= 0 ? m_notes[m_firstInGr[grNr]] : nullptr; } + + void noteChangedSlot(TscoreNote* sn); + + /** Only class TscoreStaff can do this */ + void setId(int id) { m_id = id; } + + /** + * Sets beams in measure notes starting from beam group of @p firstGroup + * till @p endGroup or through all groups if not set. + */ + void resolveBeaming(int firstGroup, int endGroup = -1); + + /** Sets appropriate @p setRhythmGroup of every note in the measure. */ + void updateRhythmicGroups(); + + /** + * Takes @p dur (duration) of notes at the measure end + * and creates a list of notes @p notesToOut to shift to the next measure. + * Also, if the last note has to be split + * - into @p newNote is put Tnote to be shifted at next measure beginning + * and tied with the last note of this measure. + * @p endNote is note number till taking out is performed (by default 0 - whole measure) + * Returned value is remaining duration. + */ + int releaseAtEnd(int dur, QList<TscoreNote*>& notesToOut, Tnote& newNote, int endNote = 0); + + /** + * Takes @p dur (duration) of notes at the measure beginning + * and packs them into @p notesToOut list. + */ + int takeAtStart(int dur, QList<TscoreNote*>& notesToOut); + + /** Checks to display or hide the bar line */ + void checkBarLine(); + +private: + + /** + * Adds @p newNote with index +1 than the last note + * by invoking @p TscoreStaff::insertNote() + */ + void addNewNote(Tnote& newNote); + + /** + * Common routine that performs: + * - shifting @p notesToOut to the next measure (if not empty) + * and creating new, tied note @p newNote at the beginning of the next measure + */ + void shiftReleased(QList<TscoreNote*>& notesToOut, Tnote& newNote); + + /** + * Common method calling the staff for notes from the next measure + * to fill this one. @p m_free has to be updated before. + */ + void fill(); + + /** + * Splits given note into two (using @p Trhythm::split()). + * It adds that second note to the staff and this measure + * and ties with given @p sn note + */ + void split(TscoreNote* sn); + + /** Splits given duration @p dur into two notes of @p note and adds them to the staff at @p id */ + void splitThenInsert(int dur, int id, const Tnote& note, bool readOnly); + + void fixStemDirection(TscoreNote* sn); + + /** Restores ties of @p thisNote by given @p tieCopy value of @p Trhythm::Etie*/ + void restoreTie(quint8 tieCopy, TscoreNote* thisNote); + + void copyRhythmParams(TscoreNote* sn, const Trhythm& r); + +private: + int m_duration; + int m_id; + int m_free; + TscoreStaff *m_staff; /**< Parent staff */ + QList<TscoreNote*> m_notes; + QList<TscoreBeam*> m_beams; + QGraphicsLineItem *m_barLine; + qint8 *m_firstInGr; /**< quint8 is sufficient - measure never has more than 127 notes */ +}; + + +#endif // TSCOREMEASURE_H diff --git a/src/libs/score/tscoremeter.cpp b/src/libs/score/tscoremeter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3be31ccf32d04aca97989070001cfadb4bdfe2f1 --- /dev/null +++ b/src/libs/score/tscoremeter.cpp @@ -0,0 +1,144 @@ +/*************************************************************************** + * Copyright (C) 2016-2017 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 "tscoremeter.h" +#include "tscorestaff.h" +// #include "graphics/tnotepixmap.h" +#include <tnoofont.h> +#include <music/tmeter.h> +// #include <widgets/tpushbutton.h> +#include <QtWidgets/QtWidgets> + + +#define METRUM_HEIGHT (8.0) + + +TscoreMeter::TscoreMeter(TscoreScene* scene, TscoreStaff* staff) : + TscoreItem(scene), + m_meter(new Tmeter(Tmeter::e_4_4)), + m_isReadOnly(false), + m_pianoStaff(false), + m_width(4.0) +{ + setStaff(staff); + setParentItem(staff); + setStatusTip(tr("Time signature")); +} + + +TscoreMeter::~TscoreMeter() +{ + delete m_meter; +} + + +void TscoreMeter::setMeter(const Tmeter& meter, bool emitSignal) { + *m_meter = meter; + m_upperDigit = TnooFont::digit(m_meter->upper()); + m_lowerDigit = TnooFont::digit(m_meter->lower()); + QFontMetrics fm(TnooFont(8)); + m_width = fm.boundingRect(m_upperDigit).width(); + update(); + +// set notes grouping + m_groups.clear(); + if (m_meter->lower() == 4) { // simple grouping: one group for each quarter + m_groups << 24 << 48; // 2/4 and above + if (m_meter->meter() > Tmeter::e_2_4) + m_groups << 72; + if (m_meter->meter() > Tmeter::e_3_4) + m_groups << 96; + if (m_meter->meter() > Tmeter::e_4_4) + m_groups << 120; + if (m_meter->meter() > Tmeter::e_5_4) + m_groups << 144; + if (m_meter->meter() > Tmeter::e_6_4) + m_groups << 168; + } else { + if (m_meter->meter() == Tmeter::e_3_8) + m_groups << 36; + else if (m_meter->meter() == Tmeter::e_5_8) + m_groups << 36 << 60; + else if (m_meter->meter() == Tmeter::e_6_8) + m_groups << 36 << 72; + else if (m_meter->meter() == Tmeter::e_7_8) + m_groups << 36 << 60 << 84; + else if (m_meter->meter() == Tmeter::e_9_8) + m_groups << 36 << 72 << 108; + else if (m_meter->meter() == Tmeter::e_12_8) + m_groups << 36 << 72 << 108 << 144; + } + + updateOptGap(); + + if (emitSignal) + emit meterChanged(); +} + + +void TscoreMeter::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + Q_UNUSED(option) + Q_UNUSED(widget) +// paintBackground(painter, Qt::yellow); + painter->setPen(qApp->palette().text().color()); + painter->setFont(TnooFont(8)); + painter->drawText(0, 0, m_width, 4.0, Qt::AlignCenter, m_upperDigit); + painter->drawText(0, 4, m_width, 4.0, Qt::AlignCenter, m_lowerDigit); + if (m_pianoStaff) { + painter->drawText(0, 14, m_width, 4.0, Qt::AlignCenter, m_upperDigit); + painter->drawText(0, 18, m_width, 4.0, Qt::AlignCenter, m_lowerDigit); + } +} + + +QRectF TscoreMeter::boundingRect() const { + return QRectF(0.0, 0.0, m_width, m_pianoStaff ? 22.0 : 8.0); +} + +//################################################################################################# +//################### PROTECTED ############################################ +//################################################################################################# + +void TscoreMeter::updateOptGap() { + if (m_meter->lower() == 4) { + m_optGap = m_meter->upper() * 2.0; // simple multiply number of quarters by gap for quarter (see TscoreNote) + } else { + if (m_meter->meter() == Tmeter::e_3_8) + m_optGap = 2.5; // quarter with dot + else if (m_meter->meter() == Tmeter::e_5_8) + m_optGap = 2.5 + 2.0; // quarter with dot and quarter + if (m_meter->meter() == Tmeter::e_6_8) + m_optGap = 2.0 * 2.5; // two quarters with dot + if (m_meter->meter() == Tmeter::e_7_8) + m_optGap = 2.5 + 2.0 * 2.0; // quarter with dot and two quarters + if (m_meter->meter() == Tmeter::e_9_8) + m_optGap = 3.0 * 2.5; // three quarters with dot + if (m_meter->meter() == Tmeter::e_12_8) + m_optGap = 4.0 * 2.5; // four quarters with dot + } +} + + + + + + + + + + diff --git a/src/libs/score/tscoremeter.h b/src/libs/score/tscoremeter.h new file mode 100644 index 0000000000000000000000000000000000000000..a19aa8a4aecd05a1df8fc9768ba73ee8c6e10020 --- /dev/null +++ b/src/libs/score/tscoremeter.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2016 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/>. * + ***************************************************************************/ + +#ifndef TSCOREMETER_H +#define TSCOREMETER_H + + +#include "nootkacoreglobal.h" +#include "tscoreitem.h" + + +class Tmeter; + + +#define MIN_GAP (1.0) /**< Minimal distance between notes of the shortest duration in a staff */ + + +/** + * Graphical representation of time signature on a staff (first one) + */ +class NOOTKACORE_EXPORT TscoreMeter : public TscoreItem +{ + + Q_OBJECT + +public: + TscoreMeter(TscoreScene* scene, TscoreStaff* staff); + virtual ~TscoreMeter(); + + void setMeter(const Tmeter& meter, bool emitSignal = true); + Tmeter* meter() const { return m_meter; } + + void setReadOnly(bool readOnly) { m_isReadOnly = readOnly; } + bool isReadOnly() { return m_isReadOnly; } + + /** This is only way to inform const boundingRect about staff type changed */ + void setPianoStaff(bool piano) { m_pianoStaff = piano; } + bool isPianoStaff() { return m_pianoStaff; } + + /** + * Returns duration of given @p grNr group starting from measure beginning + * Describes grouping (beaming - beam connections) of notes in a single measure for current meter. + * This is a group of a few int values - each representing duration of the one group: + * - for 3/8 it is only single 36 value - whole measure under one beam + * - for 3/4 it is 24, 48, 72) - three groups + */ + quint8 groupPos(int grNr) { return m_groups[grNr]; } + + /** Number of beaming groups for this meter */ + int groupCount() { return m_groups.count(); } + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual QRectF boundingRect() const; + + qreal width() { return m_width; } + + /** + * @p optimalGap is summary of note gaps for + * optimal measure content, i.e.: + * 2/4 is | 4 4 | so two gaps of quarter note + * 5/8 is | 4. 4 | so gap of quarter with dot and just quarter + * 12/8 is | 4. 4. 4. 4. | so four gaps of quarter and dot + * It is used to determine has a staff space for a new measure + */ + qreal optimalGap() { return m_optGap; } + +signals: + void meterChanged(); + +private: + void updateOptGap(); + +private: + Tmeter *m_meter; + bool m_isReadOnly, m_pianoStaff; + QString m_upperDigit, m_lowerDigit; + qreal m_width; + QList<quint8> m_groups; + qreal m_optGap; +}; + + +#endif // TSCOREMETER_H diff --git a/src/libs/score/tscorenote.cpp b/src/libs/score/tscorenote.cpp index ee23f2018b7dee2c24c5ff2ab8d23f7605b02d86..a276ba1fb6c32d0362d22dd6129e0c8a838483de 100644 --- a/src/libs/score/tscorenote.cpp +++ b/src/libs/score/tscorenote.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2016 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -21,9 +21,10 @@ #include "tscorescene.h" #include "tscorestaff.h" #include "tnotecontrol.h" +#include "tscorebeam.h" +#include "tscoretie.h" +#include "tscorelines.h" #include <graphics/tdropshadoweffect.h> -#include <animations/tcrossfadetextanim.h> -#include <animations/tcombinedanim.h> #include <music/tnote.h> #include <tnoofont.h> #include <QtCore/qeasingcurve.h> @@ -37,10 +38,18 @@ #include <QtCore/qdebug.h> + #define SHORT_TAP_TIME (150) // 150 ms takes short tap - otherwise note is edited +#define REST_Y (19.0) + +static const int accCharTable[6] = { 0xe123, 0xe11a, 0x20, 0xe10e, 0xe125, 0xe116 }; -const int accCharTable[6] = { 0xe123, 0xe11a, 0x20, 0xe10e, 0xe125, 0xe116 }; +char d(TscoreNote* sn) { + QTextStream o(stdout); + o << " \033[01;29m[" << sn->index() << " NOTE]\033[01;00m"; + return 32; // fake +} /*static*/ QString TscoreNote::getAccid(int accNr) { @@ -48,20 +57,29 @@ QString TscoreNote::getAccid(int accNr) { } -QGraphicsEllipseItem* TscoreNote::createNoteHead(QGraphicsItem* parentIt) { - QGraphicsEllipseItem *noteHead = new QGraphicsEllipseItem(); - noteHead->setParentItem(parentIt); - noteHead->setRect(0, 0, 3.5, 2); - noteHead->hide(); - return noteHead; -} - - /** To avoid creating many tips - each one for every instance and waste RAM - * this text exist as static variable + * this text exist as static variable * and TscoreNote manages itself when status tip is necessary to be displayed. */ QString TscoreNote::m_staticTip = QString(); -QString m_selectedTip = QString(); +QString TscoreNote::m_selectedTip = QString(); + +const qreal TscoreNote::m_rtmGapArray[5][3] = { +// | bare | dot | triplet | + { 5.0, 6.0, 4.5}, // whole note + { 4.0, 5.0, 3.3}, // half note + { 2.0, 2.5, 1.3}, // quarter note + { 1.0, 1.5, 0.3}, // eighth note + { 0.15, 0.5, 0.0} // sixteenth note +}; + + +qreal TscoreNote::space(const Trhythm& r) { + if (r.rhythm() == Trhythm::e_none) + return 0.0; + + int add = r.hasDot() ? 1 : (r.isTriplet() ? 2 : 0); + return m_rtmGapArray[static_cast<int>(r.rhythm()) - 1][add]; +} //################################################################################################# @@ -70,77 +88,89 @@ QString m_selectedTip = QString(); TscoreNote::TscoreNote(TscoreScene* scene, TscoreStaff* staff, int index) : TscoreItem(scene), - m_mainPosY(0.0), - m_accidental(0), + m_mainPosY(0.0), m_newPosY(0), + m_accidental(0), m_newAccid(0), m_index(index), - m_stringNr(0), m_stringText(nullptr), + m_stringNr(0), m_stringText(0), m_readOnly(false), m_emptyLinesVisible(true), m_nameText(0), m_ottava(0), m_bgColor(-1), - m_noteAnim(nullptr), m_popUpAnim(nullptr), m_selected(false), m_touchedToMove(false), m_wasTouched(false) { setStaff(staff); - setParentItem(staff); + setParentItem(staff); + setFlag(QGraphicsItem::ItemHasNoContents); m_height = staff->height(); + m_width = scene->isRhythmEnabled() ? 4.0 : 7.0; +// m_width = 7.0; m_mainColor = qApp->palette().text().color(); - m_note = new Tnote(0, 0, 0); + m_note = new Tnote(); // empty note with no rhythm + + m_lines = new TscoreLines(this); + m_lines->setPos(0.6, 0.0); + m_mainNote = new TnoteItem(scoreScene(), Trhythm(Trhythm::e_none)); + m_mainNote->setParentItem(this); + m_mainNote->hide(); + m_mainNote->setX(3.0); - m_lines = new TscoreLines(this); - m_mainNote = createNoteHead(this); - m_mainAccid = new QGraphicsSimpleTextItem(); - m_mainAccid->setParentItem(m_mainNote); - + m_mainAccid->setParentItem(m_mainNote); + m_mainAccid->setFont(TnooFont(5)); - bool prepareScale = false; - if (scoreScene()->accidScale() == -1.0) { // only when first TscoreNote is constructed - m_staticTip = - tr("Click to enter a note, use horizontal scroll to change accidental."); - m_selectedTip = "<br>" + tr("Right mouse button just selects a note."); - m_mainAccid->setText(getAccid(1)); - scoreScene()->setAccidScale(6.0 / m_mainAccid->boundingRect().height()); - prepareScale = true; - } - m_emptyText = new QGraphicsSimpleTextItem("n", this); - m_emptyText->setFont(TnooFont()); - m_emptyText->setZValue(1); - QColor cc = qApp->palette().highlight().color(); - cc.setAlpha(50); - m_emptyText->setBrush(cc); - m_emptyText->setPen(QPen(qApp->palette().highlight().color(), 0.1)); - m_emptyText->setScale(5.5 / m_emptyText->boundingRect().width()); - m_emptyText->setPos((7.0 - m_emptyText->boundingRect().width() * m_emptyText->scale()) / 2, - staff->upperLinePos() - 1 + (staff->isPianoStaff() ? 6 : 0)); - m_emptyText->hide(); - + bool prepareScale = false; + if (scoreScene()->accidScale() == -1.0) { // only when first instance of TscoreNote is constructed + m_staticTip = tr("Click to enter a note, use horizontal scroll to change accidental."); + m_selectedTip = QLatin1String("<br>") + tr("Right mouse button just selects a note."); + m_mainAccid->setText(getAccid(1)); + scoreScene()->setAccidScale(6.0 / m_mainAccid->boundingRect().height()); + prepareScale = true; + } + m_emptyText = new QGraphicsSimpleTextItem(QStringLiteral("n"), this); + m_emptyText->setFont(TnooFont(8)); + m_emptyText->setZValue(1); + QColor cc = qApp->palette().highlight().color(); + cc.setAlpha(50); + m_emptyText->setBrush(cc); + m_emptyText->setPen(QPen(qApp->palette().highlight().color(), 0.1)); +// m_emptyText->setScale(m_width / m_emptyText->boundingRect().width()); + m_emptyText->setPos((m_width - m_emptyText->boundingRect().width() * m_emptyText->scale()) / 2, + staff->upperLinePos() + 1.0 + (staff->isPianoStaff() ? 6 : 0)); + m_emptyText->hide(); + m_mainAccid->setScale(scoreScene()->accidScale()); - if (prepareScale) { - scoreScene()->setAccidYoffset(m_mainAccid->boundingRect().height() * scoreScene()->accidScale() * 0.34); - m_mainAccid->setText(QString()); - } - m_mainAccid->setPos(-3.0, - scoreScene()->accidYoffset()); - - if (!scene->views().isEmpty() && scoreScene()->right() == 0) - initNoteCursor(); - + if (prepareScale) { + scoreScene()->setAccidYoffset(m_mainAccid->boundingRect().height() * scoreScene()->accidScale() * 0.34); + m_mainAccid->setText(QString()); + } + m_mainAccid->setPos(-2.3, - scoreScene()->accidYoffset()); + setColor(m_mainColor); m_mainNote->setZValue(34); // under m_mainAccid->setZValue(m_mainNote->zValue() - 1); if (staff->isPianoStaff()) - setAmbitus(40, 2); - else - setAmbitus(34, 2); - connect(this, SIGNAL(statusTip(QString)), scene, SLOT(statusTipChanged(QString))); + setAmbitus(40, 2); + else + setAmbitus(34, 2); + connect(this, SIGNAL(statusTip(QString)), scene, SLOT(statusTipChanged(QString))); + setRhythmEnabled(scoreScene()->isRhythmEnabled()); checkEmptyText(); } -TscoreNote::~TscoreNote() { // release work note and controls from destructing parent - if (scoreScene()->right() && (scoreScene()->workNote()->parentItem() == this || scoreScene()->right()->parentItem() == parentItem())) - scoreScene()->noteDeleted(this); +TscoreNote::~TscoreNote() { + if (note()->rtm.tie() == Trhythm::e_tieEnd || note()->rtm.tie() == Trhythm::e_tieCont) { + auto prev = prevNote(); + if (prev) + prev->tieRemove(); + } + if (m_tie) + delete m_tie; + + if (staff()) + staff()->noteGoingDestroy(this); + delete m_note; } @@ -148,39 +178,76 @@ TscoreNote::~TscoreNote() { // release work note and controls from destructing p //#################### PUBLIC METHODS ####################################### //############################################################################## +Trhythm* TscoreNote::rhythm() const { + return m_mainNote->rhythm(); +} + + +void TscoreNote::setRhythm(const Trhythm& r) { +// m_mainNote->setRhythm(r); +// Trhythm old(m_note->rtm); + m_note->setRhythm(r); +// m_note->rtm.setTie(old.tie()); +// m_note->rtm.setStemDown(old.stemDown()); + update(); +} + + void TscoreNote::adjustSize() { - m_height = staff()->height(); - m_lines->adjustLines(this); - setColor(m_mainColor); - if (staff()->isPianoStaff()) - m_emptyText->setPos(m_emptyText->x(), m_emptyText->y() + 6); - else - m_emptyText->setPos(m_emptyText->x(), m_emptyText->y() - 6); + m_height = staff()->height(); + m_lines->adjustLines(this); + setColor(m_mainColor); + if (staff()->isPianoStaff()) + m_emptyText->setPos(m_emptyText->x(), m_emptyText->y() + 6); + else + m_emptyText->setPos(m_emptyText->x(), m_emptyText->y() - 6); +} + + +void TscoreNote::setRhythmEnabled(bool rhythmEnabled) { + if (rhythmEnabled != scoreScene()->isRhythmEnabled()) + qDebug() << d(this) << "Note has rhythms available different than scene!"; + + if (rhythmEnabled) { +// m_emptyText->setText(QString(QChar(TnooFont::getCharFromRhythm(scoreScene()->workRhythm()->weight(), true, scoreScene()->workRhythm()->isRest())))); +// m_mainNote->setRhythm(Trhythm(scoreScene()->workRhythm()->rhythm(), !m_note->isValid())); +// if (!m_note->isValid()) +// moveNote(15); + } else { + m_emptyText->setText(QStringLiteral("n")); +// m_mainNote->setRhythm(Trhythm(Trhythm::e_none)); + } + changeWidth(); } void TscoreNote::setColor(const QColor& color) { - m_mainColor = color; - m_mainNote->setPen(Qt::NoPen); - m_mainNote->setBrush(QBrush(m_mainColor, Qt::SolidPattern)); - m_mainAccid->setBrush(QBrush(m_mainColor)); - m_lines->setColor(color); - if (m_stringText) - m_stringText->setBrush(QBrush(m_mainColor)); + m_mainColor = color; + m_mainNote->setColor(color); +// m_mainNote->setPen(Qt::NoPen); // TODO +// m_mainNote->setBrush(QBrush(m_mainColor, Qt::SolidPattern)); + m_mainAccid->setBrush(QBrush(m_mainColor)); + m_lines->setColor(color); + if (m_stringText) + m_stringText->setBrush(QBrush(m_mainColor)); } void TscoreNote::selectNote(bool sel) { - m_selected = sel; - checkEmptyText(); + m_selected = sel; + checkEmptyText(); } void TscoreNote::moveNote(int posY) { -// if (posY == 0 || !(posY >= m_ambitMax - 1 && posY <= m_ambitMin)) { +// if (posY == 0 || !(posY >= m_ambitMax - 1 && posY <= m_ambitMin)) { bool theSame = (posY == m_mainPosY); if (posY == 0 || !(posY >= 1 && posY <= m_height - 3)) { - hideNote(); + if (m_mainNote->rhythm()->isRest()) { + m_mainNote->setY(REST_Y); + m_mainNote->show(); + } else + hideNote(); m_mainAccid->setText(QString()); m_accidental = 0; return; @@ -189,12 +256,12 @@ void TscoreNote::moveNote(int posY) { m_mainNote->show(); m_mainAccid->show(); } - if (m_noteAnim) { // initialize animation - m_noteAnim->setMoving(m_mainNote->pos(), QPointF(3.0, posY)); - m_noteAnim->startAnimations(); - } else { // just move a note - m_mainNote->setPos(3.0, posY); - } + + if (note()->isRest()) + m_mainNote->setY(REST_Y); + else if (posY != static_cast<int>(m_mainNote->y())) + m_mainNote->setY(posY); + m_mainPosY = posY; int noteNr = (56 + staff()->notePosRelatedToClef(staff()->fixNotePos(posY))) % 7; QString newAccid = getAccid(m_accidental); @@ -202,12 +269,8 @@ void TscoreNote::moveNote(int posY) { if (m_accidental == 0) { newAccid = getAccid(3); // neutral m_mainAccid->hide(); - if (scoreScene()->isAccidAnimated() && !isReadOnly() && !theSame) - emit fromKeyAnim(newAccid, m_mainAccid->scenePos(), m_mainPosY); } else { if (staff()->accidInKeyArray[noteNr] == m_accidental) { - if (scoreScene()->isAccidAnimated() && !isReadOnly() && !theSame) - emit toKeyAnim(newAccid, m_mainAccid->scenePos(), m_mainPosY); if (staff()->extraAccids()) // accidental from key signature in braces newAccid = QString(QChar(accCharTable[m_accidental + 2] + 1)); else @@ -226,101 +289,124 @@ void TscoreNote::moveNote(int posY) { } } m_mainAccid->show(); - if (m_noteAnim) - m_accidAnim->startCrossFading(newAccid, m_mainColor); - else - m_mainAccid->setText(newAccid); - + m_mainAccid->setText(newAccid); setStringPos(); m_lines->checkLines(posY); - checkEmptyText(); + checkEmptyText(); } void TscoreNote::setNote(int notePos, int accNr, const Tnote& n) { - m_accidental = accNr; - *m_note = n; - moveNote(notePos); - if (m_mainPosY == 0) - *m_note = Tnote(); // set note to null if beyond the score possibilities - if (m_nameText) - showNoteName(); + m_accidental = accNr; + *m_note = n; + m_mainNote->setRhythm(Trhythm(n.rhythm(), notePos == 0, n.hasDot(), n.isTriplet())); + moveNote(notePos); + if (m_mainPosY == 0) { + m_note->note = 0; + m_note->alter = 0; +// m_mainNote->setY(REST_Y); + } +// if (m_note->rhythm() != Trhythm::e_none) +// setRhythm(Trhythm(n.rhythm(), m_mainPosY == 0, n.hasDot(), n.isTriplet())); + changeWidth(); + if (m_nameText) + showNoteName(); checkEmptyText(); - update(); + update(); } void TscoreNote::hideNote() { - m_mainNote->hide(); - m_mainAccid->hide(); - m_lines->hideAllLines(); - m_mainPosY = 0; - m_accidental = 0; - m_mainNote->setPos(3.0, 0); + m_mainNote->hide(); + m_mainAccid->hide(); + m_lines->hideAllLines(); + m_mainPosY = 0; + m_accidental = 0; + m_mainNote->setY(0); } void TscoreNote::moveWorkNote(const QPointF& newPos) { - QGraphicsSceneHoverEvent moveEvent(QEvent::GraphicsSceneHoverMove); - moveEvent.setPos(newPos); - hoverMoveEvent(&moveEvent); -} - - -void TscoreNote::hideWorkNote() { - m_touchedToMove = false; - if (scoreScene()->workNote() && scoreScene()->workNote()->isVisible()) { - scoreScene()->workNote()->hide(); - scoreScene()->workLines()->hideAllLines(); - scoreScene()->setWorkPosY(0); - } - if (touchEnabled()) { - checkEmptyText(); - update(); - } + QGraphicsSceneHoverEvent moveEvent(QEvent::GraphicsSceneHoverMove); + moveEvent.setPos(newPos); + hoverMoveEvent(&moveEvent); } void TscoreNote::markNote(QColor blurColor) { - if (blurColor == -1) { - m_mainNote->setPen(Qt::NoPen); - m_mainNote->setGraphicsEffect(0); - } else { - m_mainNote->setPen(QPen(blurColor, 0.2)); - QGraphicsDropShadowEffect *bluredPen = new QGraphicsDropShadowEffect(); - bluredPen->setBlurRadius(10); - bluredPen->setColor(QColor(blurColor.name())); - bluredPen->setOffset(0.5, 0.5); - m_mainNote->setGraphicsEffect(bluredPen /*TdropShadowEffect(blurColor)*/); - } - update(); + if (blurColor == -1) { +// m_mainNote->setPen(Qt::NoPen); TODO: check it + m_mainNote->setGraphicsEffect(0); + } else { +// m_mainNote->setPen(QPen(blurColor, 0.2)); + QGraphicsDropShadowEffect *bluredPen = new QGraphicsDropShadowEffect(); + bluredPen->setBlurRadius(10); + bluredPen->setColor(QColor(blurColor.name())); + bluredPen->setOffset(0.5, 0.5); + m_mainNote->setGraphicsEffect(bluredPen /*TdropShadowEffect(blurColor)*/); + } + update(); +} + + +TscoreNote* TscoreNote::nextNote() { + TscoreNote* next = nullptr; // find next note first + if (this == staff()->lastNote()) { // take first note from the next staff + auto st = staff()->nextStaff(); + if (st) + next = st->firstNote(); + } else // or just next one + next = staff()->noteSegment(index() + 1); + return next; +} + + +TscoreNote* TscoreNote::prevNote() { + if (staff()->count()) { + TscoreNote* prev = nullptr; // find next note first + if (this == staff()->firstNote()) { // take first note from the next staff + auto st = staff()->prevStaff(); + if (st) + prev = st->lastNote(); + } else // or just next one + prev = staff()->noteSegment(index() - 1); + return prev; + } + return nullptr; +} + + +qreal TscoreNote::space() { + return space(note()->rtm); +} + + +QGraphicsLineItem* TscoreNote::stem() { + return m_mainNote->stem(); } void TscoreNote::setString(int realNr) { - if (realNr < 7) { - if (!m_stringText) { + if (!m_stringText) { m_stringText = new QGraphicsSimpleTextItem(); m_stringText->setFont(TnooFont(5)); m_stringText->setBrush(QBrush(m_mainColor)); m_stringText->setParentItem(this); m_stringText->setZValue(-1); - } - m_stringText->setText(QString("%1").arg(realNr)); - m_stringText->setScale(5.0 / m_stringText->boundingRect().width()); - m_stringNr = realNr; - setStringPos(); - } else - removeString(); + } + m_stringText->setText(QString("%1").arg(realNr)); + m_stringText->setScale(5.0 / m_stringText->boundingRect().width()); + m_stringNr = realNr; + setStringPos(); } void TscoreNote::removeString() { - if (m_stringText) { - delete m_stringText; - m_stringText = nullptr; - } - m_stringNr = 0; + if (m_stringText) { + delete m_stringText; + m_stringText = 0; + } + m_stringNr = 0; } @@ -329,357 +415,364 @@ void TscoreNote::setReadOnly(bool ro) { m_readOnly = ro; m_emptyLinesVisible = !ro; checkEmptyText(); - update(); } - -/** If @p dropShadowColor is not set and @p m_nameText has already existed, +/** If @dropShadowColor is not set and m_nameText has already existed, * previously color remained. */ void TscoreNote::showNoteName(const QColor& dropShadowColor) { - bool setColor = false; - if (!m_nameText) { - m_nameText = new QGraphicsTextItem(); - m_nameText->setDefaultTextColor(m_mainColor); - m_nameText->setParentItem(this); - m_nameText->setZValue(10); - m_nameText->setAcceptHoverEvents(false); - setColor = true; - } - if (dropShadowColor != -1) - setColor = true; - if (setColor) { - QGraphicsDropShadowEffect *dropEff = new QGraphicsDropShadowEffect(); - if (dropShadowColor == -1) - dropEff->setColor(scoreScene()->nameColor()); - else - dropEff->setColor(dropShadowColor); - dropEff->setOffset(0.7, 0.7); - dropEff->setBlurRadius(5.0); - m_nameText->setGraphicsEffect(dropEff); - } - if (m_note->note) { - m_nameText->setHtml(m_note->toRichText()); - m_nameText->setScale(8.0 / m_nameText->boundingRect().height()); - if (m_nameText->boundingRect().width() * m_nameText->scale() > boundingRect().width()) - m_nameText->setScale(boundingRect().width() / (m_nameText->boundingRect().width())); - m_nameText->setPos((8.0 - m_nameText->boundingRect().width() * m_nameText->scale()) * 0.75, /*yy);*/ - notePos() > staff()->upperLinePos() ? - notePos() - (m_nameText->boundingRect().height() + 2.0) * m_nameText->scale(): // above note - notePos() + m_mainNote->boundingRect().height()); // below note - m_nameText->show(); - } else - m_nameText->hide(); + bool setColor = false; + if (!m_nameText) { + m_nameText = new QGraphicsTextItem(); + m_nameText->setDefaultTextColor(m_mainColor); + m_nameText->setParentItem(this); + m_nameText->setZValue(10); + m_nameText->setAcceptHoverEvents(false); + setColor = true; + } + if (dropShadowColor != -1) + setColor = true; + if (setColor) { + QGraphicsDropShadowEffect *dropEff = new QGraphicsDropShadowEffect(); + if (dropShadowColor == -1) + dropEff->setColor(scoreScene()->nameColor()); + else + dropEff->setColor(dropShadowColor); + dropEff->setOffset(0.7, 0.7); + dropEff->setBlurRadius(5.0); + m_nameText->setGraphicsEffect(dropEff); + } + if (m_note->note) { + m_nameText->setHtml(m_note->toRichText()); + m_nameText->setScale(8.0 / m_nameText->boundingRect().height()); + if (m_nameText->boundingRect().width() * m_nameText->scale() > boundingRect().width()) + m_nameText->setScale(boundingRect().width() / (m_nameText->boundingRect().width())); + m_nameText->setPos((8.0 - m_nameText->boundingRect().width() * m_nameText->scale()) * 0.75, /*yy);*/ + notePos() > staff()->upperLinePos() ? + notePos() - (m_nameText->boundingRect().height() + 2.0) * m_nameText->scale(): // above note + notePos() + m_mainNote->boundingRect().height()); // below note + m_nameText->show(); + } else + m_nameText->hide(); } void TscoreNote::removeNoteName() { - delete m_nameText; - m_nameText = nullptr; + delete m_nameText; + m_nameText = 0; } -void TscoreNote::enableNoteAnim(bool enable, int duration) { - if (enable) { - if (!m_noteAnim) { - m_noteAnim = new TcombinedAnim(m_mainNote, this); - m_noteAnim->setDuration(duration); - m_noteAnim->setMoving(mainNote()->pos(), mainNote()->pos()); - m_noteAnim->moving()->setEasingCurveType(QEasingCurve::InExpo); - m_noteAnim->setScaling(1.0, 1.5); - m_noteAnim->scaling()->setEasingCurveType(QEasingCurve::OutQuint); - m_accidAnim = new TcrossFadeTextAnim(m_mainAccid, this); - } - m_accidAnim->setDuration(duration); - } else { - if (m_noteAnim) { - delete m_noteAnim; - m_noteAnim = nullptr; - delete m_accidAnim; - m_accidAnim = nullptr; - } - } +void TscoreNote::setAmbitus(int lo, int hi) { + m_ambitMin = qBound(2, lo, (int)m_height - 3); + m_ambitMax = qBound(2, hi, (int)m_height - 3); } -void TscoreNote::setAmbitus(int lo, int hi) { - m_ambitMin = qBound(2, lo, (int)m_height - 1); - m_ambitMax = qBound(2, hi, (int)m_height - 1); + /** When note duration is longer than shortest rhythm in current staff + * a space (gap) is added to visually represent note duration as a space between notes */ +qreal TscoreNote::rightX() { +// int dur = m_mainNote->rhythm()->duration(); + return x() + m_width + staff()->gapFactor() * space(note()->rtm); +// return x() + m_width + (dur > staff()->shortestRhythm() ? staff()->gapFactor() * (((dur / staff()->shortestRhythm()) - 1.0)) : 0.0); } -QRectF TscoreNote::boundingRect() const{ - return QRectF(0, 0, 7.0, m_height); +void TscoreNote::update() { +// if (note()->rtm != *m_mainNote->rhythm()) +// qDebug() << d(this) << "UPDATE"; + m_mainNote->setRhythm(note()->rtm); + QGraphicsItem::update(); } void TscoreNote::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - if (m_bgColor != -1) { -// paintBackground(painter, m_bgColor); - QPointF center(3.5, m_mainPosY + 1.0); - if (m_mainPosY == 0) - center.setY(staff()->upperLinePos() + 4.0); - QRadialGradient gr(center, 10.0); - QColor c1 = m_bgColor; - c1.setAlpha(40); - QColor c2 = m_bgColor; - c2.setAlpha(80); - gr.setColorAt(0.0, c1); - gr.setColorAt(0.9, c1); - gr.setColorAt(0.95, c2); - gr.setColorAt(1.0, Qt::transparent); - painter->setBrush(gr); - painter->setPen(Qt::NoPen); - painter->drawRect(0.0, qMax(center.y() - 10.0, 0.0), 7.0, qMin(center.y() + 10.0, m_height)); - } - if (scoreScene()->currentNote() == this && m_touchedToMove) { + Q_UNUSED(option) + Q_UNUSED(widget) +// paintBackground(painter, Qt::yellow); + if (m_bgColor != -1) { +// paintBackground(painter, m_bgColor); + QPointF center(m_width / 2.0, m_mainPosY + 1.0); + if (m_mainPosY == 0) + center.setY(staff()->upperLinePos() + 4.0); + QRadialGradient gr(center, 10.0); + QColor c1 = m_bgColor; + c1.setAlpha(40); + QColor c2 = m_bgColor; + c2.setAlpha(80); + gr.setColorAt(0.0, c1); + gr.setColorAt(0.9, c1); + gr.setColorAt(0.95, c2); + gr.setColorAt(1.0, Qt::transparent); + painter->setBrush(gr); painter->setPen(Qt::NoPen); - QColor workBg(scoreScene()->workColor); + painter->drawRect(0.0, qMax(center.y() - 10.0, 0.0), m_width, qMin(center.y() + 10.0, m_height)); + } + // for debug - index number, tie, group number, beam + painter->setPen(Qt::red); + QFont f(qApp->font()); + f.setPointSize(1); + painter->setFont(f); + painter->drawText(QRectF(0.0, 6.0, width(), 1.5), Qt::AlignCenter, QString::number(index())); + painter->drawText(QRectF(0.0, 9.0, width(), 1.5), Qt::AlignCenter, + (beam() ? "B " : "") + QString::number(m_group) + (note()->rtm.stemDown() ? " \\/" : " /\\")); + if (note()->rtm.tie()) { + Trhythm::Etie t = note()->rtm.tie(); + painter->drawText(QRectF(0.0, 7.5, width(), 1.5), Qt::AlignCenter, + QString("T%1").arg(t == Trhythm::e_tieStart ? "s" : (t == Trhythm::e_tieCont ? "c" : "e"))); + } + + if (scoreScene()->currentNote() == this && m_touchedToMove) { + painter->setPen(Qt::NoPen); + QColor workBg(Qt::darkBlue); workBg.setAlpha(20); painter->setBrush(QBrush(workBg)); painter->drawRect(boundingRect()); - } - if (m_emptyLinesVisible && !m_selected && m_mainPosY == 0 && !hasCursor() && !isReadOnly()) { - QColor emptyNoteColor; - if (m_mainNote->pen().style() == Qt::NoPen) - emptyNoteColor = qApp->palette().highlight().color(); - else - emptyNoteColor = m_mainNote->pen().color(); - emptyNoteColor.setAlpha(120); - painter->setPen(QPen(emptyNoteColor, 0.4, Qt::SolidLine, Qt::RoundCap)); - painter->drawLine(QLineF(0.5, staff()->upperLinePos() - 1.0, 6.5, staff()->upperLinePos() - 2.0)); - qreal loLine = staff()->isPianoStaff() ? staff()->lowerLinePos() : staff()->upperLinePos(); - painter->drawLine(QLineF(0.5, loLine + 10.0, 6.5, loLine + 9.0)); - } + } + if (m_emptyLinesVisible && !m_selected && m_mainPosY == 0 && m_mainNote->rhythm()->rhythm() == Trhythm::e_none && !hasCursor()) { + QColor emptyNoteColor; +// if (m_mainNote->pen().style() == Qt::NoPen) // TODO: check it + if (!m_mainNote->graphicsEffect()) + emptyNoteColor = qApp->palette().highlight().color(); + else + emptyNoteColor = m_mainNote->color(); //m_mainNote->pen().color(); + emptyNoteColor.setAlpha(120); + painter->setPen(QPen(emptyNoteColor, 0.4, Qt::SolidLine, Qt::RoundCap)); + painter->drawLine(QLineF(0.5, staff()->upperLinePos() - 1.0, m_width - 0.5, staff()->upperLinePos() - 2.0)); + qreal loLine = staff()->isPianoStaff() ? staff()->lowerLinePos() : staff()->upperLinePos(); + painter->drawLine(QLineF(0.5, loLine + 10.0, m_width - 0.5, loLine + 9.0)); + } } -void TscoreNote::keyAnimFinished() { - if (!m_readOnly) - m_mainAccid->show(); +bool TscoreNote::rhythmChanged() const { + return *m_mainNote->rhythm() != m_note->rtm; } -void TscoreNote::popUpAnim(int durTime) { - if (m_popUpAnim) - return; - m_popUpAnim = new TcombinedAnim(m_emptyText); - m_popUpAnim->setDuration(durTime); - m_popUpAnim->setMoving(QPointF(m_emptyText->x(), -10), m_emptyText->pos()); - connect(m_popUpAnim, SIGNAL(finished()), this, SLOT(popUpAnimFinished())); - m_popUpAnim->startAnimations(); +void TscoreNote::setX(qreal x) { + bool xChanged = QGraphicsItem::x() != x; + QGraphicsItem::setX(x); + if (xChanged) { + if (m_beam && m_beam->last() == this) // when this note is the last one in a beam - update beam + m_beam->drawBeam(); + if (note()->rtm.tie() == Trhythm::e_tieCont || note()->rtm.tie() == Trhythm::e_tieEnd) { + auto prev = prevNote(); + if (prev->tie()) + prev->tie()->updateLength(); + else // TODO: delete it + qDebug() << d(this) << "BOOOOOOOOM! No tie in previous note of" << index(); + } + } } +void TscoreNote::setPos(const QPointF& pos) { + bool xChanged = QGraphicsItem::x() == pos.x(); + QGraphicsItem::setPos(pos); + if (xChanged) { + if (m_beam && m_beam->last() == this) // when this note is the last one in a beam - update beam + m_beam->performBeaming(); + } +} + //################################################################################################# //########################################## PROTECTED ########################################## //################################################################################################# -void TscoreNote::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { -// qDebug() << "hoverEnterEvent"; - scoreScene()->noteEntered(this); - if (!isReadOnly()) { - emit statusTip(m_staticTip + (staff()->selectableNotes() ? m_selectedTip : QString())); - m_emptyText->hide(); - } - TscoreItem::hoverEnterEvent(event); - update(); -} - - -void TscoreNote::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { -// qDebug() << "hoverLeaveEvent"; - hideWorkNote(); - scoreScene()->noteLeaved(this); - TscoreItem::hoverLeaveEvent(event); - checkEmptyText(); - update(); -} - - -/** FIXME: disabling hover under Android removes mouse usage functionality. - * But so far there is no possibility to test it. - * But it solves calling this when touch only is used on real devices. - * So far a reason of calling it is unknown... */ -void TscoreNote::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { -#if defined (Q_OS_ANDROID) - qDebug() << "hoverMoveEvent"; - if (wasTouched()) // It doesn't work here - return; -#else - if ((event->pos().y() >= m_ambitMax) && (event->pos().y() <= m_ambitMin)) { - if (staff()->isPianoStaff() && // dead space between staves - (event->pos().y() >= staff()->upperLinePos() + 10.6) && (event->pos().y() <= staff()->lowerLinePos() - 2.4)) { - hideWorkNote(); - return; - } - if (event->pos().y() != scoreScene()->workPosY()) { - scoreScene()->noteMoved(this, event->pos().y() - 0.6); - } - } -#endif -} - void TscoreNote::mousePressEvent(QGraphicsSceneMouseEvent* event) { - if (scoreScene()->workPosY()) { // edit mode - if (event->button() == Qt::LeftButton) { - m_accidental = scoreScene()->currentAccid(); +/* + if (scoreScene()->workPosY()) { // edit mode + if (event->button() == Qt::LeftButton) { + m_newAccid = scoreScene()->currentAccid(); + m_newPosY = scoreScene()->workRhythm()->isRest() ? 0 : scoreScene()->workPosY(); + qreal widthDiff = 0.0; + if (scoreScene()->isRhythmEnabled()) { + Trhythm::Etie oldTie = note()->rtm.tie(); + if (note()->rtm != *scoreScene()->workRhythm()) { + note()->rtm.setRhythm(*scoreScene()->workRhythm()); + qDebug() << d(this) << "rhythm changed" << rhythmChanged(); + if (!pitchChanged() && !accidChanged()) // copy tie state + note()->rtm.setTie(oldTie); + } else { + note()->rtm.setStemDown(scoreScene()->workRhythm()->stemDown()); + qDebug() << d(this) << "rhythm the same"; + } + qreal newWidth = 4.0; + if (m_newAccid) + newWidth += 2.0; // when removed - width remains smaller + if (newWidth > m_width || newWidth < m_width) { // newWidth != m_width + widthDiff = newWidth - m_width; + qDebug() << d(this) << "Note width differs" << m_width << newWidth << widthDiff; + } + } + if (pitchChanged() || rhythmChanged() || accidChanged() || widthDiff != 0.0) { + if ((pitchChanged() || accidChanged())) { + if (note()->rtm.tie()) { + if (note()->rtm.tie() == Trhythm::e_tieEnd || note()->rtm.tie() == Trhythm::e_tieCont) { + auto prev = prevNote(); + if (prev) + prev->tieRemove(); + } + tieRemove(); + } + } + emit noteGoingToChange(this); + staff()->prepareNoteChange(this); + } +// m_mainNote->setRhythm(*m_newRhythm); + m_accidental = m_newAccid; moveNote(scoreScene()->workPosY()); + changeWidth(); + staff()->onNoteClicked(m_index); emit noteWasClicked(m_index); - if (m_nameText) - showNoteName(); - update(); + if (m_nameText) + showNoteName(); + update(); } else if (event->button() == Qt::RightButton) { - if (!isReadOnly() && staff()->selectableNotes()) { - setBackgroundColor(qApp->palette().highlight().color()); - emit noteWasSelected(m_index); - update(); - } + if (!isReadOnly() && staff()->selectableNotes()) { + setBackgroundColor(qApp->palette().highlight().color()); + emitNoteWasSelected(); + update(); + } } - } else { // read only mode - if (event->button() == Qt::LeftButton) - emit roNoteClicked(this, event->pos()); - else if (event->button() == Qt::RightButton) - emit roNoteSelected(this, event->pos()); - } + } else { // read only mode + if (event->button() == Qt::LeftButton) + emit roNoteClicked(this, event->pos()); + else if (event->button() == Qt::RightButton) + emit roNoteSelected(this, event->pos()); + } +*/ } -void TscoreNote::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { - if (scoreScene()->workPosY()) // edit mode - emit noteWasSelected(m_index); - else // read only mode - emit roNoteSelected(this, event->pos()); -} +//########################################################################################################## +//#################################### PRIVATE ################################################# +//########################################################################################################## -void TscoreNote::touched(const QPointF& scenePos) { - if (m_readOnly) - return; - m_wasTouched = true; - TscoreItem::touched(scenePos); - scoreScene()->noteEntered(this); - m_touchTime.start(); - if (m_touchedToMove) - scoreScene()->hidePanes(); +void TscoreNote::setStringPos() { + if (m_stringText) { + qreal yy = staff()->upperLinePos() + 9.0; // below the staff + if (m_mainPosY > staff()->upperLinePos() + 4.0) + yy = staff()->upperLinePos() - 7.0; // above the staff + m_stringText->setPos(m_width + 0.5 - m_stringText->boundingRect().width() * m_stringText->scale(), yy); + } } -void TscoreNote::touchMove(const QPointF& scenePos) { - if (m_readOnly) - return; - QPointF fingerPos = mapFromScene(scenePos); - if ((fingerPos.y() >= m_ambitMax) && (fingerPos.y() <= m_ambitMin)) { - if (m_touchTime.hasExpired(SHORT_TAP_TIME)) { - if (staff()->isPianoStaff() && // dead space between staves - (fingerPos.y() >= staff()->upperLinePos() + 10.6) && (fingerPos.y() <= staff()->lowerLinePos() - 2.4)) { - hideWorkNote(); - return; - } - scoreScene()->noteMoved(this, fingerPos.y()); - if (!m_touchedToMove) - scoreScene()->hidePanes(); - m_touchedToMove = true; - } +void TscoreNote::checkEmptyText() { + if (!scoreScene()->isRhythmEnabled()) { + if (!isReadOnly() && staff()->selectableNotes() && !m_selected && m_mainPosY == 0) + m_emptyText->show(); + else + m_emptyText->hide(); } } -void TscoreNote::untouched(const QPointF& scenePos) { - if (m_readOnly) { - emit roNoteClicked(this, mapFromScene(scenePos)); - return; +qreal TscoreNote::estimateWidth(const Tnote& n) { + qreal w = 4.0; + if (n.rhythm() == Trhythm::e_none) + w = 7.0; + else { + if (n.alter != 0) + w += 2.0; + if (n.hasDot() || (n.rtm.weight() > 4 && n.rtm.beam() == Trhythm::e_noBeam)) + w += 1.0; } + return w; +} - m_wasTouched = false; - TscoreItem::untouched(scenePos); - if (scenePos.isNull()) { // touch canceled - hideWorkNote(); - scoreScene()->hidePanes(); - return; - } - if (m_touchTime.hasExpired(SHORT_TAP_TIME)) { - scoreScene()->showPanes(); - } else { - if (m_touchedToMove) { // new note was selected aka clicked - m_touchedToMove = false; - QGraphicsSceneMouseEvent me(QEvent::MouseButtonPress); - me.setPos(QPointF(3.0, scoreScene()->workPosY())); // so far @p touchMove was not performed (SHORT_TAP_TIME not expired) - me.setButton(Qt::LeftButton); - mousePressEvent(&me); - } else - emit noteWasSelected(m_index); - } - scoreScene()->noteLeaved(this); +void TscoreNote::emitNoteWasSelected() { + staff()->onNoteSelected(m_index); + emit noteWasSelected(m_index); } -//########################################################################################################## -//#################################### PRIVATE ################################################# -//########################################################################################################## -void TscoreNote::setStringPos() { - if (m_stringText) { - qreal yy = staff()->upperLinePos() + 9.0; // below the staff - if (m_mainPosY > staff()->upperLinePos() + 4.0) - yy = staff()->upperLinePos() - 7.0; // above the staff - m_stringText->setPos(7.5 - m_stringText->boundingRect().width() * m_stringText->scale(), yy); - } +/** + * Note width: + * 4 - base (minimal) width of bare note (no accidental, no flag) + * 5 - eighth/sixteenth flag or a dot + * 6 - when note has accidental (neutral as well) + * 7 - an accidental and a flag or a dot (maximal width) + */ +void TscoreNote::changeWidth() { + qreal oldWidth = m_width; + qreal newWidth = 7.0; + if (scoreScene()->isRhythmEnabled()) { + newWidth = 4.0; + if (!m_mainAccid->text().isEmpty()) { + newWidth += 2.0; + m_mainNote->setX(2.5); + m_lines->setX(2.1); + } else { + m_mainNote->setX(0.5); + m_lines->setX(0.1); + } + if (m_mainNote->rhythm()->weight() > 4 && m_mainNote->rhythm()->beam() == Trhythm::e_noBeam) + newWidth += 1.0; + } + if (newWidth != oldWidth) { + qDebug() << d(this) << "Note changed width" << newWidth; + prepareGeometryChange(); + m_width = newWidth; +// staff()->noteChangedWidth(index()); + } } -void TscoreNote::initNoteCursor() { - scoreScene()->initNoteCursor(this); - hideWorkNote(); +void TscoreNote::tieWithNext() { + if (!m_tie) + m_tie = TscoreTie::check(this); + else + qDebug() << d(this) << "has already tie"; } -void TscoreNote::checkEmptyText() { - if (!isReadOnly() && (!staff()->selectableNotes() || (staff()->selectableNotes() && !m_selected)) && m_mainPosY == 0) - m_emptyText->show(); - else - m_emptyText->hide(); +void TscoreNote::tieRemove() { + if (m_tie) { + delete m_tie; // it will set this note tie to none + m_tie = nullptr; + } } -void TscoreNote::popUpAnimFinished() { - m_popUpAnim->deleteLater(); - m_popUpAnim = 0; -} /* void TscoreNote::checkOctavation() { - bool notPossible = false; - if (pos < m_ambitMax) { - m_ottava = -1; - pos += 7; - if (pos < m_ambitMax) { - m_ottava = -2; - pos += 7; - if (pos < m_ambitMax) - notPossible = true; - } - } else { - m_ottava = 1; - pos -= 7; - if (pos > m_ambitMin) { - m_ottava = 2; - pos -= 7; - if (pos > m_ambitMin) - notPossible = true; - } - } - if (notPossible) { - m_ottava = 0; - m_mainPosY = 0; - hideNote(); - return; - } - qDebug() << "octavation required" << (int)m_ottava; + bool notPossible = false; + if (pos < m_ambitMax) { + m_ottava = -1; + pos += 7; + if (pos < m_ambitMax) { + m_ottava = -2; + pos += 7; + if (pos < m_ambitMax) + notPossible = true; + } + } else { + m_ottava = 1; + pos -= 7; + if (pos > m_ambitMin) { + m_ottava = 2; + pos -= 7; + if (pos > m_ambitMin) + notPossible = true; + } + } + if (notPossible) { + m_ottava = 0; + m_mainPosY = 0; + hideNote(); + return; + } + qDebug() << "octavation required" << (int)m_ottava; } */ diff --git a/src/libs/score/tscorenote.h b/src/libs/score/tscorenote.h index 94c7ffd006e224dbd9583bfe990d3618f15bfe03..ceec86b3dfca73d52f75c9d76fde9a2e8c79b70b 100644 --- a/src/libs/score/tscorenote.h +++ b/src/libs/score/tscorenote.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2016 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -26,170 +26,232 @@ class TscoreLines; class Tnote; -class TnoteControl; -class TcombinedAnim; -class TcrossFadeTextAnim; -class TscoreControl; +class Trhythm; class TscoreScene; +class TnoteItem; +class TscoreBeam; +class TscoreTie; -/*! - * This class represents single note on a score. - * It is a rectangle area over the staff with handling mouse move event to display working note cursor. - * It also grabs wheel event to manipulate accidentals - * It can be set to read-only mode through setReadOnly() then mouse events are ignored. - * When mouse cursor appears over it, @p TscoreNote starts to be a parent for @class TscoreControl - * and it displays panes on the note sides with additional switches (accidentals, name menu, add/delete note) +/** + * This class represents single note on a score. */ class NOOTKACORE_EXPORT TscoreNote : public TscoreItem { + + friend class TscoreMeasure; + friend class TscoreBeam; + friend class TscoreTie; + Q_OBJECT - + public: - TscoreNote(TscoreScene *scene, TscoreStaff *staff, int index); - ~TscoreNote(); - - /** Index of this note instance. It is connected with note number in the list */ - int index() { return m_index; } - void changeIndex(int newIndex) { m_index = newIndex; } - - void setReadOnly(bool ro); - bool isReadOnly() { return m_readOnly; } - - /** Points to Tnote instance. - * TscoreNote is not aware of it. It is just container. - * This value has to be set through setNote() */ - Tnote* note() { return m_note; } - - void adjustSize(); /**< Grabs height from staff and adjust to it. */ - - /** Sets color of main note. */ - void setColor(const QColor& color); - - /** It sets background of the note segment. When sets to -1 means transparent - no background. */ - void setBackgroundColor(QColor bg) { m_bgColor = bg; update(); } - - /** Adds blur effect to main note. If blurColor is -1 deletes the effect. */ - void markNote(QColor blurColor); - - void moveNote(int posY); - void moveWorkNote(const QPointF& newPos); - - /** Returns true if note was selected */ - bool isSelected() { return m_selected; } - void selectNote(bool sel); - - /** Sets note-head at given position and given accidental accidental. Also puts Tnote of it. */ - void setNote(int notePos, int accNr, const Tnote& n); - - /** Returns pointer to main note QGraphicsEllipseItem. */ - QGraphicsEllipseItem* mainNote() { return m_mainNote; } - - /** Returns pointer to main accidental QGraphicsSimpleTextItem. */ - QGraphicsSimpleTextItem *mainAccid() { return m_mainAccid; } - - /** Min and Max values of Y coefficient on the staff */ - void setAmbitus(int lo, int hi); - - /** This return value of -2 is bb, 1 is #, etc... */ - int accidental() {return m_accidental;} - int ottava() { return m_ottava; } /**< NOTE: for this moment it is unused and set to 0 */ - int notePos() { return m_mainPosY; } /** Y Position of note head */ - - static QString getAccid(int accNr); /**< Returns QString with accidental symbol*/ - - /** Prepares note-head (ellipse) */ - static QGraphicsEllipseItem* createNoteHead(QGraphicsItem* parentIt); - - /** It paints string number symbol. Automatically determines above or below staff. */ - void setString(int realNr); - void removeString(); /**< Removes string number */ - int stringNumber() { return m_stringNr; } /**< Number of displayed string or 0 if none. */ - - /** Starts displaying note name of this note. - * Name will change appropriate to moved note until removeNoteName() will be invoked. - * If no color is set the default one defined in TscoreScene will be used. */ - void showNoteName(const QColor& dropShadowColor = -1); - void removeNoteName(); - QGraphicsTextItem* noteName() { return m_nameText; } /**< Graphics item of note name text */ - - /** Enables moving note animation during its position (pitch) change. - * In fact, when accidental is visible it is animated as well. */ - void enableNoteAnim(bool enable, int duration = 150); - bool isNoteAnimEnabled() { return (bool)m_noteAnim; } - - void popUpAnim(int durTime); /**< Performs pop-up animation */ - - /** Defines when lines above and below staff are visible when note is empty. */ - void setEmptyLinesVisible(bool visible) { m_emptyLinesVisible = visible; } - bool emptyLinesVisible() { return m_emptyLinesVisible; } - bool wasTouched() { return m_wasTouched; } /**< @p TRUE during touch only */ - - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); - virtual QRectF boundingRect() const; - + TscoreNote(TscoreScene *scene, TscoreStaff *staff, int index); + ~TscoreNote(); + + /** Index of this note instance. It is connected with note number in the list */ + int index() { return m_index; } + void changeIndex(int newIndex) { m_index = newIndex; } + + void setReadOnly(bool ro); + bool isReadOnly() { return m_readOnly; } + + void adjustSize(); /**< Grabs height from staff and adjust to it. */ + + /** + * Sets color of main note. + */ + void setColor(const QColor& color); + QColor color() const { return m_mainColor; } + + /** It sets background of the note segment. When sets to -1 means transparent - no background. */ + void setBackgroundColor(QColor bg) { m_bgColor = bg; update(); } + + /** Adds blur effect to main note. If blurColor is -1 deletes the effect. */ + void markNote(QColor blurColor); + + void moveNote(int posY); + void moveWorkNote(const QPointF& newPos); + + /** Returns true if note was selected */ + bool isSelected() const { return m_selected; } + void selectNote(bool sel); + + /** Points to Tnote instance. + * TscoreNote is not aware of it. It is just container. + * This value has to be set through setNote() */ + Tnote* note() { return m_note; } + + /** Sets note-head at given position and given accidental accidental. Also puts Tnote of it. */ + void setNote(int notePos, int accNr, const Tnote& n); + + Trhythm* rhythm() const; /**< Returns Trhythm of note */ + void setRhythm(const Trhythm& r); /**< Sets rhythm of note */ + + void setRhythmEnabled(bool rhythmEnabled); /**< Switches rhythm on/off */ + + /** Returns pointer to main note @p TnoteItem. */ + TnoteItem* mainNote() { return m_mainNote; } + + /** A line representing note stem */ + QGraphicsLineItem* stem(); + + /** Returns pointer to main accidental QGraphicsSimpleTextItem. */ + QGraphicsSimpleTextItem *mainAccid() { return m_mainAccid; } + + /** Min and Max values of Y coefficient on the staff */ + void setAmbitus(int lo, int hi); + + /** This return value of -2 is bb, 1 is #, etc... */ + qint8 accidental() const {return m_accidental;} + + /** NOTE: for this moment it is unused and set to 0 */ + qint8 ottava() const { return m_ottava; } + + /** Y Position of note head */ + qint16 notePos() const { return m_mainPosY; } + + /** Position of note after click but before head was moved */ + qint16 newNotePos() const { return m_newPosY; } + + /** Accidental going to be set. */ + qint8 newAccidental() const { return m_newAccid; } + + bool accidChanged() const { return (bool)m_newAccid != (bool)m_accidental; } /**< @p TRUE only when accidental appears or hides */ + bool pitchChanged() const { return m_newPosY != m_mainPosY; } + bool rhythmChanged() const; + + static QString getAccid(int accNr); /**< Returns QString with accidental symbol*/ + + /** + * Pointer to the note after this segment or the first one in the next staff + * or null if no note. + */ + TscoreNote* nextNote(); + + /** + * Pointer to the note before this segment or the last one in the previous staff + * or null if no note. + */ + TscoreNote* prevNote(); + + /** + * Returns space factor after note with given @p r rhythm. + */ + static qreal space(const Trhythm& r); + + /** + * Space factor after this note + */ + qreal space(); + + /** It paints string number symbol. Automatically determines above or below staff. */ + void setString(int realNr); + void removeString(); /**< Removes string number */ + int stringNumber() const { return m_stringNr; } /**< Number of displayed string or 0 if none. */ + + /** Starts displaying note name of this note. + * Name will change appropriate to moved note until removeNoteName() will be invoked. + * If no color is set the default one defined in TscoreScene will be used. */ + void showNoteName(const QColor& dropShadowColor = -1); + void removeNoteName(); + QGraphicsTextItem* noteName() { return m_nameText; } /**< Graphics item of note name text */ + + /** Defines when lines above and below staff are visible when note is empty. */ + void setEmptyLinesVisible(bool visible) { m_emptyLinesVisible = visible; } + bool emptyLinesVisible() const { return m_emptyLinesVisible; } + bool wasTouched() const { return m_wasTouched; } /**< @p TRUE during touch only */ + + /** Overrides standard @p QGraphicsItem::update() method, + * checks are rhythms (@p note()->rtm and @p workNote->rhythm() the same) */ + void update(); + + /** Number of rhythmical group in the measure, -1 (undefined) by default */ + qint8 rhythmGroup() { return m_group; } + void setRhythmGroup(qint8 g) { m_group = g; } + + /** + * Pointer to @p TscoreBeam or null if note has no beam + */ + TscoreBeam* beam() { return m_beam; } + void setBeam(TscoreBeam* b) { m_beam = b; } + + /** Ties this note with the next one (if they are the same) */ + void tieWithNext(); + + /** Pointer to @p TscoreTie (ligature) or null if note has no tie */ + TscoreTie* tie() const { return m_tie; } + + /** Removes the tie of this note. */ + void tieRemove(); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); + virtual QRectF boundingRect() const { return QRectF(0.0, 0.0, m_width, m_height); } + qreal width() const { return m_width; } + qreal height() const { return m_height; } + qreal rightX(); /**< shortcut to X coordinate of right note corner plus gap related to rhythm and staff gap factor */ + qreal estimateWidth(const Tnote& n); /**< Estimate width of the given note @p n */ + + void setX(qreal x); + void setPos(const QPointF &pos); + void setPos(qreal ax, qreal ay) { setPos(QPointF(ax, ay)); } + signals: - void noteWasClicked(int); - void noteWasSelected(int); /**< When right button was clicked. */ - - void toKeyAnim(const QString&, const QPointF&, int notePos); /**< Emitted when accidental has been in key already */ - void fromKeyAnim(const QString&, const QPointF&, int notePos); /**< Emitted when neutral is necessary */ - - void roNoteClicked(TscoreNote*, const QPointF&); /**< Emitted after mouse left click in read only state with clicked position. */ - void roNoteSelected(TscoreNote*, const QPointF&); /**< Emitted after mouse right click or double click in read only state */ - + void noteWasClicked(int); + void noteWasSelected(int); /**< When right button was clicked. */ + + void roNoteClicked(TscoreNote*, const QPointF&); /**< Emitted after mouse left click in read only state with clicked position. */ + void roNoteSelected(TscoreNote*, const QPointF&); /**< Emitted after mouse right click or double click in read only state */ + + void noteGoingToChange(TscoreNote*); /**< Emitted when note was pressed or @p setNote() was invoked */ + public slots: - void keyAnimFinished(); - void hideNote(); /**< Hides main note */ - void hideWorkNote(); /**< Hides pointing (work) note */ + void hideNote(); /**< Hides main note */ protected: - virtual void touched(const QPointF& scenePos); - virtual void untouched(const QPointF& scenePos); - virtual void touchMove(const QPointF& scenePos); - - virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event); - - virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); - virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); - virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event); + + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); private: - QGraphicsEllipseItem *m_mainNote; - QGraphicsSimpleTextItem *m_mainAccid; - QColor m_mainColor; - TcrossFadeTextAnim *m_accidAnim; - Tnote *m_note; - - int m_mainPosY, m_accidental; - int m_index; /**< note index in external list */ -// int m_noteNr; // note number depends on octave - int m_ambitMin, m_ambitMax; /**< Represents range (ambitus) of notes on score */ - int m_stringNr; - QGraphicsSimpleTextItem *m_stringText; - qreal m_height; - bool m_readOnly, m_emptyLinesVisible; - QGraphicsTextItem *m_nameText; - int m_ottava; /**< values from -2 (two octaves down), to 2 (two octaves up) */ - QColor m_bgColor; - TcombinedAnim *m_noteAnim, *m_popUpAnim; - QGraphicsSimpleTextItem *m_emptyText; - bool m_selected; - TscoreLines *m_lines; - - bool m_touchedToMove; /**< Determines whether cursor follows moving finger */ - bool m_wasTouched; - static QString m_staticTip; - QElapsedTimer m_touchTime; - + TnoteItem *m_mainNote; + QGraphicsSimpleTextItem *m_mainAccid; + QColor m_mainColor; + Tnote *m_note; + + qint16 m_mainPosY, m_newPosY; + qint8 m_accidental, m_newAccid; + int m_index; /**< note index in external list */ +// int m_noteNr; // note number depends on octave + quint16 m_ambitMin, m_ambitMax; /**< Represents range (ambitus) of notes on score */ + quint8 m_stringNr; + QGraphicsSimpleTextItem *m_stringText; + qreal m_width, m_height; + bool m_readOnly, m_emptyLinesVisible; + QGraphicsTextItem *m_nameText; + qint8 m_ottava; /**< values from -2 (two octaves down), to 2 (two octaves up) */ + QColor m_bgColor; + QGraphicsSimpleTextItem *m_emptyText; + bool m_selected; + TscoreLines *m_lines; + + bool m_touchedToMove; /**< Determines whether cursor follows moving finger */ + bool m_wasTouched; + static QString m_staticTip, m_selectedTip; + QElapsedTimer m_touchTime; + + qint8 m_group = -1; + TscoreBeam *m_beam = nullptr; + TscoreTie *m_tie = nullptr; + + static const qreal m_rtmGapArray[5][3]; /**< static array with space definitions for each rhythm value */ + private: - void setStringPos(); /**< Determines and set string number position (above or below the staff) depends on note position */ - void initNoteCursor(); /**< Creates note cursor when first TscoreNote instance is created and there is a view */ - void checkEmptyText(); /**< Decides whether show or hide text about empty note. */ - -private slots: - void popUpAnimFinished(); + void setStringPos(); /**< Determines and set string number position (above or below the staff) depends on note position */ + void initNoteCursor(); /**< Creates note cursor when first TscoreNote instance is created and there is a view */ + void checkEmptyText(); /**< Decides whether show or hide text about empty note. */ + void changeWidth(); /**< Changes bounding rectangle width appropriate to current accidental and rhythm */ + void emitNoteWasSelected(); + }; #endif // TSCORENOTE_H diff --git a/src/libs/score/tscorescene.cpp b/src/libs/score/tscorescene.cpp index 4b1fbf31c6edbf77057f327ac72fefa812cb59c2..0e2768d2c9e63f0fbdeefb25d205f0864d676ce7 100644 --- a/src/libs/score/tscorescene.cpp +++ b/src/libs/score/tscorescene.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2015 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -17,68 +17,45 @@ ***************************************************************************/ #include "tscorescene.h" -#include "tnotecontrol.h" -#include "tscorestaff.h" +#include "tscoremeter.h" +#include "tscoremeasure.h" #include <graphics/tdropshadoweffect.h> #include <tnoofont.h> -#include <QGraphicsView> -#include <QGraphicsEffect> -#include <QApplication> -#include <QTimer> -#include <QDebug> +#include <music/trhythm.h> +#include <music/tmeter.h> +#include <QtWidgets/qgraphicseffect.h> +#include <QtWidgets/qapplication.h> +#include <QtCore/qdebug.h> #define WORK_HIDE_DELAY (5000) +#define REST_Y (19.0) +#define WORK_X (1.2) // x coordinate of the cursor note head TscoreScene::TscoreScene(QObject* parent) : QGraphicsScene(parent), - m_workPosY(0), - m_workNote(0), - m_workAccid(0), m_workAccid2(0), m_nameColor(Qt::darkCyan), - m_rightBox(0), m_leftBox(0), m_accidYoffset(0.0), m_accidScale(-1.0), m_scoreNote(0), - m_controlledNotes(true), - m_mouseOverKey(false), m_rectIsChanging(false) + m_scoreMeter(nullptr) { - m_showTimer = new QTimer(this); - m_hideTimer = new QTimer(this); setDoubleAccidsEnabled(true); m_currentAccid = 0; - - connect(m_showTimer, SIGNAL(timeout()), this, SLOT(showTimeOut())); - connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideTimeOut())); + } -TscoreScene::~TscoreScene() +TscoreScene::~TscoreScene() { - if (m_rightBox) { // all items are into scene so they will be deleted - delete m_rightBox; // but the last TscoreNote has to skip deleting depending items itself - m_rightBox = 0; - } + delete m_scoreMeter; } void TscoreScene::setCurrentAccid(char accid) { - char prevAcc = m_currentAccid; - m_currentAccid = char(qBound(int(-m_dblAccFuse), int(accid), int(m_dblAccFuse))); - if (m_workAccid && prevAcc != m_currentAccid) { - m_workAccid->setText(TscoreNote::getAccid(m_currentAccid)); -// if (m_workAccid2) -// m_workAccid2->setText(TscoreNote::getAccid(m_currentAccid)); - if (m_currentAccid == 0) - m_workAccid->hide(); - else - m_workAccid->show(); - if (m_leftBox) - m_leftBox->setAccidental(m_currentAccid); - if (m_hideTimer->isActive()) - m_hideTimer->start(1000); - } + char prevAcc = m_currentAccid; + m_currentAccid = (char)qBound((int)-m_dblAccFuse, (int)accid, (int)m_dblAccFuse); } @@ -92,209 +69,16 @@ void TscoreScene::setDoubleAccidsEnabled(bool enable) { void TscoreScene::addBlur(QGraphicsItem* item, qreal radius) { QGraphicsBlurEffect *blur = new QGraphicsBlurEffect(); - blur->setBlurRadius(radius / views()[0]->transform().m11()); +// blur->setBlurRadius(radius / views()[0]->transform().m11()); item->setGraphicsEffect(blur); } -void TscoreScene::adjustCursor(TscoreNote* sn) { - if (m_rightBox && !views().isEmpty()) { - m_rightBox->adjustSize(); - m_leftBox->adjustSize(); - workLines()->adjustLines(sn); - setPointedColor(workColor); - } -} - - -void TscoreScene::setPointedColor(const QColor& color) { - workColor = color; - m_workNote->setPen(QPen(workColor, 0.2)); - m_workNote->setBrush(QBrush(workColor, Qt::SolidPattern)); - m_workAccid->setBrush(QBrush(workColor)); -// if (m_workAccid2) -// m_workAccid2->setBrush(QBrush(workColor)); - m_workLines->setColor(color); -} - -//########################################################################################## -//####################### Note CURSOR ########################################### -//########################################################################################## - -void TscoreScene::noteEntered(TscoreNote* sn) { - m_hideTimer->stop(); - if (!m_rectIsChanging && sn != m_scoreNote && sn != 0) { - m_scoreNote = sn; - if (controlledNotes()) { - if (right()->isEnabled()) { - if (sn->index() < sn->staff()->maxNoteCount() - 1) - right()->setPos(sn->pos().x() + sn->boundingRect().width(), (sn->parentItem()->boundingRect().height() - right()->boundingRect().height() + 6.0) / 2.0); - else // Put right pane on the left if the last note on the staff - right()->setPos(sn->pos().x() - right()->boundingRect().width(), (sn->parentItem()->boundingRect().height() - right()->boundingRect().height() + 6.0) / 2.0); - right()->setScoreNote(sn); - } - if (left()->isEnabled()) { - if (sn->index() < sn->staff()->maxNoteCount() - 1) - left()->setPos(sn->pos().x() - left()->boundingRect().width(), (sn->parentItem()->boundingRect().height() - right()->boundingRect().height() + 6.0) / 2.0); - else - left()->setPos(sn->pos().x() - left()->boundingRect().width() - (right()->isEnabled() ? right()->boundingRect().width() : 0.0), - (sn->parentItem()->boundingRect().height() - right()->boundingRect().height() + 6.0) / 2.0); - left()->setScoreNote(sn); - } - } - if (workNote()->parentItem() != sn) - setCursorParent(sn); - } -} - - -void TscoreScene::noteMoved(TscoreNote* sn, int yPos) { - if (!m_rectIsChanging) { - setWorkPosY(yPos); - workNote()->setPos(3.0, workPosY()); - workLines()->checkLines(yPos); - if (!workNote()->isVisible()) - showTimeOut(); - if (sn != m_scoreNote) { - noteEntered(sn); - if (TscoreItem::touchEnabled()) - m_workNote->show(); // show note immediately when touched - else - m_showTimer->start(300); // show with delay when mouse over to avoid flickering - } else { - m_hideTimer->start(WORK_HIDE_DELAY); - } - } -} - - -void TscoreScene::noteLeaved(TscoreNote* sn) { - Q_UNUSED(sn) - if (!m_rectIsChanging) { - m_showTimer->stop(); - m_hideTimer->start(TscoreItem::touchEnabled() ? 2000 : 300); - } -} - - -void TscoreScene::noteDeleted(TscoreNote* sn) { - if (right() && (workNote()->parentItem() == sn || right()->parentItem() == sn->parentItem())) { - right()->setScoreNote(0); - left()->setScoreNote(0); - setCursorParent(0); - hideTimeOut(); - statusTipChanged(""); // hide status tip of deleting note - } - m_scoreNote = 0; -} - - -void TscoreScene::controlMoved() { - m_hideTimer->start(WORK_HIDE_DELAY); -} - - -void TscoreScene::prepareToChangeRect() { - m_rectIsChanging = true; - hideTimeOut(); +void TscoreScene::setScoreMeter(TscoreMeter* m) { + m_scoreMeter = m; } -void TscoreScene::restoreAfterRectChange() { - m_rectIsChanging = false; -} - - -//########################################################################################## -//####################### PROTECTED ########################################### -//########################################################################################## - -void TscoreScene::initNoteCursor(TscoreNote* scoreNote) { - if (!m_workNote) { - m_workLines = new TscoreLines(scoreNote); - workColor = qApp->palette().highlight().color(); - workColor.setAlpha(200); - m_workNote = TscoreNote::createNoteHead(scoreNote); -// if (TscoreNote::touchEnabled()) -// m_workNote->setRect(-10.5, 0, 22, 2); // long ellipse for touch screens visible under finger -// else - m_workNote->setRect(0, 0, 3.5, 2); // normal note head - QGraphicsDropShadowEffect *workEffect = new QGraphicsDropShadowEffect(); - workEffect->setOffset(3.0, 3.0); - workEffect->setBlurRadius(15); - workEffect->setColor(qApp->palette().text().color()); - m_workNote->setGraphicsEffect(workEffect); - m_workNote->setZValue(35); - m_workAccid = new QGraphicsSimpleTextItem(); - m_workAccid->setBrush(QBrush(workColor)); - m_workAccid->setParentItem(m_workNote); - m_workAccid->setFont(TnooFont(5)); - m_workAccid->setScale(accidScale()); -// if (TscoreNote::touchEnabled()) -// m_workAccid->setPos(-13.5, - accidYoffset()); -// else - m_workAccid->setPos(-3.0, - accidYoffset()); -// if (TscoreNote::touchEnabled()) { // two the same accidentals on long note sides -// m_workAccid2 = new QGraphicsSimpleTextItem(); -// m_workAccid2->setBrush(QBrush(workColor)); -// m_workAccid2->setParentItem(m_workAccid); -// m_workAccid2->setFont(TnooFont(5)); -// m_workAccid2->setPos(21, 0); -// } - m_workAccid->hide(); - setPointedColor(workColor); - - m_rightBox = new TnoteControl(false, scoreNote->staff(), this); - m_leftBox = new TnoteControl(true, scoreNote->staff(), this); - m_leftBox->addAccidentals(); - } -} - - -void TscoreScene::setCursorParent(TscoreNote* sn) { - workNote()->setParentItem(sn); - m_workLines->setParent(sn); -} - - -//########################################################################################## -//####################### PROTECTED SLOTS ########################################### -//########################################################################################## - -void TscoreScene::showPanes() { - if (left()->isEnabled()) - left()->show(); - if (right()->isEnabled()) - right()->show(); -} - - -void TscoreScene::hidePanes() { - if (left()->isEnabled()) - left()->hide(); - if (right()->isEnabled()) - right()->hide(); -} - - -void TscoreScene::showTimeOut() { - m_showTimer->stop(); - m_workNote->show(); - showPanes(); -} - - -void TscoreScene::hideTimeOut() { - m_hideTimer->stop(); - if (m_scoreNote) - m_scoreNote->hideWorkNote(); - hidePanes(); - TscoreNote *sn = m_scoreNote; - m_scoreNote = 0; - if (TscoreItem::touchEnabled() && sn) - sn->update(); -} - diff --git a/src/libs/score/tscorescene.h b/src/libs/score/tscorescene.h index 70e2cd59ad5ea08211f236cc1fd9aa674f8d6d2c..22e0b04f90c2135f7b9f3f5ef4c26a194e18e8c5 100644 --- a/src/libs/score/tscorescene.h +++ b/src/libs/score/tscorescene.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2015 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -20,138 +20,81 @@ #define TSCORESCENE_H #include <nootkacoreglobal.h> -#include <QGraphicsScene> #include "tscorenote.h" +#include "tnoteitem.h" #include "tscorelines.h" +#include <QtWidgets/qgraphicsscene.h> + -class QTimer; -class TnoteControl; class QGraphicsItem; +class TscoreMeter; +class Tmeter; -/** +/** * This is subclass of QGraphicsScene that handles additional operations of Nootka score. - * - * It manages a 'note cursor' - note shape under mouse and 'note controllers' - panes on the sides of a note. - * Three methods are responsible for smart displaying cursor/panes: - * - @p noteMoved() - invokes timer to display cursor when mouse stays over a note segment - * and timer to hide it when cursor stops over the segment - * - @p noteLeaved() - invokes timer to hide cursor when mouse leaves the segment - * - @p controlEntered() - to stop timers and keep cursor and pane visible - * - @p controlLeaved() - to hide (the same as @p noteLeaved()) */ class NOOTKACORE_EXPORT TscoreScene : public QGraphicsScene { - - friend class TscoreNote; - + + friend class TscoreNote; + friend class TscoreStaff; + Q_OBJECT - + public: - TscoreScene(QObject* parent = 0); - virtual ~TscoreScene(); - - - void setDoubleAccidsEnabled(bool enable); - - /** Returns value 2 when double accidentals are enabled and 1 if not. */ - qint8 doubleAccidsFuse() { return m_dblAccFuse; } - - - void setCurrentAccid(char accid); /** Working accidental in also changed by buttons. */ - char currentAccid() { return m_currentAccid; } - - /** Adds blur graphics effect. In the contrary to QGraphicsItem::setGraphicsEffect() - * a radius value in global scale. */ - void addBlur(QGraphicsItem *item, qreal radius); - - /** Adjust note cursor and TnoteControl to new staff size. - * For performance reason it has to be called once for all adjustSize() of TscoreNote - * because there is only one instance of note cursor and TnoteControl */ - void adjustCursor(TscoreNote* sn); - bool isCursorVisible() { return m_workNote->isVisible(); } - - /** Note controllers, appear with cursor. - * There are automatically created with first note instance - * when score scene has a view. */ - TnoteControl* right() { return m_rightBox; } - TnoteControl* left() { return m_leftBox; } - void setNameColor(const QColor& nameC) { m_nameColor = nameC; } - QColor nameColor() { return m_nameColor; } - - /** Sets color of pointing (work) note. */ - void setPointedColor(const QColor& color); - - qreal accidYoffset() { return m_accidYoffset; } /** Y offset of accidental item */ - qreal accidScale() { return m_accidScale; } /** Scale of accidental text item */ - - bool isAccidAnimated() const { return m_accidAnimated; } /** Whether note accidental to key signature animations are allowed */ - void enableAccidsAnimation(bool anim) { m_accidAnimated = anim; } /** Sets state of note accidental to key signature animations */ - - /** Additional note controls are displayed when note gets cursor. - This is default behavior and without those controls accidentals can not be managed with wheel. */ - void setControlledNotes(bool controlled) { m_controlledNotes = controlled; } - bool controlledNotes() { return m_controlledNotes; } - - void noteEntered(TscoreNote* sn); /** Prepares note cursor. From @p TscoreNote::hoverEnterEvent() */ - void noteMoved(TscoreNote* sn, int yPos); /** Starts show timer if hidden or restarts hide timer. From @p TscoreNote::hoverMoveEvent() */ - void noteLeaved(TscoreNote* sn); /** Starts hide timer. From @p TscoreNote::hoverLeaveEvent() */ - void noteDeleted(TscoreNote* sn); /** From @p TscoreNote::~TscoreNote() */ - void controlMoved(); /** Restarts hide timer. From @p TnoteControl::hoverMoveEvent()*/ - void controlLeaved(TscoreNote* sn) { noteLeaved(sn); } /** From @p TnoteControl::hoverLeaveEvent */ - TscoreNote* currentNote() { return m_scoreNote; } - - void mouseEntersOnKey(bool onKey) { m_mouseOverKey = onKey; } /** score key informs that has or has not a mouse cursor */ - bool keyHasMouse() { return m_mouseOverKey; } - - void prepareToChangeRect(); /** It has to be invoked whenever score rectangle is going to change. I.e. by resize event of a view. */ - void restoreAfterRectChange(); /** Scene will try to adjust itself to new size. */ - - /** Sets note cursor parent item to 0 */ - void releaseNoteCursor() { setCursorParent(0); } - + TscoreScene(QObject* parent = 0); + virtual ~TscoreScene(); + + + void setDoubleAccidsEnabled(bool enable); + /** Returns value 2 when double accidentals are enabled and 1 if not. */ + qint8 doubleAccidsFuse() { return m_dblAccFuse; } + + void setCurrentAccid(char accid); /**< Working accidental in also changed by buttons. */ + char currentAccid() { return m_currentAccid; } + + /** Adds blur graphics effect. In the contrary to QGraphicsItem::setGraphicsEffect() + * a radius value in global scale. */ + void addBlur(QGraphicsItem *item, qreal radius); + + void setNameColor(const QColor& nameC) { m_nameColor = nameC; } + QColor nameColor() { return m_nameColor; } + + qreal accidYoffset() { return m_accidYoffset; } /**< Y offset of accidental item */ + qreal accidScale() { return m_accidScale; } /**< Scale of accidental text item */ + + bool isRhythmEnabled() { return (bool)m_scoreMeter; } /**< @p TRUE when score has rhythm enabled. */ + TscoreMeter* scoreMeter() { return m_scoreMeter; } /**< Score meter - if it is @p nullptr - there is no rhythms */ + + void noteDeleted(TscoreNote* sn); /**< From @p TscoreNote::~TscoreNote() */ + TscoreNote* currentNote() { return m_scoreNote; } + signals: - void statusTip(QString); - + void statusTip(QString); + protected: - // note cursor - QColor workColor; - int workPosY() { return m_workPosY; } - void setWorkPosY(int wpY) { m_workPosY = wpY; } - QGraphicsEllipseItem* workNote() { return m_workNote; } - TscoreLines* workLines() { return m_workLines; } - void initNoteCursor(TscoreNote* parentIt); - QGraphicsSimpleTextItem* workAccid() { return m_workAccid; } - void setAccidYoffset(qreal aYo) { m_accidYoffset = aYo; } - void setAccidScale(qreal as) { m_accidScale = as; } - void setCursorParent(TscoreNote* sn); /** Sets parent of note cursor to this instance */ - - +// note cursor + void setAccidYoffset(qreal aYo) { m_accidYoffset = aYo; } + void setAccidScale(qreal as) { m_accidScale = as; } + + /** Sets meter, enables rhythms if meter is valid or disables rhythm if it is @p nullptr. + * This is global meter for all score items and it is managed through @class TscoreStaff */ + void setScoreMeter(TscoreMeter* m); + + protected slots: - void statusTipChanged(QString status) { emit statusTip(status); } - void showTimeOut(); - void hideTimeOut(); - void showPanes(); - void hidePanes(); - + void statusTipChanged(QString status) { emit statusTip(status); } + private: - /** It is @p 2 if double accidentals are enabled and @p 1 if not*/ - qint8 m_dblAccFuse; - char m_currentAccid; - // note cursor - int m_workPosY; - QGraphicsEllipseItem *m_workNote; - QGraphicsSimpleTextItem *m_workAccid, *m_workAccid2; - TscoreLines *m_workLines; - QColor m_nameColor; - TnoteControl *m_rightBox, *m_leftBox; - qreal m_accidYoffset; /** difference between y note position. */ - qreal m_accidScale; - QTimer *m_showTimer, *m_hideTimer; - TscoreNote *m_scoreNote; /** current note segment or NULL. */ - bool m_controlledNotes; - bool m_mouseOverKey, m_rectIsChanging; - bool m_accidAnimated; + /** It is @p 2 if double accidentals are enabled and @p 1 if not*/ + qint8 m_dblAccFuse; + char m_currentAccid; + QColor m_nameColor; + qreal m_accidYoffset; /**< difference between y note position. */ + qreal m_accidScale; + TscoreNote *m_scoreNote; /**< current note segment or NULL. */ + TscoreMeter *m_scoreMeter; }; #endif // TSCORESCENE_H diff --git a/src/libs/score/tscorestaff.cpp b/src/libs/score/tscorestaff.cpp index b51d45b4a8ff930d0e6be5e3cf9a01647f5a4f7b..baa2fb3a4a0f21e7f26fb8ce7fd8830ff2214b15 100755 --- a/src/libs/score/tscorestaff.cpp +++ b/src/libs/score/tscorestaff.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2015 by Tomasz Bojczuk * + * Copyright (C) 2013-2016 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -24,314 +24,383 @@ #include "tscorescordature.h" #include "tnotecontrol.h" #include "tscore5lines.h" +#include "tscoremeter.h" +#include "tscoremeasure.h" +#include "tscoretie.h" #include <music/tnote.h> -#include <animations/tcombinedanim.h> +#include <music/trhythm.h> +#include <music/tmeter.h> #include <tnoofont.h> -#include <QApplication> -#include <QGraphicsView> -#include <QPalette> - -#include <QDebug> - - -TnoteOffset::TnoteOffset(int noteOff, int octaveOff) : - note(noteOff), - octave(octaveOff) -{} - +#include <QtWidgets/qapplication.h> +#include <QtWidgets/qgraphicsview.h> +#include <QtGui/qpalette.h> + +#include <QtCore/qdebug.h> + + +void content(TscoreStaff* st) { + QString c; + for (int i = 0; i < st->count(); ++i) { + c += QStringLiteral("<"); + if (st->noteSegment(i)->note()->isValid() && !st->noteSegment(i)->note()->isRest()) + c += st->noteSegment(i)->note()->toText(); + else + c += QLatin1String("r"); + c += QLatin1String(">"); + c += QString("%1%2").arg(st->noteSegment(i)->note()->weight()).arg(st->noteSegment(i)->note()->hasDot() ? QStringLiteral(".") : QString()); + c += QString("(%1) ").arg(st->noteSegment(i)->rhythmGroup()); + } + qDebug() << st->debug() << c; +} TscoreStaff::TscoreStaff(TscoreScene* scene, int notesNr) : TscoreItem(scene), m_staffNr(-1), m_brace(0), - m_keySignature(0), + m_keySignature(nullptr), m_upperLinePos(16.0), m_lowerStaffPos(0.0), - m_height(36.0), + m_height(38.0), m_viewWidth(0.0), m_offset(TnoteOffset(3, 2)), m_isPianoStaff(false), - m_scordature(0), m_enableScord(false), m_tidyKey(false), - m_accidAnim(0), m_flyAccid(0), - m_selectableNotes(false), m_extraAccids(false), - m_maxNotesCount(0), - m_loNotePos(28.0), m_hiNotePos(12.0), - m_lockRangeCheck(false), m_autoAddedNoteId(-1) + m_scordature(0), m_enableScord(false), m_tidyKey(false), + m_selectableNotes(false), m_extraAccids(false), + m_maxNotesCount(0), + m_loNotePos(28.0), m_hiNotePos(12.0), + m_allNotesWidth(0.0), + m_gapFactor(1.0), + m_shortestR(12), + m_lockRangeCheck(false), m_autoAddedNoteId(-1), + m_scoreMeter(nullptr) { - setFlag(QGraphicsItem::ItemHasNoContents); - setZValue(10); + setFlag(QGraphicsItem::ItemHasNoContents); + setZValue(10); setAcceptHoverEvents(true); // Clef Tclef cl = Tclef(); m_clef = new TscoreClef(scene, this, cl); connect(m_clef, SIGNAL(clefChanged(Tclef)), this, SLOT(onClefChanged(Tclef))); - m_clef->setZValue(29); // lower than key signature (if any) + m_clef->setZValue(29); // lower than key signature (if any) + m_measures << new TscoreMeasure(this, 0); // Notes for (int i = 0; i < notesNr; i++) { m_scoreNotes << new TscoreNote(scene, this, i); - m_scoreNotes[i]->setPos(7.0 + i * m_scoreNotes[i]->boundingRect().width(), 0); - m_scoreNotes[i]->setZValue(50); - connectNote(m_scoreNotes[i]); + m_scoreNotes[i]->setPos(7.0 + i * m_scoreNotes[i]->boundingRect().width(), 0.0); + m_scoreNotes[i]->setZValue(50); + measures().last()->insertNote(i, m_scoreNotes[i]); } - + // Staff lines, it also sets m_width of staff m_5lines = new Tscore5lines(scoreScene()); m_5lines->setParentItem(this); - prepareStaffLines(); - + prepareStaffLines(); + for (int i = 0; i < 7; i++) // reset accidentals array accidInKeyArray[i] = 0; -// Timer controlling automatic note adding to this staff - m_addTimer = new QTimer(this); - m_addTimer->setSingleShot(true); - connect(m_addTimer, SIGNAL(timeout()), this, SLOT(addNoteTimeOut())); } TscoreStaff::~TscoreStaff() { - if (scoreScene()->right() && scoreScene()->right()->parentItem() == this) { - scoreScene()->right()->setParentItem(0); - scoreScene()->left()->setParentItem(0); - } + m_goingDelete = true; + int measuresCnt = m_measures.count(); + for (int m = 0; m < measuresCnt; ++m) + delete m_measures.takeLast(); + int notesCnt = count(); + for (int n = 0; n < notesCnt; ++n) + delete m_scoreNotes.takeLast(); + if (m_scoreMeter) + setMeter(Tmeter()); } //#################################################################################################### //########################################## PUBLIC ################################################## //#################################################################################################### -int TscoreStaff::noteToPos(const Tnote& note) { - int nPos = m_offset.octave * 7 + m_offset.note + upperLinePos() - 1 - (note.octave * 7 + (note.note - 1)); - if (isPianoStaff() && nPos > lowerLinePos() - 5) - return nPos + 2; - else - return nPos; +int TscoreStaff::noteToPos(const Tnote& note) { + int nPos = m_offset.octave * 7 + m_offset.note + upperLinePos() - 1 - (note.octave * 7 + (note.note - 1)); + if (isPianoStaff() && nPos > lowerLinePos() - 5) + return nPos + 2; + else + return nPos; } - /** Calculation of note position works As folow: - * 1) expr: m_offset.octave * 7 + m_offset.note + upperLinePos() - 1 returns y position of note C in offset octave - * 2) (note.octave * 7 + (note.note - 1)) is number of note to be set. - * 3) Subtraction of them gives position of the note on staff with current clef and it is displayed - * when this value is in staff scale. */ + /** Calculation of note position works As folow: + * 1) expr: m_offset.octave * 7 + m_offset.note + upperLinePos() - 1 returns y position of note C in offset octave + * 2) (note.octave * 7 + (note.note - 1)) is number of note to be set. + * 3) Subtraction of them gives position of the note on staff with current clef and it is displayed + * when this value is in staff scale. */ void TscoreStaff::setNote(int index, const Tnote& note) { - if (index >= 0 && index < m_scoreNotes.size()) { - Tnote prevNote = *getNote(index); - if (note.note) - m_scoreNotes[index]->setNote(noteToPos(note), (int)note.alter, note); - else - m_scoreNotes[index]->setNote(0, 0, note); - if (prevNote != note) // check it only when note was really changed - checkNoteRange(); - } + if (index >= 0 && index < m_scoreNotes.size()) { + Tnote prevNote = *getNote(index); + if (note.isValid()) + m_scoreNotes[index]->setNote(noteToPos(note), (int)note.alter, note); + else + m_scoreNotes[index]->setNote(0, 0, note); + if (prevNote != note) // check it only when note was really changed + checkNoteRange(); + } } Tnote* TscoreStaff::getNote(int index) { - return m_scoreNotes[index]->note(); -} - - -void TscoreStaff::insertNote(int index, const Tnote& note, bool disabled) { - if (m_autoAddedNoteId > -1) // naughty user can insert or add new note just after clicking the last one what invokes auto adding - addNoteTimeOut(); - index = qBound(0, index, m_scoreNotes.size()); // 0 - adds at the begin, size() - adds at the end - insert(index); - setNote(index, note); - m_scoreNotes[index]->setZValue(50); - setNoteDisabled(index, disabled); - if (number() > -1) { - emit noteIsAdding(number(), index); - if (maxNoteCount()) { - if (count() > maxNoteCount()) { - m_scoreNotes.last()->disconnect(SIGNAL(noteWasClicked(int))); - m_scoreNotes.last()->disconnect(SIGNAL(noteWasSelected(int))); - m_scoreNotes.last()->disconnect(SIGNAL(toKeyAnim(QString,QPointF,int))); - m_scoreNotes.last()->disconnect(SIGNAL(fromKeyAnim(QString,QPointF,int))); - m_scoreNotes.last()->disconnect(SIGNAL(destroyed(QObject*))); - emit noteToMove(number(), m_scoreNotes.takeLast()); - checkNoteRange(); // find range again - } else if (count() == maxNoteCount()) - emit noMoreSpace(number()); - } - } - updateIndexes(); - updateNotesPos(index); - if (number() == -1) { - updateLines(); - updateSceneRect(); // Update only for single staff view - } + return m_scoreNotes[index]->note(); +} + + +TscoreNote* TscoreStaff::insertNote(int index, const Tnote& note, bool disabled) { + if (m_autoAddedNoteId > -1) // naughty user can insert or add new note just after clicking the last one what invokes auto adding + addNoteTimeOut(); + + qDebug() << "\n"; + + //TODO: check is inserting note fit into a measure: + // - split it if it is longer, then shift the next measure + + int measureNr = m_measures.count() - 1; // add to the last measure by default + if (index < count()) + measureNr = measureOfNoteId(index); + if (measureNr < 0) { + qDebug() << debug() << "Not such a measure for index," << index << " inserting to the last measure"; + measureNr = m_measures.count() - 1; + } + auto inserted = insertNote(note, index, disabled); + + qDebug() << debug() << "--> inserting note" << (inserted->note()->isValid() ? inserted->note()->toText() : "rest") + << inserted->note()->rtm.xmlType() << (inserted->note()->hasDot() ? "." : QString()) + << inserted->rhythm()->xmlType() + << "to measure" << measureNr; + m_measures[measureNr]->insertNote(index - m_measures[measureNr]->firstNoteId(), inserted); + + if (number() > -1) { + emit noteIsAdding(number(), inserted->index()); + } + + fit(); + updateNotesPos(/*index*/); + if (number() == -1) { + updateLines(); + updateSceneRect(); // Update only for single staff view + } + return inserted; } void TscoreStaff::insertNote(int index, bool disabled) { - insertNote(index, Tnote(0, 0, 0), disabled); + insertNote(index, Tnote(), disabled); } -void TscoreStaff::addNote(Tnote& note, bool disabled) { - insertNote(m_scoreNotes.size(), note, disabled); +void TscoreStaff::addNote(const Tnote& note, bool disabled) { + insertNote(m_scoreNotes.size(), note, disabled); } void TscoreStaff::removeNote(int index) { - if (index >= 0 && index < count()) { - emit noteIsRemoving(number(), index); - if (m_autoAddedNoteId > -1) { - if (index == m_autoAddedNoteId) // just automatically added note deleted by user - m_autoAddedNoteId = -1; - else - m_autoAddedNoteId--; - } - delete m_scoreNotes[index]; - m_scoreNotes.removeAt(index); - if (maxNoteCount() > count()) - emit freeSpace(number(), 1); - updateIndexes(); - updateNotesPos(index); - for (int i = index; i < count(); ++i) // refresh neutrals in all next notes + if (index >= 0 && index < count()) { + emit noteIsRemoving(number(), index); + if (m_autoAddedNoteId > -1) { + if (index == m_autoAddedNoteId) // just automatically added note deleted by user + m_autoAddedNoteId = -1; + else + m_autoAddedNoteId--; + } + auto removed = m_scoreNotes[index]; + qDebug() << debug() << "Preparing to remove note nr" << index << removed->note()->toText() << removed->note()->rtm.string(); + content(this); + int measureNr = measureOfNoteId(index); + if (measureNr > -1) { + m_measures[measureNr]->removeNote(index - m_measures[measureNr]->firstNoteId()); + } else + qDebug() << debug() << "not such a measure for note with index" << index; +// m_allNotesWidth -= removed->width(); + qDebug() << debug() << "After measure routines index is" << index << "removing id" << removed->index() + << (index != removed->index() ? "BOOOOOOOM!!!!" : ""); + + bool tieAfter = !goingDelete() && removed->note()->rtm.tie() == Trhythm::e_tieCont; + delete m_scoreNotes.takeAt(index); + + if (m_measures.last()->isEmpty()) + delete m_measures.takeLast(); // it is empty, so delete it then + +// if (maxNoteCount() > count()) +// if (spaceForNotes() - m_allNotesWidth - m_gapsSum <= 7.0) +// emit freeSpace(number(), 1); + updateIndexes(); + fit(); + if (index < count()) + updateNotesPos(index); + if (tieAfter) // restore tie between note before removed with note after removed (it should never crash if is TRUE) + m_scoreNotes[index - 1]->tieWithNext(); + for (int i = index; i < count(); ++i) // refresh neutrals in all next notes TODO: IT HAS TO BE DONE BY MEASURE m_scoreNotes[i]->moveNote(m_scoreNotes[i]->notePos()); - if (number() == -1) - updateSceneRect(); - } + if (number() == -1) // single note mode only + updateSceneRect(); + } } void TscoreStaff::addNotes(int index, QList<TscoreNote*>& nList) { - if (index >= 0 && index <= count() && nList.size() <= maxNoteCount() - index) - for (int i = index; i < nList.size() + index; i++) { - TscoreNote *sn = nList[i - index]; - m_scoreNotes.insert(i, sn); - connectNote(sn); - sn->setParentItem(this); - sn->setStaff(this); - } - updateNotesPos(index); - updateIndexes(); - checkNoteRange(false); +// if (index >= 0 && index <= count() && nList.size() <= maxNoteCount() - index) { + for (int i = index; i < nList.size() + index; i++) { + TscoreNote *sn = nList[i - index]; + m_scoreNotes.insert(i, sn); + sn->setStaff(this); + sn->setParentItem(this); +// sn->setStaff(this); + } +// } + updateIndexes(); +// updateNotesPos(index); // TODO: update position after resolving measure +// updateIndexes(); + checkNoteRange(false); } void TscoreStaff::addNote(int index, TscoreNote* freeNote) { - m_scoreNotes.insert(index, freeNote); - connectNote(freeNote); - freeNote->setParentItem(this); - freeNote->setStaff(this); - updateNotesPos(index); - updateIndexes(); + m_scoreNotes.insert(index, freeNote); + freeNote->setStaff(this); + freeNote->setParentItem(this); +// freeNote->setStaff(this); + updateNotesPos(index); + updateIndexes(); } void TscoreStaff::takeNotes(QList<TscoreNote*>& nList, int from, int to) { - if (from >= 0 && from < count() && to < count() && to >= from) { - for (int i = from; i <= to; i++) { // 'from' is always the next item after takeAt() on current one - m_scoreNotes[from]->disconnect(SIGNAL(noteWasClicked(int))); - m_scoreNotes[from]->disconnect(SIGNAL(noteWasSelected(int))); - m_scoreNotes[from]->setParentItem(0); // to avoid deleting with staff as its parent - nList << m_scoreNotes.takeAt(from); - } - updateNotesPos(); - updateIndexes(); - } + if (from >= 0 && from < count() && to < count() && to >= from) { + for (int i = from; i <= to; i++) { // 'from' is always the next item after takeAt() on current one + m_scoreNotes[from]->setParentItem(0); // to avoid deleting with staff as its parent + nList << m_scoreNotes.takeAt(from); + } + if (m_scoreNotes.isEmpty()) + emit staffIsEmpty(); + else { + updateNotesPos(); + updateIndexes(); + } + } } void TscoreStaff::updateSceneRect() { -// QRectF scRec = mapToScene(boundingRect()).boundingRect(); -// scene()->setSceneRect(0.0, 0.0, scRec.width() + (isPianoStaff() ? 2.5 : 1.5), scRec.height()); +// QRectF scRec = mapToScene(boundingRect()).boundingRect(); +// scene()->setSceneRect(0.0, 0.0, scRec.width() + (isPianoStaff() ? 2.5 : 1.5), scRec.height()); } void TscoreStaff::setNoteDisabled(int index, bool isDisabled) { - if (index >=0 && index < m_scoreNotes.size()) - m_scoreNotes[index]->setReadOnly(isDisabled); + if (index >=0 && index < m_scoreNotes.size()) + m_scoreNotes[index]->setReadOnly(isDisabled); } void TscoreStaff::setEnableKeySign(bool isEnabled) { - if (isEnabled != (bool)m_keySignature) { - if (isEnabled) { - m_keySignature = new TscoreKeySignature(scoreScene(), this); -// m_keySignature->setPos(7.0, 0.0); - m_keySignature->setPos(6.5, upperLinePos() - TscoreKeySignature::relatedLine); - m_keySignature->setClef(m_clef->clef()); - m_keySignature->setZValue(30); - connect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); - m_flyAccid = new QGraphicsSimpleTextItem; - registryItem(m_flyAccid); - m_flyAccid->setFont(TnooFont(5)); - m_flyAccid->setScale(scoreScene()->accidScale()); + if (isEnabled != (bool)m_keySignature) { + if (isEnabled) { + m_keySignature = new TscoreKeySignature(scoreScene(), this); + m_keySignature->setPos(6.5, upperLinePos() - TscoreKeySignature::relatedLine); + m_keySignature->setClef(m_clef->clef()); + m_keySignature->setZValue(30); + connect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); + m_flyAccid = new QGraphicsSimpleTextItem; + registryItem(m_flyAccid); + m_flyAccid->setFont(TnooFont(5)); + m_flyAccid->setScale(scoreScene()->accidScale()); m_flyAccid->setZValue(255); - m_flyAccid->hide(); - if (m_scoreNotes.size()) - m_flyAccid->setBrush(m_scoreNotes[0]->mainNote()->brush()); - m_accidAnim = new TcombinedAnim(m_flyAccid, this); - connect(m_accidAnim, SIGNAL(finished()), this, SLOT(accidAnimFinished())); - m_accidAnim->setDuration(400); - m_accidAnim->setScaling(m_flyAccid->scale(), m_flyAccid->scale() * 4.0); -// m_accidAnim->scaling()->setEasingCurveType(QEasingCurve::OutQuint); - m_accidAnim->setMoving(QPointF(), QPointF()); // initialize moving - m_accidAnim->moving()->setEasingCurveType(QEasingCurve::OutBack); - for (int i = 0; i < m_scoreNotes.size(); i++) { - connect(m_scoreNotes[i], SIGNAL(fromKeyAnim(QString,QPointF,int)), this, SLOT(fromKeyAnimSlot(QString,QPointF,int)), Qt::UniqueConnection); - connect(m_scoreNotes[i], SIGNAL(toKeyAnim(QString,QPointF,int)), this, SLOT(toKeyAnimSlot(QString,QPointF,int)), Qt::UniqueConnection); - connect(m_scoreNotes[i], SIGNAL(destroyed(QObject*)), this, SLOT(noteDestroingSlot(QObject*)), Qt::UniqueConnection); -// connect(m_accidAnim, SIGNAL(finished()), m_scoreNotes[i], SLOT(keyAnimFinished())); - } - } else { + m_flyAccid->hide(); + if (m_scoreNotes.size()) + m_flyAccid->setBrush(m_scoreNotes[0]->mainNote()->color()); + if (m_scoreMeter) + m_scoreMeter->setPos(m_keySignature->x() + m_keySignature->boundingRect().width(), upperLinePos()); + } else { m_keySignature->blockSignals(true); m_keySignature->setKeySignature(0); onKeyChanged(); delete m_keySignature; m_keySignature = 0; - m_accidAnim->deleteLater(); - m_accidAnim = 0; delete m_flyAccid; m_flyAccid = 0; - } - updateLines(); - updateNotesPos(); - } + if (m_scoreMeter) + m_scoreMeter->setPos(6.5, upperLinePos()); + } + updateLines(); + updateNotesPos(); + } } void TscoreStaff::setScordature(Ttune& tune) { - if (!hasScordature()) { - m_scordature = new TscoreScordature(scoreScene(), this); - m_scordature->setParentItem(this); - m_scordature->setZValue(35); // above key signature - } - m_scordature->setTune(tune); - if (m_scordature->isScordatured()) { - m_enableScord = true; - } else { // nothing to show - standard tune - delete m_scordature; - m_scordature = 0; - m_enableScord = false; - } - updateWidth(); - updateNotesPos(); + if (!hasScordature()) { + m_scordature = new TscoreScordature(scoreScene(), this); + m_scordature->setParentItem(this); + m_scordature->setZValue(35); // above key signature + } + m_scordature->setTune(tune); + if (m_scordature->isScordatured()) { + m_enableScord = true; + } else { // nothing to show - standard tune + delete m_scordature; + m_scordature = 0; + m_enableScord = false; + } + updateWidth(); + updateNotesPos(); } void TscoreStaff::removeScordatute() { - delete m_scordature; - m_scordature = 0; - m_enableScord = false; - updateWidth(); - updateNotesPos(); + delete m_scordature; + m_scordature = 0; + m_enableScord = false; + updateWidth(); + updateNotesPos(); } +void TscoreStaff::setMeter(const Tmeter& m) { + bool changed = false; + if (m.meter() != Tmeter::e_none && !m_scoreMeter) { // create score meter + m_scoreMeter = new TscoreMeter(scoreScene(), this); + m_scoreMeter->setPos(6.5 + (m_keySignature ? m_keySignature->boundingRect().width() : 0.0), upperLinePos()); + m_scoreMeter->setZValue(30); + m_scoreMeter->setMeter(m); + changed = true; + connect(m_scoreMeter, &TscoreMeter::meterChanged, [=]{ + updateWidth(); + updateNotesPos(); + }); + } else if (m_scoreMeter && m.meter() == Tmeter::e_none) { // delete meter + delete m_scoreMeter; + m_scoreMeter = nullptr; + if (m_keySignature) + m_keySignature->setX(6.5); + changed = true; + } else if (m_scoreMeter) + m_scoreMeter->setMeter(m); // score meter will call signal to update staff + scoreScene()->setScoreMeter(m_scoreMeter); + if (changed && !measures().isEmpty()) { + measures().first()->changeMeter(m); + for (int i = 0; i < m_scoreNotes.size(); i++) // set default rhythm value for all notes or hide all stems when no rhythms + m_scoreNotes[i]->setRhythmEnabled((bool)m_scoreMeter); + updateWidth(); + updateNotesPos(); + } +} + + + void TscoreStaff::setDisabled(bool disabled) { - scoreClef()->setReadOnly(disabled); + scoreClef()->setReadOnly(disabled); scoreClef()->setAcceptHoverEvents(!disabled); // stops displaying status tip - if (scoreKey()) { - scoreKey()->setAcceptHoverEvents(!disabled); // stops displaying status tip - scoreKey()->setReadOnly(disabled); - } - for (int i = 0; i < count(); i++) - m_scoreNotes[i]->setReadOnly(disabled); - if (disabled && count()) - m_scoreNotes[0]->hideWorkNote(); -// setControlledNotes(!disabled); + if (scoreKey()) { + scoreKey()->setAcceptHoverEvents(!disabled); // stops displaying status tip + scoreKey()->setReadOnly(disabled); + } + for (int i = 0; i < count(); i++) + m_scoreNotes[i]->setReadOnly(disabled); + if (m_scoreMeter) + m_scoreMeter->setReadOnly(!disabled); +// setControlledNotes(!disabled); } @@ -341,127 +410,347 @@ QRectF TscoreStaff::boundingRect() const { int TscoreStaff::accidNrInKey(int noteNr, char key) { - int accidNr; - switch ((56 + notePosRelatedToClef(noteNr, m_offset)) % 7 + 1) { - case 1: accidNr = 1; break; - case 2: accidNr = 3; break; - case 3: accidNr = 5; break; - case 4: accidNr = 0; break; - case 5: accidNr = 2; break; - case 6: accidNr = 4; break; - case 7: accidNr = 6; break; - } - if (key < 0) - accidNr = 6 - accidNr; - return accidNr; + int accidNr; + switch ((56 + notePosRelatedToClef(noteNr, m_offset)) % 7 + 1) { + case 1: accidNr = 1; break; + case 2: accidNr = 3; break; + case 3: accidNr = 5; break; + case 4: accidNr = 0; break; + case 5: accidNr = 2; break; + case 6: accidNr = 4; break; + case 7: accidNr = 6; break; + } + if (key < 0) + accidNr = 6 - accidNr; + return accidNr; } void TscoreStaff::setPianoStaff(bool isPiano) { - if (isPiano != m_isPianoStaff) { - m_isPianoStaff = isPiano; - if (isPiano) { - m_upperLinePos = 14.0; - m_lowerStaffPos = 28.0; - m_height = 42.0; + if (isPiano != m_isPianoStaff) { + m_isPianoStaff = isPiano; + if (isPiano) { + m_upperLinePos = 14.0; + m_lowerStaffPos = 28.0; + m_height = 44.0; createBrace(); - } else { - m_upperLinePos = 16.0; - m_lowerStaffPos = 0.0; - m_height = 36.0; + } else { + m_upperLinePos = 16.0; + m_lowerStaffPos = 0.0; + m_height = 38.0; delete m_brace; - } - prepareStaffLines(); - if (m_keySignature) - m_keySignature->setPos(7.0, upperLinePos() - TscoreKeySignature::relatedLine); - for (int i = 0; i < count(); i++) { - noteSegment(i)->adjustSize(); - noteSegment(i)->setAmbitus(isPiano ? 40 : 34, 2); // TODO It may cause problems when any other class will invoke note ambitus - } - if (count()) - scoreScene()->adjustCursor(noteSegment(0)); - emit pianoStaffSwitched(); - } + } + prepareStaffLines(); + if (m_scoreMeter) { + m_scoreMeter->setPianoStaff(m_isPianoStaff); + m_scoreMeter->setY(upperLinePos()); + } + if (m_keySignature) + m_keySignature->setPos(6.5, upperLinePos() - TscoreKeySignature::relatedLine); + for (int i = 0; i < count(); i++) { + noteSegment(i)->adjustSize(); + noteSegment(i)->setAmbitus(isPiano ? 40 : 34, 2); // TODO It may cause problems when any other class will invoke note ambitus + } + emit pianoStaffSwitched(); + } } int TscoreStaff::fixNotePos(int pianoPos) { - if (isPianoStaff() && pianoPos > lowerLinePos() - 4) - return pianoPos - 2; // piano staves gap - else - return pianoPos; + if (isPianoStaff() && pianoPos > lowerLinePos() - 4) + return pianoPos - 2; // piano staves gap + else + return pianoPos; } void TscoreStaff::setViewWidth(qreal viewW) { - m_viewWidth = viewW; - if (viewW > 0.0) - m_maxNotesCount = getMaxNotesNr(viewW); - else - m_maxNotesCount = 0; - updateLines(); // calls updateWidth() as well - updateNotesPos(); + qDebug() << debug() << "Old width" << m_viewWidth << "New width" << viewW; + if (viewW != m_viewWidth) { + m_viewWidth = viewW; + if (viewW > 0.0) + m_maxNotesCount = getMaxNotesNr(mapFromScene(viewW, 0.0).x()); + else + m_maxNotesCount = 0; + updateLines(); // calls updateWidth() as well + updateNotesPos(); + } } void TscoreStaff::checkNoteRange(bool doEmit) { - if (m_lockRangeCheck) - return; - qreal oldHi = m_hiNotePos, oldLo = m_loNotePos; - findHighestNote(); - findLowestNote(); - if (doEmit && oldHi != m_hiNotePos) - emit hiNoteChanged(number(), oldHi - m_hiNotePos); - if (doEmit && oldLo != m_loNotePos) - emit loNoteChanged(number(), m_loNotePos - oldLo); - return; + if (m_lockRangeCheck) + return; + qreal oldHi = m_hiNotePos, oldLo = m_loNotePos; + findHighestNote(); + findLowestNote(); + if (doEmit && oldHi != m_hiNotePos) + emit hiNoteChanged(number(), oldHi - m_hiNotePos); + if (doEmit && oldLo != m_loNotePos) + emit loNoteChanged(number(), m_loNotePos - oldLo); + return; +} + + +int TscoreStaff::measureOfNoteId(int id) { + if (m_measures.isEmpty()) { + qDebug() << debug() << "No measures in this staff, can not find it for note" << id; + return - 1; + } + if (m_measures.count() == 1) + return 0; + auto lastMeas = lastMeasure(); + if (lastMeas && !lastMeas->isEmpty()) { + if (id >= lastMeas->lastNoteId()) + return m_measures.count() - 1; // last measure for the last note + } + for (int i = 0; i < m_measures.size(); ++i) { + if (id >= m_measures[i]->firstNoteId() && id <= m_measures[i]->lastNoteId()) + return i; + } + + // TODO: It should never occur, delete it + qDebug() << debug() << "There is no measure for note id:" << id << "in this staff with notes number" << count(); + return -1; +} + + +TscoreMeasure* TscoreStaff::nextMeasure(TscoreMeasure* before) { + if (before == lastMeasure()) { + auto st = nextStaff(); + return st ? st->firstMeasure() : nullptr; + } else + return m_measures[before->id() + 1]; } -void TscoreStaff::enableToAddNotes(bool alowAdding) { - scoreScene()->left()->enableToAddNotes(alowAdding); - scoreScene()->right()->enableToAddNotes(alowAdding); +TscoreStaff* TscoreStaff::nextStaff() { + auto st = this; + emit getNextStaff(st); + return st; +} + + +TscoreStaff * TscoreStaff::prevStaff() { + auto st = this; + emit getPrevStaff(st); + return st; +} + + +/** + * It is hard to imagine when other than the last measure of the staff is taken + */ +TscoreMeasure* TscoreStaff::takeMeasure(int measId) { + qDebug() << debug() << "Taking measure" << measId; + if (measId >= 0 && measId < m_measures.count()) { + if (m_measures[measId]->isEmpty()) { + qDebug() << debug() << "Measure" << measId << " to take is empty"; + return nullptr; + } + int firstIndex = m_measures[measId]->firstNoteId(); + int lastIndex = m_measures[measId]->lastNoteId(); + for (int i = firstIndex; i <= lastIndex; ++i) { + m_scoreNotes[firstIndex]->setParentItem(0); // to avoid deleting with staff as its parent + m_scoreNotes.removeAt(firstIndex); // when note segment with firstIndex is removed the next segment is at the firstIndex position + } + updateIndexes(); + auto retMeasure = m_measures.takeAt(measId); // take measure from list before fitting + fit(); + // No need to update measure numbers + // TODO: positioning? + return retMeasure; + } else + qDebug() << debug() << "Unable to take measure" << measId << "out of range"; + return nullptr; +} + + +/** + * Only entire measures are inserted, eventually partial, when it is the last measure, + * so there is no need to recalculate measure (content and beaming) just set notes position. + */ +void TscoreStaff::insertMeasure(int id, TscoreMeasure* m) { + if (m == nullptr) { + qDebug() << debug() << "Trying to insert measure which is NULL of id:" << id; + return; + } + qDebug() << debug() << "Inserting measure" << id; + int noteId = 0; + if (id > 0) // find index where to insert notes of the measure + noteId = m_measures[id - 1]->lastNoteId() + 1; + addNotes(noteId, m->notes()); + m_measures.insert(id, m); +// addNotes(noteId, m->notes()); +// m->setStaff(this); + for (int i = 0; i < m_measures.size(); ++i) + m_measures[i]->setId(i); + m->setStaff(this); + fit(); + updateNotesPos(); +} + + +char TscoreStaff::debug() { + QTextStream o(stdout); + o << "\033[01;34m[" << number() << " STAFF]\033[01;00m"; + return 32; // fake } //########################################################################################################## //########################################## PROTECTED ################################################### //########################################################################################################## -void TscoreStaff::prepareStaffLines() { - m_5lines->setPianoStaff(isPianoStaff()); +void TscoreStaff::prepareStaffLines() { + m_5lines->setPianoStaff(isPianoStaff()); m_5lines->setPos(0.0, upperLinePos()); - updateLines(); - updateNotesPos(); -} - - -void TscoreStaff::insert(int index) { - TscoreNote *newNote = new TscoreNote(scoreScene(), this, index); - newNote->setZValue(50); - connectNote(newNote); - m_scoreNotes.insert(index, newNote); + updateLines(); + updateNotesPos(); } void TscoreStaff::setEnableScordtature(bool enable) { - if (enable != m_enableScord) { - m_enableScord = enable; - updateWidth(); - updateNotesPos(); - } + if (enable != m_enableScord) { + m_enableScord = enable; + updateWidth(); + updateNotesPos(); + } } qreal TscoreStaff::notesOffset() { - qreal off = 0.0; - if (m_keySignature) { + qreal off = 0.0; + if (m_keySignature) { if (m_tidyKey) off = qAbs<char>(m_keySignature->keySignature()) * 1.3; else off = KEY_WIDTH + 1; } else if (m_enableScord) - off = KEY_WIDTH / 2; - return off; + off = KEY_WIDTH / 2; + if (m_scoreMeter) + off += m_scoreMeter->width(); + return off; +} + + +void TscoreStaff::noteChangedWidth(int noteId) { + Q_UNUSED(noteId) + updateNotesPos(); +} + + +void TscoreStaff::prepareNoteChange(TscoreNote* sn) { + if (sn == nullptr) + return; + + if (sn->rhythmChanged() || sn->accidChanged()) + fit(); +} + + +TscoreNote* TscoreStaff::insertNote(const Tnote& note, int index, bool disabled) { + index = qBound(0, index, m_scoreNotes.size()); // 0 - adds at the begin, size() - adds at the end + if (index) { + auto prev = m_scoreNotes[index - 1]; + if (prev->note()->rtm.tie() == Trhythm::e_tieStart || prev->note()->rtm.tie() == Trhythm::e_tieCont) + prev->tieRemove(); // it will also set a proper tie of next note if the previous was connected with it before inserting + } + auto n = insert(index); + setNote(index, note); + m_scoreNotes[index]->setZValue(50); + setNoteDisabled(index, disabled); + updateIndexes(); + return n; +} + + +void TscoreStaff::fit() { + if (m_scoreNotes.isEmpty()) { + qDebug() << debug() << "Empty staff - nothing to fit"; + return; + } + + int measureNr = 0, mCnt = 0; + qreal factor = 2.0; + m_gapsSum = 0.0; + m_allNotesWidth = 0.0; + bool needShift = false; + + for (int n = 0; n < m_scoreNotes.size(); ++n) { + if (mCnt < m_measures.count() && !m_measures[mCnt]->isEmpty() && noteSegment(n) == m_measures[mCnt]->firstNote()) { + measureNr = mCnt; // determine number of current measure + mCnt++; + } + m_gapsSum += noteSegment(n)->space(); + m_allNotesWidth += noteSegment(n)->width(); + if (n > 1) { + factor = (spaceForNotes() - m_allNotesWidth) / m_gapsSum; + if (factor < 1.0) { // shift current measure and the next ones + needShift = true; + break; // rest of the notes goes to the next staff + } + } + } + if (needShift) { + if (measureNr == 0) { + qDebug() << debug() << "There is no space in the staff but measure is not full. Breaking measures is NOT IMPLEMENTED!"; + } else { + qDebug() << debug() << "Fitting detects need of shifting from measure" << measureNr; + if (m_measures.count() - measureNr - 1 > 1) { + qDebug() << debug() << "THE NEXT STAFF WILL FIT ITSELF A FEW TIMES" << m_measures.count() - measureNr - 1; + // TODO: if more measures are shifted this way, the next staff will fit itself a few times -TRY TO AVOID THAT + } + emit moveMeasure(this, m_measures.count() - 1); // it will perform fitting as well + } + } else if (factor > 2.0) { // staff has free space + auto st = nextStaff(); + if (st && st != this && st->count()) { + qDebug() << debug() << "staff has free space - getting fist measure of staff" << st->number(); + if (st->firstMeasure()->notesWidth() <= width() - contentWidth(1.0)) { + insertMeasure(m_measures.count(), st->takeMeasure(0)); + st->updateNotesPos(); + } + } + } + m_gapFactor = qBound(1.0, factor, 2.0); // notes in this staff are ready to positioning + qDebug() << debug() << "fitting... Gap factor is" << m_gapFactor; +} + + +void TscoreStaff::shiftToMeasure(int measureNr, QList<TscoreNote*>& notesToShift) { + qDebug() << debug() << "shift" << notesToShift.count() << "notes to measure" << measureNr; + if (measureNr == m_measures.count()) { // no such a measure - create it first + qDebug() << debug() << "Create new measure nr" << m_measures.count(); + m_measures << new TscoreMeasure(this, m_measures.count()); + } + m_measures[measureNr]->prependNotes(notesToShift); +} + + +int TscoreStaff::shiftFromMeasure(int measureNr, int dur, QList<TscoreNote*>& notesToShift) { + int retDur = 0; + if (measureNr < m_measures.count()) { + retDur = m_measures[measureNr]->takeAtStart(dur, notesToShift); + if (m_measures[measureNr]->isEmpty()) { + qDebug() << debug() << "Measure" << measureNr << "is empty - resetting duration" << retDur; + retDur = 0; // next (the last) measure is not able to return required duration, no tie required + delete m_measures.takeLast(); // it is empty, so delete it then + } + } else { + auto st = nextStaff(); + if (st && st != this) { + qDebug() << debug() << "Looking for notes in the next staff id" << st->number(); + st->shiftFromMeasure(0, dur, notesToShift); + if (!notesToShift.isEmpty()) { + QList<TscoreNote*> fakeList; // notesToShift has already all notes + st->takeNotes(fakeList, notesToShift.first()->index(), notesToShift.last()->index()); + qDebug() << debug() << "Setting new staff for" << notesToShift.size() << "notes"; + addNotes(count(), notesToShift); + content(this); + } + } + } + return retDur; } @@ -470,8 +759,8 @@ qreal TscoreStaff::notesOffset() { //########################################################################################################## void TscoreStaff::onClefChanged(Tclef clef) { - setPianoStaff(clef.type() == Tclef::e_pianoStaff); - switch(clef.type()) { + setPianoStaff(clef.type() == Tclef::e_pianoStaff); + switch(clef.type()) { case Tclef::e_treble_G: m_offset = TnoteOffset(3, 2); break; case Tclef::e_treble_G_8down: @@ -484,33 +773,27 @@ void TscoreStaff::onClefChanged(Tclef clef) { m_offset = TnoteOffset(4, 1); break; case Tclef::e_tenor_C: m_offset = TnoteOffset(2, 1); break; - case Tclef::e_pianoStaff: + case Tclef::e_pianoStaff: m_offset = TnoteOffset(3, 2); break; - default: break; + default: break; } m_lockRangeCheck = true; scoreClef()->setClef(clef); if (m_keySignature) { - disconnect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); + disconnect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); m_keySignature->setClef(m_clef->clef()); - connect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); - } - if (m_scoreNotes.size()) { - for (int i = 0; i < m_scoreNotes.size(); i++) { - if (m_scoreNotes[i]->notePos()) { - setNote(i, *(m_scoreNotes[i]->note())); - } - } - } - m_lockRangeCheck = false; - checkNoteRange(); - emit clefChanged(scoreClef()->clef()); -} - - -void TscoreStaff::noteChangedAccid(int accid) { - if (scoreScene()->left()) - scoreScene()->left()->setAccidental(accid); + connect(m_keySignature, SIGNAL(keySignatureChanged()), this, SLOT(onKeyChanged())); + } + if (m_scoreNotes.size()) { + for (int i = 0; i < m_scoreNotes.size(); i++) { + if (m_scoreNotes[i]->notePos()) { + setNote(i, *(m_scoreNotes[i]->note())); + } + } + } + m_lockRangeCheck = false; + checkNoteRange(); + emit clefChanged(scoreClef()->clef()); } @@ -525,215 +808,233 @@ void TscoreStaff::setTidyKey(bool tidy) { void TscoreStaff::applyAutoAddedNote() { if (m_autoAddedNoteId > -1) { - m_addTimer->stop(); emit noteIsAdding(number(), m_autoAddedNoteId); - if (m_autoAddedNoteId == maxNoteCount() - 1) // new staff is wanted - emit noMoreSpace(number()); +// if (m_autoAddedNoteId == maxNoteCount() - 1) // new staff is wanted +// if (spaceForNotes() - m_allNotesWidth - m_gapsSum < 7.0) +// emit noMoreSpace(number()); m_autoAddedNoteId = -1; } } + +// TODO: keep var for it instead of following calculations (it could be always actual when take care of it during clef/meter/key width changes) +qreal TscoreStaff::spaceForNotes() { + return width() - (m_clef ? m_clef->boundingRect().width() : 0.0) + - (m_keySignature ? m_keySignature->boundingRect().width() : 0.0) + - (m_scoreMeter ? m_scoreMeter->width() : 0.0); +} + + +qreal TscoreStaff::contentWidth(qreal gapFact) { + return (m_clef ? m_clef->boundingRect().width() : 0.0) + + (m_keySignature ? m_keySignature->boundingRect().width() : 0.0) + + (m_scoreMeter ? m_scoreMeter->width() : 0.0) + + m_allNotesWidth + m_gapsSum * gapFact; +} + + +bool TscoreStaff::hasSpaceFor(qreal newWidth) { + return m_allNotesWidth + newWidth + (m_gapsSum * m_gapFactor) <= spaceForNotes(); +// return width() - m_scoreNotes.last()->rightX() >= newWidth; +} + + +/** + * m_allNotesWidth + m_scoreNotes.first()->estimateWidth(n) ===> physical width of all notes + */ +bool TscoreStaff::hasSpaceFor(const Tnote& n) { + return m_allNotesWidth + m_scoreNotes.first()->estimateWidth(n) + (m_gapsSum * m_gapFactor) <= spaceForNotes(); +} + //########################################################################################################## //####################################### PROTECTED SLOTS ################################################# //########################################################################################################## void TscoreStaff::onPianoStaffChanged(Tclef clef) { - setPianoStaff(clef.type() == Tclef::e_pianoStaff); - scoreClef()->setClef(clef); + setPianoStaff(clef.type() == Tclef::e_pianoStaff); + scoreClef()->setClef(clef); } void TscoreStaff::onKeyChanged() { for (int i = 0; i < m_scoreNotes.size(); i++) { - if (m_scoreNotes[i]->notePos()) - m_scoreNotes[i]->moveNote(m_scoreNotes[i]->notePos()); + auto noteSeg = m_scoreNotes[i]; + if (noteSeg->notePos()) + noteSeg->moveNote(noteSeg->notePos()); } } void TscoreStaff::onNoteClicked(int noteIndex) { - if (m_autoAddedNoteId > -1) { - if (noteIndex == m_autoAddedNoteId - 1) { - m_addTimer->stop(); - m_addTimer->start(2000); - } else - addNoteTimeOut(); - } - int globalNr = notePosRelatedToClef(fixNotePos(m_scoreNotes[noteIndex]->notePos()) - + m_scoreNotes[noteIndex]->ottava() * 7, m_offset); - m_scoreNotes[noteIndex]->note()->note = (char)(56 + globalNr) % 7 + 1; - m_scoreNotes[noteIndex]->note()->octave = (char)(56 + globalNr) / 7 - 8; - m_scoreNotes[noteIndex]->note()->alter = (char)m_scoreNotes[noteIndex]->accidental(); - for (int i = noteIndex + 1; i < count(); ++i) // refresh neutrals in all next notes + updatePitch(noteIndex); +// auto noteOfId = m_scoreNotes[noteIndex]; +// int globalNr = notePosRelatedToClef(fixNotePos(noteOfId->notePos()) + noteOfId->ottava() * 7, m_offset); +// noteOfId->note()->note = (char)(56 + globalNr) % 7 + 1; +// noteOfId->note()->octave = (char)(56 + globalNr) / 7 - 8; +// noteOfId->note()->alter = (char)noteOfId->accidental(); + if (m_autoAddedNoteId > -1) { + addNoteTimeOut(); + } + + for (int i = noteIndex + 1; i < count(); ++i) // refresh neutrals in all next notes // TODO: move it to measure m_scoreNotes[i]->moveNote(m_scoreNotes[i]->notePos()); - emit noteChanged(noteIndex); - checkNoteRange(); - // when score is in record mode the signal above invokes adding new note so count is increased and code below is skipped - This is a magic - if (scoreScene()->right() && scoreScene()->right()->notesAddingEnabled() && noteIndex == count() - 1 && noteIndex < maxNoteCount() - 1) { - m_addTimer->stop(); - insert(noteIndex + 1); - m_scoreNotes.last()->popUpAnim(300); - updateIndexes(); - updateNotesPos(noteIndex + 1); - m_addTimer->start(2000); - m_autoAddedNoteId = noteIndex + 1; - } + + emit noteChanged(noteIndex); + checkNoteRange(); } void TscoreStaff::onNoteSelected(int noteIndex) { -// if (selectableNotes() || controlledNotes()) { // no need to check, note does it - emit noteSelected(noteIndex); +// if (selectableNotes() || controlledNotes()) { // no need to check, note does it + emit noteSelected(noteIndex); } void TscoreStaff::onAccidButtonPressed(int accid) { - scoreScene()->setCurrentAccid(accid); - /** It is enough to do this as long as every TscoreNote handles mouseHoverEvent - * which checks value set above and changes accidental symbol if necessary. */ + scoreScene()->setCurrentAccid(accid); + /** It is enough to do this as long as every TscoreNote handles mouseHoverEvent + * which checks value set above and changes accidental symbol if necessary. */ } -void TscoreStaff::fromKeyAnimSlot(const QString& accidText, const QPointF& accidPos, int notePos) { - m_flyAccid->setText(accidText); - m_accidAnim->setMoving(mapFromScene(m_keySignature->accidTextPos(accidNrInKey(notePos, scoreKey()->keySignature()))), - mapFromScene(accidPos)); - m_accidAnim->startAnimations(); - m_flyAccid->show(); -} - - -void TscoreStaff::toKeyAnimSlot(const QString& accidText, const QPointF& accidPos, int notePos) { - if (m_noteWithAccidAnimed) - return; - else - m_noteWithAccidAnimed = static_cast<TscoreNote*>(sender()); - m_flyAccid->setText(accidText); - m_accidAnim->setMoving(mapFromScene(accidPos), - mapFromScene(m_keySignature->accidTextPos(accidNrInKey(notePos, scoreKey()->keySignature())))); - m_accidAnim->startAnimations(); - m_flyAccid->show(); +void TscoreStaff::noteGoingDestroy(QObject* n) { + if (n == m_noteWithAccidAnimed) + m_noteWithAccidAnimed = 0; } -void TscoreStaff::accidAnimFinished() { - m_flyAccid->hide(); - if (m_noteWithAccidAnimed) { - m_noteWithAccidAnimed->keyAnimFinished(); - m_noteWithAccidAnimed = 0; +void TscoreStaff::addNoteTimeOut() { + if (m_autoAddedNoteId > -1) { + if (noteSegment(m_autoAddedNoteId)->notePos()) { // automatically added note was set - approve it + applyAutoAddedNote(); // puts m_autoAddedNoteId back to -1 + qDebug() << debug() << "auto note approved" << m_scoreNotes.last()->note()->rtm.xmlType() << m_scoreNotes.last()->rhythm()->xmlType(); + m_measures.last()->insertNote(m_scoreNotes.last()->index() - m_measures.last()->firstNoteId(), m_scoreNotes.last()); + fit(); + } else if (m_autoAddedNoteId != count() - 1) { // some note was added after this one - ignore + m_autoAddedNoteId = -1; + } else { // user gave up + delete noteSegment(m_autoAddedNoteId); + m_scoreNotes.removeAt(m_autoAddedNoteId); + m_autoAddedNoteId = -1; + } } } -void TscoreStaff::noteDestroingSlot(QObject* n) { - Q_UNUSED(n) - if (sender() == m_noteWithAccidAnimed) - m_noteWithAccidAnimed = 0; +void TscoreStaff::updatePitch(int noteIndex) { + auto sn = m_scoreNotes[noteIndex]; + int globalNr = notePosRelatedToClef(fixNotePos(sn->notePos()) + sn->ottava() * 7, m_offset); + sn->note()->note = (char)(56 + globalNr) % 7 + 1; + sn->note()->octave = (char)(56 + globalNr) / 7 - 8; + sn->note()->alter = (char)sn->accidental(); + qDebug() << debug() << "updating pitch of" << noteIndex << sn->note()->toText(); } -void TscoreStaff::addNoteTimeOut() { - if (m_autoAddedNoteId > -1) { - if (noteSegment(m_autoAddedNoteId)->notePos()) // automatically added note was set - approve it - applyAutoAddedNote(); // puts m_autoAddedNoteId back to -1 - else if (noteSegment(m_autoAddedNoteId) == scoreScene()->currentNote()) {// note was not set but cursor is still over it - m_addTimer->stop(); - m_addTimer->start(1000); // wait next 1000 ms - } else if (m_autoAddedNoteId != count() - 1) { // some note was added after this one - ignore - m_autoAddedNoteId = -1; - } else { // user gave up - delete noteSegment(m_autoAddedNoteId); - m_scoreNotes.removeAt(m_autoAddedNoteId); - m_autoAddedNoteId = -1; - } - } -} - //########################################################################################################## //########################################## PRIVATE ################################################### //########################################################################################################## void TscoreStaff::updateIndexes() { - for (int i = 0; i < m_scoreNotes.size(); i++) - m_scoreNotes[i]->changeIndex(i); // Update index of next notes in the list + qDebug() << debug() << "updating indexes"; + for (int i = 0; i < m_scoreNotes.size(); i++) + m_scoreNotes[i]->changeIndex(i); // Update index of next notes in the list } void TscoreStaff::updateNotesPos(int startId) { - qreal off = notesOffset(); - for (int i = startId; i < m_scoreNotes.size(); i++) // update positions of the notes - m_scoreNotes[i]->setPos(7.0 + off + i * m_scoreNotes[0]->boundingRect().width(), 0); + qDebug() << debug() << "updating notes positions from" << startId; + if (m_scoreNotes.isEmpty()) + return; + + if (startId == 0) + m_scoreNotes[0]->setX(6.5 + notesOffset()); + else + m_scoreNotes[startId]->setX(m_scoreNotes[startId - 1]->rightX()); + + int m = 0; + for (int i = startId + 1; i < m_scoreNotes.size(); i++) { // update positions of the notes + auto noteSeg = m_scoreNotes[i]; // cache pointer to TscoreNote for multiple reuse + noteSeg->setX(m_scoreNotes[i - 1]->rightX()); + if (m < m_measures.count() && !m_measures[m]->isEmpty() && noteSeg == m_measures[m]->lastNote()) { + m_measures[m]->checkBarLine(); + m++; + } + noteSeg->update(); + } +// m_scoreNotes[i]->setPos(7.0 + off + i * m_scoreNotes[0]->boundingRect().width(), 0); } void TscoreStaff::updateLines() { - updateWidth(); + updateWidth(); m_5lines->setWidth(width()); } void TscoreStaff::updateWidth() { - qreal off = notesOffset(); - if (m_scoreNotes.size() < 1) - m_width = 10.0 + off + 2.0; - else - m_width = 10.0 + off + m_scoreNotes.size() * m_scoreNotes[0]->boundingRect().width() + 2.0; - if (m_viewWidth > 0.0) - m_width = m_viewWidth; + qreal off = notesOffset(); + if (m_scoreNotes.size() < 1) + m_width = 10.0 + off + 2.0; + else + m_width = 10.0 + off + m_scoreNotes.size() * m_scoreNotes[0]->boundingRect().width() + 2.0; + if (m_viewWidth > 0.0) + m_width = m_viewWidth; } void TscoreStaff::createBrace() { - m_brace = new QGraphicsSimpleTextItem(); - registryItem(m_brace); + m_brace = new QGraphicsSimpleTextItem(); + registryItem(m_brace); m_brace->setFont(TnooFont(22)); - m_brace->setText(QString(QChar(0xe16c))); - m_brace->setBrush(qApp->palette().text().color()); + m_brace->setText(QString(QChar(0xe16c))); + m_brace->setBrush(qApp->palette().text().color()); // m_brace->setScale(22.18 / m_brace->boundingRect().height()); m_brace->setScale(1.05619047619047619047); - m_brace->setPos(-2.4 * m_brace->scale(), upperLinePos() + (22.18 - m_brace->boundingRect().height() * m_brace->scale()) / 2.0); + m_brace->setPos(-2.4 * m_brace->scale(), upperLinePos() + (22.18 - m_brace->boundingRect().height() * m_brace->scale()) / 2.0); m_brace->setZValue(7); } int TscoreStaff::getMaxNotesNr(qreal maxWidth) { - maxWidth -= 1.0; // staff lines margins - if (scoreClef()) - maxWidth -= CLEF_WIDTH; - if (scoreKey()) - maxWidth -= KEY_WIDTH + 1; - else if (hasScordature()) - maxWidth -= KEY_WIDTH / 2; - return int(maxWidth / 7.0); + maxWidth -= 1.0; // staff lines margins + if (scoreClef()) + maxWidth -= CLEF_WIDTH; + if (scoreKey()) + maxWidth -= KEY_WIDTH + 1; + else if (hasScordature()) + maxWidth -= KEY_WIDTH / 2; + return int(maxWidth / 7.0); } void TscoreStaff::findHighestNote() { - m_hiNotePos = upperLinePos() - 4.0; - for (int i = 0; i < m_scoreNotes.size(); i++) - if (m_scoreNotes[i]->notePos()) // is visible - m_hiNotePos = qMin(qreal(m_scoreNotes[i]->notePos() - 2), m_hiNotePos); + m_hiNotePos = upperLinePos() - 4.0; + for (int i = 0; i < m_scoreNotes.size(); i++) { + auto noteSeg = m_scoreNotes[i]; + if (noteSeg->notePos()) // is visible + m_hiNotePos = qMin(qreal(noteSeg->notePos() - (noteSeg->note()->rtm.stemDown() ? 2 : 4)), m_hiNotePos); + } } void TscoreStaff::findLowestNote() { - if (hasScordature()) { - m_loNotePos = height(); - return; - } - m_loNotePos = (isPianoStaff() ? lowerLinePos(): upperLinePos()) + 13.0; - for (int i = 0; i < m_scoreNotes.size(); i++) - m_loNotePos = qMax(qreal(m_scoreNotes[i]->notePos() + 2), m_loNotePos); + if (hasScordature()) { + m_loNotePos = height(); + return; + } + m_loNotePos = (isPianoStaff() ? lowerLinePos(): upperLinePos()) + 13.0; + for (int i = 0; i < m_scoreNotes.size(); i++) + m_loNotePos = qMax(qreal(m_scoreNotes[i]->notePos() + (m_scoreNotes[i]->note()->rtm.stemDown() ? 4 : 2)), m_loNotePos); } -void TscoreStaff::connectNote(TscoreNote* sn) { - connect(sn, SIGNAL(noteWasClicked(int)), this, SLOT(onNoteClicked(int))); - connect(sn, SIGNAL(noteWasSelected(int)), this, SLOT(onNoteSelected(int))); - connect(sn, SIGNAL(toKeyAnim(QString,QPointF,int)), this, SLOT(toKeyAnimSlot(QString,QPointF,int)), Qt::UniqueConnection); - connect(sn, SIGNAL(fromKeyAnim(QString,QPointF,int)), this, SLOT(fromKeyAnimSlot(QString,QPointF,int)), Qt::UniqueConnection); - connect(sn, SIGNAL(destroyed(QObject*)), this, SLOT(noteDestroingSlot(QObject*)), Qt::UniqueConnection); +TscoreNote* TscoreStaff::insert(int index) { + auto newNote = new TscoreNote(scoreScene(), this, index); + newNote->setZValue(50); + m_scoreNotes.insert(index, newNote); + return newNote; } @@ -741,3 +1042,5 @@ void TscoreStaff::connectNote(TscoreNote* sn) { + + diff --git a/src/libs/score/tscorestaff.h b/src/libs/score/tscorestaff.h index 696e93bbca7e19550d1a5cd32b3c0cb342a7ae51..64cde6a5c6b41f01be5f0890c28257f7cc0aa40d 100644 --- a/src/libs/score/tscorestaff.h +++ b/src/libs/score/tscorestaff.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2013-2015 by Tomasz Bojczuk * + * Copyright (C) 2013-2017 by Tomasz Bojczuk * * seelook@gmail.com * * * * This program is free software; you can redistribute it and/or modify * @@ -17,16 +17,16 @@ ***************************************************************************/ +class notesToShift; #ifndef TSCORESTAFF_H #define TSCORESTAFF_H #include <nootkacoreglobal.h> #include "tscoreitem.h" #include <music/tclef.h> -#include <QPointer> +#include <QtCore/qpointer.h> class QTimer; -class TcombinedAnim; class Tnote; class Ttune; class TscoreKeySignature; @@ -35,158 +35,181 @@ class TscoreNote; class TscoreClef; class TscoreScene; class Tscore5lines; +class TscoreMeter; +class Trhythm; +class Tmeter; +class TscoreMeasure; -/** - * Describes offset of a note. +/** + * Describes offset of a note. */ class NOOTKACORE_EXPORT TnoteOffset { public: - TnoteOffset(int noteOff, int octaveOff); - - int note; - int octave; + TnoteOffset(qint8 noteOff = 0, qint8 octaveOff = 0) : note(noteOff), octave(octaveOff) {} + + qint8 note; + qint8 octave; int total() { return octave * 7 + note; } }; -/** - * @class TscoreStaff manages score items on the staff. +/** + * @p TscoreStaff manages score items on the staff. * It has got: - * - clef - @p TscoreClef - accessing by @p scoreClef() - * - key signature - @p TscoreKeySignature - scoreKey() + * - clef of @p TscoreClef - available by @p scoreClef() + * - key signature - @p TscoreKeySignature - @p scoreKey() + * - meter (if enabled) - @p TscoreMeter - @p scoreMeter() * - notes (in QList) - @p TscoreNote - @p noteSegment(int nr) * - scordature - below clef through @p setScordature() */ class NOOTKACORE_EXPORT TscoreStaff : public TscoreItem { - Q_OBJECT - -public: - - TscoreStaff(TscoreScene* scene, int notesNr); - virtual ~TscoreStaff(); - /** Determines index of this staff in staff list in multi-staff mode. - * Otherwise (default) returns -1 */ - void setStafNumber(int nr) { m_staffNr = nr; } - int number() { return m_staffNr; } + friend class TscoreNote; + friend class TscoreMeasure; + friend class TmultiScore; - /** Returns pointer to TscoreNote element in the score. */ - TscoreNote* noteSegment(int nr) { return m_scoreNotes[nr]; } + Q_OBJECT - TscoreKeySignature* scoreKey() { return m_keySignature; } +public: - TscoreClef* scoreClef() { return m_clef; } + TscoreStaff(TscoreScene* scene, int notesNr); + virtual ~TscoreStaff(); - void setPianoStaff(bool isPiano); - bool isPianoStaff() { return m_isPianoStaff; } + /** Determines index of this staff in staff list in multi-staff mode. + * Otherwise (default) returns -1 */ + void setStafNumber(int nr) { m_staffNr = nr; } + int number() { return m_staffNr; } - /** Returns current @p index note or Tnote(0, 0, 0) if not set. */ - Tnote* getNote(int index); - void setNote(int index, const Tnote& note); - void setNoteDisabled(int index, bool isDisabled); + /** Returns pointer to TscoreNote element in the score. */ + TscoreNote* noteSegment(int nr) { return m_scoreNotes[nr]; } + TscoreNote* firstNote() { return m_scoreNotes.first(); } + TscoreNote* lastNote() { return m_scoreNotes.last(); } - int count() { return m_scoreNotes.size(); } /**< Number of notes on the score */ + TscoreKeySignature* scoreKey() { return m_keySignature; } + + TscoreClef* scoreClef() { return m_clef; } - /** adds note at the end of the staff - * Empty Tnote creates new instance of TscoreNote item. */ - void addNote(Tnote& note, bool disabled = false); + void setPianoStaff(bool isPiano); + bool isPianoStaff() const { return m_isPianoStaff; } + + /** Returns current @p index note or Tnote(0, 0, 0) if not set. */ + Tnote* getNote(int index); + void setNote(int index, const Tnote& note); + void setNoteDisabled(int index, bool isDisabled); - /** Adds notes from the list to this staff, starting from given @p index. - * Notes number in the list can not be bigger than available space on the staff */ - void addNotes(int index, QList<TscoreNote*>& nList); - void addNote(int index, TscoreNote* freeNotet); + int count() { return m_scoreNotes.size(); } /**< Number of notes on the score */ - /** Inserts note in given position (index). - * When @p index is out of scope adds it at the end. */ - void insertNote(int index, const Tnote& note, bool disabled = false); - void insertNote(int index, bool disabled = false); /**< Insert empty note */ - void removeNote(int index); /**< Deletes given note from the staff */ + /** adds note at the end of the staff + * Empty Tnote creates new instance of TscoreNote item. */ + void addNote(const Tnote& note, bool disabled = false); - /** Removes all note segments from @p from to @p to - * and puts those TscoreNote pointers to given @p nList. - * To grab all notes from a staff just invoke: - * takeNotes(smoeList, 0, count() - 1); */ - void takeNotes(QList<TscoreNote*>& nList, int from, int to); + /** Adds notes from the list to this staff, starting from given @p index. + * Notes number in the list can not be bigger than available space on the staff */ + void addNotes(int index, QList<TscoreNote*>& nList); + void addNote(int index, TscoreNote* freeNotet); - void setEnableKeySign(bool isEnabled); + /** + * Inserts note in given position (index). + * When @p index is out of scope adds it at the end. + * Returns pointer to inserted note + */ + TscoreNote* insertNote(int index, const Tnote& note, bool disabled = false); + void insertNote(int index, bool disabled = false); /**< Inserts empty note at @p index position*/ + void removeNote(int index); /**< Deletes given note from the staff */ - /** This array keeps values (-1, 0 or 1) for accidentals in key sign. - * It is common for TscoreKeySignature and all TscoreNote. - * TscoreKeySignature::setAccidInKeyPointer and TscoreNote::setAccidInKeyPointer - * have to be set to connect them. - * When TscoreKeySignature is deleted it should be set to 0. */ - char accidInKeyArray[7]; + /** Removes all note segments from @p from to @p to + * and puts those TscoreNote pointers to given @p nList. + * To grab all notes from a staff just invoke: + * takeNotes(someList, 0, count() - 1); */ + void takeNotes(QList<TscoreNote*>& nList, int from, int to); - /** Sets scordature according to given tune. - * To delete it just call this with Ttune::standardTune.*/ - void setScordature(Ttune& tune); - bool hasScordature() { return (bool)m_scordature; } /**< @p TRUE when staff has got scordature. */ - void removeScordatute(); + void setEnableKeySign(bool isEnabled); - qreal upperLinePos() const { return m_upperLinePos; } /**< Y position of upper line of a staff. */ - qreal lowerLinePos() const { return m_lowerStaffPos; } /**< Y position of lower line of a lower staff. */ - qreal height() const { return m_height; } // staff height - qreal width() const { return m_width; } // staff width + /** + * This array keeps values (-1, 0 or 1) for accidentals in key sign. + * It is common for TscoreKeySignature and all TscoreNote. + * TscoreKeySignature::setAccidInKeyPointer and TscoreNote::setAccidInKeyPointer + * have to be set to connect them. + * When TscoreKeySignature is deleted it should be set to 0. + */ + char accidInKeyArray[7]; - qreal loNotePos() { return m_loNotePos; } /**< Y position of lowest note on the staff */ - qreal hiNotePos() { return m_hiNotePos; } /**< Y position of highest note on the staff */ + /** Sets scordature according to given tune. + * To delete it just call this with Ttune::standardTune.*/ + void setScordature(Ttune& tune); + bool hasScordature() { return (bool)m_scordature; } /**< @p TRUE when staff has got scordature. */ + void removeScordatute(); - /** Minimal height of the staff to display all its notes. */ - qreal minHight() { return m_loNotePos - m_hiNotePos; } + void setMeter(const Tmeter& m); + TscoreMeter* scoreMeter() { return m_scoreMeter; } - /** Checks positions of all notes to find lowest and highest. - * @p doEmit determines whether this method sends appropriate signals */ - void checkNoteRange(bool doEmit = true); + qreal upperLinePos() const { return m_upperLinePos; } /**< Y position of upper line of a staff. */ + qreal lowerLinePos() const { return m_lowerStaffPos; } /**< Y position of lower line of a lower staff. */ + qreal height() const { return m_height; } // staff height + qreal width() const { return m_width; } // staff width - /** Updates rectangle of QGraphicsScene to staff bounding rectangle. */ - void updateSceneRect(); + qreal loNotePos() const { return m_loNotePos; } /**< Y position of lowest note on the staff */ + qreal hiNotePos() const { return m_hiNotePos; } /**< Y position of highest note on the staff */ - /** Returns number of a note. upperLinePos() is note nr 0 but it depends on octave (clef). */ - int notePosRelatedToClef(int pos, TnoteOffset off) { - return off.octave * 7 - (pos + 1 - (int)upperLinePos() - off.note); } + /** Minimal height of the staff to display all its notes. */ + qreal minHight() const { return m_loNotePos - m_hiNotePos; } - int notePosRelatedToClef(int pos) { return notePosRelatedToClef(pos, m_offset); } + /** Checks positions of all notes to find lowest and highest. + * @p doEmit determines whether this method sends appropriate signals */ + void checkNoteRange(bool doEmit = true); - /** Returns offset of a y coefficient of a note related to current clef. */ - int noteOffset() { return m_offset.note; } + /** Updates rectangle of QGraphicsScene to staff bounding rectangle. */ + void updateSceneRect(); - /** octave offset related to middle (one-line) octave. */ - int octaveOffset() { return m_offset.octave; } + /** Returns number of a note. upperLinePos() is note nr 0 but it depends on octave (clef). */ + int notePosRelatedToClef(int pos, TnoteOffset off) { + return off.octave * 7 - (pos + 1 - (int)upperLinePos() - off.note); } - /** Returns number of accidental in key signature, fe.: F# - 0, C# - 1 or Bb - 0, Eb - 1 */ - int accidNrInKey(int noteNr, char key); + int notePosRelatedToClef(int pos) { return notePosRelatedToClef(pos, m_offset); } - int noteToPos(const Tnote& note); /**< Return Y position of given note. */ - int fixNotePos(int pianoPos); /**< Checks is note position on grand staff and adds 2 to it. */ - qreal notesOffset(); /**< X Position of first TscoreNote on the staff (depends on clef, key and scordature) */ + /** Returns offset of a y coefficient of a note related to current clef. */ + int noteOffset() { return int(m_offset.note); } - /** Informs a staff about QGraphicsView width displaying this staff. - * With this value the staff determines maximal lines width and maximal notes count. - * If not set (0.0) - single staff, If set - m_externWidth is ignored. - * This is very important for multi-system view (vertical staves) */ - void setViewWidth(qreal viewW); + /** octave offset related to middle (one-line) octave. */ + int octaveOffset() { return int(m_offset.octave); } + + /** Returns number of accidental in key signature, fe.: F# - 0, C# - 1 or Bb - 0, Eb - 1 */ + int accidNrInKey(int noteNr, char key); + + int noteToPos(const Tnote& note); /**< Return Y position of given note. */ + int fixNotePos(int pianoPos); /**< Checks is note position on grand staff and adds 2 to it. */ + qreal notesOffset(); /**< X Position of first TscoreNote on the staff (depends on clef, key and scordature) */ - /** Returns maximal note number which staff can display in single line in view area. - * or current notes count if staff is in linear mode */ - int maxNoteCount() { return m_maxNotesCount ? m_maxNotesCount : count(); } + /** + * Informs a staff about QGraphicsView width displaying this staff. + * With this value the staff determines maximal lines width and maximal notes count. + * If not set (0.0) - single staff, If set - m_externWidth is ignored. + * This is very important for multi-system view (vertical staves) + */ + void setViewWidth(qreal viewW); + + /** + * width of QGraphicsView in scene coordinates. + */ + qreal viewWidth() { return m_viewWidth; } + + /** Returns maximal note number which staff can display in single line in view area. + * or current notes count if staff is in linear mode */ + int maxNoteCount() { return m_maxNotesCount ? m_maxNotesCount : count(); } //--- note controllers/switches - /** Switches when note segments have colored background after their note are set */ - void setSelectableNotes(bool selectable) { m_selectableNotes = selectable; } - bool selectableNotes() { return m_selectableNotes; } + /** Switches when note segments have colored background after their note are set */ + void setSelectableNotes(bool selectable) { m_selectableNotes = selectable; } + bool selectableNotes() { return m_selectableNotes; } - /** Determines whether note controllers can add/remove note to the staff. - * Notice, when enabled, 'remove' (minus) is displayed only when staff has more notes than one. */ - void enableToAddNotes(bool alowAdding); + /** Shows accidentals from key signature also near a note (in brackets) */ + void setExtraAccids(bool extra) { m_extraAccids = extra; } + bool extraAccids() { return m_extraAccids; } - /** Shows accidentals from key signature also near a note (in brackets) */ - void setExtraAccids(bool extra) { m_extraAccids = extra; } - bool extraAccids() { return m_extraAccids; } - - /** Stops/starts capturing any mouse events. */ - void setDisabled(bool disabled); + /** Stops/starts capturing any mouse events. */ + void setDisabled(bool disabled); /** With tidy key - key signature width is adjusted exactly to space occupies by visible accidentals. * Calling this invokes notes replacing when @p tidy value really changes, @@ -200,111 +223,230 @@ public: * If not exists - does nothing. */ void applyAutoAddedNote(); - virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) {}; - virtual QRectF boundingRect() const; + /** + * Width of the staff without clef, key and meter items + */ + qreal spaceForNotes(); + + /** + * Return width of the staff content (clef, key signature, meter and all notes with rhythm gaps) + * @p gapFact parameter can regulate distance between notes. + */ + qreal contentWidth(qreal gapFact = 1.0); + + int shortestRhythm() { return m_shortestR; } /**< Shortest rhythm duration in the staff */ + int longestRhythm() { return m_longestR; } /**< Longest rhythm duration in the staff */ + + /** + * Multiplexer of rhythm gaps between notes. + * It changes to place all notes nicely over entire staff width + */ + qreal gapFactor() { return m_gapFactor; } + + /** + * Virtual sum of rhythm gaps. + * Multiplied by @p m_gapFactor gives real width of all gaps + */ + qreal gapsSum() { return m_gapsSum; } + + bool hasSpaceFor(qreal newWidth = 7.0); /**< @p TRUE when there is enough space for a new note at the staff end */ + bool hasSpaceFor(const Tnote& n); /**< @p TRUE when there is enough space for given note */ + + int measureOfNoteId(int id); /**< Returns measure number where note with @p id can be placed */ + + QList<TscoreMeasure*>& measures() { return m_measures; } /**< List of measures on the staff */ + + TscoreMeasure* lastMeasure() { return m_measures.last(); } + TscoreMeasure* firstMeasure() { return m_measures.first(); } + + TscoreMeasure* nextMeasure(TscoreMeasure* before); + TscoreMeasure* nextMeasure(int id) { return id < m_measures.count() ? nextMeasure(m_measures[id]) : nextMeasure(lastMeasure()); } + + /** + * Returns pointer to the next staff or null if none or this staff is the last one. + */ + TscoreStaff* nextStaff(); + + /** + * Returns pointer to the previous staff or null if none or this staff is the first one. + */ + TscoreStaff* prevStaff(); + + bool goingDelete() { return m_goingDelete; } + + /** + * Removes @p measId measure from the list and its notes from the staff as well. + * Returns @p TscoreMeasure pointer which contains list of taken notes. + */ + TscoreMeasure* takeMeasure(int measId); + void insertMeasure(int id, TscoreMeasure* m); + + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) {}; + virtual QRectF boundingRect() const; + + /** Prints to std out debug info about this staff: [nr STAFF] in color */ + char debug(); signals: - void pianoStaffSwitched(); - void noteChanged(int index); + void pianoStaffSwitched(); + void noteChanged(int index); - /** Emitted when right button was clicked over note. - * @p selectableNotes or @p controlledNotes has to be set to @p TRUE */ - void noteSelected(int index); - void clefChanged(Tclef); + /** Emitted when right button was clicked over note. + * @p selectableNotes or @p controlledNotes has to be set to @p TRUE */ + void noteSelected(int index); + void clefChanged(Tclef); - /** When staff has no more space to display next note segment. - * Argument is staff number */ - void noMoreSpace(int); + /** When staff has no more space to display next note segment. + * Argument is staff number */ + void noMoreSpace(int); - /** Emitted usually after removing a note. Staff has extra free space. - * First argument is staff number, the second one is amount of space for new note(s) */ - void freeSpace(int, int); + /** Emitted usually after removing a note. Staff has extra free space. + * First argument is staff number, the second one is amount of space for new note(s) */ + void freeSpace(int, int); - /** There was no space for a note emitting as an argument. - * Usually it was the last note on this staff before inserting another note. */ - void noteToMove(int, TscoreNote*); + /** There was no space for a note emitting as an argument. + * Usually it was the last note on this staff before inserting another note. */ + void noteToMove(int, TscoreNote*); - /** Emitting just before note will be removed and deleted. - * First argument is staff number and second is note number in the list. - * Signal is emitted when note still exists. */ - void noteIsRemoving(int, int); + /** Emitting just before note will be removed and deleted. + * First argument is staff number and second is note number in the list. + * Signal is emitted when note still exists. */ + void noteIsRemoving(int, int); - void noteIsAdding(int, int); /**< Emitting when note is added/inserted to a staff */ + void noteIsAdding(int, int); /**< Emitting when note is added/inserted to a staff */ - /** Signals informing about changing note range on the staff. - * Sending parameters are the staff number and difference of Y position. */ - void loNoteChanged(int,qreal); - void hiNoteChanged(int,qreal); + /** Signals informing about changing note range on the staff. + * Sending parameters are the staff number and difference of Y position. */ + void loNoteChanged(int, qreal); + void hiNoteChanged(int, qreal); + /** This staff asks to take out its measure (usually the last one) with given number */ + void moveMeasure(TscoreStaff*, int); -public slots: - void onClefChanged(Tclef clef); /**< It is connected with clef, but also refresh m_offset appropriate to current clef. */ - void noteChangedAccid(int accid); /**< TscoreNote wheel event - changes accidental */ + /** + * Asks for next staff. + * In the argument pointer of the next staff is put or null if none. + */ + void getNextStaff(TscoreStaff*&); -protected: - /** Creates staff lines at first call, sets lines width, creates lower staff lines as well. - * It also calls createBrace(). */ - void prepareStaffLines(); + /** + * Asks for previous staff. + * In the argument pointer of the next staff is put or null if none. + */ + void getPrevStaff(TscoreStaff*&); + + /** When staff has no more notes */ + void staffIsEmpty(); - /** It doesn't add scordature like setScordature() method, - * just make place (re-sizes staff width if necessary) for scordature. - * setScordature calls it itself. */ - void setEnableScordtature(bool enable); - /** Calculates current width of a staff depends on is key sign. enabled. */ - void updateWidth(); +public slots: + void onClefChanged(Tclef clef); /**< It is connected with clef, but also refresh m_offset appropriate to current clef. */ - void updateIndexes(); /**< Iterates through all notes, sets theirs indexes. It must to be invoked after inserting or removing a note. */ - void updateLines(); /**< Updates staff lines */ - void updateNotesPos(int startId = 0); /**< Replaces (performs pos()) all TscoreNote items. Starts from @p startId */ - /** Protected method that creates new TscoreNote note instance and inserts it to m_scoreNotes. - * It doesn't perform any checks */ - void insert(int index); +protected: + + /** + * Creates staff lines at first call, sets lines width, creates lower staff lines as well. + * It also calls @p createBrace(). + */ + void prepareStaffLines(); + + /** + * It doesn't add scordature like @p setScordature() method, + * just make place (re-sizes staff width if necessary) for scordature. + * @p setScordature() calls it itself. + */ + void setEnableScordtature(bool enable); + + /** Calculates current width of a staff depends on is key sign. enabled. */ + void updateWidth(); + + void updateIndexes(); /**< Iterates through all notes, sets theirs indexes. It must to be invoked after inserting or removing a note. */ + void updateLines(); /**< Updates staff lines */ + void updateNotesPos(int startId = 0); /**< Replaces (performs pos()) all TscoreNote items. Starts from @p startId */ + + void noteChangedWidth(int noteId); /**< Called by @class TscoreNote */ + void prepareNoteChange(TscoreNote* sn = nullptr); + + TscoreNote* insertNote(const Tnote& note, int index, bool disabled = false); + + /** + * Fits all notes on the staff by calculating their rhythm gaps and widths. + * Determines when there is too many notes (measures) + * and shift them to the next staff. + * Updates (and fits) the gap factor @p m_gapFactor + */ + void fit(); + + /** + * Shifts given @p notesToShift list to measure @p measureNr. + * All shifted notes still belongs to this staff, + * so their positions and indexes don't change. + */ + void shiftToMeasure(int measureNr, QList<TscoreNote*>& notesToShift); + + int shiftFromMeasure(int measureNr, int dur, QList<TscoreNote*>& notesToShift); + + /** + * Every note segment call this before it will be deleted + */ + void noteGoingDestroy(QObject* n); + + /** + * Takes actual position of note item @p noteIndex and sets its @p TscoreNote::note() to appropriate value + */ + void updatePitch(int noteIndex); protected slots: - void onKeyChanged(); - void onNoteClicked(int noteIndex); - void onNoteSelected(int noteIndex); - void onAccidButtonPressed(int accid); // TnoteControl accid button pressed - void onPianoStaffChanged(Tclef clef); // clef demands piano staff - void toKeyAnimSlot(const QString& accidText, const QPointF& accidPos, int notePos); - void fromKeyAnimSlot(const QString& accidText, const QPointF& accidPos, int notePos); - void accidAnimFinished(); - void addNoteTimeOut(); - void noteDestroingSlot(QObject* n); /**< Every note segment call this before it will be deleted */ + void onKeyChanged(); + void onNoteClicked(int noteIndex); + void onNoteSelected(int noteIndex); + void onAccidButtonPressed(int accid); // TnoteControl accid button pressed + void onPianoStaffChanged(Tclef clef); // clef demands piano staff + void addNoteTimeOut(); private: - int m_staffNr; - Tscore5lines *m_5lines; - TscoreClef *m_clef; - QGraphicsSimpleTextItem *m_brace; - TscoreKeySignature *m_keySignature; - QList<TscoreNote*> m_scoreNotes; - qreal m_upperLinePos, m_lowerStaffPos; - qreal m_height, m_width; - qreal m_viewWidth; /**< width of QGraphicsView in scene coordinates. */ - TnoteOffset m_offset; - bool m_isPianoStaff; - TscoreScordature *m_scordature; - bool m_enableScord, m_tidyKey; - TcombinedAnim *m_accidAnim; - QGraphicsSimpleTextItem *m_flyAccid; - bool m_selectableNotes, m_extraAccids; - int m_maxNotesCount; - qreal m_loNotePos, m_hiNotePos; - bool m_lockRangeCheck; /**< to prevent the checking during clef switching */ - QPointer<QTimer> m_addTimer; - int m_autoAddedNoteId; /**< Index of automatically added last note. */ - QPointer<TscoreNote> m_noteWithAccidAnimed; /**< Pointer to note segment currently invoked to key animation */ + int m_staffNr; + Tscore5lines *m_5lines; + TscoreClef *m_clef; + QGraphicsSimpleTextItem *m_brace; + TscoreKeySignature *m_keySignature; + QList<TscoreNote*> m_scoreNotes; + QList<TscoreMeasure*> m_measures; + qreal m_upperLinePos, m_lowerStaffPos; + qreal m_height, m_width; + qreal m_viewWidth; /**< width of QGraphicsView in scene coordinates. */ + TnoteOffset m_offset; + bool m_isPianoStaff; + TscoreScordature *m_scordature; + bool m_enableScord, m_tidyKey; + QGraphicsSimpleTextItem *m_flyAccid; + bool m_selectableNotes, m_extraAccids; + int m_maxNotesCount; + qreal m_loNotePos, m_hiNotePos; + qreal m_allNotesWidth; /**< Width of all notes on the staff (without gaps between) */ + qreal m_gapFactor; /**< multiplexer of rhythm gaps between notes */ + qreal m_gapsSum; /**< Virtual sum of rhythm gaps - multiplied by @p m_gapFactor gives real width of all gaps */ + int m_shortestR, m_longestR; + bool m_lockRangeCheck; /**< to prevent the checking during clef switching */ + int m_autoAddedNoteId; /**< Index of automatically added last note. */ + QPointer<TscoreNote> m_noteWithAccidAnimed; /**< Pointer to note segment currently invoked to key animation */ + TscoreMeter *m_scoreMeter; + bool m_goingDelete = false; private: - void createBrace(); - int getMaxNotesNr(qreal maxWidth); /**< Calculates notes number from given width */ - void findLowestNote(); /**< Checks all Y positions of staff notes ti find lowest one */ - void findHighestNote(); /**< Checks all Y positions of staff notes ti find highest one */ - void connectNote(TscoreNote *sn); /**< Performs all TscoreNote connections to this staff */ + void createBrace(); + int getMaxNotesNr(qreal maxWidth); /**< Calculates notes number from given width */ + void findLowestNote(); /**< Checks all Y positions of staff notes ti find lowest one */ + void findHighestNote(); /**< Checks all Y positions of staff notes ti find highest one */ + + /** + * Private method that creates new TscoreNote note instance and inserts it to m_scoreNotes. + * It doesn't perform any checks, and returns instance of created note + */ + TscoreNote* insert(int index); }; #endif // TSCORESTAFF_H + diff --git a/src/libs/score/tscoretie.cpp b/src/libs/score/tscoretie.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a809630d6ca5ad92f269b6fb36bc011286d31fbc --- /dev/null +++ b/src/libs/score/tscoretie.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + * Copyright (C) 2016 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 "tscoretie.h" +#include "tscorenote.h" +#include <music/tnote.h> +#include <tnoofont.h> +#include <QtWidgets/qgraphicsitem.h> +#include <QtGui/qbrush.h> + +#include <QtCore/qdebug.h> + +/** + * @p TscoreTie class supports all kinds of tie combinations. + * As long as graphically tie bow is an independent single element between two notes + * and it is not aware about others + * logically, through @p Tnote::rtm.tie many notes can be connected. + * + * @p TrhythmPane calls @p TscoreTie::check when user clicks tie item on it + * + * TODO: - graphically a bow doesn't look like score engraving tradition used to do. + * - extra tie at the staff beginning collides with key signature + */ + +TscoreTie::TscoreTie(TscoreNote* sn1, TscoreNote* sn2) : + m_firstNote(sn1), + m_scondNote(sn2) +{ + if (firstNote()->note()->rtm.tie()) // continue tie if first note already belongs to someone + firstNote()->note()->rtm.setTie(Trhythm::e_tieCont); + else // or start a new one + firstNote()->note()->rtm.setTie(Trhythm::e_tieStart); + + if (secondNote()->tie()) // second note has a tie so set it to continue + secondNote()->note()->rtm.setTie(Trhythm::e_tieCont); + else // or end the tie on second note + secondNote()->note()->rtm.setTie(Trhythm::e_tieEnd); + + m_tieItem = newTie(firstNote()); + updateLength(); + checkStaves(); +} + + +TscoreTie::~TscoreTie() +{ + qDebug() << "[TIE] deleting" << firstNote()->index() << firstNote()->note()->toText() << "with" << secondNote()->index(); + Trhythm& firstRtm = firstNote()->note()->rtm; + if (firstRtm.tie() == Trhythm::e_tieCont) + firstRtm.setTie(Trhythm::e_tieEnd); + else + firstRtm.setTie(Trhythm::e_noTie); + + Trhythm& secondRtm = secondNote()->note()->rtm; + if (secondRtm.tie() == Trhythm::e_tieCont) // when second note has a tie - set it to tie start + secondRtm.setTie(Trhythm::e_tieStart); + else + secondRtm.setTie(Trhythm::e_noTie); + + delete m_tieItem; + if (m_extraTieItem) + delete m_extraTieItem; +} + + +/*static*/ +TscoreTie* TscoreTie::check(TscoreNote* sn) { + if (sn && !sn->tie()) { + auto next = sn->nextNote(); + if (!sn->note()->isRest() && next && !next->note()->isRest() && sn->note()->compareNotes(*next->note())) + return new TscoreTie(sn, next); + } + return nullptr; +} + + +void TscoreTie::checkStaves() { + if ((m_extraTieItem == nullptr) != (firstNote()->staff() == secondNote()->staff())) { + if (m_extraTieItem) { + delete m_extraTieItem; + m_extraTieItem = nullptr; + } else { + m_extraTieItem = newTie(secondNote()); + QTransform t; + t.scale(1.0, secondNote()->note()->rtm.stemDown() ? -1.0 : 1.0); + m_extraTieItem->setTransform(t); + m_extraTieItem->setPos(-m_extraTieItem->boundingRect().width(), + secondNote()->notePos() + (secondNote()->note()->rtm.stemDown() ? 3.7 : - 1.8)); + } + updateLength(); + } +} + +//################################################################################################# +//################### PROTECTED ############################################ +//################################################################################################# + +void TscoreTie::updateLength() { + QTransform t; + if (firstNote()->staff() == secondNote()->staff()) + t.scale((secondNote()->x() - firstNote()->x() - firstNote()->width() * 0.8) / m_tieItem->boundingRect().width(), + firstNote()->note()->rtm.stemDown() ? -1.0 : 1.0); + else + t.scale(1.0, firstNote()->note()->rtm.stemDown() ? -1.0 : 1.0); + m_tieItem->setTransform(t); + m_tieItem->setPos(firstNote()->width(), firstNote()->notePos() + (firstNote()->note()->rtm.stemDown() ? 3.7 : - 1.8)); +} + + +QGraphicsSimpleTextItem * TscoreTie::newTie(TscoreNote* parentNote) { + auto tie = new QGraphicsSimpleTextItem(QString(QChar(0xe18c)), parentNote); // tie symbol + tie->setFont(TnooFont(5)); + tie->setBrush(QBrush(parentNote->color())); + return tie; +} + + + + + + + diff --git a/src/libs/score/tscoretie.h b/src/libs/score/tscoretie.h new file mode 100644 index 0000000000000000000000000000000000000000..109726158ea70f54f6efaf61913b8deaf7749eb4 --- /dev/null +++ b/src/libs/score/tscoretie.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2016 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/>. * + ***************************************************************************/ + +#ifndef TSCORETIE_H +#define TSCORETIE_H + + +#include <nootkacoreglobal.h> + + +class QGraphicsSimpleTextItem; +class TscoreNote; + + +/** + * The bow connecting two notes (ligature) + * It could be created by constructor but there is static method + * @p TscoreTie::check() with @p TscoreNote as a parameter + * that checks given note and next one to it are they the same + * and returns @p TscoreTie instance or @p nullptr if not created. + * The common way to create tie is to call @p TscoreNote::tieWithNext() + * + * There is single bow adjusted to notes position if they lay at the same staff. + * When second note lays on the next one, an additional bow is added - before second note. + * @p checkStaves method manages it. It is invoked only from @p TscoreMeasure::setStaff. + * + * @p TscoreTie class also manages logical states of ties in @p TscoreNote::note.rtm.tie. + * Constructor and destrucor keep it up to date + */ +class NOOTKACORE_EXPORT TscoreTie +{ + + friend class TscoreNote; + +public: + explicit TscoreTie(TscoreNote* sn1, TscoreNote* sn2); + virtual ~TscoreTie(); + + /** + * Initial static method to create new tie with next note to given one + * or delete the tie if given note has it already. + */ + static TscoreTie* check(TscoreNote* sn); + + + TscoreNote* firstNote() { return m_firstNote; } + TscoreNote* secondNote() { return m_scondNote; } + + /** + * Compares are staves of notes the same. + * Adds extra tie if different. + */ + void checkStaves(); + +protected: + void updateLength(); /**< Updates ties length according to current position */ + +private: + QGraphicsSimpleTextItem* newTie(TscoreNote* parentNote); + +private: + TscoreNote *m_firstNote, *m_scondNote; + QGraphicsSimpleTextItem *m_tieItem; + QGraphicsSimpleTextItem *m_extraTieItem = nullptr; /**< Additional tie when 2nd note lays on another staff */ +}; + +#endif // TSCORETIE_H diff --git a/src/main.cpp b/src/main.cpp index 9f42f7873fcd5b919c39e87fe8aa179642946f45..3506e50e5968314d91c8d0ed35d82da3ca0baa32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,6 @@ #if defined (Q_OS_ANDROID) - #include "ttouchstyle.h" #include <Android/tandroid.h> #endif #include <tinitcorelib.h> @@ -35,6 +34,9 @@ #include <QtCore/qfile.h> #include <QtCore/qsettings.h> +#include <declarative/descore.h> +#include <declarative/denote.h> + static QString logFile; @@ -104,6 +106,10 @@ int main(int argc, char *argv[]) return 111; a->setWindowIcon(QIcon(Tpath::img("nootka"))); + + qmlRegisterType<DeScore>("Score", 1, 0, "Score"); + qmlRegisterType<DeNote>("Score", 1, 0, "Note"); + // creating main window e = new QQmlApplicationEngine; e->rootContext()->setContextProperty(QStringLiteral("Tpath"), &pathObj); diff --git a/src/qml/MainWindow.qml b/src/qml/MainWindow.qml index 76e4fe0416c4d5a29db36da1229cf5b86dffa982..6992bb8565d618a68c607b47cebe586f0eef39fa 100644 --- a/src/qml/MainWindow.qml +++ b/src/qml/MainWindow.qml @@ -21,6 +21,7 @@ import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 import QtQuick.Window 2.0 +import Score 1.0 ApplicationWindow { id: nootkaWindow @@ -46,6 +47,29 @@ ApplicationWindow { header: TtoolBar {} + ColumnLayout { + anchors.fill: parent + Score { + Layout.fillWidth: true + Layout.fillHeight: true + + Note { + pitch: 6 + octave: 1 + alter: -1 + rhythm: Note.Esixteenth + } + Note { + pitch: 5 + octave: 0 + alter: 1 + rhythm: Note.Esixteenth + } + } + + Rectangle { height: nootkaWindow.height / 3; Layout.fillWidth: true; color: "blue" } + } + Component.onCompleted: {} }