1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2002-2017 Werner Schweer & others
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 #include "log.h"
14
15 #include "scoreview.h"
16
17 #include "breaksdialog.h"
18 #include "continuouspanel.h"
19 #include "drumroll.h"
20 #include "editdrumset.h"
21 #include "editstaff.h"
22 #include "globals.h"
23 #include "zoombox.h"
24 #include "measureproperties.h"
25 #include "musescore.h"
26 #include "navigator.h"
27 #include "preferences.h"
28 #include "scoreaccessibility.h"
29 #include "scoretab.h"
30 #include "seq.h"
31 #include "splitstaff.h"
32 #include "textcursor.h"
33 #include "textpalette.h"
34 #include "texttools.h"
35 #include "fotomode.h"
36 #include "tourhandler.h"
37
38 #include "inspector/inspector.h"
39
40 #include "libmscore/articulation.h"
41 #include "libmscore/barline.h"
42 #include "libmscore/box.h"
43 #include "libmscore/chord.h"
44 #include "libmscore/clef.h"
45 #include "libmscore/dynamic.h"
46 #include "libmscore/excerpt.h"
47 #include "libmscore/figuredbass.h"
48 #include "libmscore/fingering.h"
49 #include "libmscore/hairpin.h"
50 #include "libmscore/harmony.h"
51 #include "libmscore/fret.h"
52 #include "libmscore/icon.h"
53 #include "libmscore/image.h"
54 #include "libmscore/instrchange.h"
55 #include "libmscore/keysig.h"
56 #include "libmscore/lasso.h"
57 #include "libmscore/lyrics.h"
58 #include "libmscore/measure.h"
59 #include "libmscore/navigate.h"
60 #include "libmscore/notedot.h"
61 #include "libmscore/note.h"
62 #include "libmscore/noteline.h"
63 #include "libmscore/ottava.h"
64 #include "libmscore/page.h"
65 #include "libmscore/part.h"
66 #include "libmscore/pedal.h"
67 #include "libmscore/pitchspelling.h"
68 #include "libmscore/rehearsalmark.h"
69 #include "libmscore/repeatlist.h"
70 #include "libmscore/rest.h"
71 #include "libmscore/score.h"
72 #include "libmscore/segment.h"
73 #include "libmscore/shadownote.h"
74 #include "libmscore/slur.h"
75 #include "libmscore/spanner.h"
76 #include "libmscore/staff.h"
77 #include "libmscore/stafflines.h"
78 #include "libmscore/stafftext.h"
79 #include "libmscore/stafftype.h"
80 #include "libmscore/sticking.h"
81 #include "libmscore/stringdata.h"
82 #include "libmscore/sym.h"
83 #include "libmscore/system.h"
84 #include "libmscore/systemtext.h"
85 #include "libmscore/textframe.h"
86 #include "libmscore/text.h"
87 #include "libmscore/timesig.h"
88 #include "libmscore/trill.h"
89 #include "libmscore/tuplet.h"
90 #include "libmscore/undo.h"
91 #include "libmscore/utils.h"
92 #include "libmscore/volta.h"
93 #include "libmscore/xml.h"
94 #include "libmscore/textline.h"
95 #include "libmscore/shape.h"
96
97 #ifdef AVSOMR
98 #include "avsomr/avsomr.h"
99 #include "avsomr/avsomrdrawer.h"
100 #endif
101
102 namespace Ms {
103
104 extern QErrorMessage* errorMessage;
105
106 //---------------------------------------------------------
107 // ScoreView
108 //---------------------------------------------------------
109
ScoreView(QWidget * parent)110 ScoreView::ScoreView(QWidget* parent)
111 : QWidget(parent), editData(this)
112 {
113 setObjectName("scoreview");
114 setStatusTip("scoreview");
115 setAcceptDrops(true);
116 #ifndef Q_OS_MAC
117 setAttribute(Qt::WA_OpaquePaintEvent);
118 #endif
119 setAttribute(Qt::WA_NoSystemBackground);
120 setFocusPolicy(Qt::ClickFocus);
121 setAttribute(Qt::WA_InputMethodEnabled);
122 setAttribute(Qt::WA_KeyCompression);
123 setAttribute(Qt::WA_StaticContents);
124 setAutoFillBackground(false);
125
126 state = ViewState::NORMAL;
127 _score = 0;
128 _omrView = 0;
129 dropTarget = 0;
130
131 realtimeTimer = new QTimer(this);
132 realtimeTimer->setTimerType(Qt::PreciseTimer);
133 connect(realtimeTimer, SIGNAL(timeout()), this, SLOT(triggerCmdRealtimeAdvance()));
134 extendNoteTimer = new QTimer(this);
135 extendNoteTimer->setTimerType(Qt::PreciseTimer);
136 extendNoteTimer->setSingleShot(true);
137 connect(extendNoteTimer, SIGNAL(timeout()), this, SLOT(extendCurrentNote()));
138
139 setContextMenuPolicy(Qt::DefaultContextMenu);
140
141 const auto defaultLogicalZoom = ZoomBox::getDefaultLogicalZoom();
142 _zoomIndex = defaultLogicalZoom.index;
143 const qreal physicalZoomLevel = defaultLogicalZoom.level * (mscore->physicalDotsPerInch() / DPI);
144 _matrix = QTransform(physicalZoomLevel, 0.0, 0.0, physicalZoomLevel, 0.0, 0.0);
145 imatrix = _matrix.inverted();
146
147 focusFrame = 0;
148 _bgColor = Qt::darkBlue;
149 _fgColor = Qt::white;
150 _fgPixmap = 0;
151 _bgPixmap = 0;
152
153 editData.curGrip = Grip::NO_GRIP;
154 editData.grips = 0;
155 editData.element = 0;
156
157 lasso = new Lasso(_score);
158 _foto = 0;// new Lasso(_score);
159
160 _cursor = new PositionCursor(this);
161 _cursor->setType(CursorType::POS);
162
163 _controlCursor = new PositionCursor(this);
164 _controlCursor->setType(CursorType::POS);
165 _controlCursor->setVisible(preferences.getBool(PREF_PAN_CURSOR_VISIBLE));
166 _panSettings.loadFromPreferences();
167 _controlModifier = _panSettings.controlModifierBase;
168
169 _continuousPanel = new ContinuousPanel(this);
170 _continuousPanel->setActive(true);
171
172 shadowNote = 0;
173
174 _curLoopIn = new PositionCursor(this);
175 _curLoopIn->setType(CursorType::LOOP_IN);
176 _curLoopOut = new PositionCursor(this);
177 _curLoopOut->setType(CursorType::LOOP_OUT);
178
179 if (converterMode) // HACK
180 return;
181
182 grabGesture(Qt::PinchGesture); // laptop pad (Mac) and touchscreen
183
184 //-----------------------------------------------------------------------
185
186 if (MScore::debugMode)
187 setMouseTracking(true);
188
189 if (preferences.getBool(PREF_UI_CANVAS_BG_USECOLOR))
190 setBackground(MScore::bgColor);
191 else {
192 QPixmap* pm = new QPixmap(preferences.getString(PREF_UI_CANVAS_BG_WALLPAPER));
193 setBackground(pm);
194 }
195 if (preferences.getBool(PREF_UI_CANVAS_FG_USECOLOR))
196 setForeground(preferences.getColor(PREF_UI_CANVAS_FG_COLOR));
197 else {
198 QPixmap* pm = new QPixmap(preferences.getString(PREF_UI_CANVAS_FG_WALLPAPER));
199 if (pm == 0 || pm->isNull())
200 qDebug("no valid pixmap %s", qPrintable(preferences.getString(PREF_UI_CANVAS_FG_WALLPAPER)));
201 setForeground(pm);
202 }
203
204 connect(getAction("loop"), SIGNAL(toggled(bool)), SLOT(loopToggled(bool)));
205 if (seq)
206 connect(seq, SIGNAL(stopped()), SLOT(seqStopped()));
207 }
208
209 //---------------------------------------------------------
210 // setScore
211 //---------------------------------------------------------
212
setScore(Score * s)213 void ScoreView::setScore(Score* s)
214 {
215 if (_score) {
216 if (_score->isMaster()) {
217 MasterScore* ms = static_cast<MasterScore*>(s);
218 for (MasterScore* _ms : *ms->movements()) {
219 _ms->removeViewer(this);
220 disconnect(s, SIGNAL(posChanged(POS, int)), this, SLOT(posChanged(POS,int)));
221 }
222 }
223 else {
224 _score->removeViewer(this);
225 disconnect(s, SIGNAL(posChanged(POS, int)), this, SLOT(posChanged(POS,int)));
226 }
227 }
228
229 _score = s;
230 if (_score) {
231 if (_score->isMaster()) {
232 MasterScore* ms = static_cast<MasterScore*>(s);
233 for (MasterScore* _ms : *ms->movements()) {
234 _ms->addViewer(this);
235 }
236 }
237 else
238 _score->addViewer(this);
239 }
240
241 if (shadowNote == 0) {
242 shadowNote = new ShadowNote(_score);
243 shadowNote->setVisible(false);
244 }
245 else
246 shadowNote->setScore(_score);
247 lasso->setScore(s);
248 _continuousPanel->setScore(_score);
249
250 if (_score) {
251 _curLoopIn->move(s->pos(POS::LEFT));
252 _curLoopOut->move(s->pos(POS::RIGHT));
253 loopToggled(getAction("loop")->isChecked());
254
255 connect(s, SIGNAL(posChanged(POS,unsigned)), SLOT(posChanged(POS,unsigned)));
256 connect(this, SIGNAL(viewRectChanged()), this, SLOT(updateContinuousPanel()));
257 }
258 }
259
260 //---------------------------------------------------------
261 // ScoreView
262 //---------------------------------------------------------
263
~ScoreView()264 ScoreView::~ScoreView()
265 {
266 if (_score)
267 _score->removeViewer(this);
268 delete lasso;
269 delete _foto;
270 delete _cursor;
271 delete _controlCursor;
272 delete _continuousPanel;
273 delete _curLoopIn;
274 delete _curLoopOut;
275 delete _bgPixmap;
276 delete _fgPixmap;
277 delete shadowNote;
278 }
279
280 //---------------------------------------------------------
281 // objectPopup
282 // the menu can be extended by Elements with
283 // genPropertyMenu()/propertyAction() methods
284 //---------------------------------------------------------
285
objectPopup(const QPoint & pos,Element * obj)286 void ScoreView::objectPopup(const QPoint& pos, Element* obj)
287 {
288 // show tuplet properties if number is clicked:
289 if (obj->isText() && obj->parent() && obj->parent()->isTuplet()) {
290 obj = obj->parent();
291 if (!obj->selected())
292 obj->score()->select(obj, SelectType::SINGLE, 0);
293 }
294
295 QMenu* popup = new QMenu(this);
296 popup->setSeparatorsCollapsible(false);
297 QAction* a = popup->addSeparator();
298
299 // Set Slur or Tie according to the selected object
300 if (obj->type() != ElementType::SLUR_SEGMENT) {
301 if ((obj->type() == ElementType::STAFF_TEXT) && (toStaffText(obj)->systemFlag()))
302 a->setText(tr("System Text"));
303 else
304 a->setText(obj->userName());
305 }
306 else if (static_cast<SlurSegment*>(obj)->spanner()->type() == ElementType::SLUR)
307 a->setText(tr("Slur"));
308 else if (static_cast<SlurSegment*>(obj)->spanner()->type() == ElementType::TIE)
309 a->setText(tr("Tie"));
310
311 popup->addAction(getAction("cut"));
312 popup->addAction(getAction("copy"));
313 popup->addAction(getAction("paste"));
314 popup->addAction(getAction("swap"));
315 popup->addAction(getAction("delete"));
316 if (obj->isNote() || obj->isRest()) {
317 a = getAction("time-delete");
318 popup->addAction(a);
319 }
320
321 QMenu* selMenu = popup->addMenu(tr("Select"));
322 selMenu->addAction(getAction("select-similar"));
323 selMenu->addAction(getAction("select-similar-staff"));
324 selMenu->addAction(getAction("select-similar-range"));
325 a = selMenu->addAction(tr("More…"));
326 a->setData("select-dialog");
327
328 popup->addSeparator();
329 a = getAction("edit-element");
330 popup->addAction(a);
331 a->setEnabled(obj->isEditable());
332
333 createElementPropertyMenu(obj, popup);
334
335 popup->addSeparator();
336 a = popup->addAction(tr("Help"));
337 a->setData("help");
338
339 #ifndef NDEBUG
340 popup->addSeparator();
341 popup->addAction("Debugger")->setData("list");
342 #endif
343
344 popupActive = true;
345 a = popup->exec(pos);
346 popupActive = false;
347 if (a == 0)
348 return;
349 const QByteArray& cmd(a->data().toByteArray());
350 if (cmd == "cut" || cmd =="copy" || cmd == "paste" || cmd == "swap"
351 || cmd == "delete" || cmd == "time-delete") {
352 // these actions are already activated
353 return;
354 }
355 if (cmd == "list")
356 mscore->showElementContext(obj);
357 else if (cmd == "help")
358 mscore->showHelp(QString("element:%1").arg(obj->name()));
359 else if (cmd == "edit-element") {
360 if (obj->isEditable()) {
361 if (obj->score())
362 obj->score()->select(obj);
363 startEditMode(obj);
364 return;
365 }
366 }
367 else if (cmd == "select-similar")
368 mscore->selectSimilar(obj, false);
369 else if (cmd == "select-similar-staff")
370 mscore->selectSimilar(obj, true);
371 else if (cmd == "select-similar-range")
372 mscore->selectSimilarInRange(obj);
373 else if (cmd == "select-dialog")
374 mscore->selectElementDialog(obj);
375 else if (cmd == "realize-chord-symbols-dialog") {
376 if (obj->isEditable()) {
377 // try to construct a reasonable selection
378 if (obj->score()) {
379 Score* score = obj->score();
380 if (score->selection().isRange())
381 mscore->selectSimilarInRange(obj);
382 score->select(obj, SelectType::ADD);
383 }
384 mscore->realizeChordSymbols();
385 }
386 }
387 else {
388 _score->startCmd();
389 elementPropertyAction(cmd, obj);
390 if (score()->undoStack()->active())
391 _score->endCmd();
392 }
393 }
394
395 //---------------------------------------------------------
396 // measurePopup
397 //---------------------------------------------------------
398
measurePopup(QContextMenuEvent * ev,Measure * obj)399 void ScoreView::measurePopup(QContextMenuEvent* ev, Measure* obj)
400 {
401 int staffIdx;
402 int pitch;
403 Segment* seg;
404
405 QPoint gpos = ev->globalPos();
406
407 if (!_score->pos2measure(editData.startMove, &staffIdx, &pitch, &seg, 0))
408 return;
409 if (staffIdx == -1) {
410 qDebug("ScoreView::measurePopup: staffIdx == -1!");
411 return;
412 }
413
414 Staff* staff = _score->staff(staffIdx);
415
416 QMenu* popup = new QMenu(this);
417 popup->setSeparatorsCollapsible(false);
418
419 QAction* a = popup->addSeparator();
420 a->setText(tr("Staff"));
421 a = popup->addAction(tr("Edit Drumset…"));
422 a->setData("edit-drumset");
423 a->setEnabled(staff->part()->instrument(obj->tick())->drumset() != 0);
424
425 a = popup->addAction(tr("Piano Roll Editor…"));
426 a->setData("pianoroll");
427
428 a = popup->addAction(tr("Staff/Part Properties…"));
429 a->setData("staff-properties");
430 a = popup->addAction(tr("Split Staff…"));
431 a->setData("staff-split");
432
433 a = popup->addSeparator();
434 a->setText(tr("Measure"));
435 popup->addAction(getAction("cut"));
436 popup->addAction(getAction("copy"));
437 popup->addAction(getAction("paste"));
438 popup->addAction(getAction("swap"));
439 popup->addAction(getAction("delete"));
440 popup->addAction(getAction("time-delete"));
441
442 popup->addSeparator();
443 QMenu* menuAdd = popup->addMenu(tr("Add"));
444 menuAdd->addAction(getAction("insert-measure"));
445 menuAdd->addAction(getAction("insert-measures"));
446 menuAdd->addAction(getAction("insert-hbox"));
447 menuAdd->addAction(getAction("insert-vbox"));
448 menuAdd->addAction(getAction("insert-textframe"));
449
450 popup->addSeparator();
451
452 a = popup->addAction(tr("Measure Properties…"));
453 a->setData("props");
454 a->setEnabled(!obj->isMMRest());
455 popup->addSeparator();
456
457 #ifndef NDEBUG
458 popup->addAction("Object Debugger")->setData("list");
459 #endif
460
461 a = popup->exec(gpos);
462 if (a == 0)
463 return;
464 QString cmd(a->data().toString());
465 if (cmd == "cut" || cmd =="copy" || cmd == "paste" || cmd == "swap"
466 || cmd == "insert-measure" || cmd == "select-similar"
467 || cmd == "delete" || cmd == "time-delete") {
468 // these actions are already activated
469 return;
470 }
471 _score->startCmd();
472 if (cmd == "list")
473 mscore->showElementContext(obj);
474 else if (cmd == "color")
475 _score->colorItem(obj);
476 else if (cmd == "edit") {
477 if (obj->isEditable()) {
478 startEditMode(obj);
479 return;
480 }
481 }
482 else if (cmd == "edit-drumset") {
483 EditDrumset drumsetEdit(staff->part()->instrument(obj->tick())->drumset(), this);
484 if (drumsetEdit.exec()) {
485 _score->undo(new ChangeDrumset(staff->part()->instrument(obj->tick()), drumsetEdit.drumset()));
486 mscore->updateDrumTools(drumsetEdit.drumset());
487 if (_score->undoStack()->active()) {
488 _score->setLayoutAll();
489 _score->endCmd();
490 }
491 }
492 }
493 else if (cmd == "drumroll") {
494 _score->endCmd();
495 mscore->editInDrumroll(staff);
496 }
497 else if (cmd == "pianoroll") {
498 _score->endCmd();
499 QPointF p = toLogical(ev->pos());
500 Position pp;
501 bool foundPos = _score->getPosition(&pp, p, 0);
502 mscore->editInPianoroll(staff, foundPos ? &pp : 0);
503 }
504 else if (cmd == "staff-properties") {
505 Fraction tick = obj ? obj->tick() : Fraction(-1,1);
506 EditStaff editStaff(staff, tick, this);
507 connect(&editStaff, SIGNAL(instrumentChanged()), mscore, SLOT(instrumentChanged()));
508 editStaff.exec();
509 }
510 else if (cmd == "staff-split") {
511 SplitStaff splitStaff(this);
512 if (splitStaff.exec())
513 _score->splitStaff(staffIdx, splitStaff.getSplitPoint());
514 }
515 else if (cmd == "props") {
516 MeasureProperties im(obj);
517 im.exec();
518 }
519 if (_score->undoStack()->active())
520 _score->endCmd();
521 }
522
523 //---------------------------------------------------------
524 // setBackground
525 //---------------------------------------------------------
526
setBackground(QPixmap * pm)527 void ScoreView::setBackground(QPixmap* pm)
528 {
529 delete _bgPixmap;
530 _bgPixmap = pm;
531 update();
532 }
533
setBackground(const QColor & color)534 void ScoreView::setBackground(const QColor& color)
535 {
536 delete _bgPixmap;
537 _bgPixmap = 0;
538 _bgColor = color;
539 update();
540 }
541
542 //---------------------------------------------------------
543 // setForeground
544 //---------------------------------------------------------
545
setForeground(QPixmap * pm)546 void ScoreView::setForeground(QPixmap* pm)
547 {
548 delete _fgPixmap;
549 _fgPixmap = pm;
550 update();
551 }
552
setForeground(const QColor & color)553 void ScoreView::setForeground(const QColor& color)
554 {
555 delete _fgPixmap;
556 _fgPixmap = 0;
557 _fgColor = color;
558 update();
559 }
560
561 //---------------------------------------------------------
562 // dataChanged
563 //---------------------------------------------------------
564
dataChanged(const QRectF & r)565 void ScoreView::dataChanged(const QRectF& r)
566 {
567 update(_matrix.mapRect(r).toRect()); // generate paint event
568 }
569
570 //---------------------------------------------------------
571 // moveCursor
572 // move cursor during playback
573 //---------------------------------------------------------
574
moveCursor(const Fraction & tick)575 void ScoreView::moveCursor(const Fraction& tick)
576 {
577 Measure* measure = score()->tick2measureMM(tick);
578 if (measure == 0)
579 return;
580
581 qreal x = 0.0;
582 Segment* s;
583 for (s = measure->first(SegmentType::ChordRest); s;) {
584 Fraction t1 = s->tick();
585 int x1 = s->canvasPos().x();
586 qreal x2;
587 Fraction t2;
588 Segment* ns = s->next(SegmentType::ChordRest);
589 while (ns && !ns->visible())
590 ns = ns->next(SegmentType::ChordRest);
591 if (ns) {
592 t2 = ns->tick();
593 x2 = ns->canvasPos().x();
594 }
595 else {
596 t2 = measure->endTick();
597 // measure->width is not good enough because of courtesy keysig, timesig
598 Segment* seg = measure->findSegment(SegmentType::EndBarLine, measure->tick() + measure->ticks());
599 if (seg)
600 x2 = seg->canvasPos().x();
601 else
602 x2 = measure->canvasPos().x() + measure->width(); //safety, should not happen
603 }
604 if (tick >= t1 && tick < t2) {
605 Fraction dt = t2 - t1;
606 qreal dx = x2 - x1;
607 x = x1 + dx * (tick-t1).ticks() / dt.ticks();
608 break;
609 }
610 s = ns;
611 }
612 if (s == 0)
613 return;
614
615 QColor c(MScore::selectColor[0]);
616 c.setAlpha(50);
617 _cursor->setColor(c);
618 _cursor->setTick(tick);
619
620 System* system = measure->system();
621 if (system == 0)
622 return;
623 double y = system->staffYpage(0) + system->page()->pos().y();
624 double _spatium = score()->spatium();
625
626 update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
627
628 qreal mag = _spatium / SPATIUM20;
629 double w = _spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag);
630 double h = 6 * _spatium;
631 //
632 // set cursor height for whole system
633 //
634 double y2 = 0.0;
635
636 for (int i = 0; i < _score->nstaves(); ++i) {
637 SysStaff* ss = system->staff(i);
638 if (!ss->show() || !_score->staff(i)->show())
639 continue;
640 y2 = ss->bbox().bottom();
641 }
642 h += y2;
643 x -= _spatium;
644 y -= 3 * _spatium;
645
646 _cursor->setRect(QRectF(x, y, w, h));
647 update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
648
649 if (_score->layoutMode() == LayoutMode::LINE && seq->isPlaying() && panSettings().enabled)
650 moveControlCursor(tick);
651
652 if (mscore->state() == ScoreState::STATE_PLAY && mscore->panDuringPlayback()) {
653 adjustCanvasPosition(measure, true);
654 }
655 }
656
657 //---------------------------------------------------------
658 // moveControlCursor
659 // move the control cursor during playback
660 //
661 // This works by calculating the total length (in sp) of the score
662 // and then using that information to calculate the position of the control cursor based on the time passed.
663 // Because not all measures have the same width some modifications are made
664 // to the distance moved each time that this is called (once every 20ms, or whatever the hearbeat is).
665 // That means that not all milliseconds are treated equally, some cause larger and some smaller movement.
666 //---------------------------------------------------------
667
moveControlCursor(const Fraction & tick)668 void ScoreView::moveControlCursor(const Fraction& tick)
669 {
670 QColor c(Qt::green);
671 c.setAlpha(30);
672 _controlCursor->setColor(c);
673 _controlCursor->setTick(tick);
674
675 int realX = _cursor->rect().x();
676 int controlX = _controlCursor->rect().x();
677 double distance = realX - controlX;
678
679 if (seq->isPlaying() && isCursorDistanceReasonable()) {
680 // playbackCursor in front of the controlCursor
681 if (distance > _panSettings.rightDistance)
682 _controlModifier += _panSettings.controlModifierSteps;
683 else if (distance > _panSettings.rightDistance1 && _controlModifier < _panSettings.rightMod1)
684 _controlModifier += _panSettings.controlModifierSteps;
685 else if (distance > _panSettings.rightDistance2 && _controlModifier < _panSettings.rightMod2)
686 _controlModifier += _panSettings.controlModifierSteps;
687 else if (_controlModifier > _panSettings.rightMod1 && distance < _panSettings.rightDistance1)
688 _controlModifier -= _panSettings.controlModifierSteps;
689 else if (_controlModifier > _panSettings.rightMod2 && distance < _panSettings.rightDistance2)
690 _controlModifier -= _panSettings.controlModifierSteps;
691 else if (_controlModifier > _panSettings.rightMod3 && distance < _panSettings.rightDistance3)
692 _controlModifier = _panSettings.controlModifierBase;
693 // playbackCursor behind the controlCursor
694 else if (distance < _panSettings.leftDistance)
695 _controlModifier -= _panSettings.controlModifierSteps;
696 else if (_controlModifier < _panSettings.leftMod1 && distance > _panSettings.leftDistance1)
697 _controlModifier += _panSettings.controlModifierSteps;
698 else if (_controlModifier < _panSettings.leftMod2 && distance > _panSettings.leftDistance2)
699 _controlModifier += _panSettings.controlModifierSteps;
700 else if (_controlModifier < _panSettings.leftMod3 && distance > _panSettings.leftDistance3)
701 _controlModifier = _panSettings.controlModifierBase;
702
703 // enforce limits
704 if (_controlModifier < _panSettings.minContinuousModifier)
705 _controlModifier = _panSettings.minContinuousModifier;
706 else if (_controlModifier > _panSettings.maxContinuousModifier)
707 _controlModifier = _panSettings.maxContinuousModifier;
708
709 double addition = 0;
710 if (_controlCursorTimer.isValid()) {
711 if (!_panSettings.advancedWeighting || _controlModifier == _panSettings.minContinuousModifier || _controlModifier == _panSettings.maxContinuousModifier) {
712 addition = _controlCursorTimer.elapsed() * _controlModifier;
713 }
714 else {
715 addition = _controlCursorTimer.elapsed() * _controlModifier * _panSettings.normalWeight + _controlCursorTimer.elapsed() * (_playbackCursorDistanceTravelled/500) * _panSettings.smartWeight;
716 }
717 }
718 _timeElapsed += addition;
719 }
720 else { // reposition the cursor when distance is too great
721 double curOffset = _cursor->rect().x() - score()->firstMeasure()->pos().x();
722 double length = score()->lastMeasure()->pos().x() - score()->firstMeasure()->pos().x();
723 _timeElapsed = (curOffset / length) * score()->durationWithoutRepeats() * 1000;
724 _controlModifier = _panSettings.controlModifierBase;
725 }
726
727 // Prepare for the next round of calculations
728 if (!_controlCursorTimer.isValid() && distance > 100) // distance > 100 used to hack around the count-in option
729 _controlCursorTimer.start();
730 else if (_controlCursorTimer.isValid())
731 _controlCursorTimer.restart();
732
733 if (!_playbackCursorTimer.isValid() && _controlCursorTimer.isValid()) {
734 _playbackCursorOldPosition = _cursor->rect().x();
735 _playbackCursorTimer.start();
736 }
737 else if (_playbackCursorTimer.isValid() && _playbackCursorTimer.elapsed() > _panSettings.cursorTimerDuration) {
738 _playbackCursorNewPosition = _cursor->rect().x();
739 _playbackCursorDistanceTravelled = _playbackCursorNewPosition - _playbackCursorOldPosition;
740 _playbackCursorOldPosition = _playbackCursorNewPosition;
741 _playbackCursorTimer.restart();
742 }
743
744
745 // Calculate the position of the controlCursor based on the timeElapsed (which is not the real time that has passed)
746 qreal x = score()->firstMeasure()->pos().x() + (score()->lastMeasure()->pos().x() - score()->firstMeasure()->pos().x()) * (_timeElapsed / (score()->durationWithoutRepeats() * 1000));
747 x -= score()->spatium();
748 _controlCursor->setRect(QRectF(x, _cursor->rect().y(), _cursor->rect().width(), _cursor->rect().height()));
749 update(_matrix.mapRect(_controlCursor->rect()).toRect().adjusted(-1,-1,1,1));
750 }
751
752 //---------------------------------------------------------
753 // isCursorDistanceReasonable
754 // check if the control cursor needs to be teleported
755 // to catch up with the playback cursor (for smooth panning)
756 //---------------------------------------------------------
757
isCursorDistanceReasonable()758 bool ScoreView::isCursorDistanceReasonable()
759 {
760 qreal viewWidth = canvasViewport().width();
761 qreal controlX = _controlCursor->rect().x();
762 qreal playbackX = _cursor->rect().x();
763 qreal cursorDistance = abs(controlX - playbackX);
764 double maxLeftDistance = viewWidth * (_panSettings.controlCursorScreenPos + 0.07); // 0.05 left margin + 0.02 for making this less sensitive
765 double maxRightDistance = viewWidth * (1 - _panSettings.controlCursorScreenPos + 0.15); // teleporting to the right is harder to trigger (we don't want to overdo it)
766
767 if (controlX < playbackX && _panSettings.teleportRightEnabled)
768 return cursorDistance < maxRightDistance;
769
770 if (playbackX < controlX && _panSettings.teleportLeftEnabled)
771 return cursorDistance < maxLeftDistance;
772
773 return true;
774 }
775
776 //---------------------------------------------------------
777 // moveControlCursorNearCursor
778 /// used to position the control cursor correctly
779 /// when starting playback
780 //---------------------------------------------------------
781
moveControlCursorNearCursor()782 void ScoreView::moveControlCursorNearCursor()
783 {
784 double curOffset = _cursor->rect().x() - score()->firstMeasure()->pos().x();
785 double length = score()->lastMeasure()->pos().x() - score()->firstMeasure()->pos().x();
786 _timeElapsed = (curOffset / length) * score()->durationWithoutRepeats() * 1000;
787 qreal x = score()->firstMeasure()->pos().x() + (score()->lastMeasure()->pos().x() - score()->firstMeasure()->pos().x()) * (_timeElapsed / (score()->durationWithoutRepeats() * 1000));
788 x -= score()->spatium();
789 _controlCursor->setRect(QRectF(x, _cursor->rect().y(), _cursor->rect().width(), _cursor->rect().height()));
790 }
791
792 //---------------------------------------------------------
793 // moveCursor
794 // move cursor in note input mode
795 //---------------------------------------------------------
796
moveCursor()797 void ScoreView::moveCursor()
798 {
799 const InputState& is = _score->inputState();
800 Segment* segment = is.segment();
801 if (segment && score()->styleB(Sid::createMultiMeasureRests) && segment->measure()->hasMMRest()) {
802 Measure* m = segment->measure()->mmRest();
803 segment = m->findSegment(SegmentType::ChordRest, m->tick());
804 }
805 if (!segment)
806 return;
807
808 int track = is.track() == -1 ? 0 : is.track();
809 int voice = track % VOICES;
810 int staffIdx = track / VOICES;
811
812 QColor c(MScore::selectColor[voice]);
813 c.setAlpha(50);
814 _cursor->setColor(c);
815 _cursor->setTick(segment->tick());
816
817 System* system = segment->measure()->system();
818 if (system == 0) {
819 // a new measure was appended but no layout took place
820 // or this measure was skipped by a multi measure rest
821 return;
822 }
823 double x = segment->canvasPos().x();
824 double y = system->staffYpage(staffIdx) + system->page()->pos().y();
825 double _spatium = score()->spatium();
826 x -= qMin(segment->pos().x() - score()->styleP(Sid::barNoteDistance), 0.0);
827
828 update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
829
830 double h;
831 qreal mag = _spatium / SPATIUM20;
832 double w = _spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag);
833 Staff* staff = score()->staff(staffIdx);
834 const StaffType* staffType = staff->staffType(is.tick());
835 double lineDist = staffType->lineDistance().val() * _spatium;
836 int lines = staffType->lines();
837 int strg = is.string(); // strg refers to an instrument physical string
838 x -= _spatium;
839 int instrStrgs = staff->part()->instrument(is.tick())->stringData()->strings();
840 // if on a TAB staff and InputState::_string makes sense,
841 // draw cursor around single string
842 if (staff->isTabStaff(is.tick()) && strg >= 0 && strg <= instrStrgs) {
843 h = lineDist; // cursor height is one full line distance
844 y += staffType->physStringToYOffset(strg) * _spatium;
845 // if frets are on lines, centre on string; if frets are above lines, 'sit' above string
846 y -= (staffType->onLines() ? lineDist * 0.5 : lineDist);
847 // look for a note on this string in this staff
848 // if found, it will be selected, to synchronize the 'new note input cursor' and the 'current note cursor'
849 // i.e. the point where a new note would be added and the existing note which receives any editing
850 // (like pitch change or articulation addition)
851 bool done = false;
852 Segment* seg = is.segment();
853 int minTrack = (is.track() / VOICES) * VOICES;
854 int maxTrack = minTrack + VOICES;
855 // get selected chord, if one exists and is in this segment
856 ChordRest* scr = _score->selection().cr();
857 if (scr && (scr->type() != ElementType::CHORD || scr->segment() != seg))
858 scr = nullptr;
859 // get the physical string corresponding to current visual string
860 for (int t = minTrack; t < maxTrack; t++) {
861 Element* e = seg->element(t);
862 if (e != nullptr && e->type() == ElementType::CHORD) {
863 // if there is a selected chord in this segment on this track but it is not e
864 // then the selected chord must be a grace note chord, and we should use it
865 if (scr && scr->track() == t && scr != e)
866 e = scr;
867 // search notes looking for one on current string
868 for (Note* n : static_cast<Chord*>(e)->notes())
869 // if note found on this string, make it current
870 if (n->string() == strg) {
871 if (!n->selected()) {
872 _score->select(n);
873 // restore input state after selection
874 _score->inputState().setTrack(track);
875 }
876 #if 0
877 // if using this code, we can delete the setTrack() call above
878 // the code below forces input state & cursor to match current note
879 _score->inputState().setTrack(t);
880 QColor c(MScore::selectColor[t % VOICES]);
881 c.setAlpha(50);
882 _cursor->setColor(c);
883 #endif
884 done = true;
885 break;
886 }
887 }
888 if (done)
889 break;
890 }
891 }
892 // otherwise, draw cursor across whole staff
893 else {
894 h = (lines - 1) * lineDist + 4 * _spatium;
895 y -= 2.0 * _spatium;
896 }
897 _cursor->setRect(QRectF(x, y, w, h));
898 update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
899 if (is.cr())
900 adjustCanvasPosition(is.cr(), false);
901 }
902
903 //---------------------------------------------------------
904 // cursorTick
905 //---------------------------------------------------------
906
cursorTick() const907 Fraction ScoreView::cursorTick() const
908 {
909 return _cursor->tick();
910 }
911
912 //---------------------------------------------------------
913 // setCursorOn
914 //---------------------------------------------------------
915
setCursorOn(bool val)916 void ScoreView::setCursorOn(bool val)
917 {
918 if (_cursor && (_cursor->visible() != val)) {
919 _cursor->setVisible(val);
920 update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
921 }
922 }
923
924 //---------------------------------------------------------
925 // setLoopCursor
926 // adjust the cursor shape and position to mark the loop
927 // isInPos is used to adjust the x position of In vs Out mark
928 //---------------------------------------------------------
929
setLoopCursor(PositionCursor * curLoop,const Fraction & tick,bool isInPos)930 void ScoreView::setLoopCursor(PositionCursor *curLoop, const Fraction& tick, bool isInPos)
931 {
932 //
933 // set mark height for whole system
934 //
935 Measure* measure = score()->tick2measure(tick);
936 if (measure == 0)
937 return;
938 qreal x = 0.0;
939
940 Segment* s;
941 for (s = measure->first(SegmentType::ChordRest); s;) {
942 Fraction t1 = s->tick();
943 int x1 = s->canvasPos().x();
944 qreal x2;
945 Fraction t2;
946 Segment* ns = s->next(SegmentType::ChordRest);
947 if (ns) {
948 t2 = ns->tick();
949 x2 = ns->canvasPos().x();
950 }
951 else {
952 t2 = measure->endTick();
953 x2 = measure->canvasPos().x() + measure->width();
954 }
955 if (tick >= t1 && tick < t2) {
956 Fraction dt = t2 - t1;
957 qreal dx = x2 - x1;
958 x = x1 + dx * (tick.ticks() - t1.ticks()) / dt.ticks();
959 break;
960 }
961 s = ns;
962 }
963 if (s == 0)
964 return;
965
966 System* system = measure->system();
967 if (system == 0)
968 return;
969 double y = system->staffYpage(0) + system->page()->pos().y();
970 double _spatium = score()->spatium();
971
972 qreal mag = _spatium / SPATIUM20;
973 double w = (_spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag))/3;
974 double h = 6 * _spatium;
975 //
976 // set cursor height for whole system
977 //
978 double y2 = 0.0;
979
980 for (int i = 0; i < _score->nstaves(); ++i) {
981 SysStaff* ss = system->staff(i);
982 if (!ss->show() || !_score->staff(i)->show())
983 continue;
984 y2 = ss->y() + ss->bbox().height();
985 }
986 h += y2;
987 y -= 3 * _spatium;
988
989 if (isInPos) {
990 x = x - _spatium + w/1.5;
991 }
992 else {
993 x = x - _spatium;
994 }
995 curLoop->setTick(tick);
996 update(_matrix.mapRect(curLoop->rect()).toRect().adjusted(-1,-1,1,1));
997 curLoop->setRect(QRectF(x, y, w, h));
998 update(_matrix.mapRect(curLoop->rect()).toRect().adjusted(-1,-1,1,1));
999 }
1000
1001 //---------------------------------------------------------
1002 // setShadowNote
1003 //---------------------------------------------------------
1004
setShadowNote(const QPointF & p)1005 void ScoreView::setShadowNote(const QPointF& p)
1006 {
1007 const InputState& is = _score->inputState();
1008 Position pos;
1009 if (!score()->getPosition(&pos, p, is.voice())) {
1010 shadowNote->setVisible(false);
1011 return;
1012 }
1013 // in any empty measure, pos will be right next to barline
1014 // so pad this by barNoteDistance
1015 qreal mag = score()->staff(pos.staffIdx)->mag(Fraction(0,1));
1016 qreal relX = pos.pos.x() - pos.segment->measure()->canvasPos().x();
1017 pos.pos.rx() -= qMin(relX - score()->styleP(Sid::barNoteDistance) * mag, 0.0);
1018
1019 shadowNote->setVisible(true);
1020 Staff* staff = score()->staff(pos.staffIdx);
1021 shadowNote->setMag(staff->mag(Fraction(0,1)));
1022 const Instrument* instr = staff->part()->instrument(shadowNote->tick()); // or pos.segment->tick()
1023 NoteHead::Group noteheadGroup = NoteHead::Group::HEAD_NORMAL;
1024 int line = pos.line;
1025 NoteHead::Type noteHead = is.duration().headType();
1026
1027 if (instr->useDrumset()) {
1028 const Drumset* ds = instr->drumset();
1029 int pitch = is.drumNote();
1030 if (pitch >= 0 && ds->isValid(pitch)) {
1031 line = ds->line(pitch);
1032 noteheadGroup = ds->noteHead(pitch);
1033 }
1034 }
1035
1036 shadowNote->setLine(line);
1037
1038 int voice;
1039 if (is.drumNote() != -1 && is.drumset() && is.drumset()->isValid(is.drumNote()))
1040 voice = is.drumset()->voice(is.drumNote());
1041 else
1042 voice = is.voice();
1043
1044 SymId symNotehead;
1045 TDuration d(is.duration());
1046
1047 if (is.rest()) {
1048 int yo;
1049 Rest rest(gscore, d.type());
1050 rest.setTicks(d.fraction());
1051 symNotehead = rest.getSymbol(is.duration().type(), 0, staff->lines(pos.segment->tick()), &yo);
1052 shadowNote->setState(symNotehead, voice, d, true);
1053 }
1054 else {
1055 if (NoteHead::Group::HEAD_CUSTOM == noteheadGroup)
1056 symNotehead = instr->drumset()->noteHeads(is.drumNote(), noteHead);
1057 else
1058 symNotehead = Note::noteHead(0, noteheadGroup, noteHead);
1059
1060 shadowNote->setState(symNotehead, voice, d);
1061 }
1062
1063 shadowNote->layout();
1064 shadowNote->setPos(pos.pos);
1065 }
1066
1067 //---------------------------------------------------------
1068 // drawAnchorLines
1069 //---------------------------------------------------------
drawAnchorLines(QPainter & painter)1070 void ScoreView::drawAnchorLines(QPainter& painter)
1071 {
1072 if (m_dropAnchorLines.isEmpty())
1073 return;
1074
1075 const auto dropAnchorColor = preferences.getColor(PREF_UI_SCORE_VOICE4_COLOR);
1076 QPen pen(QBrush(dropAnchorColor), 2.0 / painter.worldTransform().m11(), Qt::DotLine);
1077
1078 for (const QLineF& anchor : m_dropAnchorLines) {
1079 painter.setPen(pen);
1080 painter.drawLine(anchor);
1081
1082 qreal d = 4.0 / painter.worldTransform().m11();
1083 QRectF rect(-d, -d, 2 * d, 2 * d);
1084
1085 painter.setBrush(QBrush(dropAnchorColor));
1086 painter.setPen(Qt::NoPen);
1087 rect.moveCenter(anchor.p1());
1088 painter.drawEllipse(rect);
1089 rect.moveCenter(anchor.p2());
1090 painter.drawEllipse(rect);
1091 }
1092 }
1093
1094 //---------------------------------------------------------
1095 // paintEvent
1096 // Note: desktop background and paper background are not
1097 // scaled
1098 //---------------------------------------------------------
paintEvent(QPaintEvent * ev)1099 void ScoreView::paintEvent(QPaintEvent* ev)
1100 {
1101 if (!_score)
1102 return;
1103 QPainter vp(this);
1104 vp.setRenderHint(QPainter::Antialiasing, preferences.getBool(PREF_UI_CANVAS_MISC_ANTIALIASEDDRAWING));
1105 vp.setRenderHint(QPainter::TextAntialiasing, true);
1106
1107 paint(ev->rect(), vp);
1108
1109 vp.setTransform(_matrix);
1110 vp.setClipping(false);
1111
1112 _curLoopIn->paint(&vp);
1113 _curLoopOut->paint(&vp);
1114 _cursor->paint(&vp);
1115 if (_score->layoutMode() == LayoutMode::LINE)
1116 _controlCursor->paint(&vp);
1117
1118 if (_score->layoutMode() == LayoutMode::LINE)
1119 _continuousPanel->paint(ev->rect(), vp);
1120
1121 if (!lasso->bbox().isEmpty())
1122 lasso->draw(&vp);
1123 shadowNote->draw(&vp);
1124
1125 drawAnchorLines(vp);
1126 }
1127
1128 //---------------------------------------------------------
1129 // drawBackground
1130 //---------------------------------------------------------
1131
drawBackground(QPainter * p,const QRectF & r) const1132 void ScoreView::drawBackground(QPainter* p, const QRectF& r) const
1133 {
1134 if (score()->printing()) {
1135 p->fillRect(r, Qt::white);
1136 return;
1137 }
1138 if (_fgPixmap == 0 || _fgPixmap->isNull())
1139 p->fillRect(r, _fgColor);
1140 else {
1141 p->drawTiledPixmap(r, *_fgPixmap, r.topLeft()
1142 - QPoint(lrint(_matrix.dx()), lrint(_matrix.dy())));
1143 }
1144 }
1145
1146 //---------------------------------------------------------
1147 // paintPageBorder
1148 //---------------------------------------------------------
1149
paintPageBorder(QPainter & p,Page * page)1150 void ScoreView::paintPageBorder(QPainter& p, Page* page)
1151 {
1152 //add a black border to pages
1153 QRectF r(page->canvasBoundingRect());
1154 p.setBrush(Qt::NoBrush);
1155 p.setPen(QPen(QColor(0,0,0,102), 1));
1156 p.drawRect(r);
1157
1158 if (_score->showPageborders()) {
1159 // show page margins
1160 p.setBrush(Qt::NoBrush);
1161 p.setPen(MScore::frameMarginColor);
1162 QRectF f(page->canvasBoundingRect());
1163 f.adjust(page->lm(), page->tm(), -page->rm(), -page->bm());
1164 p.drawRect(f);
1165 if (!page->isOdd())
1166 p.drawLine(f.right(), 0.0, f.right(), f.bottom());
1167 }
1168 }
1169
1170 #ifndef NDEBUG
1171 //---------------------------------------------------------
1172 // drawDebugInfo
1173 //---------------------------------------------------------
1174
drawDebugInfo(QPainter & p,const Element * _e)1175 static void drawDebugInfo(QPainter& p, const Element* _e)
1176 {
1177 if (!MScore::showBoundingRect)
1178 return;
1179 const Element* e = _e;
1180 //
1181 // draw bounding box rectangle for all
1182 // selected Elements
1183 //
1184 QPointF pos(e->pagePos());
1185 p.translate(pos);
1186 p.setBrush(Qt::NoBrush);
1187
1188 p.setPen(QPen(Qt::red, 0.0));
1189 // p.drawRect(e->bbox());
1190 e->shape().paint(p);
1191
1192 p.setPen(QPen(Qt::red, 0.0)); // red x at 0,0 of bbox
1193 qreal w = 5.0 / p.worldTransform().toAffine().m11();
1194 qreal h = w;
1195 qreal x = 0; // e->bbox().x();
1196 qreal y = 0; // e->bbox().y();
1197 p.drawLine(QLineF(x-w, y-h, x+w, y+h));
1198 p.drawLine(QLineF(x+w, y-h, x-w, y+h));
1199
1200 p.translate(-pos);
1201 if (e->parent()) {
1202 const Element* ee = e->parent();
1203 if (e->isNote())
1204 ee = toNote(e)->chord()->segment();
1205 else if (e->isClef())
1206 ee = toClef(e)->segment();
1207
1208 p.setPen(QPen(Qt::green, 0.0));
1209
1210 p.drawRect(ee->pageBoundingRect());
1211
1212 if (ee->isSegment()) {
1213 QPointF pt = ee->pagePos();
1214 p.setPen(QPen(Qt::blue, 0.0));
1215 p.drawLine(QLineF(pt.x()-w, pt.y()-h, pt.x()+w, pt.y()+h));
1216 p.drawLine(QLineF(pt.x()+w, pt.y()-h, pt.x()-w, pt.y()+h));
1217 }
1218 }
1219 }
1220 #endif
1221
1222 //---------------------------------------------------------
1223 // drawElements
1224 //---------------------------------------------------------
1225
drawElements(QPainter & painter,QList<Element * > & el,Element * editElement)1226 void ScoreView::drawElements(QPainter& painter, QList<Element*>& el, Element* editElement)
1227 {
1228 std::stable_sort(el.begin(), el.end(), elementLessThan);
1229 for (const Element* e : el) {
1230 e->itemDiscovered = 0;
1231
1232 // harmony element representation is different in edit mode, so don't
1233 // all normal draw(). Complete drawing is done in drawEditMode()
1234 if (e == editElement)
1235 continue;
1236
1237 if (!e->visible() && (score()->printing() || !score()->showInvisible()))
1238 continue;
1239 if (e->isRest() && toRest(e)->isGap())
1240 continue;
1241 QPointF pos(e->pagePos());
1242 painter.translate(pos);
1243 e->draw(&painter);
1244 painter.translate(-pos);
1245 #ifndef NDEBUG
1246 if (e->selected())
1247 drawDebugInfo(painter, e);
1248 #endif
1249 }
1250 }
1251
1252 //---------------------------------------------------------
1253 // paint
1254 //---------------------------------------------------------
1255
paint(const QRect & r,QPainter & p)1256 void ScoreView::paint(const QRect& r, QPainter& p)
1257 {
1258 p.save();
1259 if (_fgPixmap == 0 || _fgPixmap->isNull())
1260 p.fillRect(r, _fgColor);
1261 else {
1262 p.drawTiledPixmap(r, *_fgPixmap, r.topLeft()
1263 - QPoint(lrint(_matrix.dx()), lrint(_matrix.dy())));
1264 }
1265
1266 p.setTransform(_matrix);
1267 QRectF fr = imatrix.mapRect(QRectF(r));
1268
1269 Element* editElement = 0;
1270 Lasso* lassoToDraw = 0;
1271 if (editData.element) {
1272 switch (state) {
1273 case ViewState::NORMAL:
1274 if (editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit)
1275 editData.element->drawEditMode(&p, editData);
1276 break;
1277 case ViewState::DRAG:
1278 case ViewState::DRAG_OBJECT:
1279 case ViewState::LASSO:
1280 case ViewState::NOTE_ENTRY:
1281 case ViewState::PLAY:
1282 case ViewState::ENTRY_PLAY:
1283 break;
1284 case ViewState::EDIT:
1285 case ViewState::DRAG_EDIT:
1286 case ViewState::FOTO:
1287 case ViewState::FOTO_DRAG:
1288 case ViewState::FOTO_DRAG_EDIT:
1289 case ViewState::FOTO_DRAG_OBJECT:
1290 case ViewState::FOTO_LASSO:
1291 if (editData.element->isLasso())
1292 lassoToDraw = toLasso(editData.element);
1293 else
1294 editData.element->drawEditMode(&p, editData);
1295
1296 if (editData.element->isHarmony())
1297 editElement = editData.element; // do not call paint() method
1298 break;
1299 }
1300 }
1301
1302
1303 // AvsOmr -----
1304 #ifdef AVSOMR
1305 Avs::AvsOmrDrawer omrDrawer;
1306 std::shared_ptr<Avs::AvsOmrDrawer::Context> omrDrawCtx = omrDrawer.makeContext(&p, _score);
1307 auto pageMeasures = [](Page* page) -> QList<const Measure*> {
1308 QList<const Measure*> ml;
1309 for (const System* s : page->systems()) {
1310 const std::vector<MeasureBase*>& measures = s->measures();
1311 for (const MeasureBase* mb : measures) {
1312 if (mb->isMeasure())
1313 ml << toMeasure(mb);
1314 }
1315 }
1316 return ml;
1317 };
1318 #endif
1319 // ------------
1320
1321 QRegion r1(r);
1322 if ((_score->layoutMode() == LayoutMode::LINE) || (_score->layoutMode() == LayoutMode::SYSTEM)) {
1323 if (_score->pages().size() > 0) {
1324
1325 Page* page = _score->pages().front();
1326 QList<Element*> ell = page->items(fr);
1327
1328 // AvsOmr -----
1329 #ifdef AVSOMR
1330 if (omrDrawCtx) {
1331 QList<const Measure*> ml = pageMeasures(page);
1332 omrDrawer.draw(omrDrawCtx, ml);
1333 }
1334 #endif
1335 // ------------
1336
1337 drawElements(p, ell, editElement);
1338 }
1339 }
1340 else {
1341 for (Page* page : _score->pages()) {
1342 QRectF pr(page->abbox().translated(page->pos()));
1343 if (pr.right() < fr.left())
1344 continue;
1345 if (pr.left() > fr.right())
1346 break;
1347
1348 if (!score()->printing())
1349 paintPageBorder(p, page);
1350 QList<Element*> ell = page->items(fr.translated(-page->pos()));
1351 QPointF pos(page->pos());
1352 p.translate(pos);
1353
1354 // AvsOmr -----
1355 #ifdef AVSOMR
1356 if (omrDrawCtx) {
1357 QList<const Measure*> ml = pageMeasures(page);
1358 omrDrawer.draw(omrDrawCtx, ml);
1359 }
1360 #endif
1361 // ------------
1362
1363 drawElements(p, ell, editElement);
1364
1365 #ifndef NDEBUG
1366 if (!score()->printing()) {
1367 if (MScore::showSystemBoundingRect) {
1368 for (const System* system : page->systems()) {
1369 QPointF pt(system->ipos());
1370 qreal h = system->height() + system->minBottom() + system->minTop();
1371 p.translate(pt);
1372 QRectF rect(0.0, -system->minTop(), system->width(), h);
1373 p.drawRect(rect);
1374 p.translate(-pt);
1375 }
1376 }
1377 if (MScore::showSegmentShapes) {
1378 for (const System* system : page->systems()) {
1379 for (const MeasureBase* mb : system->measures()) {
1380 if (mb->type() == ElementType::MEASURE) {
1381 const Measure* m = static_cast<const Measure*>(mb);
1382 p.setBrush(Qt::NoBrush);
1383 p.setPen(QPen(QBrush(Qt::darkYellow), 0.5));
1384 for (const Segment* s = m->first(); s; s = s->next()) {
1385 for (int i = 0; i < score()->nstaves(); ++i) {
1386 QPointF pt(s->pos().x() + m->pos().x() + system->pos().x(),
1387 system->staffYpage(i));
1388 p.translate(pt);
1389 s->shapes().at(i).paint(p);
1390 p.translate(-pt);
1391 }
1392 }
1393 }
1394 }
1395 }
1396 }
1397 if (MScore::showSkylines) {
1398 for (const System* system : page->systems()) {
1399 for (SysStaff* ss : *system->staves()) {
1400 QPointF pt(system->ipos().x(), system->ipos().y() + ss->y());
1401 p.translate(pt);
1402 ss->skyline().paint(p);
1403 p.translate(-pt);
1404 }
1405 }
1406 }
1407 if (MScore::showCorruptedMeasures) {
1408 double _spatium = score()->spatium();
1409 QPen pen;
1410 pen.setColor(Qt::red);
1411 pen.setWidthF(4);
1412 pen.setStyle(Qt::SolidLine);
1413 p.setPen(pen);
1414 p.setBrush(Qt::NoBrush);
1415 for (const System* system : page->systems()) {
1416 for (const MeasureBase* mb : system->measures()) {
1417 if (mb->type() == ElementType::MEASURE) {
1418 const Measure* m = static_cast<const Measure*>(mb);
1419 for (int staffIdx = 0; staffIdx < m->score()->nstaves(); staffIdx++) {
1420 if (m->corrupted(staffIdx)) {
1421 p.drawRect(m->staffabbox(staffIdx).adjusted(0, -_spatium, 0, _spatium));
1422 }
1423 }
1424 }
1425 }
1426 }
1427 }
1428 }
1429 #endif
1430
1431 p.translate(-pos);
1432 r1 -= _matrix.mapRect(pr).toAlignedRect();
1433 }
1434 }
1435 if (dropRectangle.isValid())
1436 p.fillRect(dropRectangle, QColor(80, 0, 0, 80));
1437
1438 const Selection& sel = _score->selection();
1439 if (sel.isRange()) {
1440 Segment* ss = sel.startSegment();
1441 Segment* es = sel.endSegment();
1442
1443 if (!ss)
1444 return;
1445
1446 if (!ss->enabled())
1447 ss = ss->next1MMenabled();
1448 if (es && !es->enabled())
1449 es = es->next1MMenabled();
1450 if (es && ss->tick() > es->tick()) // start after end?
1451 return;
1452
1453 if (!ss->measure()->system()) {
1454 // segment is in a measure that has not been laid out yet
1455 // this can happen in mmrests
1456 // first chordrest segment of mmrest instead
1457 const Measure* mmr = ss->measure()->mmRest1();
1458 if (mmr && mmr->system())
1459 ss = mmr->first(SegmentType::ChordRest);
1460 else
1461 return; // still no system?
1462 if (!ss)
1463 return; // no chordrest segment?
1464 }
1465
1466 p.setBrush(Qt::NoBrush);
1467
1468 QPen pen;
1469 pen.setColor(MScore::selectColor[0]);
1470 pen.setWidthF(2.0 / p.worldTransform().toAffine().m11());
1471
1472 pen.setStyle(Qt::SolidLine);
1473
1474 p.setPen(pen);
1475 double _spatium = score()->spatium();
1476 double x2 = ss->pagePos().x() - _spatium;
1477 int staffStart = sel.staffStart();
1478 int staffEnd = sel.staffEnd();
1479
1480 System* system2 = ss->measure()->system();
1481 QPointF pt = ss->pagePos();
1482 double y = pt.y();
1483 SysStaff* ss1 = system2->staff(staffStart);
1484
1485 // find last visible staff:
1486 int lastStaff = 0;
1487 for (int i = staffEnd-1; i >= 0; --i) {
1488 if (score()->staff(i)->show()) {
1489 lastStaff = i;
1490 break;
1491 }
1492 }
1493 SysStaff* ss2 = system2->staff(lastStaff);
1494
1495 double y1 = ss1->y() - 2 * score()->staff(staffStart)->spatium(Fraction(0,1)) + y;
1496 double y2 = ss2->y() + ss2->bbox().height() + 2 * score()->staff(lastStaff)->spatium(Fraction(0,1)) + y;
1497
1498 // drag vertical start line
1499 p.drawLine(QLineF(x2, y1, x2, y2).translated(system2->page()->pos()));
1500
1501 System* system1 = system2;
1502 double x1;
1503
1504 for (Segment* s = ss; s && (s != es); ) {
1505 Segment* ns = s->next1MMenabled();
1506 system1 = system2;
1507 system2 = s->measure()->system();
1508 if (!system2) {
1509 // as before, use mmrest if necessary
1510 const Measure* mmr = s->measure()->mmRest1();
1511 if (mmr)
1512 system2 = mmr->system();
1513 if (!system2)
1514 break;
1515 // extend rectangle to end of mmrest
1516 pt = mmr->last()->pagePos();
1517 }
1518 else
1519 pt = s->pagePos();
1520 x1 = x2;
1521 x2 = pt.x() + _spatium * 2;
1522
1523 if (ns == 0 || ns == es) { // last segment?
1524 // if any staff in selection has measure rest or repeat measure in last measure,
1525 // extend rectangle to bar line
1526 Segment* fs = s->measure()->first(SegmentType::ChordRest);
1527 if (fs) {
1528 for (int i = staffStart; i < staffEnd; ++i) {
1529 if (!score()->staff(i)->show())
1530 continue;
1531 ChordRest* cr = static_cast<ChordRest*>(fs->element(i * VOICES));
1532 if (cr && (cr->type() == ElementType::REPEAT_MEASURE || cr->durationType() == TDuration::DurationType::V_MEASURE)) {
1533 x2 = s->measure()->abbox().right() - _spatium * 0.5;
1534 break;
1535 }
1536 }
1537 }
1538 }
1539
1540 if (system2 != system1)
1541 x1 = x2 - 2 * _spatium;
1542 y = pt.y();
1543 ss1 = system2->staff(staffStart);
1544 ss2 = system2->staff(lastStaff);
1545 y1 = ss1->y() - 2 * score()->staff(staffStart)->spatium(s->tick()) + y;
1546 y2 = ss2->y() + ss2->bbox().height() + 2 * score()->staff(lastStaff)->spatium(s->tick()) + y;
1547 p.drawLine(QLineF(x1, y1, x2, y1).translated(system2->page()->pos()));
1548 p.drawLine(QLineF(x1, y2, x2, y2).translated(system2->page()->pos()));
1549 s = ns;
1550 }
1551 //
1552 // draw vertical end line
1553 //
1554 p.drawLine(QLineF(x2, y1, x2, y2).translated(system2->page()->pos()));
1555 }
1556
1557 // Draw foto lasso to ensure that it is above everything else
1558 if (lassoToDraw)
1559 lassoToDraw->drawEditMode(&p, editData);
1560
1561 p.setWorldMatrixEnabled(false);
1562 if (_score->layoutMode() != LayoutMode::LINE && _score->layoutMode() != LayoutMode::SYSTEM && !r1.isEmpty()) {
1563 p.setClipRegion(r1); // only background
1564 if (_bgPixmap == 0 || _bgPixmap->isNull())
1565 p.fillRect(r, _bgColor);
1566 else
1567 p.drawTiledPixmap(r, *_bgPixmap, r.topLeft() - QPoint(_matrix.m31(), _matrix.m32()));
1568 }
1569 p.restore();
1570 }
1571
1572 //---------------------------------------------------------
1573 // zoomBySteps
1574 // Zooms in or out by the specified number of keyboard- or mouse-based zoom steps (positive to zoom in, negative to zoom out).
1575 // usingMouse is optional and may be omitted to zoom by keyboard-based steps.
1576 // pos is optional and may be omitted to zoom relative to the top-left corner.
1577 //---------------------------------------------------------
1578
zoomBySteps(const qreal numSteps,const bool usingMouse,const QPointF & pos)1579 void ScoreView::zoomBySteps(const qreal numSteps, const bool usingMouse/* = false*/, const QPointF& pos/* = QPointF()*/)
1580 {
1581 // Calculate the new logical zoom level by multiplying it the current logical zoom level by the factor necessary to get it
1582 // to double every N steps, where N is the user's preferred "precision" for the specified zoom method (keyboard or mouse).
1583 const auto precision = preferences.getInt(usingMouse ? PREF_UI_CANVAS_ZOOM_PRECISION_MOUSE : PREF_UI_CANVAS_ZOOM_PRECISION_KEYBOARD);
1584 const auto stepFactor = std::pow(2.0, 1.0 / qBound(ZOOM_PRECISION_MIN, precision, ZOOM_PRECISION_MAX));
1585 auto logicalLevel = logicalZoomLevel() * std::pow(stepFactor, numSteps);
1586
1587 // Floating-point calculations inevitably introduce rounding errors. Check if the new logical zoom level is very close to
1588 // the mathematically correct value based on the current step; if it is, snap it to the right value. This is necessary in
1589 // order to avoid accumulating rounding errors as the user repeatedly zooms in and out.
1590 static constexpr qreal epsilon = 0.0001;
1591 const auto levelCheck = precision * std::log2(logicalLevel);
1592 if (std::abs(std::remainder(levelCheck, 1.0)) < epsilon)
1593 logicalLevel = std::pow(2.0, (levelCheck - std::remainder(levelCheck, 1.0)) / precision);
1594
1595 // If the new zoom level is exactly equal to one of the numeric presets, use the preset; otherwise, it's free zoom.
1596 const auto i = std::find(zoomEntries.cbegin(), zoomEntries.cend(), static_cast<int>(100.0 * logicalLevel));
1597 const auto index = ((i != zoomEntries.cend()) && i->isNumericPreset() && (i->level == 100.0 * logicalLevel)) ? i->index : ZoomIndex::ZOOM_FREE;
1598
1599 setLogicalZoom(index, logicalLevel, pos);
1600 }
1601
1602 //-----------------------------------------------------------------------------
1603 // constraintCanvas
1604 //-----------------------------------------------------------------------------
1605
constraintCanvas(int * dxx,int * dyy)1606 void ScoreView::constraintCanvas (int* dxx, int* dyy)
1607 {
1608 if (score()->layoutMode() == LayoutMode::SYSTEM)
1609 return;
1610 if (score()->pages().isEmpty())
1611 return;
1612 int dx = *dxx;
1613 int dy = *dyy;
1614 QRectF rect = QRectF(0, 0, width(), height());
1615
1616 Page* firstPage = score()->pages().front();
1617 Page* lastPage = score()->pages().back();
1618
1619 if (firstPage && lastPage) {
1620 QPointF offsetPt(xoffset(), yoffset());
1621 QRectF firstPageRect(firstPage->pos().x() * physicalZoomLevel(),
1622 firstPage->pos().y() * physicalZoomLevel(),
1623 firstPage->width() * physicalZoomLevel(),
1624 firstPage->height() * physicalZoomLevel());
1625 QRectF lastPageRect(lastPage->pos().x() * physicalZoomLevel(),
1626 lastPage->pos().y() * physicalZoomLevel(),
1627 lastPage->width() * physicalZoomLevel(),
1628 lastPage->height() * physicalZoomLevel());
1629 QRectF pagesRect = firstPageRect.united(lastPageRect).translated(offsetPt);
1630 bool limitScrollArea = preferences.getBool(PREF_UI_CANVAS_SCROLL_LIMITSCROLLAREA);
1631 if (!limitScrollArea) {
1632 qreal hmargin = this->width() * 0.75;
1633 qreal vmargin = this->height() * 0.75;
1634 pagesRect.adjust(-hmargin, -vmargin, hmargin, vmargin);
1635 }
1636 QRectF toPagesRect = pagesRect.translated(dx, dy);
1637
1638 if (limitScrollArea) {
1639 if (pagesRect.width() <= rect.width()) {
1640 if (score()->layoutMode() == LayoutMode::LINE)
1641 // keep score fixed in place horizontally
1642 dx = 0;
1643 else
1644 // center horizontally on screen
1645 dx = (rect.width() - pagesRect.width()) / 2 - pagesRect.left();
1646 }
1647 else if (toPagesRect.left() > rect.left())
1648 // get rid of the left margin
1649 dx = rect.left() - pagesRect.left();
1650 else if (toPagesRect.right() < rect.right())
1651 // get rid of the right margin
1652 dx = rect.right() - pagesRect.right();
1653 }
1654 else if (dx > 0) { // move right
1655 if (toPagesRect.right() > rect.right() && toPagesRect.left() > rect.left()) {
1656 if(pagesRect.width() <= rect.width()) {
1657 dx = rect.right() - pagesRect.right();
1658 }
1659 else {
1660 dx = rect.left() - pagesRect.left();
1661 }
1662 }
1663 }
1664 else if (dx < 0) { // move left
1665 if (toPagesRect.left() < rect.left() && toPagesRect.right() < rect.right()) {
1666 if (pagesRect.width() <= rect.width()) {
1667 dx = rect.left() - pagesRect.left();
1668 }
1669 else {
1670 dx = rect.right() - pagesRect.right();
1671 }
1672 }
1673 }
1674
1675 if (limitScrollArea) {
1676 if (pagesRect.height() <= rect.height()) {
1677 if (score()->layoutMode() == LayoutMode::LINE)
1678 // keep score fixed in place vertically
1679 dy = 0;
1680 else
1681 // center vertically on screen
1682 dy = (rect.height() - pagesRect.height()) / 2 - pagesRect.top();
1683 }
1684 else if (toPagesRect.top() > rect.top())
1685 // get rid of the top margin
1686 dy = rect.top() - pagesRect.top();
1687 else if (toPagesRect.bottom() < rect.bottom())
1688 // get rid of the bottom margin
1689 dy = rect.bottom() - pagesRect.bottom();
1690 }
1691 else if (dy > 0) { // move down
1692 if (toPagesRect.bottom() > rect.bottom() && toPagesRect.top() > rect.top()) {
1693 if (pagesRect.height() <= rect.height()) {
1694 dy = rect.bottom() - pagesRect.bottom();
1695 }
1696 else {
1697 dy = rect.top() - pagesRect.top();
1698 }
1699 }
1700 }
1701 else if (dy < 0) { // move up
1702 if (toPagesRect.top() < rect.top() && toPagesRect.bottom() < rect.bottom()) {
1703 if (pagesRect.height() <= rect.height()) {
1704 dy = rect.top() - pagesRect.top();
1705 }
1706 else {
1707 dy = rect.bottom() - pagesRect.bottom();
1708 }
1709 }
1710 }
1711 }
1712 *dxx = dx;
1713 *dyy = dy;
1714 }
1715
1716 //---------------------------------------------------------
1717 // setPhysicalZoomLevel
1718 //---------------------------------------------------------
1719
setPhysicalZoomLevel(const qreal physicalLevel)1720 void ScoreView::setPhysicalZoomLevel(const qreal physicalLevel)
1721 {
1722 const qreal currentPhysicalLevel = _matrix.m11();
1723
1724 if (physicalLevel == currentPhysicalLevel)
1725 return;
1726
1727 const double deltaPhysicalLevel = physicalLevel / currentPhysicalLevel;
1728
1729 _matrix.setMatrix(physicalLevel, _matrix.m12(), _matrix.m13(), _matrix.m21(),
1730 physicalLevel, _matrix.m23(), _matrix.dx() * deltaPhysicalLevel, _matrix.dy() * deltaPhysicalLevel, _matrix.m33());
1731 imatrix = _matrix.inverted();
1732 emit scaleChanged(physicalLevel * score()->spatium());
1733 if (editData.grips) {
1734 qreal w = 8.0 / physicalLevel;
1735 qreal h = 8.0 / physicalLevel;
1736 QRectF r(-w*.5, -h*.5, w, h);
1737 for (int i = 0; i < editData.grips; ++i) {
1738 QPointF p(editData.grip[i].center());
1739 editData.grip[i] = r.translated(p);
1740 }
1741 }
1742 update();
1743 }
1744
1745 //---------------------------------------------------------
1746 // setLogicalZoom
1747 // Sets the zoom type and logical zoom level. pos is optional and may be omitted to zoom relative to the top-left corner.
1748 //---------------------------------------------------------
1749
setLogicalZoom(ZoomIndex index,qreal logicalLevel,const QPointF & pos)1750 void ScoreView::setLogicalZoom(ZoomIndex index, qreal logicalLevel, const QPointF& pos/* = QPointF()*/)
1751 {
1752 _zoomIndex = index;
1753
1754 const qreal newLogicalLevel = qBound(ZOOM_LEVEL_MIN, logicalLevel, ZOOM_LEVEL_MAX);
1755
1756 const qreal newPhysicalLevel = newLogicalLevel * mscore->physicalDotsPerInch() / DPI;
1757
1758 const QPointF p1 = pos.isNull() ? pos : imatrix.map(pos);
1759
1760 setPhysicalZoomLevel(newPhysicalLevel);
1761
1762 int dx = 0;
1763 int dy = 0;
1764
1765 if (!pos.isNull()) {
1766 const QPointF p2 = imatrix.map(pos);
1767 const QPointF p3 = p2 - p1;
1768
1769 dx = lrint(p3.x() * newPhysicalLevel);
1770 dy = lrint(p3.y() * newPhysicalLevel);
1771 }
1772
1773 constraintCanvas(&dx, &dy);
1774 if (dx != 0 || dy != 0) {
1775 _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
1776 _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
1777 imatrix = _matrix.inverted();
1778 scroll(dx, dy, QRect(0, 0, width(), height()));
1779 emit offsetChanged(_matrix.dx(), _matrix.dy());
1780 }
1781
1782 emit viewRectChanged();
1783 update();
1784
1785 mscore->updateZoomBox(index, newLogicalLevel);
1786 }
1787
1788 //---------------------------------------------------------
1789 // calculateLogicalZoomLevel
1790 // Calculates the logical zoom level. logicalFreeZoomLevel is optional and may be omitted unless index is ZoomIndex::ZOOM_FREE.
1791 //---------------------------------------------------------
1792
calculateLogicalZoomLevel(const ZoomIndex index,const qreal logicalFreeZoomLevel) const1793 qreal ScoreView::calculateLogicalZoomLevel(const ZoomIndex index, const qreal logicalFreeZoomLevel/* = 0.0*/) const
1794 {
1795 return calculatePhysicalZoomLevel(index, logicalFreeZoomLevel) / (mscore->physicalDotsPerInch() / DPI);
1796 }
1797
1798 //---------------------------------------------------------
1799 // calculatePhysicalZoomLevel
1800 // Calculates the physical zoom level. logicalFreeZoomLevel is optional and may be omitted unless index is ZoomIndex::ZOOM_FREE.
1801 //---------------------------------------------------------
1802
calculatePhysicalZoomLevel(const ZoomIndex index,const qreal logicalFreeZoomLevel) const1803 qreal ScoreView::calculatePhysicalZoomLevel(const ZoomIndex index, const qreal logicalFreeZoomLevel/* = 0.0*/) const
1804 {
1805 if (!_score)
1806 return 1.0;
1807
1808 const qreal l2p = mscore->physicalDotsPerInch() / DPI;
1809 const qreal cw = width();
1810 const qreal ch = height();
1811 const qreal pw = _score->styleD(Sid::pageWidth);
1812 const qreal ph = _score->styleD(Sid::pageHeight);
1813
1814 qreal result = 0.0;
1815
1816 switch (index) {
1817 case ZoomIndex::ZOOM_PAGE_WIDTH:
1818 result = cw / (pw * DPI);
1819 break;
1820
1821 case ZoomIndex::ZOOM_WHOLE_PAGE: {
1822 const qreal mag1 = cw / (pw * DPI);
1823 const qreal mag2 = ch / (ph * DPI);
1824 result = std::min(mag1, mag2);
1825 }
1826 break;
1827
1828 case ZoomIndex::ZOOM_TWO_PAGES: {
1829 qreal mag1 = 0.0;
1830 qreal mag2 = 0.0;
1831 if (MScore::verticalOrientation()) {
1832 mag1 = ch / (ph * 2.0 * DPI + MScore::verticalPageGap);
1833 mag2 = cw / (pw * DPI);
1834 }
1835 else {
1836 mag1 = cw / (pw * 2.0 * DPI + std::max(MScore::horizontalPageGapEven, MScore::horizontalPageGapOdd));
1837 mag2 = ch / (ph * DPI);
1838 }
1839 result = std::min(mag1, mag2);
1840 }
1841 break;
1842
1843 case ZoomIndex::ZOOM_FREE:
1844 // If the zoom type is free zoom, the caller is required to pass the logical free-zoom level.
1845 Q_ASSERT(logicalFreeZoomLevel != 0.0);
1846 result = logicalFreeZoomLevel * l2p;
1847 break;
1848
1849 default: {
1850 // If the selected zoom entry is one of the numeric presets, set the physical zoom level accordingly; otherwise,
1851 // initialize the physical zoom level to 0.0 so that it can be overridden below with the actual current value.
1852 const auto i = std::find(zoomEntries.cbegin(), zoomEntries.cend(), index);
1853 result = ((i != zoomEntries.cend()) && i->isNumericPreset()) ? (i->level / 100.0 * l2p) : 0.0;
1854 }
1855 break;
1856 }
1857
1858 if (result < 0.0001)
1859 result = physicalZoomLevel();
1860
1861 return result;
1862 }
1863
1864 //---------------------------------------------------------
1865 // setFocusRect
1866 //---------------------------------------------------------
1867
setFocusRect()1868 void ScoreView::setFocusRect()
1869 {
1870 if (mscore->splitScreen()) {
1871 if (!focusFrame) {
1872 focusFrame = new QFocusFrame;
1873 QPalette p(focusFrame->palette());
1874 p.setColor(QPalette::WindowText, MScore::selectColor[0]);
1875 focusFrame->setPalette(p);
1876 }
1877 focusFrame->setWidget(static_cast<QWidget*>(this));
1878 focusFrame->show();
1879 }
1880 else {
1881 if (focusFrame)
1882 focusFrame->setWidget(0);
1883 }
1884 }
1885
1886 //---------------------------------------------------------
1887 // editCopy
1888 //---------------------------------------------------------
1889
editCopy()1890 void ScoreView::editCopy()
1891 {
1892 if (editData.element)
1893 editData.element->editCopy(editData);
1894 }
1895
1896 //---------------------------------------------------------
1897 // editCut
1898 //---------------------------------------------------------
1899
editCut()1900 void ScoreView::editCut()
1901 {
1902 _score->startCmd();
1903 if (editData.element)
1904 editData.element->editCut(editData);
1905 _score->endCmd();
1906 }
1907
1908 //---------------------------------------------------------
1909 // checkCopyOrCut
1910 //---------------------------------------------------------
1911
checkCopyOrCut()1912 bool ScoreView::checkCopyOrCut()
1913 {
1914 if (!_score->selection().canCopy()) {
1915 QMessageBox::information(0, "MuseScore",
1916 tr("Please select the complete tuplet/tremolo and retry the command"),
1917 QMessageBox::Ok, QMessageBox::NoButton);
1918 return false;
1919 }
1920 return true;
1921 }
1922
1923 //---------------------------------------------------------
1924 // normalCopy
1925 //---------------------------------------------------------
1926
normalCopy()1927 void ScoreView::normalCopy()
1928 {
1929 if (!checkCopyOrCut())
1930 return;
1931 QString mimeType = _score->selection().mimeType();
1932 if (!mimeType.isEmpty()) {
1933 QMimeData* mimeData = new QMimeData;
1934 mimeData->setData(mimeType, _score->selection().mimeData());
1935 if (MScore::debugMode)
1936 qDebug("cmd copy: <%s>", mimeData->data(mimeType).data());
1937 QApplication::clipboard()->setMimeData(mimeData);
1938 }
1939 }
1940
1941 //---------------------------------------------------------
1942 // normalCut
1943 //---------------------------------------------------------
1944
normalCut()1945 void ScoreView::normalCut()
1946 {
1947 if (!checkCopyOrCut())
1948 return;
1949 _score->startCmd();
1950 normalCopy();
1951 _score->cmdDeleteSelection();
1952 _score->endCmd();
1953 }
1954
1955 //---------------------------------------------------------
1956 // editSwap
1957 //---------------------------------------------------------
1958
editSwap()1959 void ScoreView::editSwap()
1960 {
1961 #if 0 // TODO
1962 if (editData.element && editData.element->isText() && !editData.element->isLyrics()) {
1963 Text* text = toText(editData.element);
1964 QString s = text->selectedText();
1965 text->paste(this);
1966 if (!s.isEmpty())
1967 QApplication::clipboard()->setText(s, QClipboard::Clipboard);
1968 }
1969 #endif
1970 }
1971
1972 //---------------------------------------------------------
1973 // editPaste
1974 //---------------------------------------------------------
1975
editPaste()1976 void ScoreView::editPaste()
1977 {
1978 if (textEditMode())
1979 toTextBase(editData.element)->paste(editData);
1980 }
1981
1982 //---------------------------------------------------------
1983 // normalSwap
1984 //---------------------------------------------------------
1985
normalSwap()1986 void ScoreView::normalSwap()
1987 {
1988 if (!checkCopyOrCut())
1989 return;
1990 QString mimeType = _score->selection().mimeType();
1991 const QMimeData* ms = QApplication::clipboard()->mimeData();
1992 if (mimeType == mimeStaffListFormat) { // determine size of clipboard selection
1993 Fraction tickLen = Fraction(0,1);
1994 int staves = 0;
1995 QByteArray d(ms->data(mimeStaffListFormat));
1996 XmlReader e(d);
1997 e.readNextStartElement();
1998 if (e.name() == "StaffList") {
1999 tickLen = Fraction::fromTicks(e.intAttribute("len", 0));
2000 staves = e.intAttribute("staves", 0);
2001 }
2002 if (tickLen > Fraction(0,1)) { // attempt to extend selection to match clipboard size
2003 Segment* seg = _score->selection().startSegment();
2004 Fraction tick = _score->selection().tickStart() + tickLen;
2005 Segment* segAfter = _score->tick2leftSegment(tick);
2006 int staffIdx = _score->selection().staffStart() + staves - 1;
2007 if (staffIdx >= _score->nstaves())
2008 staffIdx = _score->nstaves() - 1;
2009 tick = _score->selection().tickStart();
2010 Fraction etick = tick + tickLen;
2011 if (MScore::debugMode)
2012 _score->selection().dump();
2013 _score->selection().extendRangeSelection(seg, segAfter, staffIdx, tick, etick);
2014 _score->selection().update();
2015 if (MScore::debugMode)
2016 _score->selection().dump();
2017 if (!checkCopyOrCut())
2018 return;
2019 ms = QApplication::clipboard()->mimeData();
2020 }
2021 }
2022 QByteArray d(_score->selection().mimeData());
2023 if (this->normalPaste()) {
2024 QMimeData* mimeData = new QMimeData;
2025 mimeData->setData(mimeType, d);
2026 QApplication::clipboard()->setMimeData(mimeData);
2027 }
2028 }
2029
2030 //---------------------------------------------------------
2031 // normalPaste
2032 //---------------------------------------------------------
2033
normalPaste(Fraction scale)2034 bool ScoreView::normalPaste(Fraction scale)
2035 {
2036 _score->startCmd();
2037 const QMimeData* ms = QApplication::clipboard()->mimeData();
2038 _score->cmdPaste(ms, this, scale);
2039 bool rv = MScore::_error == MS_NO_ERROR;
2040 _score->endCmd();
2041 return rv;
2042 }
2043
2044 //---------------------------------------------------------
2045 // cmdGotoElement
2046 //---------------------------------------------------------
2047
cmdGotoElement(Element * e)2048 void ScoreView::cmdGotoElement(Element* e)
2049 {
2050 if (e) {
2051 if (e->type() == ElementType::NOTE || e->type() == ElementType::HARMONY)
2052 score()->setPlayNote(true);
2053 score()->select(e, SelectType::SINGLE, 0);
2054 if (e)
2055 adjustCanvasPosition(e, false);
2056 if (noteEntryMode())
2057 moveCursor();
2058 updateAll();
2059 }
2060 }
2061
2062 //---------------------------------------------------------
2063 // ticksTab
2064 //---------------------------------------------------------
2065
ticksTab(const Fraction & ticks)2066 void ScoreView::ticksTab(const Fraction& ticks)
2067 {
2068 if (editData.element->isHarmony())
2069 harmonyTicksTab(ticks);
2070 else if (editData.element->isFiguredBass())
2071 figuredBassTicksTab(ticks);
2072 }
2073
2074 //---------------------------------------------------------
2075 // cmd
2076 //---------------------------------------------------------
2077
cmd(const QAction * a)2078 void ScoreView::cmd(const QAction* a)
2079 {
2080 const char* s = a ? a->data().toByteArray().constData() : "";
2081 cmd(s);
2082 updateEditElement();
2083 }
2084
cmd(const char * s)2085 void ScoreView::cmd(const char* s)
2086 {
2087 struct ScoreViewCmd {
2088 std::vector<const char*> commands;
2089 std::function<void(ScoreView*, const QByteArray& cmd)> exec;
2090 };
2091
2092 const QByteArray cmd(s);
2093
2094 shadowNote->setVisible(false);
2095 if (MScore::debugMode)
2096 qDebug("ScoreView::cmd <%s>", s);
2097
2098 static const std::vector<ScoreViewCmd> cmdList {
2099 {{"escape"}, [](ScoreView* cv, const QByteArray&) {
2100 cv->escapeCmd();
2101 }},
2102 {{"note-input"}, [](ScoreView* cv, const QByteArray&) {
2103 if (cv->state == ViewState::NORMAL)
2104 cv->changeState(ViewState::NOTE_ENTRY);
2105 else if (cv->state == ViewState::NOTE_ENTRY)
2106 cv->changeState(ViewState::NORMAL);
2107 }},
2108 {{"copy"}, [](ScoreView* cv, const QByteArray&) {
2109 if (cv->fotoMode())
2110 cv->fotoModeCopy();
2111 else if (cv->state == ViewState::NORMAL)
2112 cv->normalCopy();
2113 else if (cv->state == ViewState::EDIT)
2114 cv->editCopy();
2115 }},
2116 {{"cut"}, [](ScoreView* cv, const QByteArray&) {
2117 if (cv->state == ViewState::NORMAL)
2118 cv->normalCut();
2119 else if (cv->state == ViewState::EDIT)
2120 cv->editCut();
2121 }},
2122 {{"paste"}, [](ScoreView* cv, const QByteArray&) {
2123 if (cv->state == ViewState::NORMAL)
2124 cv->normalPaste();
2125 else if (cv->state == ViewState::EDIT)
2126 cv->editPaste();
2127 }},
2128 {{"paste-half"}, [](ScoreView* cv, const QByteArray&) {
2129 cv->normalPaste(Fraction(1, 2));
2130 }},
2131 {{"paste-double"}, [](ScoreView* cv, const QByteArray&) {
2132 cv->normalPaste(Fraction(2, 1));
2133 }},
2134 {{"paste-special"}, [](ScoreView* cv, const QByteArray&) {
2135 Fraction scale = Fraction(1, 1);
2136 Fraction duration = cv->score()->inputState().duration().fraction();
2137 if (duration.isValid() && !duration.isZero()) {
2138 scale = duration * 4;
2139 scale.reduce();
2140 }
2141 cv->normalPaste(scale);
2142 }},
2143 {{"swap"}, [](ScoreView* cv, const QByteArray&) {
2144 if (cv->state == ViewState::NORMAL)
2145 cv->normalSwap();
2146 else if (cv->state == ViewState::EDIT)
2147 cv->editSwap();
2148 }},
2149 {{"lyrics"}, [](ScoreView* cv, const QByteArray&) {
2150 cv->score()->startCmd();
2151 Lyrics* lyrics = cv->score()->addLyrics();
2152 cv->score()->endCmd();
2153 if (lyrics) {
2154 cv->startEditMode(lyrics);
2155 return;
2156 }
2157 }},
2158 {{"figured-bass"}, [](ScoreView* cv, const QByteArray&) {
2159 FiguredBass* fb = cv->score()->addFiguredBass();
2160 if (fb) {
2161 cv->startEditMode(fb);
2162 return;
2163 }
2164 }},
2165 {{"mag"}, /*[](ScoreView* cv, const QByteArray&)*/ {
2166 // ??
2167 }},
2168 {{"play"}, [](ScoreView* cv, const QByteArray&) {
2169 if (seq && seq->canStart()) {
2170 if (cv->state == ViewState::NORMAL || cv->state == ViewState::NOTE_ENTRY)
2171 cv->changeState(ViewState::PLAY);
2172 else if (cv->state == ViewState::PLAY)
2173 cv->changeState(ViewState::NORMAL);
2174 }
2175 else
2176 getAction("play")->setChecked(false);
2177 }},
2178 {{"fotomode"}, [](ScoreView* cv, const QByteArray&) {
2179 if (cv->state == ViewState::NORMAL)
2180 cv->changeState(ViewState::FOTO);
2181 else if (cv->fotoMode())
2182 cv->changeState(ViewState::NORMAL);
2183 }},
2184 {{"add-slur"}, [](ScoreView* cv, const QByteArray&) {
2185 cv->cmdAddSlur();
2186 }},
2187 {{"add-hairpin"}, [](ScoreView* cv, const QByteArray&) {
2188 cv->cmdAddHairpin(HairpinType::CRESC_HAIRPIN);
2189 }},
2190 {{"add-hairpin-reverse"}, [](ScoreView* cv, const QByteArray&) {
2191 cv->cmdAddHairpin(HairpinType::DECRESC_HAIRPIN);
2192 }},
2193 {{"add-noteline"}, [](ScoreView* cv, const QByteArray&) {
2194 cv->cmdAddNoteLine();
2195 }},
2196 {{"chord-text"}, [](ScoreView* cv, const QByteArray&) {
2197 cv->changeState(ViewState::NORMAL);
2198 cv->cmdAddChordName(HarmonyType::STANDARD);
2199 }},
2200 {{"roman-numeral-text"}, [](ScoreView* cv, const QByteArray&) {
2201 cv->changeState(ViewState::NORMAL);
2202 cv->cmdAddChordName(HarmonyType::ROMAN);
2203 }},
2204 {{"nashville-number-text"}, [](ScoreView* cv, const QByteArray&) {
2205 cv->changeState(ViewState::NORMAL);
2206 cv->cmdAddChordName(HarmonyType::NASHVILLE);
2207 }},
2208 {{"title-text"}, [](ScoreView* cv, const QByteArray&) {
2209 cv->cmdAddText(Tid::TITLE);
2210 }},
2211 {{"subtitle-text"}, [](ScoreView* cv, const QByteArray&) {
2212 cv->cmdAddText(Tid::SUBTITLE);
2213 }},
2214 {{"composer-text"}, [](ScoreView* cv, const QByteArray&) {
2215 cv->cmdAddText(Tid::COMPOSER);
2216 }},
2217 {{"poet-text"}, [](ScoreView* cv, const QByteArray&) {
2218 cv->cmdAddText(Tid::POET);
2219 }},
2220 {{"part-text"}, [](ScoreView* cv, const QByteArray&) {
2221 cv->cmdAddText(Tid::INSTRUMENT_EXCERPT);
2222 }},
2223 {{"system-text"}, [](ScoreView* cv, const QByteArray&) {
2224 cv->cmdAddText(Tid::SYSTEM);
2225 }},
2226 {{"staff-text"}, [](ScoreView* cv, const QByteArray&) {
2227 cv->cmdAddText(Tid::STAFF);
2228 }},
2229 {{"expression-text"}, [](ScoreView* cv, const QByteArray&) {
2230 cv->cmdAddText(Tid::EXPRESSION);
2231 }},
2232 {{"rehearsalmark-text"}, [](ScoreView* cv, const QByteArray&) {
2233 cv->cmdAddText(Tid::REHEARSAL_MARK);
2234 }},
2235 {{"instrument-change-text"}, [](ScoreView* cv, const QByteArray&) {
2236 cv->cmdAddText(Tid::INSTRUMENT_CHANGE);
2237 }},
2238 {{"fingering-text"}, [](ScoreView* cv, const QByteArray&) {
2239 cv->cmdAddText(Tid::FINGERING);
2240 }},
2241 {{"sticking-text"}, [](ScoreView* cv, const QByteArray&) {
2242 cv->cmdAddText(Tid::STICKING);
2243 }},
2244 {{"edit-element"}, [](ScoreView* cv, const QByteArray&) {
2245 Element* e = cv->score()->selection().element();
2246 if (e && e->isEditable() && !cv->popupActive) {
2247 cv->startEditMode(e);
2248 }
2249 }},
2250 {{"select-similar"}, [](ScoreView* cv, const QByteArray&) {
2251 if (cv->score()->selection().isSingle()) {
2252 Element* e = cv->score()->selection().element();
2253 mscore->selectSimilar(e, false);
2254 }
2255 }},
2256 {{"select-similar-staff"}, [](ScoreView* cv, const QByteArray&) {
2257 if (cv->score()->selection().isSingle()) {
2258 Element* e = cv->score()->selection().element();
2259 mscore->selectSimilar(e, true);
2260 }
2261 }},
2262 {{"select-dialog"}, [](ScoreView* cv, const QByteArray&) {
2263 if (cv->score()->selection().isSingle()) {
2264 Element* e = cv->score()->selection().element();
2265 mscore->selectElementDialog(e);
2266 }
2267 }},
2268 // {{"find"}, [](ScoreView* cv, const QByteArray&) {
2269 // ; // TODO:state sm->postEvent(new CommandEvent(cmd));
2270 // }},
2271 {{"scr-prev"}, [](ScoreView* cv, const QByteArray&) {
2272 cv->screenPrev();
2273 }},
2274 {{"scr-next"}, [](ScoreView* cv, const QByteArray&) {
2275 cv->screenNext();
2276 }},
2277 {{"page-prev"}, [](ScoreView* cv, const QByteArray&) {
2278 cv->pagePrev();
2279 }},
2280 {{"page-next"}, [](ScoreView* cv, const QByteArray&) {
2281 cv->pageNext();
2282 }},
2283 {{"page-top"}, [](ScoreView* cv, const QByteArray&) {
2284 cv->pageTop();
2285 }},
2286 {{"page-end"}, [](ScoreView* cv, const QByteArray&) {
2287 cv->pageEnd();
2288 }},
2289 {{"select-next-chord",
2290 "select-prev-chord",
2291 "select-next-measure",
2292 "select-prev-measure",
2293 "select-begin-line",
2294 "select-end-line",
2295 "select-begin-score",
2296 "select-end-score",
2297 "select-staff-above",
2298 "select-staff-below"}, [](ScoreView* cv, const QByteArray& cmd) {
2299 Element* el = cv->score()->selectMove(cmd);
2300 if (el)
2301 cv->adjustCanvasPosition(el, false);
2302 cv->score()->setPlayChord(true);
2303 cv->updateAll();
2304 }},
2305 {{"next-chord",
2306 "prev-chord",
2307 "next-track",
2308 "prev-track",
2309 "next-measure",
2310 "prev-measure",
2311 "next-system",
2312 "prev-system",
2313 "next-frame",
2314 "prev-frame",
2315 "next-section",
2316 "prev-section",
2317 "empty-trailing-measure",
2318 "top-staff"}, [](ScoreView* cv, const QByteArray& cmd) {
2319
2320 if (cv->score()->selection().isLocked()) {
2321 LOGW() << "unable exec cmd: " << cmd << ", selection locked, reason: " << cv->score()->selection().lockReason();
2322 return;
2323 }
2324
2325 Element* el = cv->score()->selection().element();
2326 if (el && (el->isTextBase())) {
2327 cv->score()->startCmd();
2328 const PropertyFlags pf = PropertyFlags::UNSTYLED;
2329 if (cmd == "prev-chord")
2330 el->undoChangeProperty(Pid::OFFSET, el->offset() - QPointF (MScore::nudgeStep * el->spatium(), 0.0), pf);
2331 else if (cmd == "next-chord")
2332 el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF (MScore::nudgeStep * el->spatium(), 0.0), pf);
2333 else if (cmd == "prev-measure")
2334 el->undoChangeProperty(Pid::OFFSET, el->offset() - QPointF (MScore::nudgeStep10 * el->spatium(), 0.0), pf);
2335 else if (cmd == "next-measure")
2336 el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF (MScore::nudgeStep10 * el->spatium(), 0.0), pf);
2337 cv->score()->endCmd();
2338 }
2339 else {
2340 Element* ele = cv->score()->move(cmd);
2341 if (cmd == "empty-trailing-measure")
2342 cv->changeState(ViewState::NOTE_ENTRY);
2343 if (ele)
2344 cv->adjustCanvasPosition(ele, false);
2345 cv->score()->setPlayChord(true);
2346 cv->updateAll();
2347 }
2348 }},
2349 {{"pitch-up-diatonic"}, [](ScoreView* cv, const QByteArray&) {
2350 cv->score()->upDown(true, UpDownMode::DIATONIC);
2351 }},
2352 {{"pitch-down-diatonic"}, [](ScoreView* cv, const QByteArray&) {
2353 cv->score()->upDown(false, UpDownMode::DIATONIC);
2354 }},
2355 {{"move-up"}, [](ScoreView* cv, const QByteArray) {
2356 QList<Element*> el = cv->score()->selection().uniqueElements();
2357 foreach (Element* e, el) {
2358 ChordRest* cr = nullptr;
2359 if (e->type() == ElementType::NOTE)
2360 cr = static_cast<Note*>(e)->chord();
2361 else if (e->type() == ElementType::REST)
2362 cr = static_cast<Rest*>(e);
2363 if (cr)
2364 cv->score()->moveUp(cr);
2365 }
2366 }},
2367 {{"move-down"}, [](ScoreView* cv, const QByteArray&) {
2368 QList<Element*> el = cv->score()->selection().uniqueElements();
2369 foreach (Element* e, el) {
2370 ChordRest* cr = nullptr;
2371 if (e->type() == ElementType::NOTE)
2372 cr = static_cast<Note*>(e)->chord();
2373 else if (e->type() == ElementType::REST)
2374 cr = static_cast<Rest*>(e);
2375 if (cr)
2376 cv->score()->moveDown(cr);
2377 }
2378 }},
2379 {{"up-chord"}, [](ScoreView* cv, const QByteArray&) {
2380 Element* el = cv->score()->selection().element();
2381 Element* oel = el;
2382 if (el && (el->isNote() || el->isRest()))
2383 cv->cmdGotoElement(cv->score()->upAlt(el));
2384 el = cv->score()->selection().element();
2385 while (el && el->isRest() && toRest(el)->isGap()) {
2386 if (cv->score()->upAlt(el) == el) {
2387 cv->cmdGotoElement(oel);
2388 break;
2389 }
2390 el = cv->score()->upAlt(el);
2391 cv->cmdGotoElement(el);
2392 }
2393 }},
2394 {{"down-chord"}, [](ScoreView* cv, const QByteArray&) {
2395 Element* el = cv->score()->selection().element();
2396 Element* oel = el;
2397 if (el && (el->isNote() || el->isRest()))
2398 cv->cmdGotoElement(cv->score()->downAlt(el));
2399 el = cv->score()->selection().element();
2400 while (el && el->isRest() && toRest(el)->isGap()) {
2401 if (cv->score()->downAlt(el) == el) {
2402 cv->cmdGotoElement(oel);
2403 break;
2404 }
2405 el = cv->score()->downAlt(el);
2406 cv->cmdGotoElement(el);
2407 }
2408 }},
2409 {{"top-chord"}, [](ScoreView* cv, const QByteArray&) {
2410 Element* el = cv->score()->selection().element();
2411 if (el && el->type() == ElementType::NOTE)
2412 cv->cmdGotoElement(cv->score()->upAltCtrl(static_cast<Note*>(el)));
2413 }},
2414 {{"bottom-chord"}, [](ScoreView* cv, const QByteArray&) {
2415 Element* el = cv->score()->selection().element();
2416 if (el && el->type() == ElementType::NOTE)
2417 cv->cmdGotoElement(cv->score()->downAltCtrl(static_cast<Note*>(el)));
2418 }},
2419 {{"next-segment-element"}, [](ScoreView* cv, const QByteArray&) {
2420 Element* el = cv->score()->selection().element();
2421 if (!el && !cv->score()->selection().elements().isEmpty() )
2422 el = cv->score()->selection().elements().first();
2423
2424 if (el)
2425 cv->cmdGotoElement(el->nextSegmentElement());
2426 else
2427 cv->cmdGotoElement(cv->score()->firstElement());
2428 }},
2429 {{"prev-segment-element"}, [](ScoreView* cv, const QByteArray&) {
2430 Element* el = cv->score()->selection().element();
2431 if (!el && !cv->score()->selection().elements().isEmpty())
2432 el = cv->score()->selection().elements().last();
2433
2434 if (el)
2435 cv->cmdGotoElement(el->prevSegmentElement());
2436 else
2437 cv->cmdGotoElement(cv->score()->lastElement());
2438 }},
2439 {{"next-element"}, [](ScoreView* cv, const QByteArray&) {
2440 if (cv->editMode()) {
2441 cv->textTab(false);
2442 return;
2443 }
2444 Element* el = cv->score()->selection().element();
2445 if (!el && !cv->score()->selection().elements().isEmpty() )
2446 el = cv->score()->selection().elements().first();
2447 if (!el) {
2448 ChordRest* cr = cv->score()->selection().currentCR();
2449 if (cr) {
2450 if (cr->isChord())
2451 el = toChord(cr)->downNote();
2452 else if (cr->isRest())
2453 el = cr;
2454 cv->score()->select(el);
2455 }
2456 }
2457
2458 if (el)
2459 cv->cmdGotoElement(cv->score()->nextElement());
2460 else
2461 cv->cmdGotoElement(cv->score()->firstElement());
2462 }},
2463 {{"prev-element"}, [](ScoreView* cv, const QByteArray&) {
2464 if (cv->editMode()) {
2465 cv->textTab(true);
2466 return;
2467 }
2468 Element* el = cv->score()->selection().element();
2469 if (!el && !cv->score()->selection().elements().isEmpty())
2470 el = cv->score()->selection().elements().last();
2471 if (!el) {
2472 ChordRest* cr = cv->score()->selection().currentCR();
2473 if (cr) {
2474 if (cr->isChord())
2475 el = toChord(cr)->upNote();
2476 else if (cr->isRest())
2477 el = cr;
2478 cv->score()->select(el);
2479 }
2480 }
2481
2482 if (el)
2483 cv->cmdGotoElement(cv->score()->prevElement());
2484 else
2485 cv->cmdGotoElement(cv->score()->lastElement());
2486 }},
2487 {{"first-element"}, [](ScoreView* cv, const QByteArray&) {
2488 cv->cmdGotoElement(cv->score()->firstElement(false));
2489 }},
2490 {{"last-element"}, [](ScoreView* cv, const QByteArray&) {
2491 cv->cmdGotoElement(cv->score()->lastElement(false));
2492 }},
2493 {{"get-location"}, [](ScoreView* cv, const QByteArray&) {
2494 // get current selection
2495 Element* e = cv->score()->selection().element();
2496 if (!e) {
2497 // no current selection - restore lost selection
2498 e = cv->score()->selection().currentCR();
2499 if (e && e->isChord())
2500 e = toChord(e)->upNote();
2501 }
2502 if (!e) {
2503 // no current or last selection - fall back to first element
2504 e = cv->score()->firstElement(false);
2505 }
2506 // TODO: find & read current key & time signatures
2507 if (e) {
2508 ScoreAccessibility::instance()->clearAccessibilityInfo();
2509 cv->cmdGotoElement(e);
2510 }
2511 }},
2512 {{"playback-position"}, [](ScoreView* cv, const QByteArray&) {
2513 ChordRest* cr { nullptr };
2514 Element* el { nullptr };
2515 int currentTrack = cv->score()->getSelectedElement() ?
2516 cv->score()->getSelectedElement()->track() : 0;
2517 currentTrack = (currentTrack >= 0) ? currentTrack : 0;
2518 auto considerAllTracks { true }; // True : Consider all tracks instead of only voice-1 of first instrument
2519 auto next { false }; // False: Get current (rather than next) segment of playback cursor
2520 auto cursorPosition = cv->score()->repeatList().tick2utick(cv->score()->playPos().ticks());
2521 Fraction frac { Fraction::fromTicks(cursorPosition) };
2522 Segment* seg = next ? cv->score()->tick2rightSegment(frac, false) : cv->score()->tick2leftSegment(frac, false);
2523
2524 if (seg) {
2525 if (seg->isChordRestType()) {
2526 if ((cr = seg->cr(currentTrack)))
2527 el = cr;
2528 else {
2529 for (int t = 0; t < cv->score()->ntracks(); t++) {
2530 if ((cr = seg->cr(t))) {
2531 el = cr;
2532 break;
2533 }
2534 }
2535 }
2536 }
2537 else {
2538 // Cycle through tracks if no CR* found
2539 if (considerAllTracks) {
2540 for (int t = 0; t < cv->score()->ntracks(); t++) {
2541 if ((cr = seg->cr(t))) {
2542 el = cr;
2543 break;
2544 }
2545 }
2546 }
2547 // If still nothing found, get next CR*
2548 else el = seg->nextChordRest(0, false);
2549 }
2550 }
2551 if (el) {
2552 if (el->isChord())
2553 el = toChord(el)->upNote();
2554 cv->cmdGotoElement(el);
2555 }
2556 }},
2557 {{"rest", "rest-TAB"}, [](ScoreView* cv, const QByteArray&) {
2558 cv->cmdEnterRest();
2559 }},
2560 {{"rest-1"}, [](ScoreView* cv, const QByteArray&) {
2561 cv->cmdEnterRest(TDuration(TDuration::DurationType::V_WHOLE));
2562 }},
2563 {{"rest-2"}, [](ScoreView* cv, const QByteArray&) {
2564 cv->cmdEnterRest(TDuration(TDuration::DurationType::V_HALF));
2565 }},
2566 {{"rest-4"}, [](ScoreView* cv, const QByteArray&) {
2567 cv->cmdEnterRest(TDuration(TDuration::DurationType::V_QUARTER));
2568 }},
2569 {{"rest-8"}, [](ScoreView* cv, const QByteArray&) {
2570 cv->cmdEnterRest(TDuration(TDuration::DurationType::V_EIGHTH));
2571 }},
2572 {{"interval1",
2573 "interval2", "interval-2",
2574 "interval3", "interval-3",
2575 "interval4", "interval-4",
2576 "interval5", "interval-5",
2577 "interval6", "interval-6",
2578 "interval7", "interval-7",
2579 "interval8", "interval-8",
2580 "interval9", "interval-9"}, [](ScoreView* cv, const QByteArray& cmd) {
2581 int n = cmd.mid(8).toInt();
2582 std::vector<Note*> nl;
2583 if (cv->score()->selection().isRange()) {
2584 for (ChordRest* cr : cv->score()->getSelectedChordRests()) {
2585 if (cr->isChord())
2586 nl.push_back(n > 0 ? toChord(cr)->upNote() : toChord(cr)->downNote());
2587 }
2588 }
2589 else {
2590 nl = cv->score()->selection().noteList();
2591 }
2592 if (!nl.empty()) {
2593 //if (!noteEntryMode())
2594 // ; // TODO: state sm->postEvent(new CommandEvent("note-input"));
2595 cv->score()->cmdAddInterval(n, nl);
2596 }
2597 }},
2598 {{"tie"}, [](ScoreView* cv, const QByteArray&) {
2599 if (cv->noteEntryMode()) {
2600 cv->score()->cmdAddTie();
2601 cv->moveCursor();
2602 }
2603 else
2604 cv->score()->cmdToggleTie();
2605 }},
2606 {{"chord-tie"}, [](ScoreView* cv, const QByteArray&) {
2607 cv->score()->cmdAddTie(true);
2608 cv->moveCursor();
2609 }},
2610 {{"duplet"}, [](ScoreView* cv, const QByteArray&) {
2611 cv->cmdTuplet(2);
2612 }},
2613 {{"triplet"}, [](ScoreView* cv, const QByteArray&) {
2614 cv->cmdTuplet(3);
2615 }},
2616 {{"quadruplet"}, [](ScoreView* cv, const QByteArray&) {
2617 cv->cmdTuplet(4);
2618 }},
2619 {{"quintuplet"}, [](ScoreView* cv, const QByteArray&) {
2620 cv->cmdTuplet(5);
2621 }},
2622 {{"sextuplet"}, [](ScoreView* cv, const QByteArray&) {
2623 cv->cmdTuplet(6);
2624 }},
2625 {{"septuplet"}, [](ScoreView* cv, const QByteArray&) {
2626 cv->cmdTuplet(7);
2627 }},
2628 {{"octuplet"}, [](ScoreView* cv, const QByteArray&) {
2629 cv->cmdTuplet(8);
2630 }},
2631 {{"nonuplet"}, [](ScoreView* cv, const QByteArray&) {
2632 cv->cmdTuplet(9);
2633 }},
2634 {{"tuplet-dialog"}, [](ScoreView* cv, const QByteArray&) {
2635 cv->score()->startCmd();
2636 Tuplet* tuplet = mscore->tupletDialog();
2637 if (tuplet)
2638 cv->cmdCreateTuplet(cv->score()->getSelectedChordRest(), tuplet);
2639 cv->score()->endCmd();
2640 cv->moveCursor();
2641 }},
2642 {{"repeat-sel"}, [](ScoreView* cv, const QByteArray&) {
2643 cv->cmdRepeatSelection();
2644 }},
2645 {{"voice-1"}, [](ScoreView* cv, const QByteArray&) {
2646 cv->changeVoice(0);
2647 }},
2648 {{"voice-2"}, [](ScoreView* cv, const QByteArray&) {
2649 cv->changeVoice(1);
2650 }},
2651 {{"voice-3"}, [](ScoreView* cv, const QByteArray) {
2652 cv->changeVoice(2);
2653 }},
2654 {{"voice-4"}, [](ScoreView* cv, const QByteArray&) {
2655 cv->changeVoice(3);
2656 }},
2657 {{"enh-both"}, [](ScoreView* cv, const QByteArray&) {
2658 cv->cmdChangeEnharmonic(true);
2659 }},
2660 {{"enh-current"}, [](ScoreView* cv, const QByteArray&) {
2661 cv->cmdChangeEnharmonic(false);
2662 }},
2663 {{"revision"}, [](ScoreView* cv, const QByteArray&) {
2664 Score* sc = cv->score()->masterScore();
2665 sc->createRevision();
2666 }},
2667 {{"append-measure"}, [](ScoreView* cv, const QByteArray&) {
2668 cv->cmdAppendMeasures(1, ElementType::MEASURE);
2669 }},
2670 {{"insert-measure"}, [](ScoreView* cv, const QByteArray&) {
2671 cv->cmdInsertMeasures(1, ElementType::MEASURE);
2672 }},
2673 {{"insert-hbox"}, [](ScoreView* cv, const QByteArray&) {
2674 cv->cmdInsertMeasures(1, ElementType::HBOX);
2675 }},
2676 {{"insert-vbox"}, [](ScoreView* cv, const QByteArray&) {
2677 cv->cmdInsertMeasures(1, ElementType::VBOX);
2678 }},
2679 {{"append-hbox"}, [](ScoreView* cv, const QByteArray&) {
2680 MeasureBase* mb = cv->appendMeasure(ElementType::HBOX);
2681 cv->score()->select(mb, SelectType::SINGLE, 0);
2682 }},
2683 {{"append-vbox"}, [](ScoreView* cv, const QByteArray&) {
2684 MeasureBase* mb = cv->appendMeasure(ElementType::VBOX);
2685 cv->score()->select(mb, SelectType::SINGLE, 0);
2686 }},
2687 {{"insert-textframe"}, [](ScoreView* cv, const QByteArray&) {
2688 cv->cmdInsertMeasure(ElementType::TBOX);
2689 }},
2690 {{"append-textframe"}, [](ScoreView* cv, const QByteArray&) {
2691 MeasureBase* mb = cv->appendMeasure(ElementType::TBOX);
2692 if (mb) {
2693 TBox* tf = static_cast<TBox*>(mb);
2694 Text* text = 0;
2695 foreach(Element* e, tf->el()) {
2696 if (e->type() == ElementType::TEXT) {
2697 text = static_cast<Text*>(e);
2698 break;
2699 }
2700 }
2701 if (text) {
2702 cv->score()->select(text, SelectType::SINGLE, 0);
2703 cv->startEditMode(text);
2704 }
2705 }
2706 }},
2707 {{"insert-fretframe"}, [](ScoreView* cv, const QByteArray&) {
2708 if (!enableExperimental)
2709 return;
2710 cv->cmdInsertMeasure(ElementType::FBOX);
2711 }},
2712 {{"move-left"}, [](ScoreView* cv, const QByteArray&) {
2713 cv->cmdMoveCR(true);
2714 }},
2715 {{"move-right"}, [](ScoreView* cv, const QByteArray&) {
2716 cv->cmdMoveCR(false);
2717 }},
2718 {{"reset"}, [](ScoreView* cv, const QByteArray&) {
2719 if (cv->editMode()) {
2720 cv->editData.element->reset();
2721 cv->score()->update();
2722 }
2723 else {
2724 cv->score()->startCmd();
2725 for (Element* e : cv->score()->selection().elements()) {
2726 e->reset();
2727 if (e->isSpanner()) {
2728 Spanner* sp = toSpanner(e);
2729 for (SpannerSegment* ss : sp->spannerSegments())
2730 ss->reset();
2731 }
2732 }
2733 cv->score()->endCmd();
2734 }
2735 cv->updateGrips();
2736 }},
2737 #ifdef OMR
2738 {{"show-omr"}, [](ScoreView* cv, const QByteArray&) {
2739 if (cv->score()->masterScore()->omr())
2740 cv->showOmr(!cv->score()->masterScore()->showOmr());
2741 }},
2742 #endif
2743 {{"split-measure"}, [](ScoreView* cv, const QByteArray&) {
2744 Element* e = cv->score()->selection().element();
2745 if (!(e && (e->isNote() || e->isRest())))
2746 MScore::setError(NO_CHORD_REST_SELECTED);
2747 else {
2748 if (e->isNote())
2749 e = toNote(e)->chord();
2750 ChordRest* cr = toChordRest(e);
2751 cv->score()->cmdSplitMeasure(cr);
2752 }
2753 }},
2754 {{"join-measures"}, [](ScoreView* cv, const QByteArray&) {
2755 Measure* m1;
2756 Measure* m2;
2757 if (!cv->score()->selection().measureRange(&m1, &m2) || m1 == m2) {
2758 QMessageBox::warning(0, "MuseScore",
2759 tr("No measures selected:\n"
2760 "Please select a range of measures to join and try again"));
2761 }
2762 else {
2763 cv->score()->cmdJoinMeasure(m1, m2);
2764 }
2765 }},
2766 {{"next-lyric", "prev-lyric"}, [](ScoreView* cv, const QByteArray& cmd) {
2767 cv->editCmd(cmd);
2768 }},
2769 {{"add-remove-breaks"}, [](ScoreView* cv, const QByteArray&) {
2770 cv->cmdAddRemoveBreaks();
2771 }},
2772 {{"copy-lyrics-to-clipboard"}, [](ScoreView* cv, const QByteArray&) {
2773 cv->cmdCopyLyricsToClipboard();
2774 }},
2775
2776 // STATE_NOTE_ENTRY_REALTIME actions (auto or manual)
2777
2778 {{"realtime-advance"}, [](ScoreView* cv, const QByteArray&) {
2779 cv->realtimeAdvance(true);
2780 }},
2781
2782 // STATE_HARMONY_FIGBASS_EDIT actions
2783
2784 {{"advance-longa"}, [](ScoreView* cv, const QByteArray&) {
2785 cv->ticksTab(Fraction(4,1));
2786 }},
2787 {{"advance-breve"}, [](ScoreView* cv, const QByteArray&) {
2788 cv->ticksTab(Fraction(2,1));
2789 }},
2790 {{"advance-1"}, [](ScoreView* cv, const QByteArray&) {
2791 cv->ticksTab(Fraction(1,1));
2792 }},
2793 {{"advance-2"}, [](ScoreView* cv, const QByteArray&) {
2794 cv->ticksTab(Fraction(1,2));
2795 }},
2796 {{"advance-4"}, [](ScoreView* cv, const QByteArray&) {
2797 cv->ticksTab(Fraction(1,4));
2798 }},
2799 {{"advance-8"}, [](ScoreView* cv, const QByteArray&) {
2800 cv->ticksTab(Fraction(1,8));
2801 }},
2802 {{"advance-16"}, [](ScoreView* cv, const QByteArray&) {
2803 cv->ticksTab(Fraction(1,16));
2804 }},
2805 {{"advance-32"}, [](ScoreView* cv, const QByteArray&) {
2806 cv->ticksTab(Fraction(1,32));
2807 }},
2808 {{"advance-64"}, [](ScoreView* cv, const QByteArray&) {
2809 cv->ticksTab(Fraction(1,64));
2810 }},
2811 {{"prev-measure-TEXT"}, [](ScoreView* cv, const QByteArray&) {
2812 if (cv->editData.element->isHarmony())
2813 cv->harmonyTab(true);
2814 else if (cv->editData.element->isFiguredBass())
2815 cv->figuredBassTab(true, true);
2816 }},
2817 {{"next-measure-TEXT"}, [](ScoreView* cv, const QByteArray&) {
2818 if (cv->editData.element->isHarmony())
2819 cv->harmonyTab(false);
2820 else if (cv->editData.element->isFiguredBass())
2821 cv->figuredBassTab(true, false);
2822 }},
2823 {{"prev-beat-TEXT"}, [](ScoreView* cv, const QByteArray&) {
2824 if (cv->editData.element->isHarmony())
2825 cv->harmonyBeatsTab(false, true);
2826 }},
2827 {{"next-beat-TEXT"}, [](ScoreView* cv, const QByteArray&) {
2828 if (cv->editData.element->isHarmony())
2829 cv->harmonyBeatsTab(false, false);
2830 }},
2831
2832 // STATE_NOTE_ENTRY_TAB actions
2833
2834 // move input state string up or down, within the number of strings of the instrument;
2835 // this may move the input state cursor outside of the tab line range to accommodate
2836 // instrument strings not represented in the tab (e.g.: lute bass strings):
2837 // the appropriate visual rendition of the input cursor in those cases will be managed by moveCursor()
2838 {{"string-above",
2839 "string-below"}, [](ScoreView* cv, const QByteArray& cmd) {
2840 InputState& is = cv->score()->inputState();
2841 Staff* staff = cv->score()->staff(is.track() / VOICES);
2842 int instrStrgs = staff->part()->instrument(is.tick())->stringData()->strings();
2843 // assume "string-below": if tab is upside-down, 'below' means toward instrument top (-1)
2844 // if not, 'below' means toward instrument bottom (+1)
2845 int delta = (staff->staffType(is.tick())->upsideDown() ? -1 : +1);
2846 if (cmd == "string-above") // if "above", reverse delta
2847 delta = -delta;
2848 int strg = is.string() + delta; // dest. physical string
2849 if (strg >= 0 && strg < instrStrgs) { // if dest. string within instrument limits
2850 is.setString(strg); // update status
2851 cv->moveCursor();
2852 }
2853 }},
2854 {{"text-word-left"}, [](ScoreView* cv, const QByteArray&) {
2855 toTextBase(cv->editData.element)->movePosition(cv->editData, QTextCursor::WordLeft);
2856 }},
2857 {{"text-word-right"}, [](ScoreView* cv, const QByteArray&) {
2858 toTextBase(cv->editData.element)->movePosition(cv->editData, QTextCursor::NextWord);
2859 }},
2860 {{"concert-pitch"}, [](ScoreView* cv, const QByteArray& cmd) {
2861 QAction* a = getAction(cmd);
2862 if (cv->score()->styleB(Sid::concertPitch) != a->isChecked()) {
2863 cv->score()->startCmd();
2864 cv->score()->cmdConcertPitchChanged(a->isChecked(), true);
2865 cv->score()->endCmd();
2866 }
2867 }},
2868 };
2869
2870 auto c = std::find_if(cmdList.begin(), cmdList.end(), [cmd](const ScoreViewCmd& cc) {
2871 for (auto& name : cc.commands) {
2872 if (cmd == name)
2873 return true;
2874 }
2875 return false;
2876 });
2877
2878 if (c != cmdList.end()) {
2879 c->exec(this, cmd);
2880 }
2881 else {
2882 editData.view = this;
2883 QAction* a = getAction(cmd);
2884 _score->cmd(a, editData);
2885 }
2886 if (_score->processMidiInput())
2887 mscore->endCmd();
2888 }
2889
2890 //---------------------------------------------------------
2891 // textTab
2892 //---------------------------------------------------------
2893
textTab(bool back)2894 void ScoreView::textTab(bool back)
2895 {
2896 if (!editMode())
2897 return;
2898 Element* oe = editData.element;
2899 if (!oe || !oe->isTextBase())
2900 return;
2901
2902 if (oe->isHarmony()) {
2903 harmonyBeatsTab(true, back);
2904 return;
2905 }
2906 else if (oe->isFiguredBass()) {
2907 figuredBassTab(false, back);
2908 return;
2909 }
2910 else if (oe->isLyrics()) {
2911 // not actually hit, left/right handled elsewhere
2912 lyricsTab(back, false, true);
2913 return;
2914 }
2915
2916 Element* op = oe->parent();
2917 if (!(op->isSegment() || op->isNote()))
2918 return;
2919
2920 TextBase* ot = toTextBase(oe);
2921 Tid defaultTid = Tid(ot->propertyDefault(Pid::SUB_STYLE).toInt());
2922 Tid tid = ot->tid();
2923 ElementType type = ot->type();
2924 int staffIdx = ot->staffIdx();
2925
2926 // get prev/next element now, as current element may be deleted if empty
2927 Element* el = back ? score()->prevElement() : score()->nextElement();
2928 // end edit mode
2929 changeState(ViewState::NORMAL);
2930
2931 // find new note to add text to
2932 bool here = false; // prevent infinite loop (relevant if navigation is allowed to wrap around end of score)
2933 while (el) {
2934 if (el->isNote()) {
2935 Note* n = toNote(el);
2936 if (op->isNote() && n != op)
2937 break;
2938 else if (op->isSegment() && n->chord()->segment() != op)
2939 break;
2940 else if (here)
2941 break;
2942 here = true;
2943 }
2944 else if (el->isRest() && op->isSegment()) {
2945 // skip rests, but still check for infinite loop
2946 Rest* r = toRest(el);
2947 if (r->segment() != op)
2948 ;
2949 else if (here)
2950 break;
2951 here = true;
2952 }
2953 // get prev/next note
2954 score()->select(el);
2955 Element* el2 = back ? score()->prevElement() : score()->nextElement();
2956 // start/end of score reached
2957 if (el2 == el)
2958 break;
2959 el = el2;
2960 }
2961 if (!el || !el->isNote()) {
2962 // nothing found, exit cleanly
2963 if (op->selectable())
2964 score()->select(op);
2965 else
2966 score()->deselectAll();
2967 return;
2968 }
2969 Note* nn = toNote(el);
2970
2971 // go to note
2972 cmdGotoElement(nn);
2973
2974 // get existing text to edit
2975 el = nullptr;
2976 if (op->isNote()) {
2977 // check element list of new note
2978 for (Element* e : nn->el()) {
2979 if (e->type() != type)
2980 continue;
2981 TextBase* nt = toTextBase(e);
2982 if (nt->tid() == tid) {
2983 el = e;
2984 break;
2985 }
2986 }
2987 }
2988 else if (op->isSegment()) {
2989 // check annotation list of new segment
2990 Segment* ns = nn->chord()->segment();
2991 for (Element* e : ns->annotations()) {
2992 if (e->staffIdx() != staffIdx || e->type() != type)
2993 continue;
2994 TextBase* nt = toTextBase(e);
2995 if (nt->tid() == tid) {
2996 el = e;
2997 break;
2998 }
2999 }
3000 }
3001
3002 if (el) {
3003 // edit existing text
3004 score()->select(el);
3005 startEditMode(el);
3006 }
3007 else {
3008 // add new text if no existing element to edit
3009 // TODO: for tempo text, mscore->addTempo() could be called
3010 // but it pre-fills the text
3011 // would be better to create empty tempo element
3012 if (type != ElementType::TEMPO_TEXT) {
3013 PropertyFlags pf = oe ? oe->propertyFlags(Pid::PLACEMENT) : PropertyFlags::STYLED;
3014 Placement oePlacement = oe ? oe->placement() : Placement::ABOVE;
3015 cmdAddText(defaultTid, tid, pf, oePlacement);
3016 }
3017 }
3018 }
3019
3020 //---------------------------------------------------------
3021 // editKeySticking
3022 //---------------------------------------------------------
3023
editKeySticking()3024 bool ScoreView::editKeySticking()
3025 {
3026 Q_ASSERT(editData.element->isSticking());
3027
3028 switch (editData.key) {
3029 case Qt::Key_Space:
3030 textTab(editData.modifiers & Qt::ShiftModifier);
3031 return true;
3032 case Qt::Key_Left:
3033 textTab(true);
3034 return true;
3035 case Qt::Key_Right:
3036 case Qt::Key_Return:
3037 textTab(false);
3038 return true;
3039 default:
3040 break;
3041 }
3042
3043 return false;
3044 }
3045
3046 //---------------------------------------------------------
3047 // showOmr
3048 //---------------------------------------------------------
3049
showOmr(bool flag)3050 void ScoreView::showOmr(bool flag)
3051 {
3052 _score->masterScore()->setShowOmr(flag);
3053 ScoreTab* t = mscore->getTab1();
3054 if (t->view() != this)
3055 t = mscore->getTab2();
3056 if (t->view() == this)
3057 t->setCurrentIndex(t->currentIndex());
3058 else
3059 qDebug("view not found");
3060 }
3061
3062 //---------------------------------------------------------
3063 // startNoteEntry
3064 //---------------------------------------------------------
3065
startNoteEntry()3066 void ScoreView::startNoteEntry()
3067 {
3068 InputState& is = _score->inputState();
3069
3070 is.setSegment(0);
3071 Note* note = 0;
3072
3073 if (_score->selection().isNone()) {
3074 // no selection
3075 // choose page in current view (favor top left quadrant if possible)
3076 // select first (top/left) chordrest of that page in current view
3077 // or, CR at last selected position if that is in view
3078 Page* p = nullptr;
3079 QList<QPointF> points;
3080 points.append(toLogical(QPoint(width() * 0.25, height() * 0.25)));
3081 points.append(toLogical(QPoint(0.0, 0.0)));
3082 points.append(toLogical(QPoint(0.0, height())));
3083 points.append(toLogical(QPoint(width(), 0.0)));
3084 points.append(toLogical(QPoint(width(), height())));
3085 int i = 0;
3086 while (!p && i < points.size()) {
3087 p = point2page(points[i]);
3088 i++;
3089 }
3090 if (p) {
3091 ChordRest* topLeft = nullptr;
3092 qreal tlY = 0.0;
3093 Fraction tlTick = Fraction(0,1);
3094 QRectF viewRect = toLogical(QRectF(0.0, 0.0, width(), height()));
3095 QRectF pageRect = p->bbox().translated(p->x(), p->y());
3096 QRectF intersect = viewRect & pageRect;
3097 intersect.translate(-p->x(), -p->y());
3098 QList<Element*> el = p->items(intersect);
3099 ChordRest* lastSelected = score()->selection().currentCR();
3100 if (lastSelected && lastSelected->voice()) {
3101 // if last selected CR was not in voice 1,
3102 // find CR in voice 1 instead
3103 int track = trackZeroVoice(lastSelected->track());
3104 Segment* s = lastSelected->segment();
3105 if (s)
3106 lastSelected = s->nextChordRest(track, true);
3107 }
3108 for (Element* e : el) {
3109 // loop through visible elements
3110 // looking for the CR in voice 1 with earliest tick and highest staff position
3111 // but stop if we find the last selected CR
3112 ElementType et = e->type();
3113 if (et == ElementType::NOTE || et == ElementType::REST) {
3114 if (e->voice())
3115 continue;
3116 ChordRest* cr;
3117 if (et == ElementType::NOTE) {
3118 cr = static_cast<ChordRest*>(e->parent());
3119 if (!cr)
3120 continue;
3121 }
3122 else {
3123 cr = static_cast<ChordRest*>(e);
3124 }
3125 if (cr == lastSelected) {
3126 topLeft = cr;
3127 break;
3128 }
3129 // compare ticks rather than x position
3130 // to make sure we favor earlier rather than later systems
3131 // even though later system might have note farther to left
3132 Fraction crTick = Fraction(0,1);
3133 if (cr->segment())
3134 crTick = cr->segment()->tick();
3135 else
3136 continue;
3137 // compare staff Y position rather than note Y position
3138 // to be sure we do not reject earliest note
3139 // just because it is lower in pitch than subsequent notes
3140 qreal crY = 0.0;
3141 if (cr->measure() && cr->measure()->system())
3142 crY = cr->measure()->system()->staffYpage(cr->staffIdx());
3143 else
3144 continue;
3145 if (topLeft) {
3146 if (crTick <= tlTick && crY <= tlY) {
3147 topLeft = cr;
3148 tlTick = crTick;
3149 tlY = crY;
3150 }
3151 }
3152 else {
3153 topLeft = cr;
3154 tlTick = crTick;
3155 tlY = crY;
3156 }
3157 }
3158 }
3159 if (topLeft)
3160 _score->select(topLeft, SelectType::SINGLE);
3161 }
3162 }
3163
3164 Element* el = _score->selection().element();
3165 if (!el)
3166 el = _score->selection().firstChordRest();
3167 if (el == 0 || (el->type() != ElementType::CHORD && el->type() != ElementType::REST && el->type() != ElementType::NOTE)) {
3168 // if no note/rest is selected, start with voice 0
3169 int track = is.track() == -1 ? 0 : (is.track() / VOICES) * VOICES;
3170 // try to find an appropriate measure to start in
3171 Fraction tick = el ? el->tick() : Fraction(0,1);
3172 el = _score->searchNote(tick, track);
3173 if (!el)
3174 el = _score->searchNote(Fraction(0,1), track);
3175 }
3176 if (!el)
3177 return;
3178 if (el->type() == ElementType::CHORD) {
3179 Chord* c = static_cast<Chord*>(el);
3180 note = c->selectedNote();
3181 if (note == 0)
3182 note = c->upNote();
3183 el = note;
3184 }
3185 TDuration d(is.duration());
3186 if (!d.isValid() || d.isZero() || d.type() == TDuration::DurationType::V_MEASURE)
3187 is.setDuration(TDuration(TDuration::DurationType::V_QUARTER));
3188 is.setAccidentalType(AccidentalType::NONE);
3189
3190 _score->select(el, SelectType::SINGLE, 0);
3191 is.setRest(false);
3192 is.setNoteEntryMode(true);
3193 adjustCanvasPosition(el, false);
3194 setEditElement(el);
3195
3196 getAction("pad-rest")->setChecked(false);
3197 setMouseTracking(true);
3198 shadowNote->setVisible(true);
3199 _score->setUpdateAll();
3200 _score->update();
3201
3202 Staff* staff = _score->staff(is.track() / VOICES);
3203
3204 // if not tab, pitched/unpitched depends on instrument (override StaffGroup to allow pitched/unpitched changes)
3205 StaffGroup staffGroup = staff->staffType(is.tick())->group();
3206 if (staffGroup != StaffGroup::TAB)
3207 staffGroup = staff->part()->instrument(is.tick())->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD;
3208
3209 switch (staffGroup) {
3210 case StaffGroup::STANDARD:
3211 break;
3212 case StaffGroup::TAB: {
3213 int strg = 0; // assume topmost string as current string
3214 // if entering note entry with a note selected and the note has a string
3215 // set InputState::_string to note physical string
3216 if (el->type() == ElementType::NOTE) {
3217 strg = (static_cast<Note*>(el))->string();
3218 }
3219 is.setString(strg);
3220 break;
3221 }
3222 case StaffGroup::PERCUSSION:
3223 break;
3224 }
3225 // set cursor after setting the stafftype-dependent state
3226 moveCursor();
3227 mscore->updateInputState(_score);
3228 shadowNote->setVisible(false);
3229 setCursorOn(true);
3230 }
3231
3232 //---------------------------------------------------------
3233 // endNoteEntry
3234 //---------------------------------------------------------
3235
endNoteEntry()3236 void ScoreView::endNoteEntry()
3237 {
3238 InputState& is = _score->inputState();
3239 is.setNoteEntryMode(false);
3240 if (is.slur()) {
3241 const std::vector<SpannerSegment*>& el = is.slur()->spannerSegments();
3242 if (!el.empty())
3243 el.front()->setSelected(false);
3244 is.setSlur(0);
3245 }
3246 setMouseTracking(false);
3247 shadowNote->setVisible(false);
3248 setCursorOn(false);
3249 _score->setUpdateAll();
3250 _score->update();
3251 }
3252
3253 //---------------------------------------------------------
3254 // dragScoreView
3255 //---------------------------------------------------------
3256
dragScoreView(QMouseEvent * ev)3257 void ScoreView::dragScoreView(QMouseEvent* ev)
3258 {
3259 QPoint d = ev->pos() - _matrix.map(editData.startMove).toPoint();
3260 int dx = d.x();
3261 int dy = d.y();
3262
3263 if (dx == 0 && dy == 0)
3264 return;
3265
3266 constraintCanvas(&dx, &dy);
3267
3268 TourHandler::startTour("navigate-tour");
3269
3270 _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
3271 _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
3272 imatrix = _matrix.inverted();
3273 scroll(dx, dy, QRect(0, 0, width(), height()));
3274 // scroll schedules an update which is probably too small
3275 // hack around:
3276 if (dx > 0)
3277 update(-10, 0, dx + 50, height());
3278 else if (dx < 0)
3279 update(width() - 50 + dx, 0, width() + 10, height());
3280 emit offsetChanged(_matrix.dx(), _matrix.dy());
3281 emit viewRectChanged();
3282 }
3283
3284 //---------------------------------------------------------
3285 // doDragLasso
3286 //---------------------------------------------------------
3287
doDragLasso(QMouseEvent * ev)3288 void ScoreView::doDragLasso(QMouseEvent* ev)
3289 {
3290 TourHandler::startTour("select-tour");
3291 QPointF p = toLogical(ev->pos());
3292 _score->addRefresh(lasso->canvasBoundingRect());
3293 QRectF r;
3294 r.setCoords(editData.startMove.x(), editData.startMove.y(), p.x(), p.y());
3295 lasso->setbbox(r.normalized());
3296 QRectF _lassoRect(lasso->bbox());
3297 r = _matrix.mapRect(_lassoRect);
3298 QSize sz(r.size().toSize());
3299 mscore->statusBar()->showMessage(QString("%1 x %2").arg(sz.width()).arg(sz.height()), 3000);
3300 _score->addRefresh(lasso->canvasBoundingRect());
3301 _score->lassoSelect(lasso->bbox());
3302 _score->update();
3303 }
3304
3305 //---------------------------------------------------------
3306 // endLasso
3307 //---------------------------------------------------------
3308
endLasso()3309 void ScoreView::endLasso()
3310 {
3311 _score->addRefresh(lasso->canvasBoundingRect());
3312 lasso->setbbox(QRectF());
3313 _score->lassoSelectEnd();
3314 _score->update();
3315 mscore->endCmd();
3316 }
3317
3318 //---------------------------------------------------------
3319 // deselectAll
3320 //---------------------------------------------------------
3321
deselectAll()3322 void ScoreView::deselectAll()
3323 {
3324 _score->deselectAll();
3325 _score->update();
3326 mscore->endCmd();
3327 }
3328
3329 //---------------------------------------------------------
3330 // inputMethodQuery
3331 //---------------------------------------------------------
3332
inputMethodQuery(Qt::InputMethodQuery query) const3333 QVariant ScoreView::inputMethodQuery(Qt::InputMethodQuery query) const
3334 {
3335 // qDebug("0x%x %s", int(query), editData.element ? editData.element->name() : "-no element-");
3336 if (editData.element && editData.element->isTextBase()) {
3337 TextBase* text = toTextBase(editData.element);
3338 switch (query) {
3339 case Qt::ImCursorRectangle: {
3340 QRectF r;
3341 if (editMode()) {
3342 TextCursor* cursor = text->cursor(editData);
3343 r = toPhysical(cursor->cursorRect().translated(text->canvasPos()));
3344 r.setWidth(1); // InputMethod doesn't display properly if width left at 0
3345 }
3346 else
3347 r = toPhysical(text->canvasBoundingRect());
3348 r.setHeight(r.height() + 10); // add a little margin under the cursor
3349 qDebug("ScoreView::inputMethodQuery() updating cursorRect to: (%3f, %3f) + (%3f, %3f)", r.x(), r.y(), r.width(), r.height());
3350 return QVariant(r);
3351 }
3352 case Qt::ImEnabled:
3353 return true; // TextBase will always accept input method input
3354 case Qt::ImHints:
3355 return Qt::ImhNone; // No hints for now, but maybe in future will give hints
3356 case Qt::ImInputItemClipRectangle: // maybe give clip rect hint in future, but for now use default parent clipping rect
3357 default:
3358 return QWidget::inputMethodQuery(query); // fall back to QWidget's version as default
3359 }
3360 }
3361 QVariant d = QWidget::inputMethodQuery(query); // fall back to QWidget's version as default
3362 // qDebug() << " " << d;
3363 return d;
3364 }
3365
3366 //---------------------------------------------------------
3367 // logicalZoomLevel
3368 //---------------------------------------------------------
3369
logicalZoomLevel() const3370 qreal ScoreView::logicalZoomLevel() const
3371 {
3372 return _matrix.m11() / (mscore->physicalDotsPerInch() / DPI);
3373 }
3374
3375 //---------------------------------------------------------
3376 // physicalZoomLevel
3377 //---------------------------------------------------------
3378
physicalZoomLevel() const3379 qreal ScoreView::physicalZoomLevel() const
3380 {
3381 return _matrix.m11();
3382 }
3383
3384 //---------------------------------------------------------
3385 // setOffset
3386 //---------------------------------------------------------
3387
setOffset(qreal x,qreal y)3388 void ScoreView::setOffset(qreal x, qreal y)
3389 {
3390 _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
3391 _matrix.m22(), _matrix.m23(), x, y, _matrix.m33());
3392 imatrix = _matrix.inverted();
3393 emit viewRectChanged();
3394 emit offsetChanged(x, y);
3395 }
3396
3397 //---------------------------------------------------------
3398 // xoffset
3399 //---------------------------------------------------------
3400
xoffset() const3401 qreal ScoreView::xoffset() const
3402 {
3403 return _matrix.dx();
3404 }
3405
3406 //---------------------------------------------------------
3407 // yoffset
3408 //---------------------------------------------------------
3409
yoffset() const3410 qreal ScoreView::yoffset() const
3411 {
3412 return _matrix.dy();
3413 }
3414
3415 //---------------------------------------------------------
3416 // fsize
3417 //---------------------------------------------------------
3418
fsize() const3419 QSizeF ScoreView::fsize() const
3420 {
3421 QSize s = size();
3422 return QSizeF(s.width() * imatrix.m11(), s.height() * imatrix.m22());
3423 }
3424
3425 //---------------------------------------------------------
3426 // keyboard nav constants
3427 //---------------------------------------------------------
3428
3429 constexpr qreal scrollStep { .8 };
3430 constexpr qreal thinPadding { 10.0 };
3431 constexpr qreal thickPadding { 25.0 };
3432
3433 //---------------------------------------------------------
3434 // pageNext
3435 //---------------------------------------------------------
3436
pageNext()3437 void ScoreView::pageNext()
3438 {
3439 if (score()->pages().empty())
3440 return;
3441 if (score()->layoutMode() != LayoutMode::PAGE) {
3442 screenNext();
3443 return;
3444 }
3445 Page* page = score()->pages().back();
3446 qreal x, y;
3447 if (MScore::verticalOrientation()) {
3448 x = thinPadding;
3449 y = yoffset() - (page->height() + thickPadding) * physicalZoomLevel();
3450 qreal ly = thinPadding - page->pos().y() * physicalZoomLevel();
3451 if (y <= ly - height() * scrollStep) {
3452 pageEnd();
3453 return;
3454 }
3455 }
3456 else {
3457 y = thinPadding;
3458 x = xoffset() - (page->width() + thickPadding) * physicalZoomLevel();
3459 qreal lx = thinPadding - page->pos().x() * physicalZoomLevel();
3460 if (x <= lx - width() * scrollStep) {
3461 pageEnd();
3462 return;
3463 }
3464 }
3465 setOffset(x, y);
3466 update();
3467 }
3468
3469 //---------------------------------------------------------
3470 // screenNext
3471 //---------------------------------------------------------
3472
screenNext()3473 void ScoreView::screenNext()
3474 {
3475 if (score()->pages().empty())
3476 return;
3477 qreal x { xoffset() };
3478 qreal y { yoffset() };
3479 if (score()->layoutMode() == LayoutMode::LINE) {
3480 x -= width() * scrollStep;
3481 MeasureBase* lm = score()->lastMeasureMM();
3482 // Vertical frames aren't laid out in continuous view
3483 while (lm->isVBoxBase())
3484 lm = lm->prev();
3485 qreal lx = (lm->pos().x() + lm->width()) * physicalZoomLevel() - width() * scrollStep;
3486 if (x < -lx)
3487 x = -lx;
3488 }
3489 else {
3490 y -= height() * scrollStep;
3491 MeasureBase* lm { score()->lastMeasureMM() };
3492 qreal ly { (lm->canvasPos().y() + lm->height()) * physicalZoomLevel() - height() * scrollStep };
3493 // Special case to jump to top of next page in horizontal view.
3494 if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()
3495 && y <= -ly - height() * scrollStep) {
3496 pageNext();
3497 return;
3498 }
3499 if (y < -ly)
3500 y = -ly;
3501 }
3502 setOffset(x, y);
3503 update();
3504 }
3505
3506 //---------------------------------------------------------
3507 // pagePrev
3508 //---------------------------------------------------------
3509
pagePrev()3510 void ScoreView::pagePrev()
3511 {
3512 if (score()->pages().empty())
3513 return;
3514 if (score()->layoutMode() != LayoutMode::PAGE) {
3515 screenPrev();
3516 return;
3517 }
3518 Page* page = score()->pages().front();
3519 qreal x, y;
3520 if (MScore::verticalOrientation()) {
3521 x = thinPadding;
3522 y = yoffset() + (page->height() + thickPadding) * physicalZoomLevel();
3523 if (y > thinPadding)
3524 y = thinPadding;
3525 }
3526 else {
3527 y = thinPadding;
3528 x = xoffset() + (page->width() + thickPadding) * physicalZoomLevel();
3529 if (x > thinPadding)
3530 x = thinPadding;
3531 }
3532 setOffset(x, y);
3533 update();
3534 }
3535
3536 //---------------------------------------------------------
3537 // screenPrev
3538 //---------------------------------------------------------
3539
screenPrev()3540 void ScoreView::screenPrev()
3541 {
3542 if (score()->pages().empty())
3543 return;
3544 qreal x { xoffset() };
3545 qreal y { yoffset() };
3546 if (score()->layoutMode() == LayoutMode::LINE) {
3547 x += width() * scrollStep;
3548 if (x > thinPadding)
3549 x = thinPadding;
3550 }
3551 else {
3552 y += height() * scrollStep;
3553 // Special casing for jumping to bottom of prev page in horizontal view
3554 if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()
3555 && y >= thinPadding + height() * scrollStep) {
3556 Page* page { score()->pages().front() };
3557 x += (page->width() + thickPadding) * physicalZoomLevel();
3558 // The condition prevents jumping to the bottom of the
3559 // first page after reaching the top
3560 if (x < thinPadding + (page->width() + thickPadding) * physicalZoomLevel()) {
3561 MeasureBase* lm { score()->lastMeasureMM() };
3562 y = -(lm->canvasPos().y() + lm->height()) * physicalZoomLevel() + height() * scrollStep;
3563 }
3564 if (x > thinPadding)
3565 x = thinPadding;
3566 }
3567 if (y > thinPadding)
3568 y = thinPadding;
3569 }
3570 setOffset(x, y);
3571 update();
3572 }
3573
3574 //---------------------------------------------------------
3575 // pageTop
3576 //---------------------------------------------------------
3577
pageTop()3578 void ScoreView::pageTop()
3579 {
3580 switch (score()->layoutMode()) {
3581 case LayoutMode::PAGE:
3582 {
3583 qreal dx = thinPadding, dy = thinPadding;
3584 Page* firstPage = score()->pages().front();
3585 Page* lastPage = score()->pages().back();
3586 if (firstPage && lastPage) {
3587 QPointF offsetPt(xoffset(), yoffset());
3588 QRectF firstPageRect(firstPage->pos().x() * physicalZoomLevel(),
3589 firstPage->pos().y() * physicalZoomLevel(),
3590 firstPage->width() * physicalZoomLevel(),
3591 firstPage->height() * physicalZoomLevel());
3592 QRectF lastPageRect(lastPage->pos().x() * physicalZoomLevel(),
3593 lastPage->pos().y() * physicalZoomLevel(),
3594 lastPage->width() * physicalZoomLevel(),
3595 lastPage->height() * physicalZoomLevel());
3596 QRectF pagesRect = firstPageRect.united(lastPageRect);
3597 dx = qMax(thinPadding, (width() - pagesRect.width()) / 2);
3598 dy = qMax(thinPadding, (height() - pagesRect.height()) / 2);
3599 }
3600 setOffset(dx, dy);
3601 break;
3602 }
3603 case LayoutMode::LINE:
3604 setOffset(thinPadding, 0.0);
3605 break;
3606 case LayoutMode::SYSTEM:
3607 {
3608 qreal dx = thinPadding, dy = thinPadding;
3609 Page* page = score()->pages().front();
3610 if (page) {
3611 dx = qMax(thinPadding, (width() - page->width() * physicalZoomLevel()) / 2);
3612 }
3613 setOffset(dx, dy);
3614 break;
3615 }
3616 default:
3617 setOffset(thinPadding, thinPadding);
3618 break;
3619 }
3620 update();
3621 }
3622
3623 //---------------------------------------------------------
3624 // pageEnd
3625 //---------------------------------------------------------
3626
pageEnd()3627 void ScoreView::pageEnd()
3628 {
3629 if (score()->pages().empty())
3630 return;
3631 MeasureBase* lm = score()->lastMeasureMM();
3632 if (score()->layoutMode() == LayoutMode::LINE) {
3633 // Vertical frames aren't laid out in continuous view
3634 while (lm->isVBoxBase())
3635 lm = lm->prev();
3636 qreal lx = (lm->pos().x() + lm->width()) * physicalZoomLevel() - scrollStep * width();
3637 setOffset(-lx, yoffset());
3638 }
3639 else {
3640 qreal lx { -thinPadding };
3641 if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()) {
3642 for (int i { 0 }; i < score()->npages() - 1; ++i)
3643 lx += score()->pages().at(i)->width() * physicalZoomLevel();
3644 }
3645 if (lm->system() && lm->system()->page()->width() * physicalZoomLevel() > width())
3646 lx = (lm->canvasPos().x() + lm->width()) * physicalZoomLevel() - width() * scrollStep;
3647
3648 qreal ly { (lm->canvasPos().y() + lm->height()) * physicalZoomLevel() - height() * scrollStep };
3649 setOffset(-lx, -ly);
3650 }
3651 update();
3652 }
3653
3654 //---------------------------------------------------------
3655 // adjustCanvasPosition
3656 //---------------------------------------------------------
3657
adjustCanvasPosition(const Element * el,bool playBack,int staff)3658 void ScoreView::adjustCanvasPosition(const Element* el, bool playBack, int staff )
3659 {
3660 if (this != mscore->currentScoreView() && !_moveWhenInactive)
3661 return;
3662 // TODO: change icon, or add panning options
3663 if (!mscore->panDuringPlayback())
3664 return;
3665
3666 if (score()->layoutMode() == LayoutMode::LINE) {
3667
3668 if (!el)
3669 return;
3670
3671 /* Not used, because impossible to get panel width beforehand
3672 const MeasureBase* m = 0;
3673 if (el->type() == ElementType::MEASURE)
3674 m = static_cast<const MeasureBase*>(el);
3675 else
3676 m = static_cast<const Measure*>(el->parent()->findMeasure());
3677 */
3678
3679 qreal xo = 0.0; // new x offset
3680 QRectF curPos = playBack ? _cursor->rect() : el->canvasBoundingRect();
3681 if (playBack && _cursor && seq->isPlaying() && panSettings().enabled)
3682 curPos = _controlCursor->rect();
3683 // keep current note in view as well if applicable (note input mode)
3684 Element* current = nullptr;
3685 if (noteEntryMode()) {
3686 current = score()->selection().cr();
3687 if (current && current != el)
3688 curPos |= current->canvasBoundingRect();
3689 }
3690
3691 qreal curPosR = curPos.right(); // Position on the canvas
3692 qreal curPosL = curPos.left(); // Position on the canvas
3693 qreal curPosScrR = curPosR * physicalZoomLevel() + xoffset(); // Position in the screen
3694 qreal curPosScrL = curPosL * physicalZoomLevel() + xoffset(); // Position in the screen
3695 qreal marginLeft = width() * 0.05;
3696 qreal marginRight = width() * 0.05; // leaves 5% margin to the right
3697
3698 // if (_continuousPanel->active()) causes jump
3699 // marginLeft += _continuousPanel->width() * physicalZoomLevel();
3700
3701 // this code implements "continuous" panning
3702 // it could potentially be enabled via more panning options
3703 if (playBack && _cursor && seq->isPlaying() && preferences.getBool(PREF_PAN_SMOOTHLY_ENABLED)) {
3704 // keep playback cursor pinned at 35% (or at the percent of controlCursorScreenPos + 5%)
3705 xo = -curPosL * physicalZoomLevel() + marginLeft + width() * _panSettings.controlCursorScreenPos;
3706 }
3707 else if (round(curPosScrR) > round(width() - marginRight)) {
3708 // focus in or beyond right margin
3709 // pan to left margin in playback,
3710 // most of the way left in note entry,
3711 // otherwise just enforce right margin
3712 if (playBack)
3713 xo = -curPosL * physicalZoomLevel() + marginLeft;
3714 else if (noteEntryMode())
3715 xo = -curPosL * physicalZoomLevel() + marginLeft + width() * 0.2;
3716 else
3717 xo = -curPosR * physicalZoomLevel() + width() - marginRight;
3718 }
3719 else if (round(curPosScrL) < round(marginLeft) ) {
3720 // focus in or beyond left margin
3721 // enforce left margin
3722 // (previously we moved canvas all the way right,
3723 // but this made sense only when navigating right-to-left)
3724 xo = -curPosL * physicalZoomLevel() + marginLeft;
3725 }
3726 else {
3727 // focus is within margins, so do nothing
3728 return;
3729 }
3730 // avoid empty space on either side of "page"
3731 qreal scoreEnd = score()->pages().front()->width() * physicalZoomLevel() + xo;
3732 if (xo > 10)
3733 xo = 10;
3734 else if (scoreEnd < width())
3735 xo += width() - scoreEnd;
3736
3737 setOffset(xo, yoffset());
3738 update();
3739 return;
3740 }
3741
3742 const MeasureBase* m;
3743 if (!el)
3744 return;
3745 else if (el->type() == ElementType::NOTE)
3746 m = static_cast<const Note*>(el)->chord()->measure();
3747 else if (el->type() == ElementType::REST)
3748 m = static_cast<const Rest*>(el)->measure();
3749 else if (el->type() == ElementType::CHORD)
3750 m = static_cast<const Chord*>(el)->measure();
3751 else if (el->type() == ElementType::SEGMENT)
3752 m = static_cast<const Segment*>(el)->measure();
3753 else if (el->type() == ElementType::LYRICS)
3754 m = static_cast<const Lyrics*>(el)->measure();
3755 else if ( (el->type() == ElementType::HARMONY || el->type() == ElementType::FIGURED_BASS)
3756 && el->parent()->type() == ElementType::SEGMENT)
3757 m = static_cast<const Segment*>(el->parent())->measure();
3758 else if (el->type() == ElementType::HARMONY && el->parent()->type() == ElementType::FRET_DIAGRAM
3759 && el->parent()->parent()->type() == ElementType::SEGMENT)
3760 m = static_cast<const Segment*>(el->parent()->parent())->measure();
3761 else if (el->isMeasureBase())
3762 m = static_cast<const MeasureBase*>(el);
3763 else if (el->isSpannerSegment()) {
3764 Element* se = static_cast<const SpannerSegment*>(el)->spanner()->startElement();
3765 m = static_cast<Measure*>(se->findMeasure());
3766 }
3767 else if (el->isSpanner()) {
3768 Element* se = static_cast<const Spanner*>(el)->startElement();
3769 m = static_cast<Measure*>(se->findMeasure());
3770 }
3771 else {
3772 // attempt to find measure
3773 Element* e = el->parent();
3774 while (e && !e->isMeasureBase())
3775 e = e->parent();
3776 if (e)
3777 m = toMeasureBase(e);
3778 else
3779 return;
3780 }
3781 if (!m)
3782 return;
3783
3784 int staffIdx = el->staffIdx();
3785 System* sys = m->system();
3786 if (!sys)
3787 return;
3788
3789 QPointF p(el->canvasPos());
3790 QRectF r(canvasViewport());
3791 QRectF mRect(m->canvasBoundingRect());
3792 QRectF sysRect;
3793 if (staffIdx == -1)
3794 sysRect = sys->canvasBoundingRect();
3795 else
3796 sysRect = sys->staff(staffIdx)->bbox();
3797
3798 // only try to track measure if not during playback
3799 if (!playBack)
3800 sysRect = mRect;
3801
3802 double _spatium = score()->spatium();
3803 const qreal border = _spatium * 3;
3804 QRectF showRect;
3805 if (staff == -1) {
3806 showRect = QRectF(mRect.x(), sysRect.y(), mRect.width(), sysRect.height())
3807 .adjusted(-border, -border, border, border);
3808 }
3809 else {
3810 // find a box for the individual stave in a system
3811 QRectF stave = QRectF(sys->canvasBoundingRect().left(),
3812 sys->staffCanvasYpage(staff),
3813 sys->width(),
3814 sys->staff(staff)->bbox().height());
3815 showRect = mRect.intersected(stave).adjusted(-border, -border, border, border);
3816 }
3817
3818 /*printf("%f %f %f %f %f %f %f %f %d\n",
3819 showRect.x(), showRect.y(), showRect.width(), showRect.height(),
3820 r.x(), r.y(), r.width(), r.height(),
3821 r.contains(showRect)
3822 );
3823 */
3824 // canvas is not as wide as measure, track note instead
3825 if (r.width() < showRect.width()) {
3826 QRectF eRect(el->canvasBoundingRect());
3827 showRect.setX(eRect.x());
3828 showRect.setWidth(eRect.width());
3829 }
3830
3831 // canvas is not as tall as system
3832 if (r.height() < showRect.height()) {
3833 if (sys->staves()->size() == 1 || !playBack) {
3834 // track note if single staff
3835 showRect.setY(p.y());
3836 showRect.setHeight(el->height());
3837 }
3838 else if (sys->page()->systems().size() == 1) {
3839 // otherwise, just keep current vertical position if possible
3840 // see issue #7724
3841 showRect.setY(r.y());
3842 showRect.setHeight(r.height());
3843 }
3844 }
3845 if (shadowNote->visible())
3846 setShadowNote(p);
3847
3848 if (r.contains(showRect))
3849 return;
3850
3851 qreal x = - xoffset() / physicalZoomLevel();
3852 qreal y = - yoffset() / physicalZoomLevel();
3853
3854 qreal oldX = x, oldY = y;
3855
3856 if (showRect.left() < r.left())
3857 x = showRect.left() - border;
3858 else if (showRect.left() > r.right())
3859 x = showRect.right() - width() / physicalZoomLevel() + border;
3860 else if (r.width() >= showRect.width() && showRect.right() > r.right())
3861 x = showRect.left() - border;
3862 if (showRect.top() < r.top() && showRect.bottom() < r.bottom())
3863 y = showRect.top() - border;
3864 else if (showRect.top() > r.bottom())
3865 y = showRect.bottom() - height() / physicalZoomLevel() + border;
3866 else if (r.height() >= showRect.height() && showRect.bottom() > r.bottom())
3867 y = showRect.top() - border;
3868
3869 // align to page borders if extends beyond
3870 Page* page = sys->page();
3871 if (x < page->x() || r.width() >= page->width())
3872 x = page->x();
3873 else if (r.width() < page->width() && r.width() + x > page->width() + page->x())
3874 x = (page->width() + page->x()) - r.width();
3875 if (y < page->y() || r.height() >= page->height())
3876 y = page->y();
3877 else if (r.height() < page->height() && r.height() + y > page->height() + page->y())
3878 y = (page->height() + page->y()) - r.height();
3879
3880 // hack: don't update if we haven't changed the offset
3881 if (oldX == x && oldY == y)
3882 return;
3883
3884 setOffset(-x * physicalZoomLevel(), -y * physicalZoomLevel());
3885 update();
3886 }
3887
3888 //---------------------------------------------------------
3889 // cmdEnterRest
3890 //---------------------------------------------------------
3891
cmdEnterRest()3892 void ScoreView::cmdEnterRest()
3893 {
3894 InputState& is = _score->inputState();
3895 if (is.track() == -1 || is.segment() == 0) // invalid state
3896 return;
3897 if (!is.duration().isValid() || is.duration().isZero() || is.duration().isMeasure())
3898 is.setDuration(TDuration::DurationType::V_QUARTER);
3899 cmdEnterRest(is.duration());
3900 }
3901
3902 //---------------------------------------------------------
3903 // cmdEnterRest
3904 //---------------------------------------------------------
3905
cmdEnterRest(const TDuration & d)3906 void ScoreView::cmdEnterRest(const TDuration& d)
3907 {
3908 if (!noteEntryMode())
3909 cmd("note-input");
3910 if (_score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM))
3911 _score->cmd(getAction("pad-rest"), editData);
3912 else
3913 _score->cmdEnterRest(d);
3914 #if 0
3915 expandVoice();
3916 if (_is.cr() == 0) {
3917 qDebug("cannot enter rest here");
3918 return;
3919 }
3920
3921 int track = _is.track;
3922 Segment* seg = setNoteRest(_is.cr(), track, -1, d.fraction(), 0, AUTO);
3923 ChordRest* cr = static_cast<ChordRest*>(seg->element(track));
3924 if (cr)
3925 nextInputPos(cr, false);
3926 _is.rest = false; // continue with normal note entry
3927 #endif
3928 }
3929
3930 //---------------------------------------------------------
3931 // mscoreState
3932 //---------------------------------------------------------
3933
mscoreState() const3934 ScoreState ScoreView::mscoreState() const
3935 {
3936 if (fotoMode())
3937 return STATE_FOTO;
3938 if (state == ViewState::NOTE_ENTRY) {
3939 const InputState is = _score->inputState();
3940 Staff* staff = _score->staff(is.track() / VOICES);
3941
3942 // pitched/unpitched depending on instrument (override StaffGroup)
3943 StaffGroup staffGroup = staff->staffType(is.tick())->group();
3944 if (staffGroup != StaffGroup::TAB)
3945 staffGroup = staff->part()->instrument(is.tick())->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD;
3946 switch( staffGroup ) {
3947 case StaffGroup::STANDARD:
3948 return STATE_NOTE_ENTRY_STAFF_PITCHED;
3949 case StaffGroup::TAB:
3950 return STATE_NOTE_ENTRY_STAFF_TAB;
3951 case StaffGroup::PERCUSSION:
3952 return STATE_NOTE_ENTRY_STAFF_DRUM;
3953 }
3954 }
3955 if (state == ViewState::EDIT || state == ViewState::DRAG_EDIT) {
3956 if (editData.element && editData.element->isLyrics())
3957 return STATE_LYRICS_EDIT;
3958 else if (editData.element &&
3959 ( (editData.element->isHarmony()) || editData.element->isFiguredBass()) )
3960 return STATE_HARMONY_FIGBASS_EDIT;
3961 else if (editData.element && editData.element->isTextBase())
3962 return STATE_TEXT_EDIT;
3963 return STATE_EDIT;
3964 }
3965 if (state == ViewState::PLAY)
3966 return STATE_PLAY;
3967 return STATE_NORMAL;
3968 }
3969
3970 //---------------------------------------------------------
3971 // startUndoRedo
3972 //---------------------------------------------------------
3973
startUndoRedo(bool undo)3974 void ScoreView::startUndoRedo(bool undo)
3975 {
3976 _score->undoRedo(undo, state == ViewState::EDIT ? &editData : 0);
3977
3978 if (_score->inputState().segment())
3979 mscore->setPos(_score->inputState().tick());
3980 if (_score->noteEntryMode() && !noteEntryMode())
3981 cmd("note-input"); // enter note entry mode
3982 else if (!_score->noteEntryMode() && noteEntryMode())
3983 cmd("escape"); // leave note entry mode
3984
3985 updateEditElement();
3986 }
3987
3988 //---------------------------------------------------------
3989 // cmdAddSlur
3990 // command invoked, or icon double clicked
3991 //---------------------------------------------------------
3992
cmdAddSlur(const Slur * slurTemplate)3993 void ScoreView::cmdAddSlur(const Slur* slurTemplate)
3994 {
3995 InputState& is = _score->inputState();
3996 if (noteEntryMode() && is.slur()) {
3997 const std::vector<SpannerSegment*>& el = is.slur()->spannerSegments();
3998 if (!el.empty()) {
3999 el.front()->setSelected(false);
4000 // Now make sure that the slur segment is redrawn so that it does not *look* selected
4001 update();
4002 }
4003 is.setSlur(nullptr);
4004 return;
4005 }
4006
4007 _score->startCmd();
4008
4009 ChordRest* cr1;
4010 ChordRest* cr2;
4011 const auto& sel = _score->selection();
4012 auto el = sel.uniqueElements();
4013
4014 if (sel.isRange()) {
4015 int startTrack = sel.staffStart() * VOICES;
4016 int endTrack = sel.staffEnd() * VOICES;
4017 for (int track = startTrack; track < endTrack; ++track) {
4018 cr1 = 0;
4019 cr2 = 0;
4020 for (Element* e : el) {
4021 if (e->track() != track)
4022 continue;
4023 if (e->isNote())
4024 e = toNote(e)->chord();
4025 if (!e->isChord())
4026 continue;
4027 ChordRest* cr = toChordRest(e);
4028 if (!cr1 || cr1->tick() > cr->tick())
4029 cr1 = cr;
4030 if (!cr2 || cr2->tick() < cr->tick())
4031 cr2 = cr;
4032 }
4033 if (cr1 && (cr1 != cr2))
4034 addSlur(cr1, cr2, slurTemplate);
4035 }
4036 }
4037 else {
4038 cr1 = 0;
4039 cr2 = 0;
4040 for (Element* e : el) {
4041 if (e->isNote())
4042 e = toNote(e)->chord();
4043 if (!e->isChord())
4044 continue;
4045 ChordRest* cr = toChordRest(e);
4046 if (!cr1 || cr->isBefore(cr1))
4047 cr1 = cr;
4048 if (!cr2 || cr2->isBefore(cr))
4049 cr2 = cr;
4050 }
4051 if (cr1 == cr2)
4052 cr2 = 0;
4053 if (cr1)
4054 addSlur(cr1, cr2, slurTemplate);
4055 }
4056 _score->endCmd();
4057 }
4058
4059 //---------------------------------------------------------
4060 // addSlur
4061 //---------------------------------------------------------
4062
addSlur(ChordRest * cr1,ChordRest * cr2,const Slur * slurTemplate)4063 void ScoreView::addSlur(ChordRest* cr1, ChordRest* cr2, const Slur* slurTemplate)
4064 {
4065 bool switchToSlur = false;
4066 if (cr2 == 0) {
4067 cr2 = nextChordRest(cr1);
4068 if (cr2 == 0)
4069 cr2 = cr1;
4070 switchToSlur = true; // select slur for editing if last chord is not given
4071 }
4072
4073 Slur* slur = slurTemplate ? slurTemplate->clone() : new Slur(cr1->score());
4074 slur->setScore(cr1->score());
4075 slur->setTick(cr1->tick());
4076 slur->setTick2(cr2->tick());
4077 slur->setTrack(cr1->track());
4078 if (cr2->staff()->part() == cr1->staff()->part() && !cr2->staff()->isLinked(cr1->staff()))
4079 slur->setTrack2(cr2->track());
4080 else
4081 slur->setTrack2(cr1->track());
4082 slur->setStartElement(cr1);
4083 slur->setEndElement(cr2);
4084
4085 cr1->score()->undoAddElement(slur);
4086 SlurSegment* ss = new SlurSegment(cr1->score());
4087 ss->setSpannerSegmentType(SpannerSegmentType::SINGLE);
4088 if (cr1 == cr2)
4089 ss->setSlurOffset(Grip::END, QPointF(3.0 * cr1->score()->spatium(), 0.0));
4090 slur->add(ss);
4091
4092 if (noteEntryMode()) {
4093 _score->inputState().setSlur(slur);
4094 ss->setSelected(true);
4095 }
4096 else if (switchToSlur) {
4097 startEditMode(ss);
4098 }
4099 }
4100
4101 //---------------------------------------------------------
4102 // cmdAddHairpin
4103 // '<' typed on keyboard
4104 //---------------------------------------------------------
4105
cmdAddHairpin(HairpinType type)4106 void ScoreView::cmdAddHairpin(HairpinType type)
4107 {
4108 const Selection& selection = _score->selection();
4109 // special case for two selected chordrests on same staff
4110 bool twoNotesSameStaff = false;
4111 if (selection.isList() && selection.elements().size() == 2) {
4112 ChordRest* cr1 = selection.firstChordRest();
4113 ChordRest* cr2 = selection.lastChordRest();
4114 if (cr1 && cr2 && cr1 != cr2 && cr1->staffIdx() == cr2->staffIdx())
4115 twoNotesSameStaff = true;
4116 }
4117 // add hairpin on each staff if possible
4118 if (selection.isRange() && selection.staffStart() != selection.staffEnd() - 1) {
4119 _score->startCmd();
4120 for (int staffIdx = selection.staffStart() ; staffIdx < selection.staffEnd(); ++staffIdx) {
4121 ChordRest* cr1 = selection.firstChordRest(staffIdx * VOICES);
4122 ChordRest* cr2 = selection.lastChordRest(staffIdx * VOICES);
4123 _score->addHairpin(type, cr1, cr2, /* toCr2End */ true);
4124 }
4125 _score->endCmd();
4126 }
4127 else if (selection.isRange() || selection.isSingle() || twoNotesSameStaff) {
4128 // for single staff range selection, or single selection,
4129 // find start & end elements elements
4130 ChordRest* cr1;
4131 ChordRest* cr2;
4132 _score->getSelectedChordRest2(&cr1, &cr2);
4133 _score->startCmd();
4134 Hairpin* pin = _score->addHairpin(type, cr1, cr2, /* toCr2End */ !twoNotesSameStaff);
4135 _score->endCmd();
4136 if (!pin)
4137 return;
4138
4139 const std::vector<SpannerSegment*>& el = pin->spannerSegments();
4140 if (!noteEntryMode()) {
4141 if (!el.empty()) {
4142 startEditMode(el.front());
4143 }
4144 }
4145 }
4146 else {
4147 // do not attempt for list selection
4148 // or we will keep adding hairpins to the same chordrests
4149 return;
4150 }
4151 }
4152
4153 //---------------------------------------------------------
4154 // cmdAddNoteLine
4155 //---------------------------------------------------------
4156
cmdAddNoteLine()4157 void ScoreView::cmdAddNoteLine()
4158 {
4159 Note* firstNote = 0;
4160 Note* lastNote = 0;
4161 if (_score->selection().isRange()) {
4162 int startTrack = _score->selection().staffStart() * VOICES;
4163 int endTrack = _score->selection().staffEnd() * VOICES;
4164 for (int track = startTrack; track < endTrack; ++track) {
4165 for (Note* n : _score->selection().noteList(track)) {
4166 if (firstNote == 0 || firstNote->chord()->tick() > n->chord()->tick())
4167 firstNote = n;
4168 if (lastNote == 0 || lastNote->chord()->tick() < n->chord()->tick())
4169 lastNote = n;
4170 }
4171 }
4172 }
4173 else {
4174 for (Note* n : _score->selection().noteList()) {
4175 if (firstNote == 0 || firstNote->chord()->tick() > n->chord()->tick())
4176 firstNote = n;
4177 if (lastNote == 0 || lastNote->chord()->tick() < n->chord()->tick())
4178 lastNote = n;
4179 }
4180 }
4181 if (!firstNote || !lastNote) {
4182 qDebug("addNoteLine: no note %p %p", firstNote, lastNote);
4183 return;
4184 }
4185 if (firstNote == lastNote) {
4186 qDebug("addNoteLine: no support for note to same note line %p", firstNote);
4187 return;
4188 }
4189 TextLine* tl = new TextLine(_score);
4190 tl->setParent(firstNote);
4191 tl->setStartElement(firstNote);
4192 tl->setEndElement(lastNote);
4193 tl->setDiagonal(true);
4194 tl->setAnchor(Spanner::Anchor::NOTE);
4195 tl->setTick(firstNote->chord()->tick());
4196 _score->startCmd();
4197 _score->undoAddElement(tl);
4198 _score->endCmd();
4199 }
4200
4201 //---------------------------------------------------------
4202 // cmdChangeEnharmonic
4203 //---------------------------------------------------------
4204
cmdChangeEnharmonic(bool both)4205 void ScoreView::cmdChangeEnharmonic(bool both)
4206 {
4207 _score->startCmd();
4208 QList<Note*> notes = _score->selection().uniqueNotes();
4209 for (Note* n : notes) {
4210 Staff* staff = n->staff();
4211 if (staff->part()->instrument(n->tick())->useDrumset())
4212 continue;
4213 if (staff->isTabStaff(n->tick())) {
4214 int string = n->line() + (both ? 1 : -1);
4215 int fret = staff->part()->instrument(n->tick())->stringData()->fret(n->pitch(), string, staff, n->chord()->tick());
4216 if (fret != -1) {
4217 n->undoChangeProperty(Pid::FRET, fret);
4218 n->undoChangeProperty(Pid::STRING, string);
4219 }
4220 }
4221 else {
4222 static const int tab[36] = {
4223 26, 14, 2, // 60 B# C Dbb
4224 21, 21, 9, // 61 C# C# Db
4225 28, 16, 4, // 62 C## D Ebb
4226 23, 23, 11, // 63 D# D# Eb
4227 30, 18, 6, // 64 D## E Fb
4228 25, 13, 1, // 65 E# F Gbb
4229 20, 20, 8, // 66 F# F# Gb
4230 27, 15, 3, // 67 F## G Abb
4231 22, 22, 10, // 68 G# G# Ab
4232 29, 17, 5, // 69 G## A Bbb
4233 24, 24, 12, // 70 A# A# Bb
4234 31, 19, 7, // 71 A## B Cb
4235 };
4236 int tpc = n->tpc();
4237 int i;
4238 for (i = 0; i < 36; ++i) {
4239 if (tab[i] == tpc) {
4240 if ((i % 3) < 2) {
4241 if (tab[i] == tab[i + 1])
4242 tpc = tab[i + 2];
4243 else
4244 tpc = tab[i + 1];
4245 }
4246 else {
4247 tpc = tab[i - 2];
4248 }
4249 break;
4250 }
4251 }
4252 if (i == 36) {
4253 qDebug("tpc %d not found", tpc);
4254 }
4255 else {
4256 n->undoSetTpc(tpc);
4257 if (both || staff->part()->instrument(n->chord()->tick())->transpose().isZero()) {
4258 // change both spellings
4259 int t = n->transposeTpc(tpc);
4260 if (n->concertPitch())
4261 n->undoChangeProperty(Pid::TPC2, t);
4262 else
4263 n->undoChangeProperty(Pid::TPC1, t);
4264 }
4265 }
4266 }
4267 }
4268
4269 Selection& selection = _score->selection();
4270 selection.clear();
4271 for (Note* n : notes)
4272 selection.add(n);
4273 _score->endCmd();
4274 }
4275
4276 //---------------------------------------------------------
4277 // cloneElement
4278 //---------------------------------------------------------
4279
cloneElement(Element * e)4280 void ScoreView::cloneElement(Element* e)
4281 {
4282 if (e->isMeasure() || e->isNote() || e->isVBox())
4283 return;
4284 QDrag* drag = new QDrag(this);
4285 QMimeData* mimeData = new QMimeData;
4286 if (e->isSpannerSegment())
4287 e = toSpannerSegment(e)->spanner();
4288 mimeData->setData(mimeSymbolFormat, e->mimeData(QPointF()));
4289 drag->setMimeData(mimeData);
4290 static QPixmap pixmap = QPixmap(2, 2); // null or 1x1 crashes on Linux under ChromeOS?!
4291 pixmap.fill(Qt::white);
4292 drag->setPixmap(pixmap);
4293 drag->exec(Qt::CopyAction);
4294 }
4295
4296 //---------------------------------------------------------
4297 // changeEditElement
4298 //---------------------------------------------------------
4299
changeEditElement(Element * e)4300 void ScoreView::changeEditElement(Element* e)
4301 {
4302 if (editData.element == e)
4303 return;
4304
4305 const Grip grip = editData.curGrip;
4306 const bool dragEdit = (state == ViewState::DRAG_EDIT);
4307
4308 if (editData.element && dragEdit)
4309 editData.element->endEditDrag(editData);
4310
4311 endEdit();
4312 startEdit(e, grip);
4313
4314 if (editData.element && dragEdit)
4315 editData.element->startEditDrag(editData);
4316 }
4317
4318 //---------------------------------------------------------
4319 // setCursorVisible
4320 //---------------------------------------------------------
4321
setCursorVisible(bool v)4322 void ScoreView::setCursorVisible(bool v)
4323 {
4324 _cursor->setVisible(v);
4325 }
4326
4327 //---------------------------------------------------------
4328 // setContinuousCursorVisible
4329 //---------------------------------------------------------
4330
setControlCursorVisible(bool v)4331 void ScoreView::setControlCursorVisible(bool v)
4332 {
4333 _controlCursor->setVisible(v);
4334 }
4335
4336 //---------------------------------------------------------
4337 // cmdTuplet
4338 //---------------------------------------------------------
4339
cmdTuplet(int n,ChordRest * cr)4340 void ScoreView::cmdTuplet(int n, ChordRest* cr)
4341 {
4342 if (cr->durationType() < TDuration(TDuration::DurationType::V_512TH) && cr->durationType() != TDuration(TDuration::DurationType::V_MEASURE)) {
4343 mscore->noteTooShortForTupletDialog();
4344 return;
4345 }
4346 Measure* measure = cr->measure();
4347 if (measure && measure->isMMRest())
4348 return;
4349
4350 Fraction f(cr->ticks());
4351 Tuplet* ot = cr->tuplet();
4352
4353 f.reduce(); //measure duration might not be reduced
4354 Fraction ratio(n, f.numerator());
4355 Fraction fr(1, f.denominator());
4356 while (ratio.numerator() >= ratio.denominator() * 2) {
4357 ratio.setDenominator(ratio.denominator() * 2); // operator*= reduces, we don't want that here
4358 fr *= Fraction(1,2);
4359 }
4360
4361 Tuplet* tuplet = new Tuplet(_score);
4362 tuplet->setRatio(ratio);
4363
4364 //
4365 // "fr" is the fraction value of one tuple element
4366 //
4367 // "tuplet time" is "normal time" / tuplet->ratio()
4368 // Example: an 1/8 has 240 midi ticks, in an 1/8 triplet the note
4369 // has a tick duration of 240 / (3/2) = 160 ticks
4370 //
4371
4372 tuplet->setTicks(f);
4373 TDuration baseLen(fr);
4374 tuplet->setBaseLen(baseLen);
4375
4376 tuplet->setTrack(cr->track());
4377 tuplet->setTick(cr->tick());
4378 tuplet->setParent(measure);
4379
4380 if (ot)
4381 tuplet->setTuplet(ot);
4382
4383 cmdCreateTuplet(cr, tuplet);
4384 }
4385
4386 //---------------------------------------------------------
4387 // cmdCreateTuplet
4388 //---------------------------------------------------------
4389
cmdCreateTuplet(ChordRest * cr,Tuplet * tuplet)4390 void ScoreView::cmdCreateTuplet(ChordRest* cr, Tuplet* tuplet)
4391 {
4392 _score->cmdCreateTuplet(cr, tuplet);
4393
4394 const std::vector<DurationElement*>& cl = tuplet->elements();
4395 size_t ne = cl.size();
4396 DurationElement* el = 0;
4397 if (ne && cl[0]->type() == ElementType::REST)
4398 el = cl[0];
4399 else if (ne > 1)
4400 el = cl[1];
4401 if (el) {
4402 _score->select(el, SelectType::SINGLE, 0);
4403 _score->inputState().setDuration(tuplet->baseLen());
4404 changeState(ViewState::NOTE_ENTRY);
4405 }
4406 }
4407
4408 //---------------------------------------------------------
4409 // changeVoice
4410 //---------------------------------------------------------
4411
changeVoice(int voice)4412 void ScoreView::changeVoice(int voice)
4413 {
4414 InputState* is = &score()->inputState();
4415 int track = (is->track() / VOICES) * VOICES + voice;
4416
4417 if (is->noteEntryMode()) {
4418 is->setTrack(track);
4419 if (is->segment()) { // can be null for eg repeatMeasure
4420 is->setSegment(is->segment()->measure()->first(SegmentType::ChordRest));
4421 moveCursor();
4422 score()->setUpdateAll();
4423 score()->update();
4424 mscore->setPos(is->segment()->tick());
4425 }
4426 }
4427 else {
4428 // treat as command to move notes to another voice
4429 score()->changeVoice(voice);
4430 // modify the input state only if the command was successful
4431 for (ChordRest* cr : score()->getSelectedChordRests())
4432 if (cr->voice() == voice) {
4433 is->setTrack(track);
4434 break;
4435 }
4436 }
4437 }
4438
4439 //---------------------------------------------------------
4440 // cmdTuplet
4441 //---------------------------------------------------------
4442
cmdTuplet(int n)4443 void ScoreView::cmdTuplet(int n)
4444 {
4445 _score->startCmd();
4446 if (noteEntryMode()) {
4447 _score->expandVoice();
4448 ChordRest* cr = _score->inputState().cr();
4449 if (cr) {
4450 _score->changeCRlen(cr, _score->inputState().duration());
4451 cmdTuplet(n, cr);
4452 }
4453 }
4454 else {
4455 for (ChordRest* cr : _score->getSelectedChordRests()) {
4456 if (!cr->isGrace()) {
4457 cmdTuplet(n, cr);
4458 }
4459 }
4460 }
4461 _score->endCmd();
4462 moveCursor(); // do this after endCmd to make sure segment has been laid out
4463 }
4464
4465 //---------------------------------------------------------
4466 // midiNoteReceived
4467 //---------------------------------------------------------
4468
midiNoteReceived(int pitch,bool chord,int velocity)4469 void ScoreView::midiNoteReceived(int pitch, bool chord, int velocity)
4470 {
4471 qDebug("midiNoteReceived: pitch %d, chord %d, velocity %d", pitch, chord, velocity);
4472
4473 MidiInputEvent ev;
4474 ev.pitch = pitch;
4475 ev.chord = chord;
4476 ev.velocity = velocity;
4477
4478 score()->masterScore()->enqueueMidiEvent(ev);
4479
4480 if (!score()->undoStack()->active())
4481 cmd((const char*)0);
4482
4483 if (!chord && velocity && !realtimeTimer->isActive() && score()->usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO)) {
4484 // First note pressed in automatic real-time mode.
4485 extendNoteTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY)); // set timer to trigger repeatedly
4486 triggerCmdRealtimeAdvance(); // also trigger once immediately
4487 }
4488
4489 }
4490
4491 //---------------------------------------------------------
4492 // extendCurrentNote
4493 // Called after user has held down a midi key for a while.
4494 // TODO: adapt to allow calling from StepTime mode.
4495 //---------------------------------------------------------
4496
extendCurrentNote()4497 void ScoreView::extendCurrentNote()
4498 {
4499 if (!noteEntryMode() || realtimeTimer->isActive())
4500 return;
4501
4502 allowRealtimeRests = false;
4503 realtimeTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY)); // set timer to trigger repeatedly
4504 triggerCmdRealtimeAdvance(); // also trigger once immediately
4505 }
4506
4507 //---------------------------------------------------------
4508 // realtimeAdvance
4509 //---------------------------------------------------------
4510
realtimeAdvance(bool allowRests)4511 void ScoreView::realtimeAdvance(bool allowRests)
4512 {
4513 if (!noteEntryMode())
4514 return;
4515 InputState& is = score()->inputState();
4516 switch (is.noteEntryMethod()) {
4517 case NoteEntryMethod::REALTIME_MANUAL:
4518 allowRealtimeRests = allowRests;
4519 triggerCmdRealtimeAdvance();
4520 break;
4521 case NoteEntryMethod::REALTIME_AUTO:
4522 if (realtimeTimer->isActive())
4523 realtimeTimer->stop();
4524 else {
4525 allowRealtimeRests = allowRests;
4526 realtimeTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY));
4527 }
4528 break;
4529 default:
4530 break;
4531 }
4532 }
4533
4534 //---------------------------------------------------------
4535 // triggerCmdRealtimeAdvance
4536 //---------------------------------------------------------
4537
triggerCmdRealtimeAdvance()4538 void ScoreView::triggerCmdRealtimeAdvance()
4539 {
4540 InputState& is = score()->inputState();
4541 bool realtime = is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO) || is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_MANUAL);
4542 if (!realtime || !noteEntryMode() || (!allowRealtimeRests && score()->activeMidiPitches()->empty())) {
4543 if (realtimeTimer->isActive())
4544 realtimeTimer->stop();
4545 allowRealtimeRests = true;
4546 return;
4547 }
4548 // give audible feedback immediately to indicate a beat, but don’t advance just yet.
4549 seq->playMetronomeBeat(_score->tick2beatType(is.tick()));
4550 // The user will want to press notes "on the beat" and not before the beat, so wait a
4551 // little in case midi input event is received just after realtime-advance was called.
4552 QTimer::singleShot(100, Qt::PreciseTimer, this, SLOT(cmdRealtimeAdvance()));
4553 }
4554
4555 //---------------------------------------------------------
4556 // cmdRealtimeAdvance
4557 // move input forwards and extend current chord/rest.
4558 //---------------------------------------------------------
4559
cmdRealtimeAdvance()4560 void ScoreView::cmdRealtimeAdvance()
4561 {
4562 InputState& is = _score->inputState();
4563 if (!is.noteEntryMode())
4564 return;
4565 _score->startCmd();
4566 Fraction ticks2measureEnd = is.segment()->measure()->ticks() - is.segment()->rtick();
4567 if (!is.cr() || (is.cr()->ticks() != is.duration().fraction() && is.duration() < ticks2measureEnd))
4568 _score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
4569 ChordRest* prevCR = toChordRest(is.cr());
4570 if (_score->inputState().endOfScore())
4571 _score->appendMeasures(1);
4572 is.moveToNextInputPos();
4573 if (_score->activeMidiPitches()->empty())
4574 _score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
4575 else {
4576 Chord* prevChord = prevCR->isChord() ? toChord(prevCR) : 0;
4577 bool partOfChord = false;
4578 for (const MidiInputEvent &ev : *_score->activeMidiPitches()) {
4579 _score->addTiedMidiPitch(ev.pitch, partOfChord, prevChord);
4580 partOfChord = true;
4581 }
4582 }
4583 if (prevCR->measure() != is.segment()->measure()) {
4584 // just advanced across barline. Now simplify tied notes.
4585 score()->regroupNotesAndRests(prevCR->measure()->tick(), is.segment()->measure()->tick(), is.track());
4586 }
4587 _score->endCmd();
4588 }
4589
4590 //---------------------------------------------------------
4591 // cmdAddChordName
4592 //---------------------------------------------------------
4593
cmdAddChordName(HarmonyType ht)4594 void ScoreView::cmdAddChordName(HarmonyType ht)
4595 {
4596 if (!_score->checkHasMeasures())
4597 return;
4598
4599 int track = -1;
4600 Element* newParent = nullptr;
4601 Element* el = _score->selection().element();
4602 if (el && el->isFretDiagram()) {
4603 FretDiagram* fd = toFretDiagram(el);
4604 track = fd->track();
4605 newParent = fd;
4606 }
4607 else {
4608 ChordRest* cr = _score->getSelectedChordRest();
4609 if (cr) {
4610 track = cr->track();
4611 newParent = toElement(cr->segment());
4612 }
4613 }
4614 if (track == -1 || !newParent)
4615 return;
4616
4617 _score->startCmd();
4618 Harmony* harmony = new Harmony(_score);
4619 harmony->setTrack(track);
4620 harmony->setParent(newParent);
4621 harmony->setHarmonyType(ht);
4622 _score->undoAddElement(harmony);
4623 _score->endCmd();
4624
4625 _score->select(harmony, SelectType::SINGLE, 0);
4626 startEditMode(harmony);
4627 _score->update();
4628 }
4629
4630 //---------------------------------------------------------
4631 // cmdAddText
4632 //---------------------------------------------------------
4633
cmdAddText(Tid tid,Tid customTid,PropertyFlags pf,Placement p)4634 void ScoreView::cmdAddText(Tid tid, Tid customTid, PropertyFlags pf, Placement p)
4635 {
4636 if (!_score->checkHasMeasures())
4637 return;
4638 if (noteEntryMode()) // force out of entry mode
4639 changeState(ViewState::NORMAL);
4640
4641 TextBase* s = 0;
4642 _score->startCmd();
4643 if (tid == Tid::STAFF && customTid == Tid::EXPRESSION)
4644 tid = customTid; // expression is not first class element, but treat as such
4645 switch (tid) {
4646 case Tid::TITLE:
4647 case Tid::SUBTITLE:
4648 case Tid::COMPOSER:
4649 case Tid::POET:
4650 case Tid::INSTRUMENT_EXCERPT:
4651 {
4652 MeasureBase* measure = _score->first();
4653 if (!measure->isVBox()) {
4654 _score->insertMeasure(ElementType::VBOX, measure);
4655 measure = measure->prev();
4656 }
4657 s = new Text(_score, tid);
4658 s->setParent(measure);
4659 _score->undoAddElement(s);
4660 }
4661 break;
4662
4663 case Tid::REHEARSAL_MARK:
4664 {
4665 ChordRest* cr = _score->getSelectedChordRest();
4666 if (!cr)
4667 break;
4668 s = new RehearsalMark(_score);
4669 cr->undoAddAnnotation(s);
4670 }
4671 break;
4672 case Tid::STAFF:
4673 {
4674 ChordRest* cr = _score->getSelectedChordRest();
4675 if (!cr)
4676 break;
4677 s = new StaffText(_score, Tid::STAFF);
4678 // if the previous text is UNSTYLED or NOSTYLE
4679 // and the next text is created using text tab command
4680 // (only those created in this way can make pf not STYLED, see definition of ScoreView::textTab())
4681 // it inherits the same PropertyFlags and Placement
4682 // making it much easier to put all texts above/below staves
4683 if (pf != PropertyFlags::STYLED) {
4684 s->setPlacement(p);
4685 s->setPropertyFlags(Pid::PLACEMENT, pf);
4686 }
4687 cr->undoAddAnnotation(s);
4688 }
4689 break;
4690 case Tid::SYSTEM:
4691 {
4692 ChordRest* cr = _score->getSelectedChordRest();
4693 if (!cr)
4694 break;
4695 s = new SystemText(_score, Tid::SYSTEM);
4696 if (pf != PropertyFlags::STYLED) {
4697 s->setPlacement(p);
4698 s->setPropertyFlags(Pid::PLACEMENT, pf);
4699 }
4700 cr->undoAddAnnotation(s);
4701 }
4702 break;
4703 case Tid::EXPRESSION:
4704 {
4705 ChordRest* cr = _score->getSelectedChordRest();
4706 if (!cr)
4707 break;
4708 s = new StaffText(_score, Tid::EXPRESSION);
4709 s->setPlacement(Placement::BELOW);
4710 s->setPropertyFlags(Pid::PLACEMENT, PropertyFlags::UNSTYLED);
4711 cr->undoAddAnnotation(s);
4712 }
4713 break;
4714 case Tid::INSTRUMENT_CHANGE:
4715 {
4716 ChordRest* cr = _score->getSelectedChordRest();
4717 if (!cr)
4718 break;
4719 s = new InstrumentChange(_score);
4720 cr->undoAddAnnotation(s);
4721 }
4722 break;
4723 case Tid::STICKING:
4724 {
4725 ChordRest* cr = _score->getSelectedChordRest();
4726 if (!cr)
4727 break;
4728 s = new Sticking(_score);
4729 cr->undoAddAnnotation(s);
4730 }
4731 break;
4732 case Tid::FINGERING:
4733 case Tid::LH_GUITAR_FINGERING:
4734 case Tid::RH_GUITAR_FINGERING:
4735 case Tid::STRING_NUMBER:
4736 {
4737 Element* e = _score->getSelectedElement();
4738 if (!e || !e->isNote())
4739 break;
4740 bool isTablature = e->staff()->isTabStaff(e->tick());
4741 bool tabFingering = e->staff()->staffType(e->tick())->showTabFingering();
4742 if (isTablature && !tabFingering)
4743 break;
4744 s = new Fingering(_score, tid);
4745 s->setTrack(e->track());
4746 s->setParent(e);
4747 if (pf != PropertyFlags::STYLED) {
4748 s->setPlacement(p);
4749 s->setPropertyFlags(Pid::PLACEMENT, pf);
4750 }
4751 _score->undoAddElement(s);
4752 }
4753 break;
4754 default:
4755 break;
4756 }
4757
4758 if (s) {
4759 if (customTid != Tid::DEFAULT && customTid != tid)
4760 s->initTid(customTid);
4761 _score->select(s, SelectType::SINGLE, 0);
4762 _score->endCmd();
4763 Measure* m = s->findMeasure();
4764 if (m && m->hasMMRest() && s->links()) {
4765 Measure* mmRest = m->mmRest();
4766 for (ScoreElement* se : *s->links()) {
4767 TextBase* s1 = toTextBase(se);
4768 if (s != s1 && s1->findMeasure() == mmRest) {
4769 s = s1;
4770 break;
4771 }
4772 }
4773 }
4774 adjustCanvasPosition(s, false);
4775 startEditMode(s);
4776 }
4777 else
4778 _score->endCmd();
4779 }
4780
4781 //---------------------------------------------------------
4782 // cmdAppendMeasures
4783 /// Append \a n measures.
4784 ///
4785 /// Keyboard callback, called from pulldown menu.
4786 //
4787 // - called from pulldown menu
4788 //---------------------------------------------------------
4789
cmdAppendMeasures(int n,ElementType type)4790 void ScoreView::cmdAppendMeasures(int n, ElementType type)
4791 {
4792 _score->startCmd();
4793 appendMeasures(n, type);
4794 _score->endCmd();
4795 }
4796
4797 //---------------------------------------------------------
4798 // appendMeasure
4799 //---------------------------------------------------------
4800
appendMeasure(ElementType type)4801 MeasureBase* ScoreView::appendMeasure(ElementType type)
4802 {
4803 _score->startCmd();
4804 _score->insertMeasure(type, 0);
4805 MeasureBase* mb = _score->lastMeasureMM();
4806 _score->endCmd();
4807 return mb;
4808 }
4809
4810 //---------------------------------------------------------
4811 // appendMeasures
4812 //---------------------------------------------------------
4813
appendMeasures(int n,ElementType type)4814 void ScoreView::appendMeasures(int n, ElementType type)
4815 {
4816 if (_score->noStaves()) {
4817 QMessageBox::warning(0, "MuseScore",
4818 tr("No staves found:\n"
4819 "Please use the instruments dialog to\n"
4820 "first create some staves"));
4821 return;
4822 }
4823 for (int i = 0; i < n; ++i)
4824 _score->insertMeasure(type, 0);
4825 }
4826
4827 //---------------------------------------------------------
4828 // cmdInsertMeasures
4829 //---------------------------------------------------------
4830
cmdInsertMeasures(int n,ElementType type)4831 void ScoreView::cmdInsertMeasures(int n, ElementType type)
4832 {
4833 MeasureBase* mb = checkSelectionStateForInsertMeasure();
4834 if (!mb)
4835 return;
4836 _score->startCmd();
4837 for (int i = 0; i < n; ++i)
4838 _score->insertMeasure(type, mb);
4839 _score->endCmd();
4840
4841 if (mb->type() == ElementType::MEASURE) {
4842 // re-select the original measure (which may now be covered by an mmrest)
4843 // do this after the layout so mmrests are updated
4844 Measure* m = _score->tick2measureMM(mb->tick());
4845 _score->select(m, SelectType::SINGLE, 0);
4846 }
4847 else {
4848 // original selection was not a measure, just re-select it
4849 _score->select(mb, SelectType::SINGLE, 0);
4850 }
4851 }
4852
4853 //---------------------------------------------------------
4854 // cmdInsertMeasure
4855 //---------------------------------------------------------
4856
cmdInsertMeasure(ElementType type)4857 void ScoreView::cmdInsertMeasure(ElementType type)
4858 {
4859 MeasureBase* mb = checkSelectionStateForInsertMeasure();
4860 if (!mb)
4861 return;
4862 _score->startCmd();
4863 _score->insertMeasure(type, mb);
4864 mb = mb->prev();
4865 if (mb->type() == ElementType::TBOX) {
4866 TBox* tbox = static_cast<TBox*>(mb);
4867 Text* s = tbox->text();
4868 _score->select(s, SelectType::SINGLE, 0);
4869 _score->endCmd();
4870 startEditMode(s);
4871 return;
4872 }
4873 if (mb)
4874 _score->select(mb, SelectType::SINGLE, 0);
4875 _score->endCmd();
4876 }
4877
4878 //---------------------------------------------------------
4879 // checkSelectionStateForInsertMeasure
4880 //---------------------------------------------------------
4881
checkSelectionStateForInsertMeasure()4882 MeasureBase* ScoreView::checkSelectionStateForInsertMeasure()
4883 {
4884 MeasureBase* mb = 0;
4885 if (_score->selection().isRange()) {
4886 mb = _score->selection().startSegment()->measure();
4887 return mb;
4888 }
4889
4890 mb = _score->selection().findMeasure();
4891 if (mb)
4892 return mb;
4893
4894 Element* e = _score->selection().element();
4895 if (e) {
4896 if (e->type() == ElementType::VBOX || e->type() == ElementType::TBOX || e->type() == ElementType::HBOX)
4897 return static_cast<MeasureBase*>(e);
4898 }
4899 QMessageBox::warning(0, "MuseScore",
4900 tr("No measure selected:\n" "Please select a measure and try again"));
4901 return 0;
4902 }
4903
4904 //---------------------------------------------------------
4905 // cmdRepeatSelection
4906 //---------------------------------------------------------
4907
cmdRepeatSelection()4908 void ScoreView::cmdRepeatSelection()
4909 {
4910 const Selection& selection = _score->selection();
4911
4912 if (noteEntryMode() && selection.isSingle()) {
4913 Element* el = _score->selection().element();
4914 if (el && el->type() == ElementType::NOTE) {
4915 if (!_score->inputState().endOfScore()) {
4916 _score->startCmd();
4917 bool addTo = false;
4918 Chord* c = static_cast<Note*>(el)->chord();
4919 for (Note* note : c->notes()) {
4920 NoteVal nval = note->noteVal();
4921 _score->addPitch(nval, addTo);
4922 addTo = true;
4923 }
4924 _score->endCmd();
4925 }
4926 }
4927 return;
4928 }
4929 if (!selection.isRange()) {
4930 ChordRest* cr = _score->getSelectedChordRest();
4931 if (!cr)
4932 return;
4933 _score->select(cr, SelectType::RANGE);
4934 }
4935
4936 if (!checkCopyOrCut())
4937 return;
4938
4939 QString mimeType = selection.mimeType();
4940 if (mimeType.isEmpty()) {
4941 qDebug("mime type is empty");
4942 return;
4943 }
4944 QMimeData* mimeData = new QMimeData;
4945 mimeData->setData(mimeType, selection.mimeData());
4946 if (MScore::debugMode)
4947 qDebug("cmdRepeatSelection: <%s>", mimeData->data(mimeType).data());
4948 QApplication::clipboard()->setMimeData(mimeData);
4949
4950 QByteArray d(mimeData->data(mimeType));
4951 XmlReader xml(d);
4952 xml.setPasteMode(true);
4953
4954 int dStaff = selection.staffStart();
4955 Segment* endSegment = selection.endSegment();
4956
4957 if (endSegment && endSegment->segmentType() != SegmentType::ChordRest)
4958 endSegment = endSegment->next1(SegmentType::ChordRest);
4959 if (endSegment && endSegment->element(dStaff * VOICES)) {
4960 Element* e = endSegment->element(dStaff * VOICES);
4961 if (e) {
4962 ChordRest* cr = toChordRest(e);
4963 _score->startCmd();
4964 _score->pasteStaff(xml, cr->segment(), cr->staffIdx());
4965 _score->endCmd();
4966 }
4967 else
4968 qDebug("ScoreView::cmdRepeatSelection: cannot paste: %p <%s>", e, e ? e->name() : "");
4969 }
4970 else {
4971 qDebug("cmdRepeatSelection: cannot paste: endSegment: %p dStaff %d", endSegment, dStaff);
4972 }
4973 }
4974
4975 //---------------------------------------------------------
4976 // searchPage
4977 //---------------------------------------------------------
4978
searchPage(int n)4979 bool ScoreView::searchPage(int n)
4980 {
4981 bool result = true;
4982 n -= score()->pageNumberOffset();
4983 if (n <= 0) {
4984 n = 1;
4985 result = false;
4986 }
4987 n--;
4988 if (n >= _score->npages()) {
4989 result = false;
4990 n = _score->npages() - 1;
4991 }
4992 const Page* page = _score->pages()[n];
4993 foreach (System* s, page->systems()) {
4994 if (s->firstMeasure()) {
4995 gotoMeasure(s->firstMeasure());
4996 break;
4997 }
4998 }
4999 return result;
5000 }
5001
5002 //---------------------------------------------------------
5003 // searchMeasure
5004 //---------------------------------------------------------
5005
searchMeasure(int n)5006 bool ScoreView::searchMeasure(int n)
5007 {
5008 if (n <= 0)
5009 return false;
5010 bool result = true;
5011 --n;
5012 int i = 0;
5013 Measure* measure;
5014 for (measure = _score->firstMeasureMM(); measure; measure = measure->nextMeasureMM()) {
5015 int nn = _score->styleB(Sid::createMultiMeasureRests) && measure->isMMRest()
5016 ? measure->mmRestCount() : 1;
5017 if (n >= i && n < (i+nn))
5018 break;
5019 i += nn;
5020 }
5021 if (!measure) {
5022 measure = score()->lastMeasureMM();
5023 result = false;
5024 }
5025 gotoMeasure(measure);
5026 return result;
5027 }
5028
5029 //---------------------------------------------------------
5030 // searchRehearsalMark
5031 //---------------------------------------------------------
5032
searchRehearsalMark(const QString & s)5033 bool ScoreView::searchRehearsalMark(const QString& s)
5034 {
5035 //search rehearsal marks
5036 QString ss = s.toLower();
5037 bool found = false;
5038 for (Segment* seg = score()->firstSegment(SegmentType::ChordRest); seg; seg = seg->next1(SegmentType::ChordRest)) {
5039 for (Element* e : seg->annotations()){
5040 if (e->type() == ElementType::REHEARSAL_MARK) {
5041 RehearsalMark* rm = static_cast<RehearsalMark*>(e);
5042 QString rms = rm->plainText().toLower();
5043 if (rms.startsWith(ss)) {
5044 gotoMeasure(seg->measure());
5045 found = true;
5046 break;
5047 }
5048 }
5049 }
5050 if (found)
5051 break;
5052 }
5053 return found;
5054 }
5055
5056 //---------------------------------------------------------
5057 // gotoMeasure
5058 //---------------------------------------------------------
5059
gotoMeasure(Measure * measure)5060 void ScoreView::gotoMeasure(Measure* measure)
5061 {
5062 adjustCanvasPosition(measure, state != ViewState::NORMAL);
5063 int tracks = _score->nstaves() * VOICES;
5064 for (Segment* segment = measure->first(); segment; segment = segment->next()) {
5065 if (segment->segmentType() != SegmentType::ChordRest)
5066 continue;
5067 int track;
5068 for (track = 0; track < tracks; ++track) {
5069 ChordRest* cr = static_cast<ChordRest*>(segment->element(track));
5070 if (cr) {
5071 Element* e;
5072 if (cr->type() == ElementType::CHORD)
5073 e = static_cast<Chord*>(cr)->upNote();
5074 else //REST
5075 e = cr;
5076
5077 _score->select(e, SelectType::SINGLE, 0);
5078 break;
5079 }
5080 }
5081 if (track != tracks)
5082 break;
5083 }
5084 _score->setUpdateAll();
5085 _score->update();
5086 }
5087
5088 //---------------------------------------------------------
5089 // layoutChanged
5090 //---------------------------------------------------------
5091
layoutChanged()5092 void ScoreView::layoutChanged()
5093 {
5094 if (mscore->navigator())
5095 mscore->navigator()->layoutChanged();
5096 _curLoopIn->move(_score->pos(POS::LEFT));
5097 Measure* lm = _score->lastMeasure();
5098 if (lm && _score->pos(POS::RIGHT) > lm->endTick())
5099 _score->setPos(POS::RIGHT, lm->endTick());
5100 _curLoopOut->move(_score->pos(POS::RIGHT));
5101 }
5102
5103 //---------------------------------------------------------
5104 // elementLower
5105 //---------------------------------------------------------
5106
elementLower(const Element * e1,const Element * e2)5107 static bool elementLower(const Element* e1, const Element* e2)
5108 {
5109 if (!e1->selectable())
5110 return false;
5111 if (!e2->selectable())
5112 return true;
5113 if (e1->isNote() && e2->isStem())
5114 return true;
5115 if (e2->isNote() && e1->isStem())
5116 return false;
5117 if (e1->z() == e2->z()) {
5118 // same stacking order, prefer non-hidden elements
5119 if (e1->type() == e2->type()) {
5120 if (e1->type() == ElementType::NOTEDOT) {
5121 const NoteDot* n1 = static_cast<const NoteDot*>(e1);
5122 const NoteDot* n2 = static_cast<const NoteDot*>(e2);
5123 if (n1->note() && n1->note()->hidden())
5124 return false;
5125 else if (n2->note() && n2->note()->hidden())
5126 return true;
5127 }
5128 else if (e1->type() == ElementType::NOTE) {
5129 const Note* n1 = static_cast<const Note*>(e1);
5130 const Note* n2 = static_cast<const Note*>(e2);
5131 if (n1->hidden())
5132 return false;
5133 else if (n2->hidden())
5134 return true;
5135 }
5136 }
5137 // different types, or same type but nothing hidden - use track
5138 return e1->track() <= e2->track();
5139 }
5140
5141 // default case, use stacking order
5142 return e1->z() <= e2->z();
5143 }
5144
5145 //---------------------------------------------------------
5146 // elementsNear
5147 //---------------------------------------------------------
5148
elementsNear(QPointF p)5149 QList<Element*> ScoreView::elementsNear(QPointF p)
5150 {
5151 QList<Element*> ll;
5152 Page* page = point2page(p);
5153 if (!page)
5154 return ll;
5155
5156 p -= page->pos();
5157 double w = (preferences.getInt(PREF_UI_CANVAS_MISC_SELECTIONPROXIMITY) * .5) / matrix().m11();
5158 QRectF r(p.x() - w, p.y() - w, 3.0 * w, 3.0 * w);
5159
5160 QList<Element*> el = page->items(r);
5161 for (int i = 0; i < MAX_HEADERS; i++)
5162 if (score()->headerText(i) != nullptr) // gives the ability to select the header
5163 el.push_back(score()->headerText(i));
5164 for (int i = 0; i < MAX_FOOTERS; i++)
5165 if (score()->footerText(i) != nullptr) // gives the ability to select the footer
5166 el.push_back(score()->footerText(i));
5167 for (Element* e : el) {
5168 e->itemDiscovered = 0;
5169 if (!e->selectable() || e->isPage())
5170 continue;
5171 if (e->contains(p))
5172 ll.append(e);
5173 }
5174 int n = ll.size();
5175 if ((n == 0) || ((n == 1) && (ll[0]->isMeasure()))) {
5176 //
5177 // if no relevant element hit, look nearby
5178 //
5179 for (Element* e : el) {
5180 if (e->isPage() || !e->selectable())
5181 continue;
5182 if (e->intersects(r))
5183 ll.append(e);
5184 }
5185 }
5186 if (!ll.empty())
5187 std::sort(ll.begin(), ll.end(), elementLower);
5188 return ll;
5189 }
5190
5191 //---------------------------------------------------------
5192 // elementNear
5193 //---------------------------------------------------------
5194
elementNear(QPointF p)5195 Element* ScoreView::elementNear(QPointF p)
5196 {
5197 QList<Element*> ll = elementsNear(p);
5198 if (ll.empty()) {
5199 // qDebug(" nothing found");
5200 return 0;
5201 }
5202 #if 0
5203 qDebug("==");
5204 for (const Element* e : ll)
5205 qDebug(" %s selected %d z %d", e->name(), e->selected(), e->z());
5206 #endif
5207 Element* e = ll.at(0);
5208 return e;
5209 }
5210
5211 //---------------------------------------------------------
5212 // posChanged
5213 //---------------------------------------------------------
5214
posChanged(POS pos,unsigned tick)5215 void ScoreView::posChanged(POS pos, unsigned tick)
5216 {
5217 switch (pos) {
5218 case POS::CURRENT:
5219 // draw playback cursor only in the currently active view
5220 if (this != mscore->currentScoreView() && !_moveWhenInactive)
5221 return;
5222 if (noteEntryMode())
5223 moveCursor(); // update input cursor position
5224 else
5225 moveCursor(Fraction::fromTicks(tick)); // update play position
5226 break;
5227 case POS::LEFT:
5228 _curLoopIn->move(_score->pos(POS::LEFT));
5229 break;
5230 case POS::RIGHT:
5231 _curLoopOut->move(_score->pos(POS::RIGHT));
5232 break;
5233 }
5234 }
5235
5236 //---------------------------------------------------------
5237 // loopToggled
5238 //---------------------------------------------------------
5239
loopToggled(bool val)5240 void ScoreView::loopToggled(bool val)
5241 {
5242 if (_score->lastMeasure() == 0)
5243 return;
5244 if (_score->pos(POS::LEFT).isZero() && _score->pos(POS::RIGHT).isZero())
5245 _score->setPos(POS::RIGHT, _score->lastMeasure()->endTick());
5246 _curLoopIn->move(_score->loopInTick());
5247 _curLoopOut->move(_score->loopOutTick());
5248 _curLoopIn->setVisible(val);
5249 _curLoopOut->setVisible(val);
5250 update();
5251 }
5252
5253 //---------------------------------------------------------
5254 // cmdMoveCR
5255 // swap selected cr with cr to the left or right
5256 // - not across measure boundaries
5257 //---------------------------------------------------------
5258
cmdMoveCR(bool left)5259 void ScoreView::cmdMoveCR(bool left)
5260 {
5261 Element* e = _score->getSelectedElement();
5262 if (e && (e->type() == ElementType::NOTE || e->type() == ElementType::REST)) {
5263 if (e->type() == ElementType::NOTE)
5264 e = e->parent();
5265 QList<ChordRest*> crl;
5266 if (e->links()) {
5267 for (ScoreElement* cr : *e->links())
5268 crl.append(static_cast<ChordRest*>(cr));
5269 }
5270 else
5271 crl.append(static_cast<ChordRest*>(e));
5272
5273 bool cmdActive = false;
5274 for (ChordRest* cr1 : crl) {
5275 if (cr1->type() == ElementType::REST) {
5276 Rest* r = static_cast<Rest*>(cr1);
5277 if (r->measure() && r->measure()->isMMRest())
5278 break;
5279 }
5280 ChordRest* cr2 = left ? prevChordRest(cr1) : nextChordRest(cr1);
5281 // ensures cr1 is the left chord, useful in SwapCR::flip()
5282 if (left)
5283 std::swap(cr1, cr2);
5284 if (cr1 && cr2 && cr1->measure() == cr2->measure() && !cr1->tuplet() && !cr2->tuplet()
5285 && cr1->durationType() == cr2->durationType() && cr1->ticks() == cr2->ticks()
5286 // if two chords belong to different two-note tremolos, abort
5287 && !(cr1->isChord() && toChord(cr1)->tremolo() && toChord(cr1)->tremolo()->twoNotes()
5288 && cr2->isChord() && toChord(cr2)->tremolo() && toChord(cr2)->tremolo()->twoNotes()
5289 && toChord(cr1)->tremolo() != toChord(cr2)->tremolo())) {
5290 if (!cmdActive) {
5291 _score->startCmd();
5292 cmdActive = true;
5293 }
5294 _score->undo(new SwapCR(cr1, cr2));
5295 }
5296 }
5297 if (cmdActive)
5298 _score->endCmd();
5299 }
5300 }
5301
5302 //---------------------------------------------------------
5303 // cmdAddRemoveBreaks
5304 /// add or remove line breaks within a range selection
5305 /// or, if nothing is selected, the entire score
5306 //---------------------------------------------------------
5307
cmdAddRemoveBreaks()5308 void ScoreView::cmdAddRemoveBreaks()
5309 {
5310 bool noSelection = !_score->selection().isRange();
5311
5312 if (noSelection)
5313 _score->cmdSelectAll();
5314 else if (!_score->selection().isRange())
5315 return;
5316
5317 BreaksDialog bd;
5318 if (!bd.exec())
5319 return;
5320
5321 int interval = bd.remove || bd.lock ? 0 : bd.interval;
5322
5323 _score->addRemoveBreaks(interval, bd.lock);
5324
5325 if (noSelection)
5326 _score->deselectAll();
5327 }
5328
5329 //---------------------------------------------------------
5330 // cmdCopyLyricsToClipboard
5331 /// Copy the score lyrics into clipboard
5332 //---------------------------------------------------------
5333
cmdCopyLyricsToClipboard()5334 void ScoreView::cmdCopyLyricsToClipboard()
5335 {
5336 QApplication::clipboard()->setText(_score->extractLyrics());
5337 }
5338
5339 //---------------------------------------------------------
5340 // updateContinuousPanel
5341 // slot triggered when moving around the score to keep the panel visible
5342 //---------------------------------------------------------
5343
updateContinuousPanel()5344 void ScoreView::updateContinuousPanel()
5345 {
5346 if (_score->layoutMode() == LayoutMode::LINE)
5347 update();
5348 }
5349
5350 //---------------------------------------------------------
5351 // updateShadowNotes
5352 //---------------------------------------------------------
5353
updateShadowNotes()5354 void ScoreView::updateShadowNotes()
5355 {
5356 if (shadowNote->visible())
5357 setShadowNote(shadowNote->pos());
5358 }
5359
5360 //---------------------------------------------------------
5361 // getEditElement
5362 //---------------------------------------------------------
5363
getEditElement()5364 Element* ScoreView::getEditElement()
5365 {
5366 return editData.element;
5367 }
5368
5369 //---------------------------------------------------------
5370 // onElementDestruction
5371 //---------------------------------------------------------
5372
onElementDestruction(Element * e)5373 void ScoreView::onElementDestruction(Element* e)
5374 {
5375 if (editData.element == e) {
5376 editData.element = nullptr;
5377 if (editMode())
5378 changeState(ViewState::NORMAL);
5379 }
5380 }
5381
5382 //---------------------------------------------------------
5383 // startNoteEntryMode
5384 //---------------------------------------------------------
5385
startNoteEntryMode()5386 void ScoreView::startNoteEntryMode()
5387 {
5388 changeState(ViewState::NOTE_ENTRY);
5389 }
5390
5391 //---------------------------------------------------------
5392 // fotoMode
5393 //---------------------------------------------------------
5394
fotoMode() const5395 bool ScoreView::fotoMode() const
5396 {
5397 switch (state) {
5398 case ViewState::NORMAL:
5399 case ViewState::DRAG:
5400 case ViewState::DRAG_OBJECT:
5401 case ViewState::EDIT:
5402 case ViewState::DRAG_EDIT:
5403 case ViewState::LASSO:
5404 case ViewState::NOTE_ENTRY:
5405 case ViewState::PLAY:
5406 case ViewState::ENTRY_PLAY:
5407 break;
5408
5409 case ViewState::FOTO:
5410 case ViewState::FOTO_DRAG:
5411 case ViewState::FOTO_DRAG_EDIT:
5412 case ViewState::FOTO_DRAG_OBJECT:
5413 case ViewState::FOTO_LASSO:
5414 return true;
5415 }
5416 return false;
5417 }
5418
5419 //---------------------------------------------------------
5420 // setEditElement
5421 //---------------------------------------------------------
5422
setEditElement(Element * e)5423 void ScoreView::setEditElement(Element* e)
5424 {
5425 if (editData.element == e)
5426 return;
5427
5428 const bool normalState = (state == ViewState::NORMAL);
5429
5430 if (normalState && editData.element && editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit)
5431 endEdit();
5432
5433 editData.element = e;
5434
5435 if (normalState && e && e->normalModeEditBehavior() == Element::EditBehavior::Edit)
5436 startEdit(/* editMode */ false);
5437 else if (editData.grips) {
5438 editData.grips = 0;
5439 editData.curGrip = Grip::NO_GRIP;
5440 }
5441 }
5442
5443 //---------------------------------------------------------
5444 // updateEditElement
5445 //---------------------------------------------------------
5446
updateEditElement()5447 void ScoreView::updateEditElement()
5448 {
5449 if (state != ViewState::NORMAL)
5450 return;
5451
5452 if (!_score) {
5453 setEditElement(nullptr);
5454 return;
5455 }
5456
5457 const Selection& sel = _score->selection();
5458
5459 switch (sel.state()) {
5460 case SelState::NONE:
5461 case SelState::RANGE:
5462 setEditElement(nullptr);
5463 break;
5464 case SelState::LIST:
5465 if (Element* e = sel.element()) {
5466 if (editData.element == e)
5467 updateGrips();
5468 else
5469 setEditElement(e);
5470 }
5471 else {
5472 setEditElement(nullptr);
5473 }
5474 break;
5475 }
5476 }
5477
5478 //---------------------------------------------------------
5479 // visibleElementInScore
5480 //---------------------------------------------------------
5481
visibleElementInScore(const Element * orig,const Score * s)5482 static const Element* visibleElementInScore(const Element* orig, const Score* s)
5483 {
5484 if (!orig)
5485 return nullptr;
5486 if (orig->score() == s && orig->bbox().isValid())
5487 return orig;
5488
5489 for (const ScoreElement* se : orig->linkList()) {
5490 const Element* e = toElement(se);
5491 if (e->score() == s && e->bbox().isValid()) // bbox check to ensure the element is indeed visible
5492 return e;
5493 }
5494
5495 return nullptr;
5496 }
5497
5498 //---------------------------------------------------------
5499 // needViewportMove
5500 //---------------------------------------------------------
5501
needViewportMove(Score * cs,ScoreView * cv)5502 static bool needViewportMove(Score* cs, ScoreView* cv)
5503 {
5504 if (!cs || !cv)
5505 return false;
5506
5507 const CmdState& state = cs->cmdState();
5508
5509 if (state.startTick() < Fraction(0, 1))
5510 return false;
5511
5512 const QRectF viewport = cv->canvasViewport(); // TODO: margins for intersection check?
5513
5514 const Element* editElement = visibleElementInScore(state.element(), cs);
5515 if (editElement && editElement->bbox().isValid() && !editElement->isSpanner())
5516 return !viewport.intersects(editElement->canvasBoundingRect());
5517
5518 if (state.startTick().isZero() && state.endTick() == cs->endTick())
5519 return false; // TODO: adjust staff perhaps?
5520
5521 // Is any part of the range visible?
5522 Measure* mStart = cs->tick2measureMM(state.startTick());
5523 Measure* mEnd = cs->tick2measureMM(state.endTick());
5524 mEnd = mEnd ? mEnd->nextMeasureMM() : nullptr;
5525
5526 const bool isExcerpt = !cs->isMaster();
5527 const bool csStaves = (isExcerpt || (state.endStaff() < 0) || (state.endStaff() >= cs->nstaves()));
5528 const int startStaff = csStaves ? 0 : state.startStaff();
5529 const int endStaff = csStaves ? (cs->nstaves() - 1) : state.endStaff();
5530
5531 for (Measure* m = mStart; m && m != mEnd; m = m->nextMeasureMM()) {
5532 for (int st = startStaff; st <= endStaff; ++st) {
5533 const StaffLines* l = m->staffLines(st);
5534 if (l) {
5535 const QRectF r = l->bbox().translated(l->canvasPos());
5536 if (viewport.intersects(r))
5537 return false;
5538 }
5539 }
5540 }
5541
5542 // maybe viewport is at least near the desired position?
5543 const QPointF p = viewport.center();
5544 int staffIdx = 0;
5545 const Measure* m = cs->pos2measure(p, &staffIdx, nullptr, nullptr, nullptr);
5546 if (m && m->tick() >= state.startTick() && m->tick() <= state.endTick())
5547 return false;
5548
5549 return true;
5550 }
5551
5552 //---------------------------------------------------------
5553 // moveViewportToLastEdit
5554 //---------------------------------------------------------
5555
moveViewportToLastEdit()5556 void ScoreView::moveViewportToLastEdit()
5557 {
5558 if (_blockShowEdit)
5559 return;
5560
5561 Score* sc = score();
5562
5563 if (!needViewportMove(sc, this))
5564 return;
5565
5566 const CmdState& st = sc->cmdState();
5567 const Element* editElement = visibleElementInScore(st.element(), sc);
5568
5569 const MeasureBase* mb = nullptr;
5570 if (editElement) {
5571 if (editElement->isSpannerSegment()) {
5572 const SpannerSegment* s = toSpannerSegment(editElement);
5573 Fraction tick = s->tick();
5574 if (System* sys = s->system()) {
5575 Measure* fm = sys->firstMeasure();
5576 if (fm)
5577 tick = std::max(tick, fm->tick());
5578 }
5579 mb = sc->tick2measureMM(tick);
5580 }
5581 else {
5582 mb = editElement->findMeasureBase();
5583 }
5584 }
5585 if (!mb)
5586 mb = sc->tick2measureMM(st.startTick());
5587 if (!mb)
5588 return;
5589
5590 const Element* viewportElement = (editElement && editElement->bbox().isValid() && !mb->isMeasure()) ? editElement : mb;
5591
5592 const int staff = sc->isMaster() && mb->isMeasure() ? st.startStaff() : -1; // TODO: choose the closest staff to the current viewport?
5593 adjustCanvasPosition(viewportElement, /* playback */ false, staff);
5594 }
5595
5596 //---------------------------------------------------------
5597 // loadFromPreferences
5598 //---------------------------------------------------------
5599
loadFromPreferences()5600 void SmoothPanSettings::loadFromPreferences()
5601 {
5602 enabled = preferences.getBool(PREF_PAN_SMOOTHLY_ENABLED);
5603 controlModifierBase = preferences.getDouble(PREF_PAN_MODIFIER_BASE);
5604 if(mscore->currentScoreView() != nullptr)
5605 mscore->currentScoreView()->_controlModifier = controlModifierBase;
5606 controlModifierSteps = preferences.getDouble(PREF_PAN_MODIFIER_STEP);
5607 minContinuousModifier = preferences.getDouble(PREF_PAN_MODIFIER_MIN);
5608 maxContinuousModifier = preferences.getDouble(PREF_PAN_MODIFIER_MAX);
5609 // leftDistance = preferences.getDouble(PREF_PAN_DISTANCE_LEFT);
5610 // leftDistance1 = preferences.getDouble(PREF_PAN_DISTANCE_LEFT1);
5611 // leftDistance2 = preferences.getDouble(PREF_PAN_DISTANCE_LEFT2);
5612 // leftDistance3 = preferences.getDouble(PREF_PAN_DISTANCE_LEFT3);
5613 // leftMod1 = preferences.getDouble(PREF_PAN_MODIFIER_LEFT1);
5614 // leftMod2 = preferences.getDouble(PREF_PAN_MODIFIER_LEFT2);
5615 // leftMod3 = preferences.getDouble(PREF_PAN_MODIFIER_LEFT3);
5616 // rightDistance = preferences.getDouble(PREF_PAN_DISTANCE_RIGHT);
5617 // rightDistance1 = preferences.getDouble(PREF_PAN_DISTANCE_RIGHT1);
5618 // rightDistance2 = preferences.getDouble(PREF_PAN_DISTANCE_RIGHT2);
5619 // rightDistance3 = preferences.getDouble(PREF_PAN_DISTANCE_RIGHT3);
5620 // rightMod1 = preferences.getDouble(PREF_PAN_MODIFIER_RIGHT1);
5621 // rightMod2 = preferences.getDouble(PREF_PAN_MODIFIER_RIGHT2);
5622 // rightMod3 = preferences.getDouble(PREF_PAN_MODIFIER_RIGHT3);
5623 // normalWeight = preferences.getDouble(PREF_PAN_WEIGHT_NORMAL);
5624 // smartWeight = preferences.getDouble(PREF_PAN_WEIGHT_SMART);
5625 // advancedWeighting = preferences.getBool(PREF_PAN_WEIGHT_ADVANCED);
5626 // cursorTimerDuration = preferences.getInt(PREF_PAN_SMART_TIMER_DURATION);
5627 controlCursorScreenPos = preferences.getDouble(PREF_PAN_CURSOR_POS);
5628 teleportLeftEnabled = preferences.getBool(PREF_PAN_TELEPORT_LEFT);
5629 teleportRightEnabled = preferences.getBool(PREF_PAN_TELEPORT_RIGHT);
5630 }
5631 } // namespace Ms
5632