//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2009-2013 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= #include "pianoview.h" #include "pianoruler.h" #include "pianokeyboard.h" #include "shortcut.h" #include "musescore.h" #include "scoreview.h" #include "preferences.h" #include "libmscore/part.h" #include "libmscore/staff.h" #include "libmscore/measure.h" #include "libmscore/chord.h" #include "libmscore/rest.h" #include "libmscore/score.h" #include "libmscore/note.h" #include "libmscore/slur.h" #include "libmscore/tie.h" #include "libmscore/tuplet.h" #include "libmscore/segment.h" #include "libmscore/noteevent.h" namespace Ms { extern MuseScore* mscore; static const qreal MIN_DRAG_DIST_SQ = 9; const BarPattern PianoView::barPatterns[] = { {"C maj/A min", {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1}}, {"Db maj/Bb min", {1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0}}, {"D maj/B min", {0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}}, {"Eb maj/C min", {1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0}}, {"E maj/Db min", {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1}}, {"F maj/D min", {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0}}, {"Gb maj/Eb min", {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}}, {"G maj/E min", {1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}}, {"Ab maj/F min", {1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0}}, {"A maj/Gb min", {0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1}}, {"Bb maj/G min", {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0}}, {"B maj/Ab min", {0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1}}, {"C Diminished", {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}}, {"Db Diminished", {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}}, {"D Diminished", {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1}}, {"C Half/Whole", {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}}, {"Db Half/Whole", {0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1}}, {"D Half/Whole", {1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1}}, {"C Whole tone", {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}}, {"Db Whole tone", {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}}, {"C Augmented", {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}}, {"Db Augmented", {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}}, {"D Augmented", {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0}}, {"Eb Augmented", {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}}, {"", {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}} }; //--------------------------------------------------------- // PianoItem //--------------------------------------------------------- PianoItem::PianoItem(Note* n, PianoView* pianoView) : _note(n), _pianoView(pianoView) { } //--------------------------------------------------------- // boundingRectTicks //--------------------------------------------------------- QRect PianoItem::boundingRectTicks(NoteEvent* evt) { Chord* chord = _note->chord(); int ticks = chord->ticks().ticks(); int tieLen = _note->playTicks() - ticks; int pitch = _note->pitch() + (evt ? evt->pitch() : 0); int len = (evt ? ticks * evt->len() / 1000 : ticks) + tieLen; int x1 = _note->chord()->tick().ticks() + (evt ? evt->ontime() * ticks / 1000 : 0); qreal y1 = pitch; QRect rect; rect.setRect(x1, y1, len, 1); return rect; } //--------------------------------------------------------- // boundingRectPixels //--------------------------------------------------------- QRect PianoItem::boundingRectPixels(NoteEvent* evt) { QRect rect = boundingRectTicks(evt); qreal tix2pix = _pianoView->xZoom(); int noteHeight = _pianoView->noteHeight(); rect.setRect(_pianoView->tickToPixelX(rect.x()), (127 - rect.y()) * noteHeight, rect.width() * tix2pix, rect.height() * noteHeight ); return rect; } //--------------------------------------------------------- // boundingRect //--------------------------------------------------------- QRect PianoItem::boundingRect() { Chord* chord = _note->chord(); int ticks = chord->ticks().ticks(); int tieLen = _note->playTicks() - ticks; int len = ticks + tieLen; int pitch = _note->pitch(); qreal tix2pix = _pianoView->xZoom(); int noteHeight = _pianoView->noteHeight(); qreal x1 = _pianoView->tickToPixelX(_note->chord()->tick().ticks()); qreal y1 = (127 - pitch) * noteHeight; QRect rect; rect.setRect(x1, y1, len * tix2pix, noteHeight); return rect; } //--------------------------------------------------------- // intersects //--------------------------------------------------------- bool PianoItem::intersectsBlock(int startTick, int endTick, int highPitch, int lowPitch, NoteEvent* evt) { QRect r = boundingRectTicks(evt); int pitch = r.y(); return r.right() >= startTick && r.left() <= endTick && pitch >= lowPitch && pitch <= highPitch; } //--------------------------------------------------------- // intersects //--------------------------------------------------------- bool PianoItem::intersects(int startTick, int endTick, int highPitch, int lowPitch) { if (_pianoView->playEventsView()) { for (NoteEvent& e : _note->playEvents()) if (intersectsBlock(startTick, endTick, highPitch, lowPitch, &e)) return true; return false; } else return intersectsBlock(startTick, endTick, highPitch, lowPitch, 0); } //--------------------------------------------------------- // getTweakNoteEvent //--------------------------------------------------------- NoteEvent* PianoItem::getTweakNoteEvent() { //Get topmost play event for note if (_note->playEvents().size() > 0) return &(_note->playEvents()[_note->playEvents().size() - 1]); return 0; } //--------------------------------------------------------- // paintNoteBlock //--------------------------------------------------------- void PianoItem::paintNoteBlock(QPainter* painter, NoteEvent* evt) { int roundRadius = 3; QColor noteDeselected; QColor noteSelected; switch (preferences.globalStyle()) { case MuseScoreStyleType::DARK_FUSION: noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_UNSEL_COLOR)); noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_SEL_COLOR)); break; default: noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_UNSEL_COLOR)); noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_SEL_COLOR)); break; } QColor noteColor = _note->selected() ? noteSelected : noteDeselected; painter->setBrush(noteColor); painter->setPen(QPen(noteColor.darker(250))); QRectF bounds = boundingRectPixels(evt); painter->drawRoundedRect(bounds, roundRadius, roundRadius); //Pitch name if (bounds.width() >= 20 && bounds.height() >= 12) { QRectF textRect(bounds.x() + 2, bounds.y(), bounds.width() - 6, bounds.height() + 1); QRectF textHiliteRect(bounds.x() + 3, bounds.y() + 1, bounds.width() - 6, bounds.height()); QFont f("FreeSans", 8); painter->setFont(f); //Note name QString name = tpc2name(_note->tpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false); painter->setPen(QPen(noteColor.lighter(130))); painter->drawText(textHiliteRect, Qt::AlignLeft | Qt::AlignTop, name); painter->setPen(QPen(noteColor.darker(180))); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, name); //Voice number if (bounds.width() >= 26) { painter->setPen(QPen(noteColor.lighter(130))); painter->drawText(textHiliteRect, Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1)); painter->setPen(QPen(noteColor.darker(180))); painter->drawText(textRect, Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1)); } } } //--------------------------------------------------------- // paint //--------------------------------------------------------- void PianoItem::paint(QPainter* painter) { painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); if (_pianoView->playEventsView()) { for (NoteEvent& e : _note->playEvents()) paintNoteBlock(painter, &e); } else paintNoteBlock(painter, 0); } //--------------------------------------------------------- // PianoView //--------------------------------------------------------- PianoView::PianoView() : QGraphicsView() { setFrameStyle(QFrame::NoFrame); setLineWidth(0); setMidLineWidth(0); setScene(new QGraphicsScene); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorUnderMouse); setMouseTracking(true); _timeType = TType::TICKS; _playEventsView = true; _staff = 0; chord = 0; _barPattern = 0; _tuplet = 1; _subdiv = 0; _noteHeight = DEFAULT_KEY_HEIGHT; _xZoom = X_ZOOM_INITIAL; dragStarted = false; mouseDown = false; dragStyle = DragStyle::NONE; inProgressUndoEvent = false; } //--------------------------------------------------------- // ~PianoView //--------------------------------------------------------- PianoView::~PianoView() { clearNoteData(); } //--------------------------------------------------------- // drawBackground //--------------------------------------------------------- void PianoView::drawBackground(QPainter* p, const QRectF& r) { if (_staff == 0) return; Score* _score = _staff->score(); setFrameShape(QFrame::NoFrame); QColor colSelectionBox; QColor colWhiteKeyBg; QColor colGutter; QColor colBlackKeyBg; QColor colGridLine; switch (preferences.globalStyle()) { case MuseScoreStyleType::DARK_FUSION: colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_SELECTION_BOX_COLOR)); colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_WHITE_COLOR)); colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_BASE_COLOR)); colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_BLACK_COLOR)); colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_GRIDLINE_COLOR)); break; default: colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_SELECTION_BOX_COLOR)); colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_WHITE_COLOR)); colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_BASE_COLOR)); colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_BLACK_COLOR)); colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_GRIDLINE_COLOR)); break; } const QColor colSelectionBoxFill = QColor( colSelectionBox.red(), colSelectionBox.green(), colSelectionBox.blue(), 128); const QPen penLineMajor = QPen(colGridLine, 2.0, Qt::SolidLine); const QPen penLineMinor = QPen(colGridLine, 1.0, Qt::SolidLine); const QPen penLineSub = QPen(colGridLine, 1.0, Qt::DotLine); QRectF r1; r1.setCoords(-1000000.0, 0.0, tickToPixelX(0), 1000000.0); QRectF r2; r2.setCoords(tickToPixelX(ticks), 0.0, 1000000.0, 1000000.0); p->fillRect(r, colWhiteKeyBg); if (r.intersects(r1)) p->fillRect(r.intersected(r1), colGutter); if (r.intersects(r2)) p->fillRect(r.intersected(r2), colGutter); // // draw horizontal grid lines // qreal y1 = r.y(); qreal y2 = y1 + r.height(); qreal x1 = qMax(r.x(), (qreal)tickToPixelX(0)); qreal x2 = qMin(x1 + r.width(), (qreal)tickToPixelX(ticks)); int topPitch = ceil((_noteHeight * 128 - y1) / _noteHeight); int bmPitch = floor((_noteHeight * 128 - y2) / _noteHeight); Part* part = _staff->part(); Interval transp = part->instrument()->transpose(); //MIDI notes span [0, 127] and map to pitches starting at C-1 for (int pitch = bmPitch; pitch <= topPitch; ++pitch) { int y = (127 - pitch) * _noteHeight; int degree = (pitch - transp.chromatic + 60) % 12; const BarPattern& pat = barPatterns[_barPattern]; // if (degree == 1 || degree == 3 || degree == 6 || degree == 8 || degree == 10) { if (!pat.isWhiteKey[degree]) { qreal px0 = qMax(r.x(), (qreal)tickToPixelX(0)); qreal px1 = qMin(r.x() + r.width(), (qreal)tickToPixelX(ticks)); QRectF hbar; hbar.setCoords(px0, y, px1, y + _noteHeight); p->fillRect(hbar, colBlackKeyBg); } //Lines between rows p->setPen(degree == 0 ? penLineMajor : penLineMinor); p->drawLine(QLineF(x1, y + _noteHeight, x2, y + _noteHeight)); } // // draw vertical grid lines // Pos pos1(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x1), 0), TType::TICKS); Pos pos2(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x2), 0), TType::TICKS); int bar1, bar2, beat, tick; pos1.mbt(&bar1, &beat, &tick); pos2.mbt(&bar2, &beat, &tick); //Draw bar lines const int minBeatGap = 20; for (int bar = bar1; bar <= bar2; ++bar) { Pos barPos(_score->tempomap(), _score->sigmap(), bar, 0, 0); //Beat lines int beatsInBar = barPos.timesig().timesig().numerator(); int ticksPerBeat = barPos.timesig().timesig().beatTicks(); double pixPerBeat = ticksPerBeat * _xZoom; int beatSkip = ceil(minBeatGap / pixPerBeat); // int subExp = qMin((int)floor(log2(pixPerBeat / minBeatGap)), _subBeats); // int numSubBeats = pow(2, subExp); //Round up to next power of 2 beatSkip = (int)pow(2, ceil(log(beatSkip)/log(2))); for (int beat1 = 0; beat1 < beatsInBar; beat1 += beatSkip) { Pos beatPos(_score->tempomap(), _score->sigmap(), bar, beat1, 0); double x = tickToPixelX(beatPos.time(TType::TICKS)); p->setPen(penLineMinor); p->drawLine(x, y1, x, y2); int subbeats = _tuplet * (1 << _subdiv); for (int sub = 1; sub < subbeats; ++sub) { Pos subBeatPos(_score->tempomap(), _score->sigmap(), bar, beat1, sub * MScore::division / subbeats); x = tickToPixelX(subBeatPos.time(TType::TICKS)); p->setPen(penLineSub); p->drawLine(x, y1, x, y2); } } //Bar line double x = tickToPixelX(barPos.time(TType::TICKS)); p->setPen(x > 0 ? penLineMajor : QPen(Qt::black, 2.0)); p->drawLine(x, y1, x, y2); } //Draw notes for (int i = 0; i < noteList.size(); ++i) noteList[i]->paint(p); //Draw locators for (int i = 0; i < 3; ++i) { if (_locator[i].valid()) { p->setPen(QPen(i == 0 ? Qt::red : Qt::blue, 2)); qreal x = tickToPixelX(_locator[i].time(TType::TICKS)); p->drawLine(x, y1, x, y2); } } //Draw drag selection box if (dragStarted && dragStyle == DragStyle::SELECTION_RECT) { int minX = qMin(mouseDownPos.x(), lastMousePos.x()); int minY = qMin(mouseDownPos.y(), lastMousePos.y()); int maxX = qMax(mouseDownPos.x(), lastMousePos.x()); int maxY = qMax(mouseDownPos.y(), lastMousePos.y()); QRectF rect(minX, minY, maxX - minX + 1, maxY - minY + 1); p->setPen(QPen(colSelectionBox, 2)); p->setBrush(QBrush(colSelectionBoxFill, Qt::SolidPattern)); p->drawRect(rect); } } //--------------------------------------------------------- // moveLocator //--------------------------------------------------------- void PianoView::moveLocator(int /*i*/) { scene()->update(); } //--------------------------------------------------------- // pixelXToTick //--------------------------------------------------------- int PianoView::pixelXToTick(int pixX) { return (int)(pixX / _xZoom) - MAP_OFFSET; } //--------------------------------------------------------- // tickToPixelX //--------------------------------------------------------- int PianoView::tickToPixelX(int tick) { return (int)(tick + MAP_OFFSET) * _xZoom; } //--------------------------------------------------------- // wheelEvent //--------------------------------------------------------- void PianoView::wheelEvent(QWheelEvent* event) { int step = event->delta() / 120; if (event->modifiers() == 0) { //Vertical scroll QGraphicsView::wheelEvent(event); } else if (event->modifiers() == Qt::ShiftModifier) { //Horizontal scroll QWheelEvent we(event->pos(), event->delta(), event->buttons(), 0, Qt::Horizontal); QGraphicsView::wheelEvent(&we); } else if (event->modifiers() == Qt::ControlModifier) { //Vertical zoom QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect(); qreal mouseYNote = (event->y() + (int)viewRect.y()) / (qreal)_noteHeight; _noteHeight = qMax(qMin(_noteHeight + step, MAX_KEY_HEIGHT), MIN_KEY_HEIGHT); emit noteHeightChanged(_noteHeight); updateBoundingSize(); updateNotes(); int mousePixY = (int)(mouseYNote * _noteHeight); verticalScrollBar()->setValue(mousePixY - event->y()); scene()->update(); } else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) { //Horizontal zoom QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect(); int mouseXTick = pixelXToTick(event->x() + (int)viewRect.x()); _xZoom *= pow(X_ZOOM_RATIO, step); emit xZoomChanged(_xZoom); updateBoundingSize(); updateNotes(); int mousePixX = tickToPixelX(mouseXTick); horizontalScrollBar()->setValue(mousePixX - event->x()); scene()->update(); } } //--------------------------------------------------------- // showPopupMenu //--------------------------------------------------------- void PianoView::showPopupMenu(const QPoint& pos) { QMenu popup(this); // popup.addAction(getAction("cut")); // popup.addAction(getAction("copy")); popup.addAction(getAction("paste")); // popup.addAction(getAction("swap")); popup.addAction(getAction("delete")); popup.exec(pos); } //--------------------------------------------------------- // contextMenuEvent //--------------------------------------------------------- void PianoView::contextMenuEvent(QContextMenuEvent *event) { showPopupMenu(event->globalPos()); } //--------------------------------------------------------- // mousePressEvent //--------------------------------------------------------- void PianoView::mousePressEvent(QMouseEvent* event) { bool rightBn = event->button() == Qt::RightButton; if (!rightBn) { mouseDown = true; mouseDownPos = mapToScene(event->pos()); lastMousePos = mouseDownPos; } } //--------------------------------------------------------- // mouseReleaseEvent //--------------------------------------------------------- void PianoView::mouseReleaseEvent(QMouseEvent* event) { int modifiers = QGuiApplication::keyboardModifiers(); bool bnShift = modifiers & Qt::ShiftModifier; bool bnCtrl = modifiers & Qt::ControlModifier; bool rightBn = event->button() == Qt::RightButton; if (rightBn) { //Right clicks have been handled as popup menu return; } NoteSelectType selType = bnShift ? (bnCtrl ? NoteSelectType::SUBTRACT : NoteSelectType::XOR) : (bnCtrl ? NoteSelectType::ADD : NoteSelectType::REPLACE); if (dragStarted) { if (dragStyle == DragStyle::SELECTION_RECT) { //Update selection qreal minX = qMin(mouseDownPos.x(), lastMousePos.x()); qreal minY = qMin(mouseDownPos.y(), lastMousePos.y()); qreal maxX = qMax(mouseDownPos.x(), lastMousePos.x()); qreal maxY = qMax(mouseDownPos.y(), lastMousePos.y()); int startTick = pixelXToTick((int)minX); int endTick = pixelXToTick((int)maxX); int lowPitch = (int)floor(128 - maxY / noteHeight()); int highPitch = (int)ceil(128 - minY / noteHeight()); selectNotes(startTick, endTick, lowPitch, highPitch, selType); } else if (dragStyle == DragStyle::MOVE_NOTES) { //Keep last note drag event, if any if (inProgressUndoEvent) inProgressUndoEvent = false; } dragStarted = false; } else { Score* score = _staff->score(); int pickTick = pixelXToTick((int)mouseDownPos.x()); int pickPitch = pixelYToPitch(mouseDownPos.y()); PianoItem *pn = pickNote(pickTick, pickPitch); if (pn) { if (selType == NoteSelectType::REPLACE) selType = NoteSelectType::FIRST; mscore->play(pn->note()); score->setPlayNote(false); selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType); } else { if (!bnShift && !bnCtrl) { //Select an empty pixel - should clear selection selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType); } else if (!bnShift && bnCtrl) { //Insert a new note at nearest subbeat int subbeats = _tuplet * (1 << _subdiv); int subbeatTicks = MScore::division / subbeats; int roundedTick = (pickTick / subbeatTicks) * subbeatTicks; InputState& is = score->inputState(); int voice = score->inputState().voice(); int track = _staff->idx() * VOICES + voice; NoteVal nv(pickPitch); Fraction t = Fraction::fromTicks(roundedTick); Segment* seg = score->tick2segment(t); score->expandVoice(seg, track); ChordRest* e = score->findCR(t, track); if (e && !e->tuplet() && _tuplet == 1) { //Ignore tuplets score->startCmd(); ChordRest* cr0; ChordRest* cr1; Fraction frac = is.duration().fraction(); //Default to quarter note if faction is invalid if (!frac.isValid() || frac.isZero()) frac.set(1, 4); if (cutChordRest(e, track, roundedTick, cr0, cr1)) { score->setNoteRest(cr1->segment(), track, nv, frac); } else { if (cr0->isChord() && cr0->ticks().ticks() == frac.ticks()) { Chord* ch = toChord(cr0); score->addNote(ch, nv); } else { score->setNoteRest(cr0->segment(), track, nv, frac); } } score->endCmd(); } } else if (bnShift && !bnCtrl) { //Append a pitch to our curent chord/rest int voice = score->inputState().voice(); //Find best chord to add to int track = _staff->idx() * VOICES + voice; Fraction pt = Fraction::fromTicks(pickTick); Segment* seg = score->tick2segment(pt); score->expandVoice(seg, track); ChordRest* e = score->findCR(pt, track); if (e && e->isChord()) { Chord* ch = toChord(e); if (pt >= e->tick() && pt < (ch->tick() + ch->ticks())) { NoteVal nv(pickPitch); score->startCmd(); score->addNote(ch, nv); score->endCmd(); } } else if (e && e->isRest()) { Rest* r = toRest(e); NoteVal nv(pickPitch); score->startCmd(); score->setNoteRest(r->segment(), track, nv, r->ticks()); score->endCmd(); } } else if (bnShift && bnCtrl) { //Cut the chord/rest at the nearest subbeat int voice = score->inputState().voice(); //Find best chord to add to int track = _staff->idx() * VOICES + voice; int subbeats = _tuplet * (1 << _subdiv); int subbeatTicks = MScore::division / subbeats; int roundedTick = (pickTick / subbeatTicks) * subbeatTicks; Fraction rt = Fraction::fromTicks(roundedTick); Segment* seg = score->tick2segment(rt); score->expandVoice(seg, track); ChordRest* e = score->findCR(rt, track); if (e && !e->tuplet() && _tuplet == 1) { score->startCmd(); int startTick = e->tick().ticks(); if (roundedTick != startTick) { ChordRest* cr0; ChordRest* cr1; cutChordRest(e, track, roundedTick, cr0, cr1); } score->endCmd(); } } } } dragStyle = DragStyle::NONE; mouseDown = false; scene()->update(); } //--------------------------------------------------------- // mouseMoveEvent //--------------------------------------------------------- void PianoView::mouseMoveEvent(QMouseEvent* event) { lastMousePos = mapToScene(event->pos()); if (mouseDown && !dragStarted) { qreal dx = lastMousePos.x() - mouseDownPos.x(); qreal dy = lastMousePos.y() - mouseDownPos.y(); if (dx * dx + dy * dy >= MIN_DRAG_DIST_SQ) { //Start dragging dragStarted = true; //Check for move note int tick = pixelXToTick(mouseDownPos.x()); int mouseDownPitch = pixelYToPitch(mouseDownPos.y()); PianoItem* pi = pickNote(tick, mouseDownPitch); if (pi) { if (!pi->note()->selected()) { selectNotes(tick, tick, mouseDownPitch, mouseDownPitch, NoteSelectType::REPLACE); } dragStyle = DragStyle::MOVE_NOTES; lastDragPitch = mouseDownPitch; } else { dragStyle = DragStyle::SELECTION_RECT; } } } if (dragStarted) { if (dragStyle == DragStyle::MOVE_NOTES) { int curPitch = pixelYToPitch(lastMousePos.y()); if (curPitch != lastDragPitch) { int pitchDelta = curPitch - lastDragPitch; Score* score = _staff->score(); if (inProgressUndoEvent) { // score->undoRedo(true, 0, false); inProgressUndoEvent = false; } score->startCmd(); score->upDownDelta(pitchDelta); score->endCmd(); inProgressUndoEvent = true; lastDragPitch = curPitch; } } scene()->update(); } //Update mouse tracker QPointF p(mapToScene(event->pos())); int pitch = (int)((_noteHeight * 128 - p.y()) / _noteHeight); emit pitchChanged(pitch); int tick = pixelXToTick(p.x()); if (tick < 0) { tick = 0; trackingPos.setTick(tick); trackingPos.setInvalid(); } else trackingPos.setTick(tick); emit trackingPosChanged(trackingPos); } //--------------------------------------------------------- // cutChordRest //--------------------------------------------------------- bool PianoView::cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& cr0, ChordRest*& cr1) { int startTick = e->segment()->tick().ticks(); int tcks = e->ticks().ticks(); if (cutTick <= startTick || cutTick > startTick + tcks) { cr0 = e; cr1 = 0; return false; } //Deselect note being cut if (e->isChord()) { Chord* ch = toChord(e); for (Note* n: ch->notes()) { n->setSelected(false); } } else if (e->isRest()) { Rest* r = toRest(e); r->setSelected(false); } //Subdivide at the cut tick NoteVal nv(-1); Score* score = _staff->score(); score->setNoteRest(e->segment(), track, nv, Fraction::fromTicks(cutTick) - e->tick()); ChordRest *nextCR = score->findCR(Fraction::fromTicks(cutTick), track); // nextCR->segment()->setTick(cutTick); Chord* ch0 = 0; if (nextCR->isChord()) { //Copy chord into initial segment Chord* ch1 = toChord(nextCR); for (Note* n: ch1->notes()) { NoteVal nx = n->noteVal(); if (!ch0) { ChordRest* cr = score->findCR(Fraction::fromTicks(startTick), track); score->setNoteRest(cr->segment(), track, nx, cr->ticks()); ch0 = toChord(score->findCR(Fraction::fromTicks(startTick), track)); } else { score->addNote(ch0, nx); } } } cr0 = ch0; cr1 = nextCR; return true; } //--------------------------------------------------------- // selectNotes //--------------------------------------------------------- PianoItem* PianoView::pickNote(int tick, int pitch) { for (int i = 0; i < noteList.size(); ++i) { PianoItem* pi = noteList[i]; if (pi->intersects(tick, tick, pitch, pitch)) return pi; } return 0; } //--------------------------------------------------------- // selectNotes //--------------------------------------------------------- void PianoView::selectNotes(int startTick, int endTick, int lowPitch, int highPitch, NoteSelectType selType) { Score* score = _staff->score(); //score->masterScore()->cmdState().reset(); // DEBUG: should not be necessary score->startCmd(); QList oldSel; for (int i = 0; i < noteList.size(); ++i) { PianoItem* pi = noteList[i]; if (pi->note()->selected()) oldSel.append(pi); } Selection& selection = score->selection(); selection.deselectAll(); for (int i = 0; i < noteList.size(); ++i) { PianoItem* pi = noteList[i]; bool inBounds = pi->intersects(startTick, endTick, highPitch, lowPitch); bool sel; switch (selType) { default: case NoteSelectType::REPLACE: sel = inBounds; break; case NoteSelectType::XOR: sel = inBounds != oldSel.contains(pi); break; case NoteSelectType::ADD: sel = inBounds || oldSel.contains(pi); break; case NoteSelectType::SUBTRACT: sel = !inBounds && oldSel.contains(pi); break; case NoteSelectType::FIRST: sel = inBounds && selection.elements().empty(); break; } if (sel) selection.add(pi->note()); } for (MuseScoreView* view : score->getViewer()) view->updateAll(); scene()->update(); score->setUpdateAll(); score->update(); score->endCmd(); emit selectionChanged(); } //--------------------------------------------------------- // leaveEvent //--------------------------------------------------------- void PianoView::leaveEvent(QEvent* event) { emit pitchChanged(-1); trackingPos.setInvalid(); emit trackingPosChanged(trackingPos); QGraphicsView::leaveEvent(event); } //--------------------------------------------------------- // ensureVisible //--------------------------------------------------------- void PianoView::ensureVisible(int tick) { QRectF rect = mapToScene(viewport()->geometry()).boundingRect(); qreal xpos = tickToPixelX(tick); qreal margin = rect.width() / 2; if (xpos < rect.x() + margin) horizontalScrollBar()->setValue(qMax(xpos - margin, 0.0)); else if (xpos >= rect.x() + rect.width() - margin) horizontalScrollBar()->setValue(qMax(xpos - rect.width() + margin, 0.0)); } //--------------------------------------------------------- // updateBoundingSize //--------------------------------------------------------- void PianoView::updateBoundingSize() { Measure* lm = _staff->score()->lastMeasure(); ticks = (lm->tick() + lm->ticks()).ticks(); scene()->setSceneRect(0.0, 0.0, double((ticks + MAP_OFFSET * 2) * _xZoom), _noteHeight * 128); } //--------------------------------------------------------- // setStaff //--------------------------------------------------------- void PianoView::setStaff(Staff* s, Pos* l) { _locator = l; if (_staff == s) return; _staff = s; setEnabled(_staff != nullptr); if (!_staff) { scene()->blockSignals(true); // block changeSelection() scene()->clear(); clearNoteData(); scene()->blockSignals(false); return; } trackingPos.setContext(_staff->score()->tempomap(), _staff->score()->sigmap()); updateBoundingSize(); updateNotes(); QRectF boundingRect; bool brInit = false; QRectF boundingRectSel; bool brsInit = false; foreach (PianoItem* item, noteList) { if (!brInit) { boundingRect = item->boundingRect(); brInit = true; } else boundingRect |= item->boundingRect(); if (item->note()->selected()) { if (!brsInit) { boundingRectSel = item->boundingRect(); brsInit = true; } else boundingRectSel |= item->boundingRect(); } } QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect(); if (brsInit) { horizontalScrollBar()->setValue(boundingRectSel.x()); verticalScrollBar()->setValue(qMax(boundingRectSel.y() + (boundingRectSel.height() - viewRect.height()) / 2, 0.0)); } else if (brInit) { horizontalScrollBar()->setValue(boundingRect.x()); verticalScrollBar()->setValue(qMax(boundingRect.y() - (boundingRectSel.height() - viewRect.height()) / 2, 0.0)); } else { horizontalScrollBar()->setValue(0); verticalScrollBar()->setValue(qMax(viewRect.y() - viewRect.height() / 2, 0.0)); } } //--------------------------------------------------------- // addChord //--------------------------------------------------------- void PianoView::addChord(Chord* chrd, int voice) { for (Chord* c : chrd->graceNotes()) addChord(c, voice); for (Note* note : chrd->notes()) { if (note->tieBack()) continue; noteList.append(new PianoItem(note, this)); } } //--------------------------------------------------------- // updateNotes //--------------------------------------------------------- void PianoView::updateNotes() { scene()->blockSignals(true); // block changeSelection() scene()->clearFocus(); scene()->clear(); clearNoteData(); int staffIdx = _staff->idx(); if (staffIdx == -1) return; SegmentType st = SegmentType::ChordRest; for (Segment* s = _staff->score()->firstSegment(st); s; s = s->next1(st)) { for (int voice = 0; voice < VOICES; ++voice) { int track = voice + staffIdx * VOICES; Element* e = s->element(track); if (e && e->isChord()) addChord(toChord(e), voice); } } for (int i = 0; i < 3; ++i) moveLocator(i); scene()->blockSignals(false); scene()->update(sceneRect()); } //--------------------------------------------------------- // updateNotes //--------------------------------------------------------- void PianoView::clearNoteData() { for (int i = 0; i < noteList.size(); ++i) delete noteList[i]; noteList.clear(); } //--------------------------------------------------------- // getSelectedItems //--------------------------------------------------------- QList PianoView::getSelectedItems() { QList list; for (int i = 0; i < noteList.size(); ++i) { if (noteList[i]->note()->selected()) list.append(noteList[i]); } return list; } //--------------------------------------------------------- // getItems //--------------------------------------------------------- QList PianoView::getItems() { QList list; for (int i = 0; i < noteList.size(); ++i) list.append(noteList[i]); return list; } //--------------------------------------------------------- // getAction // returns action for shortcut //--------------------------------------------------------- QAction* PianoView::getAction(const char* id) { Shortcut* s = Shortcut::getShortcut(id); return s ? s->action() : 0; } //--------------------------------------------------------- // setXZoom //--------------------------------------------------------- void PianoView::setXZoom(int value) { if (_xZoom != value) { _xZoom = value; scene()->update(); emit xZoomChanged(_xZoom); } } //--------------------------------------------------------- // setBarPattern //--------------------------------------------------------- void PianoView::setBarPattern(int value) { if (_barPattern != value) { _barPattern = value; scene()->update(); emit barPatternChanged(_barPattern); } } //--------------------------------------------------------- // setSubBeats //--------------------------------------------------------- void PianoView::setTuplet(int value) { if (_tuplet != value) { _tuplet = value; scene()->update(); emit tupletChanged(_tuplet); } } //--------------------------------------------------------- // setSubdiv //--------------------------------------------------------- void PianoView::setSubdiv(int value) { if (_subdiv != value) { _subdiv = value; scene()->update(); emit subdivChanged(_subdiv); } } }