1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2009-2013 Werner Schweer
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2
9 //  as published by the Free Software Foundation and appearing in
10 //  the file LICENCE.GPL
11 //=============================================================================
12 
13 
14 #include "pianoview.h"
15 #include "pianoruler.h"
16 #include "pianokeyboard.h"
17 #include "shortcut.h"
18 #include "musescore.h"
19 #include "scoreview.h"
20 #include "preferences.h"
21 #include "libmscore/part.h"
22 #include "libmscore/staff.h"
23 #include "libmscore/measure.h"
24 #include "libmscore/chord.h"
25 #include "libmscore/rest.h"
26 #include "libmscore/score.h"
27 #include "libmscore/note.h"
28 #include "libmscore/slur.h"
29 #include "libmscore/tie.h"
30 #include "libmscore/tuplet.h"
31 #include "libmscore/segment.h"
32 #include "libmscore/noteevent.h"
33 #include "libmscore/undo.h"
34 #include "libmscore/utils.h"
35 
36 namespace Ms {
37 
38 extern MuseScore* mscore;
39 
40 static const QString PIANO_NOTE_MIME_TYPE = "application/musescore/pianorollnotes";
41 
42 static const qreal MIN_DRAG_DIST_SQ = 9;
43 
44 const BarPattern PianoView::barPatterns[] = {
45       {QT_TRANSLATE_NOOP("BarPattern", "C major / A minor"),   {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1}},
46       {QT_TRANSLATE_NOOP("BarPattern", "D♭ major / B♭ minor"), {1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0}},
47       {QT_TRANSLATE_NOOP("BarPattern", "D major / B minor"),   {0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}},
48       {QT_TRANSLATE_NOOP("BarPattern", "E♭ major / C minor"),  {1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0}},
49       {QT_TRANSLATE_NOOP("BarPattern", "E major / C♯ minor"),  {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1}},
50       {QT_TRANSLATE_NOOP("BarPattern", "F major / D minor"),   {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0}},
51       {QT_TRANSLATE_NOOP("BarPattern", "G♭ major / E♭ minor"), {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}},
52       {QT_TRANSLATE_NOOP("BarPattern", "G major / E minor"),   {1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}},
53       {QT_TRANSLATE_NOOP("BarPattern", "A♭ major / F minor"),  {1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0}},
54       {QT_TRANSLATE_NOOP("BarPattern", "A major / F♯ minor"),  {0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1}},
55       {QT_TRANSLATE_NOOP("BarPattern", "B♭ major / G minor"),  {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0}},
56       {QT_TRANSLATE_NOOP("BarPattern", "B major / G♯ minor"),  {0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1}},
57       {QT_TRANSLATE_NOOP("BarPattern", "C Diminished"),  {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}},
58       {QT_TRANSLATE_NOOP("BarPattern", "D♭ Diminished"), {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}},
59       {QT_TRANSLATE_NOOP("BarPattern", "D Diminished"),  {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1}},
60       {QT_TRANSLATE_NOOP("BarPattern", "C Half/Whole"),  {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}},
61       {QT_TRANSLATE_NOOP("BarPattern", "D♭ Half/Whole"), {0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1}},
62       {QT_TRANSLATE_NOOP("BarPattern", "D Half/Whole"),  {1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1}},
63       {QT_TRANSLATE_NOOP("BarPattern", "C Whole tone"),  {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}},
64       {QT_TRANSLATE_NOOP("BarPattern", "D♭ Whole tone"), {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}},
65       {QT_TRANSLATE_NOOP("BarPattern", "C Augmented"),   {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}},
66       {QT_TRANSLATE_NOOP("BarPattern", "D♭ Augmented"),  {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}},
67       {QT_TRANSLATE_NOOP("BarPattern", "D Augmented"),   {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0}},
68       {QT_TRANSLATE_NOOP("BarPattern", "E♭ Augmented"),  {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}},
69       {"",              {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}
70 };
71 
72 //---------------------------------------------------------
73 //   PianoItem
74 //---------------------------------------------------------
75 
PianoItem(Note * n,PianoView * pianoView)76 PianoItem::PianoItem(Note* n, PianoView* pianoView)
77    : _note(n), _pianoView(pianoView)
78       {
79       }
80 
81 
82 //---------------------------------------------------------
83 //   boundingRectTicks
84 //---------------------------------------------------------
85 
boundingRectTicks(NoteEvent * evt)86 QRect PianoItem::boundingRectTicks(NoteEvent* evt)
87       {
88       Chord* chord = _note->chord();
89       int ticks = chord->ticks().ticks();
90       int tieLen = _note->playTicks() - ticks;
91       int pitch = _note->pitch() + (evt ? evt->pitch() : 0);
92       int len = (evt ? ticks * evt->len() / 1000 : ticks) + tieLen;
93 
94       int x1 = _note->chord()->tick().ticks()
95             + (evt ? evt->ontime() * ticks / 1000 : 0);
96       qreal y1 = pitch;
97 
98       QRect rect;
99       rect.setRect(x1, y1, len, 1);
100       return rect;
101       }
102 
103 //---------------------------------------------------------
104 //   boundingRectPixels
105 //---------------------------------------------------------
106 
boundingRectPixels(NoteEvent * evt)107 QRect PianoItem::boundingRectPixels(NoteEvent* evt)
108       {
109       QRect rect = boundingRectTicks(evt);
110 
111       qreal tix2pix = _pianoView->xZoom();
112       int noteHeight = _pianoView->noteHeight();
113 
114       rect.setRect(_pianoView->tickToPixelX(rect.x()),
115               (127 - rect.y()) * noteHeight,
116               rect.width() * tix2pix,
117               rect.height() * noteHeight
118               );
119 
120       return rect;
121       }
122 
123 //---------------------------------------------------------
124 //   boundingRect
125 //---------------------------------------------------------
126 
boundingRect()127 QRect PianoItem::boundingRect() {
128       Chord* chord = _note->chord();
129       int ticks = chord->ticks().ticks();
130       int tieLen = _note->playTicks() - ticks;
131       int len = ticks + tieLen;
132       int pitch = _note->pitch();
133 
134       qreal tix2pix = _pianoView->xZoom();
135       int noteHeight = _pianoView->noteHeight();
136 
137       qreal x1 = _pianoView->tickToPixelX(_note->chord()->tick().ticks());
138       qreal y1 = (127 - pitch) * noteHeight;
139 
140       QRect rect;
141       rect.setRect(x1, y1, len * tix2pix, noteHeight);
142       return rect;
143       }
144 
145 
146 
147 //---------------------------------------------------------
148 //   intersects
149 //---------------------------------------------------------
150 
intersectsBlock(int startTick,int endTick,int highPitch,int lowPitch,NoteEvent * evt)151 bool PianoItem::intersectsBlock(int startTick, int endTick, int highPitch, int lowPitch, NoteEvent* evt)
152       {
153       QRect r = boundingRectTicks(evt);
154       int pitch = r.y();
155 
156       return r.right() >= startTick && r.left() <= endTick
157             && pitch >= lowPitch && pitch <= highPitch;
158       }
159 
160 //---------------------------------------------------------
161 //   intersects
162 //---------------------------------------------------------
163 
intersects(int startTick,int endTick,int highPitch,int lowPitch)164 bool PianoItem::intersects(int startTick, int endTick, int highPitch, int lowPitch)
165       {
166       if (_pianoView->playEventsView()) {
167             for (NoteEvent& e : _note->playEvents())
168                   if (intersectsBlock(startTick, endTick, highPitch, lowPitch, &e))
169                         return true;
170             return false;
171             }
172       else
173             return intersectsBlock(startTick, endTick, highPitch, lowPitch, 0);
174 
175       }
176 
177 
178 //---------------------------------------------------------
179 //   getTweakNoteEvent
180 //---------------------------------------------------------
181 
getTweakNoteEvent()182 NoteEvent* PianoItem::getTweakNoteEvent()
183       {
184       //Get topmost play event for note
185       if (_note->playEvents().size() > 0)
186             return &(_note->playEvents()[_note->playEvents().size() - 1]);
187 
188       return 0;
189       }
190 
191 
192 //---------------------------------------------------------
193 //   paintNoteBlock
194 //---------------------------------------------------------
195 
paintNoteBlock(QPainter * painter,NoteEvent * evt)196 void PianoItem::paintNoteBlock(QPainter* painter, NoteEvent* evt)
197       {
198       QColor noteDeselected;
199       QColor noteSelected;
200       QColor tieColor;
201 
202       switch (preferences.effectiveGlobalStyle()) {
203             case MuseScoreEffectiveStyleType::DARK_FUSION:
204                   noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_UNSEL_COLOR));
205                   noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_SEL_COLOR));
206                   tieColor = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_TIE_COLOR));
207                   break;
208             default:
209                   noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_UNSEL_COLOR));
210                   noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_SEL_COLOR));
211                   tieColor = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_TIE_COLOR));
212                   break;
213             }
214 
215       QColor noteColor = _note->selected() ? noteSelected : noteDeselected;
216       painter->setBrush(noteColor);
217 
218       painter->setPen(QPen(noteColor.darker(250)));
219       QRectF bounds = boundingRectPixels(evt);
220       painter->drawRoundedRect(bounds, NOTE_BLOCK_CORNER_RADIUS, NOTE_BLOCK_CORNER_RADIUS);
221 
222       //Tie markings
223       painter->setPen(QPen(tieColor));
224 
225       for (Note* note = _note; note->tieFor(); note = note->tieFor()->endNote()) {
226             Chord* chord = note->chord();
227             int start = chord->tick().ticks();
228             int duration = chord->ticks().ticks();
229             int xpos = _pianoView->tickToPixelX(start + duration);
230 
231             painter->drawLine(QLineF(xpos, bounds.y(), xpos, bounds.y() + bounds.height()));
232             }
233 
234       //Pitch name
235       if (bounds.width() >= 20 && bounds.height() >= 12) {
236             QRectF textRect(bounds.x() + 2, bounds.y(), bounds.width() - 6, bounds.height() + 1);
237             QRectF textHiliteRect(bounds.x() + 3, bounds.y() + 1, bounds.width() - 6, bounds.height());
238 
239             QFont f("FreeSans", 8);
240             painter->setFont(f);
241 
242             //Note name
243             QString name = qApp->translate("InspectorAmbitus", tpc2name(_note->tpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false).replace("b", "♭").replace("#", "♯").toUtf8().constData());
244             painter->setPen(QPen(noteColor.lighter(130)));
245             painter->drawText(textHiliteRect,
246                   Qt::AlignLeft | Qt::AlignTop, name);
247 
248             painter->setPen(QPen(noteColor.darker(180)));
249             painter->drawText(textRect,
250                   Qt::AlignLeft | Qt::AlignTop, name);
251 
252             //Voice number
253             if (bounds.width() >= 26) {
254                   painter->setPen(QPen(noteColor.lighter(130)));
255                   painter->drawText(textHiliteRect,
256                         Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1));
257 
258                   painter->setPen(QPen(noteColor.darker(180)));
259                   painter->drawText(textRect,
260                         Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1));
261                   }
262             }
263       }
264 
265 
266 //---------------------------------------------------------
267 //   paint
268 //---------------------------------------------------------
269 
paint(QPainter * painter)270 void PianoItem::paint(QPainter* painter)
271       {
272       painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
273 
274       if (_pianoView->playEventsView()) {
275             for (NoteEvent& e : _note->playEvents())
276                   paintNoteBlock(painter, &e);
277             }
278       else
279             paintNoteBlock(painter, 0);
280       }
281 
282 
283 //---------------------------------------------------------
284 //   PianoView
285 //---------------------------------------------------------
286 
PianoView()287 PianoView::PianoView()
288    : QGraphicsView()
289       {
290       setFrameStyle(QFrame::NoFrame);
291       setLineWidth(0);
292       setMidLineWidth(0);
293       setScene(new QGraphicsScene);
294       setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
295       setResizeAnchor(QGraphicsView::AnchorUnderMouse);
296       setMouseTracking(true);
297       _timeType   = TType::TICKS;
298       _playEventsView = true;
299       _staff      = nullptr;
300       _chord      = nullptr;
301       _locator    = nullptr;
302       _ticks      = 0;
303       _barPattern = 0;
304       _tuplet     = 1;
305       _subdiv     = 0;
306       _noteHeight = DEFAULT_KEY_HEIGHT;
307       _xZoom      = X_ZOOM_INITIAL;
308       _dragStarted = false;
309       _dragStartPitch = 0;
310       _mouseDown   = false;
311       _dragStyle   = DragStyle::NONE;
312       _inProgressUndoEvent = false;
313 
314       memset(_pitchHighlight, 0, 128);
315       }
316 
317 //---------------------------------------------------------
318 //   ~PianoView
319 //---------------------------------------------------------
320 
~PianoView()321 PianoView::~PianoView()
322       {
323       clearNoteData();
324       }
325 
326 //---------------------------------------------------------
327 //   drawBackground
328 //---------------------------------------------------------
329 
drawBackground(QPainter * p,const QRectF & r)330 void PianoView::drawBackground(QPainter* p, const QRectF& r)
331       {
332       if (_staff == 0)
333             return;
334       Score* _score = _staff->score();
335       setFrameShape(QFrame::NoFrame);
336 
337       QColor colSelectionBox;
338 
339       QColor colWhiteKeyBg;
340       QColor colGutter;
341       QColor colBlackKeyBg;
342       QColor colHilightKeyBg;
343 
344       QColor colGridLine;
345 
346       switch (preferences.effectiveGlobalStyle()) {
347             case MuseScoreEffectiveStyleType::DARK_FUSION:
348                   colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_SELECTION_BOX_COLOR));
349 
350                   colHilightKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_HIGHLIGHT_COLOR));
351                   colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_WHITE_COLOR));
352                   colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_BASE_COLOR));
353                   colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_BLACK_COLOR));
354 
355                   colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_GRIDLINE_COLOR));
356                   break;
357             default:
358                   colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_SELECTION_BOX_COLOR));
359 
360                   colHilightKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_HIGHLIGHT_COLOR));
361                   colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_WHITE_COLOR));
362                   colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_BASE_COLOR));
363                   colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_BLACK_COLOR));
364 
365                   colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_GRIDLINE_COLOR));
366                   break;
367             }
368 
369       const QColor colSelectionBoxFill = QColor(
370                         colSelectionBox.red(), colSelectionBox.green(), colSelectionBox.blue(),
371                         128);
372 
373       const QPen penLineMajor = QPen(colGridLine, 2.0, Qt::SolidLine);
374       const QPen penLineMinor = QPen(colGridLine, 1.0, Qt::SolidLine);
375       const QPen penLineSub = QPen(colGridLine, 1.0, Qt::DotLine);
376 
377       QRectF r1;
378       r1.setCoords(-1000000.0, 0.0, tickToPixelX(0), 1000000.0);
379       QRectF r2;
380       r2.setCoords(tickToPixelX(_ticks), 0.0, 1000000.0, 1000000.0);
381 
382       p->fillRect(r, colWhiteKeyBg);
383       if (r.intersects(r1))
384             p->fillRect(r.intersected(r1), colGutter);
385       if (r.intersects(r2))
386             p->fillRect(r.intersected(r2), colGutter);
387 
388       //
389       // draw horizontal grid lines
390       //
391       qreal y1 = r.y();
392       qreal y2 = y1 + r.height();
393       qreal x1 = qMax(r.x(), (qreal)tickToPixelX(0));
394       qreal x2 = qMin(x1 + r.width(), (qreal)tickToPixelX(_ticks));
395 
396       int topPitch = ceil((_noteHeight * 128 - y1) / _noteHeight);
397       int bmPitch = floor((_noteHeight * 128 - y2) / _noteHeight);
398 
399       Part* part = _staff->part();
400       Interval transp = part->instrument()->transpose();
401 
402       //MIDI notes span [0, 127] and map to pitches starting at C-1
403       for (int pitch = bmPitch; pitch <= topPitch; ++pitch) {
404             int y = (127 - pitch) * _noteHeight;
405 
406             int degree = (pitch - transp.chromatic + 60) % 12;
407             const BarPattern& pat = barPatterns[_barPattern];
408 
409             if (!pat.isWhiteKey[degree] || _pitchHighlight[pitch]) {
410                   qreal px0 = qMax(r.x(), (qreal)tickToPixelX(0));
411                   qreal px1 = qMin(r.x() + r.width(), (qreal)tickToPixelX(_ticks));
412                   QRectF hbar;
413 
414                   hbar.setCoords(px0, y, px1, y + _noteHeight);
415                   p->fillRect(hbar,
416                         _pitchHighlight[pitch] ? colHilightKeyBg : colBlackKeyBg);
417             }
418 
419             //Lines between rows
420             p->setPen(degree == 0 ? penLineMajor : penLineMinor);
421             p->drawLine(QLineF(x1, y + _noteHeight, x2, y + _noteHeight));
422             }
423 
424       //
425       // draw vertical grid lines
426       //
427       Pos pos1(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x1), 0), TType::TICKS);
428       Pos pos2(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x2), 0), TType::TICKS);
429 
430       int bar1, bar2, beat, tick;
431       pos1.mbt(&bar1, &beat, &tick);
432       pos2.mbt(&bar2, &beat, &tick);
433 
434       //Draw bar lines
435       const int minBeatGap = 20;
436 
437       for (int bar = bar1; bar <= bar2; ++bar) {
438             Pos barPos(_score->tempomap(), _score->sigmap(), bar, 0, 0);
439 
440             //Beat lines
441             int beatsInBar = barPos.timesig().timesig().numerator();
442             int ticksPerBeat = barPos.timesig().timesig().beatTicks();
443             double pixPerBeat = ticksPerBeat * _xZoom;
444             int beatSkip = ceil(minBeatGap / pixPerBeat);
445 
446             //Round up to next power of 2
447             beatSkip = (int)pow(2, ceil(log(beatSkip)/log(2)));
448 
449             for (int beat1 = 0; beat1 < beatsInBar; beat1 += beatSkip) {
450                   Pos beatPos(_score->tempomap(), _score->sigmap(), bar, beat1, 0);
451                   double x = tickToPixelX(beatPos.time(TType::TICKS));
452                   p->setPen(penLineMinor);
453                   p->drawLine(x, y1, x, y2);
454 
455                   int subbeats = _tuplet * (1 << _subdiv);
456 
457                   for (int sub = 1; sub < subbeats; ++sub) {
458                       Pos subBeatPos(_score->tempomap(), _score->sigmap(), bar, beat1, sub * MScore::division / subbeats);
459                       x = tickToPixelX(subBeatPos.time(TType::TICKS));
460 
461                       p->setPen(penLineSub);
462                       p->drawLine(x, y1, x, y2);
463                       }
464 
465                   }
466 
467             //Bar line
468             double x = tickToPixelX(barPos.time(TType::TICKS));
469             p->setPen(x > 0 ? penLineMajor : QPen(Qt::black, 2.0));
470             p->drawLine(x, y1, x, y2);
471             }
472 
473       //Draw notes
474       for (int i = 0; i < _noteList.size(); ++i)
475             _noteList[i]->paint(p);
476 
477       if (_dragStyle == DragStyle::NOTES)
478             drawDraggedNotes(p);
479 
480       //Draw locators
481       for (int i = 0; i < 3; ++i) {
482             if (_locator[i].valid())
483                   {
484                   p->setPen(QPen(i == 0 ? Qt::red : Qt::blue, 2));
485                   qreal x = tickToPixelX(_locator[i].time(TType::TICKS));
486                   p->drawLine(x, y1, x, y2);
487                   }
488             }
489 
490       //Draw drag selection box
491       if (_dragStarted && _dragStyle == DragStyle::SELECTION_RECT && _editNoteTool == PianoRollEditTool::SELECT) {
492             int minX = qMin(_mouseDownPos.x(), _lastMousePos.x());
493             int minY = qMin(_mouseDownPos.y(), _lastMousePos.y());
494             int maxX = qMax(_mouseDownPos.x(), _lastMousePos.x());
495             int maxY = qMax(_mouseDownPos.y(), _lastMousePos.y());
496             QRectF rect(minX, minY, maxX - minX + 1, maxY - minY + 1);
497 
498             p->setPen(QPen(colSelectionBox, 2));
499             p->setBrush(QBrush(colSelectionBoxFill, Qt::SolidPattern));
500             p->drawRect(rect);
501             }
502       }
503 
504 
505 //---------------------------------------------------------
506 //   moveLocator
507 //---------------------------------------------------------
508 
moveLocator(int)509 void PianoView::moveLocator(int /*i*/)
510       {
511       scene()->update();
512       }
513 
514 
515 //---------------------------------------------------------
516 //   pixelXToTick
517 //---------------------------------------------------------
518 
pixelXToTick(int pixX)519 int PianoView::pixelXToTick(int pixX)
520       {
521       return static_cast<int>(pixX / _xZoom) - MAP_OFFSET;
522       }
523 
524 
525 //---------------------------------------------------------
526 //   tickToPixelX
527 //---------------------------------------------------------
528 
tickToPixelX(int tick)529 int PianoView::tickToPixelX(int tick)
530       {
531       return static_cast<int>(tick + MAP_OFFSET) * _xZoom;
532       }
533 
534 
535 //---------------------------------------------------------
536 //   zoomView
537 //---------------------------------------------------------
538 
zoomView(int step,bool horizontal,int centerX,int centerY)539 void PianoView::zoomView(int step, bool horizontal, int centerX, int centerY)
540     {
541     if (horizontal) {
542             //Horizontal zoom
543             QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
544 
545             int mouseXTick = pixelXToTick(centerX + (int)viewRect.x());
546 
547             _xZoom *= pow(X_ZOOM_RATIO, step);
548             emit xZoomChanged(_xZoom);
549 
550             updateBoundingSize();
551             updateNotes();
552 
553             int mousePixX = tickToPixelX(mouseXTick);
554             horizontalScrollBar()->setValue(mousePixX - centerX);
555 
556             scene()->update();
557             }
558       else {
559             //Vertical zoom
560             QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
561             qreal mouseYNote = (centerY + (int)viewRect.y()) / (qreal)_noteHeight;
562 
563             _noteHeight = qMax(qMin(_noteHeight + step, MAX_KEY_HEIGHT), MIN_KEY_HEIGHT);
564             emit noteHeightChanged(_noteHeight);
565 
566             updateBoundingSize();
567             updateNotes();
568 
569             int mousePixY = static_cast<int>(mouseYNote * _noteHeight);
570             verticalScrollBar()->setValue(mousePixY - centerY);
571 
572             scene()->update();
573             }
574 
575       }
576 
577 //---------------------------------------------------------
578 //   wheelEvent
579 //---------------------------------------------------------
580 
wheelEvent(QWheelEvent * event)581 void PianoView::wheelEvent(QWheelEvent* event)
582       {
583       int step = event->angleDelta().y() / 120;
584 
585       if (event->modifiers() == 0) {
586             //Vertical scroll
587             QGraphicsView::wheelEvent(event);
588             }
589       else if (event->modifiers() == Qt::ShiftModifier) {
590             //Horizontal scroll
591             QWheelEvent we(event->pos(), event->delta(), event->buttons(), 0, Qt::Horizontal);
592             QGraphicsView::wheelEvent(&we);
593             }
594       else if (event->modifiers() == Qt::ControlModifier) {
595             //Vertical zoom
596             zoomView(step, false, event->x(), event->y());
597             }
598       else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
599             //Horizontal zoom
600             zoomView(step, true, event->x(), event->y());
601             }
602       }
603 
604 //---------------------------------------------------------
605 //   showPopupMenu
606 //---------------------------------------------------------
607 
showPopupMenu(const QPoint & posGlobal)608 void PianoView::showPopupMenu(const QPoint& posGlobal)
609       {
610       QMenu popup(this);
611 
612       QAction* act;
613 
614       act = new QAction(tr("Cut notes"));
615       connect(act, &QAction::triggered, this, &PianoView::cutNotes);
616       popup.addAction(act);
617 
618       act = new QAction(tr("Copy notes"));
619       connect(act, &QAction::triggered, this, &PianoView::copyNotes);
620       popup.addAction(act);
621 
622       act = new QAction(tr("Paste notes here"));
623       connect(act, &QAction::triggered, this, &PianoView::pasteNotesAtCursor);
624       popup.addAction(act);
625 
626       popup.addAction(getAction("delete"));
627 
628       popup.addSeparator();
629 
630       act = new QAction(tr("Set Voice 1"));
631       connect(act, &QAction::triggered, this, [=](){this->setNotesToVoice(0);});
632       popup.addAction(act);
633 
634       act = new QAction(tr("Set Voice 2"));
635       connect(act, &QAction::triggered, this, [=](){this->setNotesToVoice(1);});
636       popup.addAction(act);
637 
638       act = new QAction(tr("Set Voice 3"));
639       connect(act, &QAction::triggered, this, [=](){this->setNotesToVoice(2);});
640       popup.addAction(act);
641 
642       act = new QAction(tr("Set Voice 4"));
643       connect(act, &QAction::triggered, this, [=](){this->setNotesToVoice(3);});
644       popup.addAction(act);
645 
646       popup.addSeparator();
647 
648       QMenu* menuTuplet = new QMenu(tr("Tuplets"));
649       for (auto i : { "duplet", "triplet", "quadruplet", "quintuplet", "sextuplet",
650             "septuplet", "octuplet", "nonuplet", "tuplet-dialog" })
651             menuTuplet->addAction(getAction(i));
652       popup.addMenu(menuTuplet);
653 
654       popup.exec(posGlobal);
655       }
656 
657 //---------------------------------------------------------
658 //   contextMenuEvent
659 //---------------------------------------------------------
660 
contextMenuEvent(QContextMenuEvent * event)661 void PianoView::contextMenuEvent(QContextMenuEvent *event)
662       {
663       _popupMenuPos = mapToScene(event->pos());
664 
665       showPopupMenu(event->globalPos());
666       }
667 
668 //---------------------------------------------------------
669 //   keyReleaseEvent
670 //---------------------------------------------------------
671 
keyReleaseEvent(QKeyEvent * event)672 void PianoView::keyReleaseEvent(QKeyEvent* event) {
673       if (_dragStyle == DragStyle::NOTES || _dragStyle == DragStyle::SELECTION_RECT) {
674             if (event->key() == Qt::Key_Escape) {
675                   //Cancel drag
676                   _dragStyle = DragStyle::CANCELLED;
677                   _dragNoteCache = "";
678                   _dragStarted = false;
679                   scene()->update();
680                   }
681             }
682       }
683 
684 
685 //---------------------------------------------------------
686 //   mousePressEvent
687 //---------------------------------------------------------
688 
mousePressEvent(QMouseEvent * event)689 void PianoView::mousePressEvent(QMouseEvent* event)
690       {
691       bool rightBn = event->button() == Qt::RightButton;
692       if (!rightBn) {
693             _mouseDown = true;
694             _mouseDownPos = mapToScene(event->pos());
695             _lastMousePos = _mouseDownPos;
696             scene()->update();
697             }
698       }
699 
700 //---------------------------------------------------------
701 //   mouseReleaseEvent
702 //---------------------------------------------------------
703 
mouseReleaseEvent(QMouseEvent * event)704 void PianoView::mouseReleaseEvent(QMouseEvent* event)
705       {
706       if (_dragStyle == DragStyle::CANCELLED) {
707             _dragStyle = DragStyle::NONE;
708             _mouseDown = false;
709             scene()->update();
710             return;
711             }
712 
713       int modifiers = QGuiApplication::keyboardModifiers();
714       bool bnShift = modifiers & Qt::ShiftModifier;
715       bool bnCtrl = modifiers & Qt::ControlModifier;
716 
717       bool rightBn = event->button() == Qt::RightButton;
718       if (rightBn) {
719             //Right clicks have been handled as popup menu
720             return;
721             }
722 
723 
724       NoteSelectType selType = bnShift ? (bnCtrl ? NoteSelectType::SUBTRACT : NoteSelectType::XOR)
725               : (bnCtrl ? NoteSelectType::ADD : NoteSelectType::REPLACE);
726 
727       if (_dragStarted) {
728             if (_dragStyle == DragStyle::SELECTION_RECT) {
729                   //Update selection
730                   qreal minX = qMin(_mouseDownPos.x(), _lastMousePos.x());
731                   qreal minY = qMin(_mouseDownPos.y(), _lastMousePos.y());
732                   qreal maxX = qMax(_mouseDownPos.x(), _lastMousePos.x());
733                   qreal maxY = qMax(_mouseDownPos.y(), _lastMousePos.y());
734 
735                   int startTick = pixelXToTick((int)minX);
736                   int endTick = pixelXToTick((int)maxX);
737                   int lowPitch = (int)floor(128 - maxY / noteHeight());
738                   int highPitch = (int)ceil(128 - minY / noteHeight());
739 
740                   selectNotes(startTick, endTick, lowPitch, highPitch, selType);
741                   }
742             else if (_dragStyle == DragStyle::NOTES) {
743                   if (toolCanDragNotes()) {
744                         finishNoteGroupDrag();
745 
746                         //Keep last note drag event, if any
747                         if (_inProgressUndoEvent)
748                               _inProgressUndoEvent = false;
749                         }
750                   }
751 
752             _dragStarted = false;
753             }
754       else {
755             //This was just a click, not a drag
756             switch (_editNoteTool) {
757             case SELECT:
758                   handleSelectionClick();
759                   break;
760             case CHANGE_LENGTH:
761                   changeChordLength(_mouseDownPos);
762                   break;
763             case ERASE:
764                   eraseNote(_mouseDownPos);
765                   break;
766             case INSERT_NOTE:
767                   insertNote(modifiers);
768                   break;
769             case APPEND_NOTE:
770                   appendNoteToChord(_mouseDownPos);
771                   break;
772             case CUT_CHORD:
773                   cutChord(_mouseDownPos);
774                   break;
775             case TIE:
776                   toggleTie(_mouseDownPos);
777                   break;
778             default:
779                 break;
780                 }
781 
782             }
783 
784 
785       _dragStyle = DragStyle::NONE;
786       _mouseDown = false;
787       scene()->update();
788       }
789 
790 
791 
792 //---------------------------------------------------------
793 //   mouseMoveEvent
794 //---------------------------------------------------------
795 
mouseMoveEvent(QMouseEvent * event)796 void PianoView::mouseMoveEvent(QMouseEvent* event)
797       {
798       if (_dragStyle == DragStyle::CANCELLED)
799             return;
800 
801       _lastMousePos = mapToScene(event->pos());
802 
803       if (_mouseDown && !_dragStarted) {
804             qreal dx = _lastMousePos.x() - _mouseDownPos.x();
805             qreal dy = _lastMousePos.y() - _mouseDownPos.y();
806 
807             if (dx * dx + dy * dy >= MIN_DRAG_DIST_SQ) {
808                   //Start dragging
809                   _dragStarted = true;
810 
811                   //Check for move note
812                   int tick = pixelXToTick(_mouseDownPos.x());
813                   int mouseDownPitch = pixelYToPitch(_mouseDownPos.y());
814                   PianoItem* pi = pickNote(tick, mouseDownPitch);
815                   if (pi && toolCanDragNotes()) {
816                         if (!pi->note()->selected()) {
817                               selectNotes(tick, tick, mouseDownPitch, mouseDownPitch, NoteSelectType::REPLACE);
818                               }
819                         _dragStyle = DragStyle::NOTES;
820                         _dragStartPitch = mouseDownPitch;
821                         _dragNoteCache = serializeSelectedNotes();
822                         }
823                   else if (!pi && _editNoteTool == PianoRollEditTool::SELECT)
824                         _dragStyle = DragStyle::SELECTION_RECT;
825                   else
826                         _dragStyle = DragStyle::NONE;
827                   }
828             }
829 
830       if (_dragStarted) {
831           switch (_editNoteTool) {
832           case SELECT:
833           case INSERT_NOTE:
834           case APPEND_NOTE:
835           case CUT_CHORD:
836           case TIE:
837                 scene()->update();
838                 break;
839           case CHANGE_LENGTH:
840                 changeChordLength(_lastMousePos);
841                 break;
842           case ERASE:
843                 eraseNote(_lastMousePos);
844                 break;
845           default:
846                 break;
847                 }
848             }
849 
850 
851       //Update mouse tracker
852       QPointF p(mapToScene(event->pos()));
853       int pitch = static_cast<int>((_noteHeight * 128 - p.y()) / _noteHeight);
854       emit pitchChanged(pitch);
855 
856       int tick = pixelXToTick(p.x());
857       if (tick < 0) {
858             tick = 0;
859             trackingPos.setTick(tick);
860             trackingPos.setInvalid();
861             }
862       else
863             trackingPos.setTick(tick);
864       emit trackingPosChanged(trackingPos);
865       }
866 
867 
868 //---------------------------------------------------------
869 //   dragSelectionNoteGroup
870 //---------------------------------------------------------
871 
dragSelectionNoteGroup()872 void PianoView::dragSelectionNoteGroup() {
873       int curPitch = pixelYToPitch(_lastMousePos.y());
874       if (curPitch != _dragStartPitch) {
875             int pitchDelta = curPitch - _dragStartPitch;
876 
877             Score* score = _staff->score();
878             if (_inProgressUndoEvent) {
879                   _inProgressUndoEvent = false;
880                   }
881 
882             score->startCmd();
883             score->upDownDelta(pitchDelta);
884             score->endCmd();
885 
886             _inProgressUndoEvent = true;
887             _dragStartPitch = curPitch;
888             }
889       scene()->update();
890 
891       }
892 
893 
894 //---------------------------------------------------------
895 //   getSegmentNotes
896 //---------------------------------------------------------
897 
getSegmentNotes(Segment * seg,int track)898 QVector<Note*> PianoView::getSegmentNotes(Segment* seg, int track)
899       {
900       QVector<Note*> notes;
901 
902       ChordRest* cr = seg->cr(track);
903       if (cr && cr->isChord()) {
904             Chord* chord = toChord(cr);
905             notes.append(QVector<Note*>::fromStdVector(chord->notes()));
906             }
907 
908       return notes;
909       }
910 
911 
912 //---------------------------------------------------------
913 //   addNote
914 //---------------------------------------------------------
915 
addNote(Fraction startTick,Fraction duration,int pitch,int track)916 QVector<Note*> PianoView::addNote(Fraction startTick, Fraction duration, int pitch, int track)
917       {
918       NoteVal nv(pitch);
919 
920       Score* score = _staff->score();
921 
922       QVector<Note*> addedNotes;
923 
924       ChordRest* curCr = score->findCR(startTick, track);
925       if (curCr) {
926             ChordRest* cr0 = nullptr;
927             ChordRest* cr1 = nullptr;
928 
929             if (startTick > curCr->tick())
930                   cutChordRest(curCr, track, startTick, cr0, cr1);  //Cut at the start of existing chord rest
931             else
932                   cr1 = curCr;  //We are inserting at start of chordrest
933 
934             Fraction cr1End = cr1->tick() + cr1->ticks();
935             if (cr1End > startTick + duration) {
936                   //Cut from middle of enveloping chord
937                   ChordRest* crMid = nullptr;
938                   ChordRest* crEnd = nullptr;
939 
940                   cutChordRest(cr1, track, startTick + duration, crMid, crEnd);
941                   if (crMid->isChord()) {
942                         Chord* ch = toChord(crMid);
943                         addedNotes.append(score->addNote(ch, nv));
944                         }
945                   else {
946                         Segment* newSeg = score->setNoteRest(crMid->segment(), track, nv, duration);
947                         if (newSeg)
948                               addedNotes.append(getSegmentNotes(newSeg, track));
949                         }
950                   }
951             else if (cr1End == startTick + duration) {
952                   if (cr1->isChord()) {
953                         Chord* ch = toChord(cr1);
954                         addedNotes.append(score->addNote(ch, nv));
955                         }
956                   else {
957                         Segment* newSeg = score->setNoteRest(cr1->segment(), track, nv, duration);
958                         if (newSeg)
959                               addedNotes.append(getSegmentNotes(newSeg, track));
960                         }
961                   }
962             else {
963                   Segment* newSeg = score->setNoteRest(cr1->segment(), track, nv, duration);
964                   if (newSeg)
965                         addedNotes.append(getSegmentNotes(newSeg, track));
966                   }
967 
968             }
969 
970       return addedNotes;
971       }
972 
973 
974 //---------------------------------------------------------
975 //   eraseNote
976 //---------------------------------------------------------
977 
eraseNote(const QPointF & pos)978 void PianoView::eraseNote(const QPointF& pos) {
979       Score* score = _staff->score();
980       int pickTick = pixelXToTick((int)pos.x());
981       int pickPitch = pixelYToPitch(pos.y());
982       PianoItem *pn = pickNote(pickTick, pickPitch);
983 
984       if (pn) {
985             score->startCmd();
986             score->deleteItem(pn->note());
987             score->endCmd();
988             }
989       }
990 
991 //---------------------------------------------------------
992 //   changeChordLength
993 //---------------------------------------------------------
994 
changeChordLength(const QPointF & pos)995 void PianoView::changeChordLength(const QPointF& pos) {
996       Score* score = _staff->score();
997       int pickTick = pixelXToTick((int)pos.x());
998       int pickPitch = pixelYToPitch(pos.y());
999       PianoItem *pn = pickNote(pickTick, pickPitch);
1000 
1001       if (pn) {
1002             Note* note = pn->note();
1003             int track = _staff->idx() * VOICES + note->voice();
1004             Fraction frac = noteEditLength();
1005             Chord* chord = note->chord();
1006             if (chord->ticks() != frac) {
1007                   //Copy existing cord
1008                   QList<NoteVal> nvList;
1009                   for (Note* n: chord->notes())
1010                       nvList.push_back(n->noteVal());
1011 
1012                   //Rebuild chord
1013                   score->startCmd();
1014                   score->deleteItem(chord);
1015                   Fraction startTick = chord->segment()->tick();
1016 
1017                   for (int i = 0; i < nvList.length(); ++i) {
1018                         if (i == 0) {
1019                               ChordRest* cr = score->findCR(startTick, track);
1020                               score->setNoteRest(cr->segment(), track, nvList.at(i), frac);
1021                               chord = toChord(score->findCR(startTick, track));
1022                               }
1023                         else
1024                               score->addNote(chord, nvList.at(i));
1025                         }
1026                   score->endCmd();
1027                   }
1028             }
1029       }
1030 
1031 
1032 //---------------------------------------------------------
1033 //   roundToStartBeat
1034 //---------------------------------------------------------
1035 
roundToStartBeat(int tick) const1036 Fraction PianoView::roundToStartBeat(int tick)  const
1037       {
1038       Score* _score = _staff->score();
1039       Pos barPos(_score->tempomap(), _score->sigmap(), tick, TType::TICKS);
1040 
1041       int beatsInBar = barPos.timesig().timesig().numerator();
1042 
1043       //Number of smaller pieces the beat is divided into
1044       int subbeats = _tuplet * (1 << _subdiv);
1045       int divisions = beatsInBar * subbeats;
1046 
1047       //Round down to nearest division
1048       Fraction pickFrac = Fraction::fromTicks(tick);
1049       int numDiv = (int)floor((pickFrac.numerator() * divisions / (double)pickFrac.denominator()));
1050       return Fraction(numDiv, divisions);
1051       }
1052 
1053 
1054 //---------------------------------------------------------
1055 //   noteEditLength
1056 //---------------------------------------------------------
1057 
noteEditLength() const1058 Fraction PianoView::noteEditLength() const
1059       {
1060       //Find n, d such that n/d = 2^_editNoteLength
1061       int n = _editNoteLength > 0 ? (1 << _editNoteLength) : 1;
1062       int d = _editNoteLength < 0 ? (1 << -_editNoteLength) : 1;
1063 
1064       //dots multiplier is (2^(n + 1) - 1)/(2^n) where n is the number of dots
1065       int dotN = (1 << (_editNoteDots + 1)) - 1;
1066       int dotD = 1 << _editNoteDots;
1067 
1068       return Fraction(n * dotN, d * dotD);
1069       }
1070 
1071 
1072 //---------------------------------------------------------
1073 //   appendNoteToChord
1074 //---------------------------------------------------------
1075 
appendNoteToChord(const QPointF & pos)1076 void PianoView::appendNoteToChord(const QPointF& pos) {
1077       Score* score = _staff->score();
1078 
1079       int pickTick = pixelXToTick((int)pos.x());
1080       int pickPitch = pixelYToPitch(_mouseDownPos.y());
1081       int voice = _editNoteVoice;
1082 
1083       //Find best chord to add to
1084       int track = _staff->idx() * VOICES + voice;
1085 
1086       Fraction pt = Fraction::fromTicks(pickTick);
1087       Segment* seg = score->tick2segment(pt);
1088       score->expandVoice(seg, track);
1089 
1090       ChordRest* e = score->findCR(pt, track);
1091 
1092       if (e && e->isChord()) {
1093             Chord* ch = toChord(e);
1094 
1095             if (pt >= e->tick() && pt < (ch->tick() + ch->ticks())) {
1096                   NoteVal nv(pickPitch);
1097                   score->startCmd();
1098                   score->addNote(ch, nv);
1099                   score->endCmd();
1100                   }
1101             }
1102       else if (e && e->isRest()) {
1103             Rest* r = toRest(e);
1104             NoteVal nv(pickPitch);
1105             score->startCmd();
1106             score->setNoteRest(r->segment(), track, nv, r->ticks());
1107             score->endCmd();
1108             }
1109       }
1110 
1111 //---------------------------------------------------------
1112 //   insertNote
1113 //---------------------------------------------------------
1114 
insertNote(int modifiers)1115 void PianoView::insertNote(int modifiers)
1116       {
1117       bool bnShift = modifiers & Qt::ShiftModifier;
1118 
1119       Score* score = _staff->score();
1120 
1121       int pickTick = pixelXToTick((int)_mouseDownPos.x());
1122       int pickPitch = pixelYToPitch(_mouseDownPos.y());
1123 
1124 
1125       if (bnShift) {
1126             //If shift is held, select note instead
1127             PianoItem *pn = pickNote(pickTick, pickPitch);
1128             if (pn) {
1129                   mscore->play(pn->note());
1130                   score->setPlayNote(false);
1131 
1132                   selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, NoteSelectType::REPLACE);
1133                   }
1134             return;
1135             }
1136 
1137 
1138       Fraction insertPosition = roundToStartBeat(pickTick);
1139 
1140       int voice = _editNoteVoice;
1141       int track = _staff->idx() * VOICES + voice;
1142       Fraction noteLen = noteEditLength();
1143 
1144       Segment* seg = score->tick2segment(insertPosition);
1145       score->expandVoice(seg, track);
1146 
1147       Fraction tupletRatio(_tuplet, 1 << _subdiv);
1148 
1149       ChordRest* e = score->findCR(insertPosition, track);
1150       if (e) {
1151 
1152             score->startCmd();
1153 
1154             addNote(insertPosition, noteLen, pickPitch, track);
1155 
1156             score->endCmd();
1157             }
1158       }
1159 
1160 
1161 //---------------------------------------------------------
1162 //   toggleTie
1163 //---------------------------------------------------------
1164 
toggleTie(const QPointF & pos)1165 void PianoView::toggleTie(const QPointF& pos) {
1166       Score* score = _staff->score();
1167 
1168       int pickTick = pixelXToTick((int)pos.x());
1169       int pickPitch = pixelYToPitch(pos.y());
1170       PianoItem *pn = pickNote(pickTick, pickPitch);
1171 
1172       if (pn) {
1173             score->startCmd();
1174             toggleTie(pn->note());
1175             score->endCmd();
1176             }
1177       }
1178 
1179 
1180 //---------------------------------------------------------
1181 //   toggleTie
1182 //---------------------------------------------------------
1183 
toggleTie(Note * note)1184 void PianoView::toggleTie(Note* note) {
1185       //Based on Score::cmdToggleTie()
1186 
1187       Score* score = _staff->score();
1188 
1189       Tie* tie = note->tieFor();
1190       if (tie)
1191             score->undoRemoveElement(tie);
1192       else {
1193             Note* note2 = searchTieNote(note);
1194 
1195             if (note2) {
1196                   tie = new Tie(score);
1197                   tie->setStartNote(note);
1198                   tie->setEndNote(note2);
1199                   tie->setTrack(note->track());
1200                   tie->setTick(note->chord()->segment()->tick());
1201                   tie->setTicks(note2->chord()->segment()->tick() - note->chord()->segment()->tick());
1202                   score->undoAddElement(tie);
1203                   }
1204             }
1205       }
1206 
1207 //---------------------------------------------------------
1208 //   cutChord
1209 //---------------------------------------------------------
1210 
cutChord(const QPointF & pos)1211 void PianoView::cutChord(const QPointF& pos) {
1212       Score* score = _staff->score();
1213 
1214       int pickTick = pixelXToTick((int)pos.x());
1215       int pickPitch = pixelYToPitch(pos.y());
1216       PianoItem *pn = pickNote(pickTick, pickPitch);
1217 
1218       int voice = pn ? pn->note()->voice() : _editNoteVoice;
1219 
1220       //Find best chord to add to
1221       int track = _staff->idx() * VOICES + voice;
1222 
1223       Fraction insertPosition = roundToStartBeat(pickTick);
1224 
1225       Segment* seg = score->tick2segment(insertPosition);
1226       score->expandVoice(seg, track);
1227 
1228       ChordRest* e = score->findCR(insertPosition, track);
1229       if (e && !e->tuplet() && _tuplet == 1) {
1230             score->startCmd();
1231             Fraction startTick = e->tick();
1232 
1233             if (insertPosition != startTick) {
1234                   ChordRest* cr0;
1235                   ChordRest* cr1;
1236                   cutChordRest(e, track, insertPosition, cr0, cr1);
1237                   }
1238             score->endCmd();
1239             }
1240       }
1241 
1242 
1243 //---------------------------------------------------------
1244 //   handleSelectionClick
1245 //---------------------------------------------------------
1246 
handleSelectionClick()1247 void PianoView::handleSelectionClick()
1248 {
1249       int modifiers = QGuiApplication::keyboardModifiers();
1250       bool bnShift = modifiers & Qt::ShiftModifier;
1251       bool bnCtrl = modifiers & Qt::ControlModifier;
1252       NoteSelectType selType = bnShift ? (bnCtrl ? NoteSelectType::SUBTRACT : NoteSelectType::XOR)
1253               : (bnCtrl ? NoteSelectType::ADD : NoteSelectType::REPLACE);
1254 
1255       Score* score = _staff->score();
1256 
1257       int pickTick = pixelXToTick((int)_mouseDownPos.x());
1258       int pickPitch = pixelYToPitch(_mouseDownPos.y());
1259 
1260       PianoItem *pn = pickNote(pickTick, pickPitch);
1261 
1262       if (pn) {
1263             if (selType == NoteSelectType::REPLACE)
1264                   selType = NoteSelectType::FIRST;
1265 
1266             mscore->play(pn->note());
1267             score->setPlayNote(false);
1268 
1269             selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType);
1270             }
1271       else {
1272             if (!bnShift && !bnCtrl) {
1273                   //Select an empty pixel - should clear selection
1274                   selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType);
1275                   }
1276             else if (!bnShift && bnCtrl) {
1277 
1278                   //Insert a new note at nearest subbeat
1279                   Fraction insertPosition = roundToStartBeat(pickTick);
1280 
1281                   InputState& is = score->inputState();
1282                   int voice = _editNoteVoice;
1283                   int track = _staff->idx() * VOICES + voice;
1284 
1285                   NoteVal nv(pickPitch);
1286 
1287                   Segment* seg = score->tick2segment(insertPosition);
1288                   score->expandVoice(seg, track);
1289 
1290                   ChordRest* e = score->findCR(insertPosition, track);
1291                   if (e && !e->tuplet() && _tuplet == 1) {
1292                         //Ignore tuplets
1293                         score->startCmd();
1294 
1295                         ChordRest* cr0;
1296                         ChordRest* cr1;
1297                         Fraction frac = is.duration().fraction();
1298 
1299                         //Default to quarter note if faction is invalid
1300                         if (!frac.isValid() || frac.isZero())
1301                               frac.set(1, 4);
1302 
1303                         if (cutChordRest(e, track, insertPosition, cr0, cr1)) {
1304                               score->setNoteRest(cr1->segment(), track, nv, frac);
1305                               }
1306                         else {
1307                               if (cr0->isChord() && cr0->ticks().ticks() == frac.ticks()) {
1308                                     Chord* ch = toChord(cr0);
1309                                     score->addNote(ch, nv);
1310                                     }
1311                               else {
1312                                     score->setNoteRest(cr0->segment(), track, nv, frac);
1313                                     }
1314                               }
1315 
1316                         score->endCmd();
1317                         }
1318 
1319                   }
1320             else if (bnShift && !bnCtrl) {
1321                   //Append a pitch to our current chord/rest
1322                   int voice = _editNoteVoice;
1323 
1324                   //Find best chord to add to
1325                   int track = _staff->idx() * VOICES + voice;
1326 
1327                   Fraction pt = Fraction::fromTicks(pickTick);
1328                   Segment* seg = score->tick2segment(pt);
1329                   score->expandVoice(seg, track);
1330 
1331                   ChordRest* e = score->findCR(pt, track);
1332 
1333                   if (e && e->isChord()) {
1334                         Chord* ch = toChord(e);
1335 
1336                         if (pt >= e->tick() && pt < (ch->tick() + ch->ticks())) {
1337                               NoteVal nv(pickPitch);
1338                               score->startCmd();
1339                               score->addNote(ch, nv);
1340                               score->endCmd();
1341                               }
1342 
1343                         }
1344                   else if (e && e->isRest()) {
1345                         Rest* r = toRest(e);
1346                         NoteVal nv(pickPitch);
1347                         score->startCmd();
1348                         score->setNoteRest(r->segment(), track, nv, r->ticks());
1349                         score->endCmd();
1350                         }
1351                   }
1352             else if (bnShift && bnCtrl) {
1353                   //Cut the chord/rest at the nearest subbeat
1354                   int voice = _editNoteVoice;
1355 
1356                   //Find best chord to add to
1357                   int track = _staff->idx() * VOICES + voice;
1358 
1359                   Fraction insertPosition = roundToStartBeat(pickTick);
1360 
1361                   Segment* seg = score->tick2segment(insertPosition);
1362                   score->expandVoice(seg, track);
1363 
1364                   ChordRest* e = score->findCR(insertPosition, track);
1365                   if (e && !e->tuplet() && _tuplet == 1) {
1366                         score->startCmd();
1367                         Fraction startTick = e->tick();
1368 
1369                         if (insertPosition != startTick) {
1370                               ChordRest* cr0;
1371                               ChordRest* cr1;
1372                               cutChordRest(e, track, insertPosition, cr0, cr1);
1373                               }
1374                         score->endCmd();
1375                         }
1376                   }
1377             }
1378       }
1379 
1380 
1381 //---------------------------------------------------------
1382 //   cutChordRest
1383 //   @cr0 Will be set to the first piece of the split chord, or targetCr if no split occurs
1384 //   @cr1 Will be set to the second piece of the split chord, or nullptr if no split occurs
1385 //   @return true if chord was cut
1386 //---------------------------------------------------------
1387 
cutChordRest(ChordRest * targetCr,int track,Fraction cutTick,ChordRest * & cr0,ChordRest * & cr1)1388 bool PianoView::cutChordRest(ChordRest* targetCr, int track, Fraction cutTick, ChordRest*& cr0, ChordRest*& cr1)
1389       {
1390       Fraction startTick = targetCr->segment()->tick();
1391       Fraction durationTuplet = targetCr->ticks();
1392 
1393       Fraction measureToTuplet(1, 1);
1394       Fraction tupletToMeasure(1, 1);
1395       if (targetCr->tuplet()) {
1396             Fraction ratio = targetCr->tuplet()->ratio();
1397             measureToTuplet = ratio;
1398             tupletToMeasure = ratio.inverse();
1399             }
1400 
1401       Fraction durationMeasure = durationTuplet * tupletToMeasure;
1402 
1403       if (cutTick <= startTick || cutTick >= startTick + durationMeasure) {
1404             cr0 = targetCr;
1405             cr1 = nullptr;
1406             return false;
1407             }
1408 
1409       //Deselect note being cut
1410       if (targetCr->isChord()) {
1411             Chord* ch = toChord(targetCr);
1412             for (Note* n: ch->notes()) {
1413                   n->setSelected(false);
1414                   }
1415             }
1416       else if (targetCr->isRest()) {
1417             Rest* r = toRest(targetCr);
1418             r->setSelected(false);
1419             }
1420 
1421       //Subdivide at the cut tick
1422       NoteVal nv(-1);
1423 
1424       Score* score = _staff->score();
1425       score->setNoteRest(targetCr->segment(), track, nv, (cutTick - targetCr->tick()) * measureToTuplet);
1426       ChordRest *nextCR = score->findCR(cutTick, track);
1427 
1428       Chord* ch0 = 0;
1429 
1430       if (nextCR->isChord()) {
1431             //Copy chord into initial segment
1432             Chord* ch1 = toChord(nextCR);
1433 
1434             for (Note* n: ch1->notes()) {
1435                   NoteVal nx = n->noteVal();
1436                   if (!ch0) {
1437                         ChordRest* cr = score->findCR(startTick, track);
1438                         score->setNoteRest(cr->segment(), track, nx, cr->ticks());
1439                         ch0 = toChord(score->findCR(startTick, track));
1440                         }
1441                   else {
1442                         score->addNote(ch0, nx);
1443                         }
1444                   }
1445             cr0 = ch0;
1446             }
1447       else
1448             cr0 = score->findCR(startTick, track);
1449 
1450       cr1 = nextCR;
1451       return true;
1452       }
1453 
1454 //---------------------------------------------------------
1455 //   selectNotes
1456 //---------------------------------------------------------
1457 
pickNote(int tick,int pitch)1458 PianoItem* PianoView::pickNote(int tick, int pitch)
1459       {
1460       for (int i = 0; i < _noteList.size(); ++i) {
1461             PianoItem* pi = _noteList[i];
1462 
1463             if (pi->intersects(tick, tick, pitch, pitch))
1464                   return pi;
1465             }
1466 
1467       return 0;
1468       }
1469 
1470 //---------------------------------------------------------
1471 //   selectNotes
1472 //---------------------------------------------------------
1473 
selectNotes(int startTick,int endTick,int lowPitch,int highPitch,NoteSelectType selType)1474 void PianoView::selectNotes(int startTick, int endTick, int lowPitch, int highPitch, NoteSelectType selType)
1475       {
1476       Score* score = _staff->score();
1477       //score->masterScore()->cmdState().reset();      // DEBUG: should not be necessary
1478       score->startCmd();
1479 
1480       QList<PianoItem*> oldSel;
1481       for (int i = 0; i < _noteList.size(); ++i) {
1482             PianoItem* pi = _noteList[i];
1483             if (pi->note()->selected())
1484                   oldSel.append(pi);
1485             }
1486 
1487       Selection& selection = score->selection();
1488       selection.deselectAll();
1489 
1490       for (int i = 0; i < _noteList.size(); ++i) {
1491             PianoItem* pi = _noteList[i];
1492             bool inBounds = pi->intersects(startTick, endTick, highPitch, lowPitch);
1493 
1494             bool sel;
1495             switch (selType) {
1496                   default:
1497                   case NoteSelectType::REPLACE:
1498                         sel = inBounds;
1499                         break;
1500                   case NoteSelectType::XOR:
1501                         sel = inBounds != oldSel.contains(pi);
1502                         break;
1503                   case NoteSelectType::ADD:
1504                         sel = inBounds || oldSel.contains(pi);
1505                         break;
1506                   case NoteSelectType::SUBTRACT:
1507                         sel = !inBounds && oldSel.contains(pi);
1508                         break;
1509                   case NoteSelectType::FIRST:
1510                         sel = inBounds && selection.elements().empty();
1511                         break;
1512                   }
1513 
1514             if (sel)
1515                   selection.add(pi->note());
1516             }
1517 
1518       for (MuseScoreView* view : score->getViewer())
1519             view->updateAll();
1520 
1521       scene()->update();
1522       score->setUpdateAll();
1523       score->update();
1524       score->endCmd();
1525 
1526       emit selectionChanged();
1527       }
1528 
1529 //---------------------------------------------------------
1530 //   leaveEvent
1531 //---------------------------------------------------------
1532 
leaveEvent(QEvent * event)1533 void PianoView::leaveEvent(QEvent* event)
1534       {
1535       emit pitchChanged(-1);
1536       trackingPos.setInvalid();
1537       emit trackingPosChanged(trackingPos);
1538       QGraphicsView::leaveEvent(event);
1539       }
1540 
1541 //---------------------------------------------------------
1542 //   ensureVisible
1543 //---------------------------------------------------------
1544 
ensureVisible(int tick)1545 void PianoView::ensureVisible(int tick)
1546       {
1547       QRectF rect = mapToScene(viewport()->geometry()).boundingRect();
1548 
1549       qreal xpos = tickToPixelX(tick);
1550       qreal margin = rect.width() / 2;
1551       if (xpos < rect.x() + margin)
1552             horizontalScrollBar()->setValue(qMax(xpos - margin, 0.0));
1553       else if (xpos >= rect.x() + rect.width() - margin)
1554             horizontalScrollBar()->setValue(qMax(xpos - rect.width() + margin, 0.0));
1555       }
1556 
1557 //---------------------------------------------------------
1558 //   updateBoundingSize
1559 //---------------------------------------------------------
updateBoundingSize()1560 void PianoView::updateBoundingSize()
1561       {
1562       Measure* lm = _staff->score()->lastMeasure();
1563       _ticks       = (lm->tick() + lm->ticks()).ticks();
1564       scene()->setSceneRect(0.0, 0.0,
1565               double((_ticks + MAP_OFFSET * 2) * _xZoom),
1566               _noteHeight * 128);
1567       }
1568 
1569 //---------------------------------------------------------
1570 //   setStaff
1571 //---------------------------------------------------------
1572 
setStaff(Staff * s,Pos * l)1573 void PianoView::setStaff(Staff* s, Pos* l)
1574       {
1575       _locator = l;
1576 
1577       if (_staff == s)
1578             return;
1579 
1580       _staff    = s;
1581       setEnabled(_staff != nullptr);
1582       if (!_staff) {
1583             scene()->blockSignals(true);  // block changeSelection()
1584             scene()->clear();
1585             clearNoteData();
1586             scene()->blockSignals(false);
1587             return;
1588             }
1589 
1590       trackingPos.setContext(_staff->score()->tempomap(), _staff->score()->sigmap());
1591       updateBoundingSize();
1592 
1593       updateNotes();
1594 
1595       QRectF boundingRect;
1596       bool brInit = false;
1597       QRectF boundingRectSel;
1598       bool brsInit = false;
1599 
1600       foreach (PianoItem* item, _noteList) {
1601             if (!brInit) {
1602                   boundingRect = item->boundingRect();
1603                   brInit = true;
1604                   }
1605             else
1606                   boundingRect |= item->boundingRect();
1607 
1608             if (item->note()->selected()) {
1609                   if (!brsInit) {
1610                         boundingRectSel = item->boundingRect();
1611                         brsInit = true;
1612                         }
1613                   else
1614                         boundingRectSel |= item->boundingRect();
1615                   }
1616 
1617             }
1618 
1619       QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
1620 
1621       if (brsInit) {
1622             horizontalScrollBar()->setValue(boundingRectSel.x());
1623             verticalScrollBar()->setValue(qMax(boundingRectSel.y() + (boundingRectSel.height() - viewRect.height()) / 2, 0.0));
1624             }
1625       else if (brInit) {
1626             horizontalScrollBar()->setValue(boundingRect.x());
1627             verticalScrollBar()->setValue(qMax(boundingRect.y() - (boundingRectSel.height() - viewRect.height()) / 2, 0.0));
1628             }
1629       else {
1630             horizontalScrollBar()->setValue(0);
1631             verticalScrollBar()->setValue(qMax(viewRect.y() - viewRect.height() / 2, 0.0));
1632             }
1633       }
1634 
1635 //---------------------------------------------------------
1636 //   addChord
1637 //---------------------------------------------------------
1638 
addChord(Chord * chrd,int voice)1639 void PianoView::addChord(Chord* chrd, int voice)
1640       {
1641       for (Chord* c : chrd->graceNotes())
1642             addChord(c, voice);
1643       for (Note* note : chrd->notes()) {
1644             if (note->tieBack())
1645                   continue;
1646             _noteList.append(new PianoItem(note, this));
1647             }
1648       }
1649 
1650 //---------------------------------------------------------
1651 //   updateNotes
1652 //---------------------------------------------------------
1653 
updateNotes()1654 void PianoView::updateNotes()
1655       {
1656       scene()->blockSignals(true);  // block changeSelection()
1657       scene()->clearFocus();
1658       scene()->clear();
1659       clearNoteData();
1660 
1661       if (!_staff) {
1662             return;
1663             }
1664 
1665       int staffIdx = _staff->idx();
1666       if (staffIdx == -1)
1667             return;
1668 
1669       SegmentType st = SegmentType::ChordRest;
1670       for (Segment* s = _staff->score()->firstSegment(st); s; s = s->next1(st)) {
1671             for (int voice = 0; voice < VOICES; ++voice) {
1672                   int track = voice + staffIdx * VOICES;
1673                   Element* e = s->element(track);
1674                   if (e && e->isChord())
1675                         addChord(toChord(e), voice);
1676                   }
1677             }
1678       for (int i = 0; i < 3; ++i)
1679             moveLocator(i);
1680       scene()->blockSignals(false);
1681 
1682       scene()->update(sceneRect());
1683       }
1684 
1685 //---------------------------------------------------------
1686 //   updateNotes
1687 //---------------------------------------------------------
1688 
clearNoteData()1689 void PianoView::clearNoteData()
1690       {
1691       for (int i = 0; i < _noteList.size(); ++i)
1692             delete _noteList[i];
1693 
1694       _noteList.clear();
1695       }
1696 
1697 
1698 //---------------------------------------------------------
1699 //   getSelectedItems
1700 //---------------------------------------------------------
1701 
getSelectedItems()1702 QList<PianoItem*> PianoView::getSelectedItems()
1703       {
1704       QList<PianoItem*> list;
1705       for (int i = 0; i < _noteList.size(); ++i) {
1706             if (_noteList.at(i)->note()->selected())
1707                   list.append(_noteList[i]);
1708             }
1709       return list;
1710       }
1711 
1712 //---------------------------------------------------------
1713 //   getItems
1714 //---------------------------------------------------------
1715 
getItems()1716 QList<PianoItem*> PianoView::getItems()
1717       {
1718       QList<PianoItem*> list;
1719       for (int i = 0; i < _noteList.size(); ++i)
1720             list.append(_noteList[i]);
1721       return list;
1722       }
1723 
1724 //---------------------------------------------------------
1725 //   getAction
1726 //    returns action for shortcut
1727 //---------------------------------------------------------
1728 
getAction(const char * id)1729 QAction* PianoView::getAction(const char* id)
1730       {
1731       Shortcut* s = Shortcut::getShortcut(id);
1732       return s ? s->action() : 0;
1733       }
1734 
1735 //---------------------------------------------------------
1736 //   showNoteTweaker
1737 //---------------------------------------------------------
1738 
showNoteTweaker()1739 void PianoView::showNoteTweaker()
1740       {
1741       emit showNoteTweakerRequest();
1742       }
1743 
1744 
1745 //---------------------------------------------------------
1746 //   setVoices
1747 //---------------------------------------------------------
1748 
setNotesToVoice(int voice)1749 void PianoView::setNotesToVoice(int voice) {
1750       if (_noteList.isEmpty())
1751             return;
1752 
1753       //Make a copy of the selection
1754       QList<Note*> notes;
1755       for (int i = 0; i < _noteList.size(); ++i)
1756             if (_noteList.at(i)->note()->selected())
1757                   notes.append(_noteList.at(i)->note());
1758 
1759       Score* score = _staff->score();
1760       score->startCmd();
1761 
1762       for (int i = 0; i < notes.size(); ++i) {
1763             Note* note = notes.at(i);
1764 
1765             addNote(note->tick(), note->chord()->ticks(), note->pitch(), voice);
1766             score->deleteItem(note);
1767             }
1768 
1769       score->endCmd();
1770 
1771       scene()->update();
1772       }
1773 
1774 
1775 //---------------------------------------------------------
1776 //   setXZoom
1777 //---------------------------------------------------------
1778 
setXZoom(int value)1779 void PianoView::setXZoom(int value)
1780       {
1781       if (_xZoom != value) {
1782             _xZoom = value;
1783             scene()->update();
1784             emit xZoomChanged(_xZoom);
1785             }
1786       }
1787 
1788 //---------------------------------------------------------
1789 //   setBarPattern
1790 //---------------------------------------------------------
1791 
setBarPattern(int value)1792 void PianoView::setBarPattern(int value)
1793       {
1794       if (_barPattern != value) {
1795             _barPattern = value;
1796             scene()->update();
1797             emit barPatternChanged(_barPattern);
1798             }
1799       }
1800 
1801 //---------------------------------------------------------
1802 //   setBarPattern
1803 //---------------------------------------------------------
1804 
togglePitchHighlight(int pitch)1805 void PianoView::togglePitchHighlight(int pitch)
1806       {
1807       _pitchHighlight[pitch] = _pitchHighlight[pitch] ? 0 : 1;
1808       scene()->update();
1809       }
1810 
1811 //---------------------------------------------------------
1812 //   setSubBeats
1813 //---------------------------------------------------------
1814 
setTuplet(int value)1815 void PianoView::setTuplet(int value)
1816       {
1817       if (_tuplet != value) {
1818             _tuplet = value;
1819             scene()->update();
1820             emit tupletChanged(_tuplet);
1821             }
1822       }
1823 
1824 //---------------------------------------------------------
1825 //   setSubdiv
1826 //---------------------------------------------------------
1827 
setSubdiv(int value)1828 void PianoView::setSubdiv(int value)
1829       {
1830       if (_subdiv != value) {
1831             _subdiv = value;
1832             scene()->update();
1833             emit subdivChanged(_subdiv);
1834             }
1835       }
1836 
1837 
1838 //---------------------------------------------------------
1839 //   serializeSelectedNotes
1840 //---------------------------------------------------------
1841 
serializeSelectedNotes()1842 QString PianoView::serializeSelectedNotes()
1843       {
1844       Fraction firstTick;
1845       bool init = false;
1846       for (int i = 0; i < _noteList.size(); ++i) {
1847             if (_noteList[i]->note()->selected()) {
1848                   Note* note = _noteList.at(i)->note();
1849                   Fraction startTick = note->chord()->tick();
1850 
1851                   if (!init || firstTick > startTick) {
1852                         firstTick = startTick;
1853                         init = true;
1854                         }
1855                   }
1856             }
1857 
1858       //No valid notes
1859       if (!init)
1860             return QByteArray();
1861 
1862       QString xmlStrn;
1863       QXmlStreamWriter xml(&xmlStrn);
1864       xml.setAutoFormatting(true);
1865       xml.writeStartDocument();
1866 
1867       xml.writeStartElement("notes");
1868       xml.writeAttribute("firstN", QString::number(firstTick.numerator()));
1869       xml.writeAttribute("firstD", QString::number(firstTick.denominator()));
1870 
1871       //bundle notes into XML file & send to clipboard.
1872       //This is only affects pianoview and is not part of the regular copy/paste process
1873       for (int i = 0; i < _noteList.size(); ++i) {
1874             if (_noteList[i]->note()->selected()) {
1875                   Note* note = _noteList[i]->note();
1876 
1877                   Fraction flen = note->playTicksFraction();
1878 
1879                   Fraction startTick = note->chord()->tick();
1880                   int pitch = note->pitch();
1881 
1882                   int voice = note->voice();
1883 
1884                   int veloOff = note->veloOffset();
1885                   Note::ValueType veloType = note->veloType();
1886 
1887                   xml.writeStartElement("note");
1888                   xml.writeAttribute("startN", QString::number(startTick.numerator()));
1889                   xml.writeAttribute("startD", QString::number(startTick.denominator()));
1890                   xml.writeAttribute("lenN", QString::number(flen.numerator()));
1891                   xml.writeAttribute("lenD", QString::number(flen.denominator()));
1892                   xml.writeAttribute("pitch", QString::number(pitch));
1893                   xml.writeAttribute("voice", QString::number(voice));
1894                   xml.writeAttribute("veloOff", QString::number(veloOff));
1895                   xml.writeAttribute("veloType", veloType == Note::ValueType::OFFSET_VAL ? "o" : "u");
1896 
1897                   for (NoteEvent& evt : note->playEvents()) {
1898                         int ontime = evt.ontime();
1899                         int len = evt.len();
1900 
1901                         xml.writeStartElement("evt");
1902                         xml.writeAttribute("ontime", QString::number(ontime));
1903                         xml.writeAttribute("len", QString::number(len));
1904                         xml.writeEndElement();
1905                         }
1906 
1907                   xml.writeEndElement();
1908                   }
1909             }
1910 
1911       xml.writeEndElement();
1912       xml.writeEndDocument();
1913 
1914       return xmlStrn;
1915       }
1916 
1917 
1918 //---------------------------------------------------------
1919 //   cutNotes
1920 //---------------------------------------------------------
1921 
cutNotes()1922 void PianoView::cutNotes()
1923       {
1924       copyNotes();
1925 
1926       Score* score = _staff->score();
1927       score->startCmd();
1928 
1929       score->cmdDeleteSelection();
1930 
1931       score->endCmd();
1932       }
1933 
1934 //---------------------------------------------------------
1935 //   copyNotes
1936 //---------------------------------------------------------
1937 
copyNotes()1938 void PianoView::copyNotes()
1939       {
1940       QString copiedNotes = serializeSelectedNotes();
1941       if (copiedNotes.isEmpty())
1942           return;
1943 
1944       QMimeData* mimeData = new QMimeData;
1945       mimeData->setData(PIANO_NOTE_MIME_TYPE, copiedNotes.toUtf8());
1946       QApplication::clipboard()->setMimeData(mimeData);
1947       }
1948 
1949 //---------------------------------------------------------
1950 //   pasteNotesAtCursor
1951 //---------------------------------------------------------
1952 
pasteNotesAtCursor()1953 void PianoView::pasteNotesAtCursor()
1954       {
1955       //ScoreView::normalPaste();
1956       const QMimeData* ms = QApplication::clipboard()->mimeData();
1957       if (!ms)
1958             return;
1959 
1960       Score* score = _staff->score();
1961       Pos barPos(score->tempomap(), score->sigmap(), pixelXToTick(_popupMenuPos.x()), TType::TICKS);
1962 
1963       int beatsInBar = barPos.timesig().timesig().numerator();
1964       int pickTick = barPos.tick();
1965       Fraction pickFrac = Fraction::fromTicks(pickTick);
1966 
1967       //Number of smaller pieces the beat is divided into
1968       int subbeats = _tuplet * (1 << _subdiv);
1969       int divisions = beatsInBar * subbeats;
1970 
1971       //Round down to nearest division
1972       int numDiv = (int)floor((pickFrac.numerator() * divisions / (double)pickFrac.denominator()));
1973       Fraction pasteStartTick(numDiv, divisions);
1974 
1975       if (ms->hasFormat(PIANO_NOTE_MIME_TYPE)) {
1976             //Decode our XML format and recreate the notes
1977             QByteArray copiedNotes = ms->data(PIANO_NOTE_MIME_TYPE);
1978 
1979             score->startCmd();
1980             pasteNotes(copiedNotes, pasteStartTick, 0);
1981             score->endCmd();
1982             }
1983 
1984       }
1985 
1986 
1987 //---------------------------------------------------------
1988 //   finishNoteGroupDrag
1989 //---------------------------------------------------------
1990 
finishNoteGroupDrag()1991 void PianoView::finishNoteGroupDrag() {
1992       Score* score = _staff->score();
1993 
1994       Pos barPos(score->tempomap(), score->sigmap(), pixelXToTick(_lastMousePos.x()), TType::TICKS);
1995 
1996       int beatsInBar = barPos.timesig().timesig().numerator();
1997 
1998       //Number of smaller pieces the beat is divided into
1999       int subbeats = _tuplet * (1 << _subdiv);
2000       int divisions = beatsInBar * subbeats;
2001 
2002       //Round down to nearest division
2003       QPointF offset = _lastMousePos - _mouseDownPos;
2004 
2005       Fraction tickOffset = Fraction::fromTicks(offset.x() / _xZoom);
2006       //Round down to nearest division
2007       int numDiv = (int)floor((tickOffset.numerator() * divisions / (double)tickOffset.denominator()));
2008       Fraction pasteTickOffset(numDiv, divisions);
2009 
2010       int pitchOffset = (int)(-offset.y() / _noteHeight);
2011 
2012 
2013       //Do command
2014       score->startCmd();
2015 
2016       score->cmdDeleteSelection();
2017       pasteNotes(_dragNoteCache, pasteTickOffset, pitchOffset, true);
2018 
2019       score->endCmd();
2020 
2021       _dragNoteCache = QByteArray();
2022       }
2023 
2024 //---------------------------------------------------------
2025 //   pasteNotes
2026 //---------------------------------------------------------
2027 
pasteNotes(const QString & copiedNotes,Fraction pasteStartTick,int pitchOffset,bool xIsOffset)2028 void PianoView::pasteNotes(const QString& copiedNotes, Fraction pasteStartTick, int pitchOffset, bool xIsOffset)
2029       {
2030 
2031       QXmlStreamReader xml(copiedNotes);
2032       Fraction firstTick;
2033       QVector<Note*> addedNotes;
2034 
2035       while (!xml.atEnd()) {
2036             QXmlStreamReader::TokenType tt = xml.readNext();
2037             if (tt == QXmlStreamReader::StartElement){
2038                   if (xml.name().toString() == "notes") {
2039                         int n = xml.attributes().value("firstN").toString().toInt();
2040                         int d = xml.attributes().value("firstD").toString().toInt();
2041                         firstTick = Fraction(n, d);
2042                         }
2043                   if (xml.name().toString() == "note") {
2044                         int sn = xml.attributes().value("startN").toString().toInt();
2045                         int sd = xml.attributes().value("startD").toString().toInt();
2046                         Fraction startTick = Fraction(sn, sd);
2047 
2048                         int tn = xml.attributes().value("lenN").toString().toInt();
2049                         int td = xml.attributes().value("lenD").toString().toInt();
2050                         Fraction tickLen = Fraction(tn, td);
2051 
2052                         int pitch = xml.attributes().value("pitch").toString().toInt();
2053                         int voice = xml.attributes().value("voice").toString().toInt();
2054 
2055                         int veloOff = xml.attributes().value("veloOff").toString().toInt();
2056                         QString veloTypeStrn = xml.attributes().value("veloType").toString();
2057                         Note::ValueType veloType = veloTypeStrn == "o" ? Note::ValueType::OFFSET_VAL : Note::ValueType::USER_VAL;
2058 
2059                         int track = _staff->idx() * VOICES + voice;
2060 
2061                         Fraction pos = xIsOffset ? startTick + pasteStartTick : startTick - firstTick + pasteStartTick;
2062 
2063                         addedNotes = addNote(pos, tickLen, pitch + pitchOffset, track);
2064                         for (Note* note: qAsConst(addedNotes)) {
2065                               note->setVeloOffset(veloOff);
2066                               note->setVeloType(veloType);
2067                               }
2068                         }
2069                   if (xml.name().toString() == "evt") {
2070                         int ontime = xml.attributes().value("ontime").toString().toInt();
2071                         int len = xml.attributes().value("len").toString().toInt();
2072 
2073                         NoteEvent ne;
2074                         ne.setOntime(ontime);
2075                         ne.setLen(len);
2076                         for (Note* note: qAsConst(addedNotes)) {
2077                               NoteEventList& evtList = note->playEvents();
2078                               if (!evtList.isEmpty()) {
2079                                     NoteEvent* evt = note->noteEvent(evtList.length() - 1);
2080                                     _staff->score()->undo(new ChangeNoteEvent(note, evt, ne));
2081                                     }
2082                               }
2083                         }
2084                   }
2085             }
2086       }
2087 
2088 
2089 //---------------------------------------------------------
2090 //   drawDraggedNotes
2091 //---------------------------------------------------------
drawDraggedNotes(QPainter * painter)2092 void PianoView::drawDraggedNotes(QPainter* painter)
2093       {
2094       QColor noteColor;
2095       switch (preferences.effectiveGlobalStyle()) {
2096             case MuseScoreEffectiveStyleType::DARK_FUSION:
2097                   noteColor = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_DRAG_COLOR));
2098                   break;
2099             default:
2100                   noteColor = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_DRAG_COLOR));
2101                   break;
2102             }
2103 
2104 
2105       Score* score = _staff->score();
2106 
2107       Pos barPos(score->tempomap(), score->sigmap(), pixelXToTick(_lastMousePos.x()), TType::TICKS);
2108 
2109       int beatsInBar = barPos.timesig().timesig().numerator();
2110 
2111       //Number of smaller pieces the beat is divided into
2112       int subbeats = _tuplet * (1 << _subdiv);
2113       int divisions = beatsInBar * subbeats;
2114 
2115       QPointF offset = _lastMousePos - _mouseDownPos;
2116 
2117       Fraction tickOffset = Fraction::fromTicks(offset.x() / _xZoom);
2118       //Round down to nearest division
2119       int numDiv = (int)floor((tickOffset.numerator() * divisions / (double)tickOffset.denominator()));
2120       Fraction pasteTickOffset(numDiv, divisions);
2121 
2122       int pitchOffset = (int)(-offset.y() / _noteHeight);
2123 
2124       //Iterate thorugh note data
2125       QXmlStreamReader xml(_dragNoteCache);
2126       Fraction firstTick;
2127 
2128       while (!xml.atEnd()) {
2129             QXmlStreamReader::TokenType tt = xml.readNext();
2130             if (tt == QXmlStreamReader::StartElement){
2131                   if (xml.name().toString() == "notes") {
2132                         int n = xml.attributes().value("firstN").toString().toInt();
2133                         int d = xml.attributes().value("firstD").toString().toInt();
2134                         firstTick = Fraction(n, d);
2135                         }
2136                   if (xml.name().toString() == "note") {
2137                         int sn = xml.attributes().value("startN").toString().toInt();
2138                         int sd = xml.attributes().value("startD").toString().toInt();
2139                         Fraction startTick = Fraction(sn, sd);
2140 
2141                         int tn = xml.attributes().value("lenN").toString().toInt();
2142                         int td = xml.attributes().value("lenD").toString().toInt();
2143                         Fraction tickLen = Fraction(tn, td);
2144 
2145                         int pitch = xml.attributes().value("pitch").toString().toInt();
2146                         int voice = xml.attributes().value("voice").toString().toInt();
2147 
2148                         int track = _staff->idx() * VOICES + voice;
2149 
2150                         drawDraggedNote(painter, startTick + pasteTickOffset, tickLen, pitch + pitchOffset, track, noteColor);
2151                         }
2152                   }
2153             }
2154       }
2155 
2156 //---------------------------------------------------------
2157 //   drawDraggedNote
2158 //---------------------------------------------------------
2159 
drawDraggedNote(QPainter * painter,Fraction startTick,Fraction frac,int pitch,int track,QColor color)2160 void PianoView::drawDraggedNote(QPainter* painter, Fraction startTick, Fraction frac, int pitch, int track, QColor color)
2161       {
2162       Q_UNUSED(track);
2163       painter->setBrush(color);
2164 
2165       painter->setPen(QPen(color.darker(250)));
2166       int x0 = tickToPixelX(startTick.ticks());
2167       int x1 = tickToPixelX((startTick + frac).ticks());
2168       int y0 = pitchToPixelY(pitch);
2169 
2170       QRectF bounds(x0, y0 - _noteHeight, x1 - x0, _noteHeight);
2171       painter->drawRoundedRect(bounds, PianoItem::NOTE_BLOCK_CORNER_RADIUS, PianoItem::NOTE_BLOCK_CORNER_RADIUS);
2172       }
2173 
2174 }
2175 
2176 
2177 
2178