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