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