//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2002-2017 Werner Schweer & others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "log.h"

#include "scoreview.h"

#include "breaksdialog.h"
#include "continuouspanel.h"
#include "drumroll.h"
#include "editdrumset.h"
#include "editstaff.h"
#include "globals.h"
#include "magbox.h"
#include "measureproperties.h"
#include "musescore.h"
#include "navigator.h"
#include "preferences.h"
#include "scoreaccessibility.h"
#include "scoretab.h"
#include "seq.h"
#include "splitstaff.h"
#include "textcursor.h"
#include "textpalette.h"
#include "texttools.h"
#include "fotomode.h"
#include "tourhandler.h"

#include "inspector/inspector.h"

#include "libmscore/articulation.h"
#include "libmscore/barline.h"
#include "libmscore/box.h"
#include "libmscore/chord.h"
#include "libmscore/clef.h"
#include "libmscore/dynamic.h"
#include "libmscore/excerpt.h"
#include "libmscore/figuredbass.h"
#include "libmscore/fingering.h"
#include "libmscore/hairpin.h"
#include "libmscore/harmony.h"
#include "libmscore/fret.h"
#include "libmscore/icon.h"
#include "libmscore/image.h"
#include "libmscore/instrchange.h"
#include "libmscore/keysig.h"
#include "libmscore/lasso.h"
#include "libmscore/lyrics.h"
#include "libmscore/measure.h"
#include "libmscore/navigate.h"
#include "libmscore/notedot.h"
#include "libmscore/note.h"
#include "libmscore/noteline.h"
#include "libmscore/ottava.h"
#include "libmscore/page.h"
#include "libmscore/part.h"
#include "libmscore/pedal.h"
#include "libmscore/pitchspelling.h"
#include "libmscore/rehearsalmark.h"
#include "libmscore/repeatlist.h"
#include "libmscore/rest.h"
#include "libmscore/score.h"
#include "libmscore/segment.h"
#include "libmscore/shadownote.h"
#include "libmscore/slur.h"
#include "libmscore/spanner.h"
#include "libmscore/staff.h"
#include "libmscore/stafflines.h"
#include "libmscore/stafftext.h"
#include "libmscore/stafftype.h"
#include "libmscore/sticking.h"
#include "libmscore/stringdata.h"
#include "libmscore/sym.h"
#include "libmscore/system.h"
#include "libmscore/systemtext.h"
#include "libmscore/textframe.h"
#include "libmscore/text.h"
#include "libmscore/timesig.h"
#include "libmscore/trill.h"
#include "libmscore/tuplet.h"
#include "libmscore/undo.h"
#include "libmscore/utils.h"
#include "libmscore/volta.h"
#include "libmscore/xml.h"
#include "libmscore/textline.h"
#include "libmscore/shape.h"

#ifdef AVSOMR
#include "avsomr/avsomr.h"
#include "avsomr/avsomrdrawer.h"
#endif

namespace Ms {

extern QErrorMessage* errorMessage;

//---------------------------------------------------------
//   ScoreView
//---------------------------------------------------------

ScoreView::ScoreView(QWidget* parent)
   : QWidget(parent), editData(this)
      {
      setObjectName("scoreview");
      setStatusTip("scoreview");
      setAcceptDrops(true);
#ifndef Q_OS_MAC
      setAttribute(Qt::WA_OpaquePaintEvent);
#endif
      setAttribute(Qt::WA_NoSystemBackground);
      setFocusPolicy(Qt::StrongFocus);
      setAttribute(Qt::WA_InputMethodEnabled);
      setAttribute(Qt::WA_KeyCompression);
      setAttribute(Qt::WA_StaticContents);
      setAutoFillBackground(false);

      state       = ViewState::NORMAL;
      _score      = 0;
      _omrView    = 0;
      dropTarget  = 0;

      realtimeTimer = new QTimer(this);
      realtimeTimer->setTimerType(Qt::PreciseTimer);
      connect(realtimeTimer, SIGNAL(timeout()), this, SLOT(triggerCmdRealtimeAdvance()));
      extendNoteTimer = new QTimer(this);
      extendNoteTimer->setTimerType(Qt::PreciseTimer);
      extendNoteTimer->setSingleShot(true);
      connect(extendNoteTimer, SIGNAL(timeout()), this, SLOT(extendCurrentNote()));

      setContextMenuPolicy(Qt::DefaultContextMenu);

      double mag  = preferences.getDouble(PREF_SCORE_MAGNIFICATION) * (mscore->physicalDotsPerInch() / DPI);
      _matrix     = QTransform(mag, 0.0, 0.0, mag, 0.0, 0.0);
      imatrix     = _matrix.inverted();
      _magIdx     = preferences.getDouble(PREF_SCORE_MAGNIFICATION) == 1.0 ? MagIdx::MAG_100 : MagIdx::MAG_FREE;
      focusFrame  = 0;
      _bgColor    = Qt::darkBlue;
      _fgColor    = Qt::white;
      _fgPixmap    = 0;
      _bgPixmap    = 0;

      editData.curGrip = Grip::NO_GRIP;
      editData.grips   = 0;
      editData.element = 0;

      lasso       = new Lasso(_score);
      _foto       = 0;// new Lasso(_score);

      _cursor     = new PositionCursor(this);
      _cursor->setType(CursorType::POS);
      _continuousPanel = new ContinuousPanel(this);
      _continuousPanel->setActive(true);

      shadowNote  = 0;

      _curLoopIn  = new PositionCursor(this);
      _curLoopIn->setType(CursorType::LOOP_IN);
      _curLoopOut = new PositionCursor(this);
      _curLoopOut->setType(CursorType::LOOP_OUT);

      if (converterMode)      // HACK
            return;

      grabGesture(Qt::PinchGesture);      // laptop pad (Mac) and touchscreen

      //-----------------------------------------------------------------------

      if (MScore::debugMode)
            setMouseTracking(true);

      if (preferences.getBool(PREF_UI_CANVAS_BG_USECOLOR))
            setBackground(MScore::bgColor);
      else {
            QPixmap* pm = new QPixmap(preferences.getString(PREF_UI_CANVAS_BG_WALLPAPER));
            setBackground(pm);
            }
      if (preferences.getBool(PREF_UI_CANVAS_FG_USECOLOR))
            setForeground(preferences.getColor(PREF_UI_CANVAS_FG_COLOR));
      else {
            QPixmap* pm = new QPixmap(preferences.getString(PREF_UI_CANVAS_FG_WALLPAPER));
            if (pm == 0 || pm->isNull())
                  qDebug("no valid pixmap %s", qPrintable(preferences.getString(PREF_UI_CANVAS_FG_WALLPAPER)));
            setForeground(pm);
            }

      connect(getAction("loop"), SIGNAL(toggled(bool)), SLOT(loopToggled(bool)));
      if (seq)
            connect(seq, SIGNAL(stopped()), SLOT(seqStopped()));
      }

//---------------------------------------------------------
//   setScore
//---------------------------------------------------------

void ScoreView::setScore(Score* s)
      {
      if (_score) {
            if (_score->isMaster()) {
                  MasterScore* ms = static_cast<MasterScore*>(s);
                  for (MasterScore* _ms : *ms->movements()) {
                        _ms->removeViewer(this);
                        disconnect(s, SIGNAL(posChanged(POS, int)), this, SLOT(posChanged(POS,int)));
                        }
                  }
            else {
                  _score->removeViewer(this);
                  disconnect(s, SIGNAL(posChanged(POS, int)), this, SLOT(posChanged(POS,int)));
                  }
            }

      _score = s;
      if (_score) {
            if (_score->isMaster()) {
                  MasterScore* ms = static_cast<MasterScore*>(s);
                  for (MasterScore* _ms : *ms->movements()) {
                        _ms->addViewer(this);
                        }
                  }
            else
                  _score->addViewer(this);
            }

      if (shadowNote == 0) {
            shadowNote = new ShadowNote(_score);
            shadowNote->setVisible(false);
            }
      else
            shadowNote->setScore(_score);
      lasso->setScore(s);
      _continuousPanel->setScore(_score);

      if (_score) {
            _curLoopIn->move(s->pos(POS::LEFT));
            _curLoopOut->move(s->pos(POS::RIGHT));
            loopToggled(getAction("loop")->isChecked());

            connect(s, SIGNAL(posChanged(POS,unsigned)), SLOT(posChanged(POS,unsigned)));
            connect(this, SIGNAL(viewRectChanged()), this, SLOT(updateContinuousPanel()));
            }
      }

//---------------------------------------------------------
//   ScoreView
//---------------------------------------------------------

ScoreView::~ScoreView()
      {
      if (_score)
            _score->removeViewer(this);
      delete lasso;
      delete _foto;
      delete _cursor;
      delete _continuousPanel;
      delete _curLoopIn;
      delete _curLoopOut;
      delete _bgPixmap;
      delete _fgPixmap;
      delete shadowNote;
      }

//---------------------------------------------------------
//   objectPopup
//    the menu can be extended by Elements with
//      genPropertyMenu()/propertyAction() methods
//---------------------------------------------------------

void ScoreView::objectPopup(const QPoint& pos, Element* obj)
      {
      // show tuplet properties if number is clicked:
      if (obj->isText() && obj->parent() && obj->parent()->isTuplet()) {
            obj = obj->parent();
            if (!obj->selected())
                  obj->score()->select(obj, SelectType::SINGLE, 0);
            }

      QMenu* popup = new QMenu(this);
      popup->setSeparatorsCollapsible(false);
      QAction* a = popup->addSeparator();

      // Set Slur or Tie according to the selected object
      if (obj->type() != ElementType::SLUR_SEGMENT) {
            if ((obj->type() == ElementType::STAFF_TEXT) && (toStaffText(obj)->systemFlag()))
                  a->setText(tr("System Text"));
            else
                  a->setText(obj->userName());
            }
      else if (static_cast<SlurSegment*>(obj)->spanner()->type() == ElementType::SLUR)
            a->setText(tr("Slur"));
      else if (static_cast<SlurSegment*>(obj)->spanner()->type() == ElementType::TIE)
            a->setText(tr("Tie"));

      popup->addAction(getAction("cut"));
      popup->addAction(getAction("copy"));
      popup->addAction(getAction("paste"));
      popup->addAction(getAction("swap"));
      popup->addAction(getAction("delete"));

      QMenu* selMenu = popup->addMenu(tr("Select"));
      selMenu->addAction(getAction("select-similar"));
      selMenu->addAction(getAction("select-similar-staff"));
      selMenu->addAction(getAction("select-similar-range"));
      a = selMenu->addAction(tr("More…"));
      a->setData("select-dialog");

      popup->addSeparator();
      a = getAction("edit-element");
      popup->addAction(a);
      a->setEnabled(obj->isEditable());

      createElementPropertyMenu(obj, popup);

      popup->addSeparator();
      a = popup->addAction(tr("Help"));
      a->setData("help");

#ifndef NDEBUG
      popup->addSeparator();
      popup->addAction("Debugger")->setData("list");
#endif

      popupActive = true;
      a = popup->exec(pos);
      popupActive = false;
      if (a == 0)
            return;
      const QByteArray& cmd(a->data().toByteArray());
      if (cmd == "cut" || cmd =="copy" || cmd == "paste" || cmd == "swap" || cmd == "delete") {
            // these actions are already activated
            return;
            }
      if (cmd == "list")
            mscore->showElementContext(obj);
      else if (cmd == "help")
            mscore->showHelp(QString("element:%1").arg(obj->name()));
      else if (cmd == "edit-element") {
            if (obj->isEditable()) {
                  if (obj->score())
                        obj->score()->select(obj);
                  startEditMode(obj);
                  return;
                  }
            }
      else if (cmd == "select-similar")
            mscore->selectSimilar(obj, false);
      else if (cmd == "select-similar-staff")
            mscore->selectSimilar(obj, true);
      else if (cmd == "select-similar-range")
            mscore->selectSimilarInRange(obj);
      else if (cmd == "select-dialog")
            mscore->selectElementDialog(obj);
      else {
            _score->startCmd();
            elementPropertyAction(cmd, obj);
            if (score()->undoStack()->active())
                  _score->endCmd();
            }
      }

//---------------------------------------------------------
//   measurePopup
//---------------------------------------------------------

void ScoreView::measurePopup(QContextMenuEvent* ev, Measure* obj)
      {
      int staffIdx;
      int pitch;
      Segment* seg;

      QPoint gpos = ev->globalPos();

      if (!_score->pos2measure(editData.startMove, &staffIdx, &pitch, &seg, 0))
            return;
      if (staffIdx == -1) {
            qDebug("ScoreView::measurePopup: staffIdx == -1!");
            return;
            }

      Staff* staff = _score->staff(staffIdx);

      QMenu* popup = new QMenu(this);
      popup->setSeparatorsCollapsible(false);

      QAction* a = popup->addSeparator();
      a->setText(tr("Staff"));
      a = popup->addAction(tr("Edit Drumset…"));
      a->setData("edit-drumset");
      a->setEnabled(staff->part()->instrument()->drumset() != 0);

      a = popup->addAction(tr("Piano Roll Editor…"));
      a->setData("pianoroll");

      a = popup->addAction(tr("Staff/Part Properties…"));
      a->setData("staff-properties");
      a = popup->addAction(tr("Split Staff…"));
      a->setData("staff-split");

      a = popup->addSeparator();
      a->setText(tr("Measure"));
      popup->addAction(getAction("cut"));
      popup->addAction(getAction("copy"));
      popup->addAction(getAction("paste"));
      popup->addAction(getAction("swap"));
      popup->addAction(getAction("delete"));

      QMenu* menuAdd = popup->addMenu(tr("Add"));
      menuAdd->addAction(getAction("insert-measure"));
      menuAdd->addAction(getAction("insert-measures"));
      menuAdd->addAction(getAction("insert-hbox"));
      menuAdd->addAction(getAction("insert-vbox"));
      menuAdd->addAction(getAction("insert-textframe"));

      a = popup->addAction(tr("Remove Selected Measures"));
      a->setData("delete-selected-measures");

      popup->addSeparator();

      a = popup->addAction(tr("Measure Properties…"));
      a->setData("props");
      a->setEnabled(!obj->isMMRest());
      popup->addSeparator();

#ifndef NDEBUG
      popup->addAction("Object Debugger")->setData("list");
#endif

      a = popup->exec(gpos);
      if (a == 0)
            return;
      QString cmd(a->data().toString());
      if (cmd == "cut" || cmd =="copy" || cmd == "paste" || cmd == "swap"
         || cmd == "insert-measure" || cmd == "select-similar"
         || cmd == "delete") {
            // these actions are already activated
            return;
            }
      _score->startCmd();
      if (cmd == "list")
            mscore->showElementContext(obj);
      else if (cmd == "color")
            _score->colorItem(obj);
      else if (cmd == "edit") {
            if (obj->isEditable()) {
                  startEditMode(obj);
                  return;
                  }
            }
      else if (cmd == "edit-drumset") {
            EditDrumset drumsetEdit(staff->part()->instrument()->drumset(), this);
            if (drumsetEdit.exec()) {
                  _score->undo(new ChangeDrumset(staff->part()->instrument(), drumsetEdit.drumset()));
                  mscore->updateDrumTools(drumsetEdit.drumset());
                  }
            }
      else if (cmd == "drumroll") {
            _score->endCmd();
            mscore->editInDrumroll(staff);
            }
      else if (cmd == "pianoroll") {
            _score->endCmd();
            QPointF p = toLogical(ev->pos());
            Position pp;
            bool foundPos = _score->getPosition(&pp, p, 0);
            mscore->editInPianoroll(staff, foundPos ? &pp : 0);
            }
      else if (cmd == "staff-properties") {
            Fraction tick = obj ? obj->tick() : Fraction(-1,1);
            EditStaff editStaff(staff, tick, this);
            connect(&editStaff, SIGNAL(instrumentChanged()), mscore, SLOT(instrumentChanged()));
            editStaff.exec();
            }
      else if (cmd == "staff-split") {
            SplitStaff splitStaff(this);
            if (splitStaff.exec())
                  _score->splitStaff(staffIdx, splitStaff.getSplitPoint());
            }
      else if (cmd == "props") {
            MeasureProperties im(obj);
            im.exec();
            }
      else if (cmd == "delete-selected-measures") {
            _score->cmdTimeDelete();
            }
      if (_score->undoStack()->active())
            _score->endCmd();
      }

//---------------------------------------------------------
//   setBackground
//---------------------------------------------------------

void ScoreView::setBackground(QPixmap* pm)
      {
      delete _bgPixmap;
      _bgPixmap = pm;
      update();
      }

void ScoreView::setBackground(const QColor& color)
      {
      delete _bgPixmap;
      _bgPixmap = 0;
      _bgColor = color;
      update();
      }

//---------------------------------------------------------
//   setForeground
//---------------------------------------------------------

void ScoreView::setForeground(QPixmap* pm)
      {
      delete _fgPixmap;
      _fgPixmap = pm;
      update();
      }

void ScoreView::setForeground(const QColor& color)
      {
      delete _fgPixmap;
      _fgPixmap = 0;
      _fgColor = color;
      update();
      }

//---------------------------------------------------------
//   dataChanged
//---------------------------------------------------------

void ScoreView::dataChanged(const QRectF& r)
      {
      update(_matrix.mapRect(r).toRect());  // generate paint event
      }

//---------------------------------------------------------
//   moveCursor
//    move cursor during playback
//---------------------------------------------------------

void ScoreView::moveCursor(const Fraction& tick)
      {
      Measure* measure = score()->tick2measureMM(tick);
      if (measure == 0)
            return;

      qreal x = 0.0;
      Segment* s;
      for (s = measure->first(SegmentType::ChordRest); s;) {
            Fraction t1 = s->tick();
            int x1 = s->canvasPos().x();
            qreal x2;
            Fraction t2;
            Segment* ns = s->next(SegmentType::ChordRest);
            while (ns && !ns->visible())
                  ns = ns->next(SegmentType::ChordRest);
            if (ns) {
                  t2 = ns->tick();
                  x2 = ns->canvasPos().x();
                  }
            else {
                  t2 = measure->endTick();
                  // measure->width is not good enough because of courtesy keysig, timesig
                  Segment* seg = measure->findSegment(SegmentType::EndBarLine, measure->tick() + measure->ticks());
                  if (seg)
                        x2 = seg->canvasPos().x();
                  else
                        x2 = measure->canvasPos().x() + measure->width(); //safety, should not happen
                  }
            if (tick >= t1 && tick < t2) {
                  Fraction   dt = t2 - t1;
                  qreal dx = x2 - x1;
                  x = x1 + dx * (tick-t1).ticks() / dt.ticks();
                  break;
                  }
            s = ns;
            }
      if (s == 0)
            return;

      QColor c(MScore::selectColor[0]);
      c.setAlpha(50);
      _cursor->setColor(c);
      _cursor->setTick(tick);

      System* system = measure->system();
      if (system == 0)
            return;
      double y        = system->staffYpage(0) + system->page()->pos().y();
      double _spatium = score()->spatium();

      update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));

      qreal mag = _spatium / SPATIUM20;
      double w  = _spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag);
      double h  = 6 * _spatium;
      //
      // set cursor height for whole system
      //
      double y2 = 0.0;

      for (int i = 0; i < _score->nstaves(); ++i) {
            SysStaff* ss = system->staff(i);
            if (!ss->show() || !_score->staff(i)->show())
                  continue;
            y2 = ss->bbox().bottom();
            }
      h += y2;
      x -= _spatium;
      y -= 3 * _spatium;

      _cursor->setRect(QRectF(x, y, w, h));
      update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
      if (mscore->state() == ScoreState::STATE_PLAY && mscore->panDuringPlayback())
            adjustCanvasPosition(measure, true);
      }

//---------------------------------------------------------
//   moveCursor
//    move cursor in note input mode
//---------------------------------------------------------

void ScoreView::moveCursor()
      {
      const InputState& is = _score->inputState();
      Segment* segment = is.segment();
      if (segment && score()->styleB(Sid::createMultiMeasureRests) && segment->measure()->hasMMRest()) {
            Measure* m = segment->measure()->mmRest();
            segment = m->findSegment(SegmentType::ChordRest, m->tick());
            }
      if (!segment)
            return;

      int track    = is.track() == -1 ? 0 : is.track();
      int voice    = track % VOICES;
      int staffIdx = track / VOICES;

      QColor c(MScore::selectColor[voice]);
      c.setAlpha(50);
      _cursor->setColor(c);
      _cursor->setTick(segment->tick());

      System* system = segment->measure()->system();
      if (system == 0) {
            // a new measure was appended but no layout took place
            // or this measure was skipped by a multi measure rest
            return;
            }
      double x        = segment->canvasPos().x();
      double y        = system->staffYpage(staffIdx) + system->page()->pos().y();
      double _spatium = score()->spatium();
      x              -= qMin(segment->pos().x() - score()->styleP(Sid::barNoteDistance), 0.0);

      update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));

      double h;
      qreal mag               = _spatium / SPATIUM20;
      double w                = _spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag);
      Staff* staff            = score()->staff(staffIdx);
      const StaffType* staffType    = staff->staffType(is.tick());
      double lineDist         = staffType->lineDistance().val() * _spatium;
      int lines               = staffType->lines();
      int strg                = is.string();          // strg refers to an instrument physical string
      x                       -= _spatium;
      int instrStrgs          = staff->part()->instrument()->stringData()->strings();
      // if on a TAB staff and InputState::_string makes sense,
      // draw cursor around single string
      if (staff->isTabStaff(is.tick()) && strg >= 0 && strg <= instrStrgs) {
            h = lineDist;                 // cursor height is one full line distance
            y += staffType->physStringToYOffset(strg) * _spatium;
            // if frets are on lines, centre on string; if frets are above lines, 'sit' above string
            y -= (staffType->onLines() ? lineDist * 0.5 : lineDist);
            // look for a note on this string in this staff
            // if found, it will be selected, to synchronize the 'new note input cursor' and the 'current note cursor'
            // i.e. the point where a new note would be added and the existing note which receives any editing
            // (like pitch change or articulation addition)
            bool        done  = false;
            Segment*    seg   = is.segment();
            int         minTrack = (is.track() / VOICES) * VOICES;
            int         maxTrack = minTrack + VOICES;
            // get selected chord, if one exists and is in this segment
            ChordRest* scr = _score->selection().cr();
            if (scr && (scr->type() != ElementType::CHORD || scr->segment() != seg))
                  scr = nullptr;
            // get the physical string corresponding to current visual string
            for (int t = minTrack; t < maxTrack; t++) {
                  Element* e = seg->element(t);
                  if (e != nullptr && e->type() == ElementType::CHORD) {
                        // if there is a selected chord in this segment on this track but it is not e
                        // then the selected chord must be a grace note chord, and we should use it
                        if (scr && scr->track() == t && scr != e)
                              e = scr;
                        // search notes looking for one on current string
                        for (Note* n : static_cast<Chord*>(e)->notes())
                              // if note found on this string, make it current
                              if (n->string() == strg) {
                                    if (!n->selected()) {
                                          _score->select(n);
                                          // restore input state after selection
                                          _score->inputState().setTrack(track);
                                          }
#if 0
                                    // if using this code, we can delete the setTrack() call above
                                    // the code below forces input state & cursor to match current note
                                    _score->inputState().setTrack(t);
                                    QColor c(MScore::selectColor[t % VOICES]);
                                    c.setAlpha(50);
                                    _cursor->setColor(c);
#endif
                                    done = true;
                                    break;
                                    }
                        }
                  if (done)
                        break;
                  }
      }
      // otherwise, draw cursor across whole staff
      else {
            h = (lines - 1) * lineDist + 4 * _spatium;
            y -= 2.0 * _spatium;
            }
      _cursor->setRect(QRectF(x, y, w, h));
      update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
      if (is.cr())
            adjustCanvasPosition(is.cr(), false);
      }

//---------------------------------------------------------
//   cursorTick
//---------------------------------------------------------

Fraction ScoreView::cursorTick() const
      {
      return _cursor->tick();
      }

//---------------------------------------------------------
//   setCursorOn
//---------------------------------------------------------

void ScoreView::setCursorOn(bool val)
      {
      if (_cursor && (_cursor->visible() != val)) {
            _cursor->setVisible(val);
            update(_matrix.mapRect(_cursor->rect()).toRect().adjusted(-1,-1,1,1));
            }
      }

//---------------------------------------------------------
//   setLoopCursor
//    adjust the cursor shape and position to mark the loop
//    isInPos is used to adjust the x position of In vs Out mark
//---------------------------------------------------------

void ScoreView::setLoopCursor(PositionCursor *curLoop, const Fraction& tick, bool isInPos)
      {
      //
      // set mark height for whole system
      //
      Measure* measure = score()->tick2measure(tick);
      if (measure == 0)
            return;
      qreal x = 0.0;

      Segment* s;
      for (s = measure->first(SegmentType::ChordRest); s;) {
            Fraction t1 = s->tick();
            int x1 = s->canvasPos().x();
            qreal x2;
            Fraction t2;
            Segment* ns = s->next(SegmentType::ChordRest);
            if (ns) {
                  t2 = ns->tick();
                  x2 = ns->canvasPos().x();
                  }
            else {
                  t2 = measure->endTick();
                  x2 = measure->canvasPos().x() + measure->width();
                  }
            if (tick >= t1 && tick < t2) {
                  Fraction   dt = t2 - t1;
                  qreal dx = x2 - x1;
                  x = x1 + dx * (tick.ticks() - t1.ticks()) / dt.ticks();
                  break;
                  }
            s = ns;
            }
      if (s == 0)
            return;

      System* system = measure->system();
      if (system == 0)
            return;
      double y        = system->staffYpage(0) + system->page()->pos().y();
      double _spatium = score()->spatium();

      qreal mag = _spatium / SPATIUM20;
      double w  = (_spatium * 2.0 + score()->scoreFont()->width(SymId::noteheadBlack, mag))/3;
      double h  = 6 * _spatium;
      //
      // set cursor height for whole system
      //
      double y2 = 0.0;

      for (int i = 0; i < _score->nstaves(); ++i) {
            SysStaff* ss = system->staff(i);
            if (!ss->show() || !_score->staff(i)->show())
                  continue;
            y2 = ss->y() + ss->bbox().height();
            }
      h += y2;
      y -= 3 * _spatium;

      if (isInPos) {
            x = x - _spatium + w/1.5;
            }
      else {
            x = x - _spatium;
            }
      curLoop->setTick(tick);
      update(_matrix.mapRect(curLoop->rect()).toRect().adjusted(-1,-1,1,1));
      curLoop->setRect(QRectF(x, y, w, h));
      update(_matrix.mapRect(curLoop->rect()).toRect().adjusted(-1,-1,1,1));
      }

//---------------------------------------------------------
//   setShadowNote
//---------------------------------------------------------

void ScoreView::setShadowNote(const QPointF& p)
      {
      const InputState& is = _score->inputState();
      Position pos;
      if (!score()->getPosition(&pos, p, is.voice())) {
            shadowNote->setVisible(false);
            return;
            }
      // in any empty measure, pos will be right next to barline
      // so pad this by barNoteDistance
      qreal mag     = score()->staff(pos.staffIdx)->mag(Fraction(0,1));
      qreal relX    = pos.pos.x() - pos.segment->measure()->canvasPos().x();
      pos.pos.rx() -= qMin(relX - score()->styleP(Sid::barNoteDistance) * mag, 0.0);

      shadowNote->setVisible(true);
      Staff* staff = score()->staff(pos.staffIdx);
      shadowNote->setMag(staff->mag(Fraction(0,1)));
      const Instrument* instr       = staff->part()->instrument();
      NoteHead::Group noteheadGroup = NoteHead::Group::HEAD_NORMAL;
      int line                      = pos.line;
      NoteHead::Type noteHead       = is.duration().headType();

      if (instr->useDrumset()) {
            const Drumset* ds  = instr->drumset();
            int pitch    = is.drumNote();
            if (pitch >= 0 && ds->isValid(pitch)) {
                  line     = ds->line(pitch);
                  noteheadGroup = ds->noteHead(pitch);
                  }
            }

      shadowNote->setLine(line);

      int voice;
      if (is.drumNote() != -1 && is.drumset() && is.drumset()->isValid(is.drumNote()))
            voice = is.drumset()->voice(is.drumNote());
      else
            voice = is.voice();

      SymId symNotehead;
      TDuration d(is.duration());

      if (is.rest()) {
            int yo;
            Rest rest(gscore, d.type());
            rest.setTicks(d.fraction());
            symNotehead = rest.getSymbol(is.duration().type(), 0, staff->lines(pos.segment->tick()), &yo);
            shadowNote->setState(symNotehead, voice, d, true);
            }
      else {
            if (NoteHead::Group::HEAD_CUSTOM == noteheadGroup)
                  symNotehead = instr->drumset()->noteHeads(is.drumNote(), noteHead);
            else
                  symNotehead = Note::noteHead(0, noteheadGroup, noteHead);

            shadowNote->setState(symNotehead, voice, d);
            }

      shadowNote->layout();
      shadowNote->setPos(pos.pos);
      }

//---------------------------------------------------------
//   drawAnchorLines
//---------------------------------------------------------
void ScoreView::drawAnchorLines(QPainter& painter)
      {
      if (m_dropAnchorLines.isEmpty())
            return;

      const auto dropAnchorColor = preferences.getColor(PREF_UI_SCORE_VOICE4_COLOR);
      QPen pen(QBrush(dropAnchorColor), 2.0 / painter.worldTransform().m11(), Qt::DotLine);

      for (const QLineF& anchor : m_dropAnchorLines) {
            painter.setPen(pen);
            painter.drawLine(anchor);

            qreal d = 4.0 / painter.worldTransform().m11();
            QRectF rect(-d, -d, 2 * d, 2 * d);

            painter.setBrush(QBrush(dropAnchorColor));
            painter.setPen(Qt::NoPen);
            rect.moveCenter(anchor.p1());
            painter.drawEllipse(rect);
            rect.moveCenter(anchor.p2());
            painter.drawEllipse(rect);
            }
      }

//---------------------------------------------------------
//   paintEvent
//    Note: desktop background and paper background are not
//    scaled
//---------------------------------------------------------
void ScoreView::paintEvent(QPaintEvent* ev)
      {
      if (!_score)
            return;
      QPainter vp(this);
      vp.setRenderHint(QPainter::Antialiasing, preferences.getBool(PREF_UI_CANVAS_MISC_ANTIALIASEDDRAWING));
      vp.setRenderHint(QPainter::TextAntialiasing, true);

      paint(ev->rect(), vp);

      vp.setTransform(_matrix);
      vp.setClipping(false);

      _curLoopIn->paint(&vp);
      _curLoopOut->paint(&vp);
      _cursor->paint(&vp);

      if (_score->layoutMode() == LayoutMode::LINE)
            _continuousPanel->paint(ev->rect(), vp);

      if (!lasso->bbox().isEmpty())
            lasso->draw(&vp);
      shadowNote->draw(&vp);

      drawAnchorLines(vp);
      }

//---------------------------------------------------------
//   drawBackground
//---------------------------------------------------------

void ScoreView::drawBackground(QPainter* p, const QRectF& r) const
      {
      if (score()->printing()) {
            p->fillRect(r, Qt::white);
            return;
            }
      if (_fgPixmap == 0 || _fgPixmap->isNull())
            p->fillRect(r, _fgColor);
      else {
            p->drawTiledPixmap(r, *_fgPixmap, r.topLeft()
               - QPoint(lrint(_matrix.dx()), lrint(_matrix.dy())));
            }
      }

//---------------------------------------------------------
//   paintPageBorder
//---------------------------------------------------------

void ScoreView::paintPageBorder(QPainter& p, Page* page)
      {
      //add a black border to pages
      QRectF r(page->canvasBoundingRect());
      p.setBrush(Qt::NoBrush);
      p.setPen(QPen(QColor(0,0,0,102), 1));
      p.drawRect(r);

      if (_score->showPageborders()) {
            // show page margins
            p.setBrush(Qt::NoBrush);
            p.setPen(MScore::frameMarginColor);
            QRectF f(page->canvasBoundingRect());
            f.adjust(page->lm(), page->tm(), -page->rm(), -page->bm());
            p.drawRect(f);
            if (!page->isOdd())
                  p.drawLine(f.right(), 0.0, f.right(), f.bottom());
            }
      }

#ifndef NDEBUG
//---------------------------------------------------------
//   drawDebugInfo
//---------------------------------------------------------

static void drawDebugInfo(QPainter& p, const Element* _e)
      {
      if (!MScore::showBoundingRect)
            return;
      const Element* e = _e;
      //
      //  draw bounding box rectangle for all
      //  selected Elements
      //
      QPointF pos(e->pagePos());
      p.translate(pos);
      p.setBrush(Qt::NoBrush);

      p.setPen(QPen(Qt::red, 0.0));
//      p.drawRect(e->bbox());
      e->shape().paint(p);

      p.setPen(QPen(Qt::red, 0.0));             // red x at 0,0 of bbox
      qreal w = 5.0 / p.matrix().m11();
      qreal h = w;
      qreal x = 0; // e->bbox().x();
      qreal y = 0; // e->bbox().y();
      p.drawLine(QLineF(x-w, y-h, x+w, y+h));
      p.drawLine(QLineF(x+w, y-h, x-w, y+h));

      p.translate(-pos);
      if (e->parent()) {
            const Element* ee = e->parent();
            if (e->isNote())
                  ee = toNote(e)->chord()->segment();
            else if (e->isClef())
                  ee = toClef(e)->segment();

            p.setPen(QPen(Qt::green, 0.0));

            p.drawRect(ee->pageBoundingRect());

            if (ee->isSegment()) {
                  QPointF pt = ee->pagePos();
                  p.setPen(QPen(Qt::blue, 0.0));
                  p.drawLine(QLineF(pt.x()-w, pt.y()-h, pt.x()+w, pt.y()+h));
                  p.drawLine(QLineF(pt.x()+w, pt.y()-h, pt.x()-w, pt.y()+h));
                  }
            }
      }
#endif

//---------------------------------------------------------
//   drawElements
//---------------------------------------------------------

void ScoreView::drawElements(QPainter& painter, QList<Element*>& el, Element* editElement)
      {
      qStableSort(el.begin(), el.end(), elementLessThan);
      for (const Element* e : el) {
            e->itemDiscovered = 0;

            // harmony element representation is different in edit mode, so don't
            // all normal draw(). Complete drawing is done in drawEditMode()
            if (e == editElement)
                  continue;

            if (!e->visible() && (score()->printing() || !score()->showInvisible()))
                  continue;
            if (e->isRest() && toRest(e)->isGap())
                  continue;
            QPointF pos(e->pagePos());
            painter.translate(pos);
            e->draw(&painter);
            painter.translate(-pos);
#ifndef NDEBUG
            if (e->selected())
                  drawDebugInfo(painter, e);
#endif
            }
      }

//---------------------------------------------------------
//   paint
//---------------------------------------------------------

void ScoreView::paint(const QRect& r, QPainter& p)
      {
      p.save();
      if (_fgPixmap == 0 || _fgPixmap->isNull())
            p.fillRect(r, _fgColor);
      else {
            p.drawTiledPixmap(r, *_fgPixmap, r.topLeft()
               - QPoint(lrint(_matrix.dx()), lrint(_matrix.dy())));
            }

      p.setTransform(_matrix);
      QRectF fr = imatrix.mapRect(QRectF(r));

      Element* editElement = 0;
      Lasso* lassoToDraw = 0;
      if (editData.element) {
            switch (state) {
                  case ViewState::NORMAL:
                        if (editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit)
                              editData.element->drawEditMode(&p, editData);
                        break;
                  case ViewState::DRAG:
                  case ViewState::DRAG_OBJECT:
                  case ViewState::LASSO:
                  case ViewState::NOTE_ENTRY:
                  case ViewState::PLAY:
                  case ViewState::ENTRY_PLAY:
                        break;
                  case ViewState::EDIT:
                  case ViewState::DRAG_EDIT:
                  case ViewState::FOTO:
                  case ViewState::FOTO_DRAG:
                  case ViewState::FOTO_DRAG_EDIT:
                  case ViewState::FOTO_DRAG_OBJECT:
                  case ViewState::FOTO_LASSO:
                        if (editData.element->isLasso())
                              lassoToDraw = toLasso(editData.element);
                        else
                              editData.element->drawEditMode(&p, editData);

                        if (editData.element->isHarmony())
                              editElement = editData.element;     // do not call paint() method
                        break;
                  }
            }


      // AvsOmr -----
#ifdef AVSOMR
      Avs::AvsOmrDrawer omrDrawer;
      std::shared_ptr<Avs::AvsOmrDrawer::Context> omrDrawCtx = omrDrawer.makeContext(&p, _score);
      auto pageMeasures = [](Page* page) -> QList<const Measure*> {
            QList<const Measure*> ml;
            for (const System* s : page->systems()) {
                  const std::vector<MeasureBase*>& measures = s->measures();
                  for (const MeasureBase* mb : measures) {
                        if (mb->isMeasure())
                              ml << toMeasure(mb);
                        }
                  }
            return ml;
            };
#endif
      // ------------

      QRegion r1(r);
      if ((_score->layoutMode() == LayoutMode::LINE) || (_score->layoutMode() == LayoutMode::SYSTEM)) {
            if (_score->pages().size() > 0) {

                  Page* page = _score->pages().front();
                  QList<Element*> ell = page->items(fr);

                  // AvsOmr -----
#ifdef AVSOMR
                  if (omrDrawCtx) {
                        QList<const Measure*> ml = pageMeasures(page);
                        omrDrawer.draw(omrDrawCtx, ml);
                        }
#endif
                  // ------------

                  drawElements(p, ell, editElement);
                  }
            }
      else {
            for (Page* page : _score->pages()) {
                  QRectF pr(page->abbox().translated(page->pos()));
                  if (pr.right() < fr.left())
                        continue;
                  if (pr.left() > fr.right())
                        break;

                  if (!score()->printing())
                        paintPageBorder(p, page);
                  QList<Element*> ell = page->items(fr.translated(-page->pos()));
                  QPointF pos(page->pos());
                  p.translate(pos);

                  // AvsOmr -----
#ifdef AVSOMR
                  if (omrDrawCtx) {
                        QList<const Measure*> ml = pageMeasures(page);
                        omrDrawer.draw(omrDrawCtx, ml);
                        }
#endif
                  // ------------

                  drawElements(p, ell, editElement);

#ifndef NDEBUG
                  if (!score()->printing()) {
                        if (MScore::showSystemBoundingRect) {
                              for (const System* system : page->systems()) {
                                    QPointF pt(system->ipos());
                                    qreal h = system->height() + system->minBottom() + system->minTop();
                                    p.translate(pt);
                                    QRectF rect(0.0, -system->minTop(), system->width(), h);
                                    p.drawRect(rect);
                                    p.translate(-pt);
                                    }
                              }
                        if (MScore::showSegmentShapes) {
                              for (const System* system : page->systems()) {
                                    for (const MeasureBase* mb : system->measures()) {
                                          if (mb->type() == ElementType::MEASURE) {
                                                const Measure* m = static_cast<const Measure*>(mb);
                                                p.setBrush(Qt::NoBrush);
                                                p.setPen(QPen(QBrush(Qt::darkYellow), 0.5));
                                                for (const Segment* s = m->first(); s; s = s->next()) {
                                                      for (int i = 0; i < score()->nstaves(); ++i) {
                                                            QPointF pt(s->pos().x() + m->pos().x() + system->pos().x(),
                                                               system->staffYpage(i));
                                                            p.translate(pt);
                                                            s->shapes().at(i).paint(p);
                                                            p.translate(-pt);
                                                            }
                                                      }
                                                }
                                          }
                                    }
                              }
                        if (MScore::showSkylines) {
                              for (const System* system : page->systems()) {
                                    for (SysStaff* ss : *system->staves()) {
                                          QPointF pt(system->ipos().x(), system->ipos().y() + ss->y());
                                          p.translate(pt);
                                          ss->skyline().paint(p);
                                          p.translate(-pt);
                                          }
                                    }
                              }
                        if (MScore::showCorruptedMeasures) {
                              double _spatium = score()->spatium();
                              QPen pen;
                              pen.setColor(Qt::red);
                              pen.setWidthF(4);
                              pen.setStyle(Qt::SolidLine);
                              p.setPen(pen);
                              p.setBrush(Qt::NoBrush);
                              for (const System* system : page->systems()) {
                                    for (const MeasureBase* mb : system->measures()) {
                                          if (mb->type() == ElementType::MEASURE) {
                                                const Measure* m = static_cast<const Measure*>(mb);
                                                for (int staffIdx = 0; staffIdx < m->score()->nstaves(); staffIdx++) {
                                                      if (m->corrupted(staffIdx)) {
                                                            p.drawRect(m->staffabbox(staffIdx).adjusted(0, -_spatium, 0, _spatium));
                                                            }
                                                      }
                                                }
                                          }
                                    }
                              }
                        }
#endif

                  p.translate(-pos);
                  r1 -= _matrix.mapRect(pr).toAlignedRect();
                  }
            }
      if (dropRectangle.isValid())
            p.fillRect(dropRectangle, QColor(80, 0, 0, 80));

      const Selection& sel = _score->selection();
      if (sel.isRange()) {
            Segment* ss = sel.startSegment();
            Segment* es = sel.endSegment();

            if (!ss)
                  return;

            if (!ss->enabled())
                  ss = ss->next1MMenabled();
            if (es && !es->enabled())
                  es = es->next1MMenabled();
            if (es && ss->tick() > es->tick())  // start after end?
                  return;

            if (!ss->measure()->system()) {
                  // segment is in a measure that has not been laid out yet
                  // this can happen in mmrests
                  // first chordrest segment of mmrest instead
                  const Measure* mmr = ss->measure()->mmRest1();
                  if (mmr && mmr->system())
                        ss = mmr->first(SegmentType::ChordRest);
                  else
                        return;                 // still no system?
                  if (!ss)
                        return;                 // no chordrest segment?
                  }

            p.setBrush(Qt::NoBrush);

            QPen pen;
            pen.setColor(MScore::selectColor[0]);
            pen.setWidthF(2.0 / p.matrix().m11());

            pen.setStyle(Qt::SolidLine);

            p.setPen(pen);
            double _spatium = score()->spatium();
            double x2      = ss->pagePos().x() - _spatium;
            int staffStart = sel.staffStart();
            int staffEnd   = sel.staffEnd();

            System* system2 = ss->measure()->system();
            QPointF pt      = ss->pagePos();
            double y        = pt.y();
            SysStaff* ss1   = system2->staff(staffStart);

            // find last visible staff:
            int lastStaff = 0;
            for (int i = staffEnd-1; i >= 0; --i) {
                  if (score()->staff(i)->show()) {
                        lastStaff = i;
                        break;
                        }
                  }
            SysStaff* ss2 = system2->staff(lastStaff);

            double y1 = ss1->y() - 2 * score()->staff(staffStart)->spatium(Fraction(0,1)) + y;
            double y2 = ss2->y() + ss2->bbox().height() + 2 * score()->staff(lastStaff)->spatium(Fraction(0,1)) + y;

            // drag vertical start line
            p.drawLine(QLineF(x2, y1, x2, y2).translated(system2->page()->pos()));

            System* system1 = system2;
            double x1;

            for (Segment* s = ss; s && (s != es); ) {
                  Segment* ns = s->next1MMenabled();
                  system1  = system2;
                  system2  = s->measure()->system();
                  if (!system2) {
                        // as before, use mmrest if necessary
                        const Measure* mmr = s->measure()->mmRest1();
                        if (mmr)
                              system2 = mmr->system();
                        if (!system2)
                              break;
                        // extend rectangle to end of mmrest
                        pt = mmr->last()->pagePos();
                        }
                  else
                        pt = s->pagePos();
                  x1  = x2;
                  x2  = pt.x() + _spatium * 2;

                  if (ns == 0 || ns == es) {    // last segment?
                        // if any staff in selection has measure rest or repeat measure in last measure,
                        // extend rectangle to bar line
                        Segment* fs = s->measure()->first(SegmentType::ChordRest);
                        if (fs) {
                              for (int i = staffStart; i < staffEnd; ++i) {
                                    if (!score()->staff(i)->show())
                                          continue;
                                    ChordRest* cr = static_cast<ChordRest*>(fs->element(i * VOICES));
                                    if (cr && (cr->type() == ElementType::REPEAT_MEASURE || cr->durationType() == TDuration::DurationType::V_MEASURE)) {
                                          x2 = s->measure()->abbox().right() - _spatium * 0.5;
                                          break;
                                          }
                                    }
                              }
                        }

                  if (system2 != system1)
                        x1  = x2 - 2 * _spatium;
                  y   = pt.y();
                  ss1 = system2->staff(staffStart);
                  ss2 = system2->staff(lastStaff);
                  y1  = ss1->y() - 2 * score()->staff(staffStart)->spatium(s->tick()) + y;
                  y2  = ss2->y() + ss2->bbox().height() + 2 * score()->staff(lastStaff)->spatium(s->tick()) + y;
                  p.drawLine(QLineF(x1, y1, x2, y1).translated(system2->page()->pos()));
                  p.drawLine(QLineF(x1, y2, x2, y2).translated(system2->page()->pos()));
                  s = ns;
                  }
            //
            // draw vertical end line
            //
            p.drawLine(QLineF(x2, y1, x2, y2).translated(system2->page()->pos()));
            }

      // Draw foto lasso to ensure that it is above everything else
      if (lassoToDraw)
            lassoToDraw->drawEditMode(&p, editData);

      p.setWorldMatrixEnabled(false);
      if (_score->layoutMode() != LayoutMode::LINE && _score->layoutMode() != LayoutMode::SYSTEM && !r1.isEmpty()) {
            p.setClipRegion(r1);  // only background
            if (_bgPixmap == 0 || _bgPixmap->isNull())
                  p.fillRect(r, _bgColor);
            else
                  p.drawTiledPixmap(r, *_bgPixmap, r.topLeft() - QPoint(_matrix.m31(), _matrix.m32()));
            }
      p.restore();
      }

//---------------------------------------------------------
//   zoomStep: zoom in or out by some number of steps
//---------------------------------------------------------

void ScoreView::zoomStep(qreal step, const QPoint& pos)
      {
      qreal _mag = lmag();

      _mag *= qPow(1.1, step);

      zoom(_mag, QPointF(pos));
      }

//---------------------------------------------------------
//   zoom: zoom to some absolute zoom level
//---------------------------------------------------------

void ScoreView::zoom(qreal _mag, const QPointF& pos)
      {
      QPointF p1 = imatrix.map(pos);

      if (_mag > 16.0)
            _mag = 16.0;
      else if (_mag < 0.05)
            _mag = 0.05;

      mscore->setMag(_mag);

      double m = _mag * mscore->physicalDotsPerInch() / DPI;

      setMag(m);

      _magIdx    = MagIdx::MAG_FREE;
      QPointF p2 = imatrix.map(pos);
      QPointF p3 = p2 - p1;
      int dx     = lrint(p3.x() * m);
      int dy     = lrint(p3.y() * m);

      constraintCanvas(&dx, &dy);

      _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
         _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
      imatrix = _matrix.inverted();
      scroll(dx, dy, QRect(0, 0, width(), height()));
      emit viewRectChanged();
      emit offsetChanged(_matrix.dx(), _matrix.dy());
      update();
      }

//-----------------------------------------------------------------------------
//   constraintCanvas
//-----------------------------------------------------------------------------

void ScoreView::constraintCanvas (int* dxx, int* dyy)
      {
      if (score()->layoutMode() == LayoutMode::SYSTEM)
            return;
      if (score()->pages().isEmpty())
            return;
      int dx = *dxx;
      int dy = *dyy;
      QRectF rect = QRectF(0, 0, width(), height());

      Page* firstPage = score()->pages().front();
      Page* lastPage  = score()->pages().back();

      if (firstPage && lastPage) {
            QPointF offsetPt(xoffset(), yoffset());
            QRectF firstPageRect(firstPage->pos().x() * mag(),
                                      firstPage->pos().y() * mag(),
                                      firstPage->width() * mag(),
                                      firstPage->height() * mag());
            QRectF lastPageRect(lastPage->pos().x() * mag(),
                                         lastPage->pos().y() * mag(),
                                         lastPage->width() * mag(),
                                         lastPage->height() * mag());
            QRectF pagesRect     = firstPageRect.united(lastPageRect).translated(offsetPt);
            bool limitScrollArea = preferences.getBool(PREF_UI_CANVAS_SCROLL_LIMITSCROLLAREA);
            if (!limitScrollArea) {
                  qreal hmargin = this->width() * 0.75;
                  qreal vmargin = this->height() * 0.75;
                  pagesRect.adjust(-hmargin, -vmargin, hmargin, vmargin);
                  }
            QRectF toPagesRect = pagesRect.translated(dx, dy);

            if (limitScrollArea) {
                  if (pagesRect.width() <= rect.width()) {
                        if (score()->layoutMode() == LayoutMode::LINE)
                              // keep score fixed in place horizontally
                              dx = 0;
                        else
                              // center horizontally on screen
                              dx = (rect.width() - pagesRect.width()) / 2 - pagesRect.left();
                        }
                  else if (toPagesRect.left() > rect.left())
                        // get rid of the left margin
                        dx = rect.left() - pagesRect.left();
                  else if (toPagesRect.right() < rect.right())
                        // get rid of the right margin
                        dx = rect.right() - pagesRect.right();
                  }
            else if (dx > 0) { // move right
                  if (toPagesRect.right() > rect.right() && toPagesRect.left() > rect.left()) {
                        if(pagesRect.width() <= rect.width()) {
                              dx = rect.right() - pagesRect.right();
                              }
                        else {
                              dx = rect.left() - pagesRect.left();
                              }
                        }
                  }
            else { // move left, dx < 0
                  if (toPagesRect.left() < rect.left() && toPagesRect.right() < rect.right()) {
                        if (pagesRect.width() <= rect.width()) {
                              dx = rect.left() - pagesRect.left();
                              }
                        else {
                              dx = rect.right() - pagesRect.right();
                              }
                        }
                  }

            if (limitScrollArea) {
                  if (pagesRect.height() <= rect.height()) {
                        if (score()->layoutMode() == LayoutMode::LINE)
                              // keep score fixed in place vertically
                              dy = 0;
                        else
                              // center vertically on screen
                              dy = (rect.height() - pagesRect.height()) / 2 - pagesRect.top();
                        }
                  else if (toPagesRect.top() > rect.top())
                        // get rid of the top margin
                        dy = rect.top() - pagesRect.top();
                  else if (toPagesRect.bottom() < rect.bottom())
                        // get rid of the bottom margin
                        dy = rect.bottom() - pagesRect.bottom();
                  }
            else if (dy > 0) { // move down
                  if (toPagesRect.bottom() > rect.bottom() && toPagesRect.top() > rect.top()) {
                        if (pagesRect.height() <= rect.height()) {
                              dy = rect.bottom() - pagesRect.bottom();
                              }
                        else {
                              dy = rect.top() - pagesRect.top();
                              }
                        }
                  }
            else { // move up, dy < 0
                  if (toPagesRect.top() < rect.top() && toPagesRect.bottom() < rect.bottom()) {
                        if (pagesRect.height() <= rect.height()) {
                              dy = rect.top() - pagesRect.top();
                              }
                        else {
                              dy = rect.bottom() - pagesRect.bottom();
                              }
                        }
                  }
            }
      *dxx = dx;
      *dyy = dy;
      }

//---------------------------------------------------------
//   setMag
//    nmag - physical scale
//---------------------------------------------------------

void ScoreView::setMag(qreal nmag)
      {
      qreal m = _matrix.m11();

      if (nmag == m)
            return;
      double deltamag = nmag / m;

      _matrix.setMatrix(nmag, _matrix.m12(), _matrix.m13(), _matrix.m21(),
         nmag, _matrix.m23(), _matrix.dx()*deltamag, _matrix.dy()*deltamag, _matrix.m33());
      imatrix = _matrix.inverted();
      emit scaleChanged(nmag * score()->spatium());
      if (editData.grips) {
            qreal w = 8.0 / nmag;
            qreal h = 8.0 / nmag;
            QRectF r(-w*.5, -h*.5, w, h);
            for (int i = 0; i < editData.grips; ++i) {
                  QPointF p(editData.grip[i].center());
                  editData.grip[i] = r.translated(p);
                  }
            }
      update();
      }

//---------------------------------------------------------
//   setMag
//    mag - logical scale
//---------------------------------------------------------

void ScoreView::setMag(MagIdx idx, double mag)
      {
      _magIdx = idx;
      setMag(mag * (mscore->physicalDotsPerInch() / DPI));
      int dx = 0, dy = 0;
      constraintCanvas(&dx, &dy);
      if (dx != 0 || dy != 0) {
            _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
               _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
            imatrix = _matrix.inverted();
            scroll(dx, dy, QRect(0, 0, width(), height()));
            emit offsetChanged(_matrix.dx(), _matrix.dy());
            }
      emit viewRectChanged();
      update();
      }

//---------------------------------------------------------
//   setFocusRect
//---------------------------------------------------------

void ScoreView::setFocusRect()
      {
      if (mscore->splitScreen()) {
            if (!focusFrame) {
                  focusFrame = new QFocusFrame;
                  QPalette p(focusFrame->palette());
                  p.setColor(QPalette::WindowText, MScore::selectColor[0]);
                  focusFrame->setPalette(p);
                  }
            focusFrame->setWidget(static_cast<QWidget*>(this));
            focusFrame->show();
            }
      else {
            if (focusFrame)
                  focusFrame->setWidget(0);
            }
      }

//---------------------------------------------------------
//   editCopy
//---------------------------------------------------------

void ScoreView::editCopy()
      {
      if (editData.element)
            editData.element->editCopy(editData);
      }

//---------------------------------------------------------
//   editCut
//---------------------------------------------------------

void ScoreView::editCut()
      {
      _score->startCmd();
      if (editData.element)
            editData.element->editCut(editData);
      _score->endCmd();
      }

//---------------------------------------------------------
//   checkCopyOrCut
//---------------------------------------------------------

bool ScoreView::checkCopyOrCut()
      {
      if (!_score->selection().canCopy()) {
            QMessageBox::information(0, "MuseScore",
               tr("Please select the complete tuplet/tremolo and retry the command"),
               QMessageBox::Ok, QMessageBox::NoButton);
            return false;
            }
      return true;
      }

//---------------------------------------------------------
//   normalCopy
//---------------------------------------------------------

void ScoreView::normalCopy()
      {
      if (!checkCopyOrCut())
            return;
      QString mimeType = _score->selection().mimeType();
      if (!mimeType.isEmpty()) {
            QMimeData* mimeData = new QMimeData;
            mimeData->setData(mimeType, _score->selection().mimeData());
            if (MScore::debugMode)
                  qDebug("cmd copy: <%s>", mimeData->data(mimeType).data());
            QApplication::clipboard()->setMimeData(mimeData);
            }
      }

//---------------------------------------------------------
//   normalCut
//---------------------------------------------------------

void ScoreView::normalCut()
      {
      if (!checkCopyOrCut())
            return;
      _score->startCmd();
      normalCopy();
      _score->cmdDeleteSelection();
      _score->endCmd();
      }

//---------------------------------------------------------
//   editSwap
//---------------------------------------------------------

void ScoreView::editSwap()
      {
#if 0 // TODO
      if (editData.element && editData.element->isText() && !editData.element->isLyrics()) {
            Text* text = toText(editData.element);
            QString s = text->selectedText();
            text->paste(this);
            if (!s.isEmpty())
                  QApplication::clipboard()->setText(s, QClipboard::Clipboard);
            }
#endif
      }

//---------------------------------------------------------
//   editPaste
//---------------------------------------------------------

void ScoreView::editPaste()
      {
      if (textEditMode())
            toTextBase(editData.element)->paste(editData);
      }

//---------------------------------------------------------
//   normalSwap
//---------------------------------------------------------

void ScoreView::normalSwap()
      {
      if (!checkCopyOrCut())
            return;
      QString mimeType = _score->selection().mimeType();
      const QMimeData* ms = QApplication::clipboard()->mimeData();
      if (mimeType == mimeStaffListFormat) { // determine size of clipboard selection
            Fraction tickLen = Fraction(0,1);
            int staves = 0;
            QByteArray d(ms->data(mimeStaffListFormat));
            XmlReader e(d);
            e.readNextStartElement();
            if (e.name() == "StaffList") {
                  tickLen = Fraction::fromTicks(e.intAttribute("len", 0));
                  staves  = e.intAttribute("staves", 0);
                  }
            if (tickLen > Fraction(0,1)) { // attempt to extend selection to match clipboard size
                  Segment* seg = _score->selection().startSegment();
                  Fraction tick = _score->selection().tickStart() + tickLen;
                  Segment* segAfter = _score->tick2leftSegment(tick);
                  int staffIdx = _score->selection().staffStart() + staves - 1;
                  if (staffIdx >= _score->nstaves())
                        staffIdx = _score->nstaves() - 1;
                  tick = _score->selection().tickStart();
                  Fraction  etick = tick + tickLen;
                  if (MScore::debugMode)
                        _score->selection().dump();
                  _score->selection().extendRangeSelection(seg, segAfter, staffIdx, tick, etick);
                  _score->selection().update();
                  if (MScore::debugMode)
                        _score->selection().dump();
                  if (!checkCopyOrCut())
                        return;
                  ms = QApplication::clipboard()->mimeData();
                  }
            }
      QByteArray d(_score->selection().mimeData());
      if (this->normalPaste()) {
            QMimeData* mimeData = new QMimeData;
            mimeData->setData(mimeType, d);
            QApplication::clipboard()->setMimeData(mimeData);
            }
      }

//---------------------------------------------------------
//   normalPaste
//---------------------------------------------------------

bool ScoreView::normalPaste(Fraction scale)
      {
      _score->startCmd();
      const QMimeData* ms = QApplication::clipboard()->mimeData();
      _score->cmdPaste(ms, this, scale);
      bool rv = MScore::_error == MS_NO_ERROR;
      _score->endCmd();
      return rv;
      }

//---------------------------------------------------------
//   cmdGotoElement
//---------------------------------------------------------

void ScoreView::cmdGotoElement(Element* e)
      {
      if (e) {
            if (e->type() == ElementType::NOTE || e->type() == ElementType::HARMONY)
                  score()->setPlayNote(true);
            score()->select(e, SelectType::SINGLE, 0);
            if (e)
                  adjustCanvasPosition(e, false);
            if (noteEntryMode())
                  moveCursor();
            updateAll();
            }
      }

//---------------------------------------------------------
//   ticksTab
//---------------------------------------------------------

void ScoreView::ticksTab(const Fraction& ticks)
      {
      if (editData.element->isHarmony())
            harmonyTicksTab(ticks);
      else if (editData.element->isFiguredBass())
            figuredBassTicksTab(ticks);
      }

//---------------------------------------------------------
//   cmd
//---------------------------------------------------------

void ScoreView::cmd(const QAction* a)
      {
      const char* s = a ? a->data().toByteArray().constData() : "";
      cmd(s);
      updateEditElement();
      }

void ScoreView::cmd(const char* s)
      {
      struct ScoreViewCmd {
            std::vector<const char*> commands;
            std::function<void(ScoreView*, const QByteArray& cmd)> exec;
            };

      const QByteArray cmd(s);

      shadowNote->setVisible(false);
      if (MScore::debugMode)
            qDebug("ScoreView::cmd <%s>", s);

      static const std::vector<ScoreViewCmd> cmdList {
            {{"escape"}, [](ScoreView* cv, const QByteArray&) {
                  cv->escapeCmd();
                  }},
            {{"note-input"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->state == ViewState::NORMAL)
                        cv->changeState(ViewState::NOTE_ENTRY);
                  else if (cv->state == ViewState::NOTE_ENTRY)
                        cv->changeState(ViewState::NORMAL);
                  }},
            {{"copy"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->fotoMode())
                        cv->fotoModeCopy();
                  else if (cv->state == ViewState::NORMAL)
                        cv->normalCopy();
                  else if (cv->state == ViewState::EDIT)
                        cv->editCopy();
                  }},
            {{"cut"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->state == ViewState::NORMAL)
                        cv->normalCut();
                  else if (cv->state == ViewState::EDIT)
                        cv->editCut();
                  }},
            {{"paste"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->state == ViewState::NORMAL)
                        cv->normalPaste();
                  else if (cv->state == ViewState::EDIT)
                        cv->editPaste();
                  }},
            {{"paste-half"}, [](ScoreView* cv, const QByteArray&) {
                  cv->normalPaste(Fraction(1, 2));
                  }},
            {{"paste-double"}, [](ScoreView* cv, const QByteArray&) {
                  cv->normalPaste(Fraction(2, 1));
                  }},
            {{"paste-special"}, [](ScoreView* cv, const QByteArray&) {
                  Fraction scale = Fraction(1, 1);
                  Fraction duration = cv->score()->inputState().duration().fraction();
                  if (duration.isValid() && !duration.isZero()) {
                        scale = duration * 4;
                        scale.reduce();
                        }
                  cv->normalPaste(scale);
                  }},
            {{"swap"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->state == ViewState::NORMAL)
                        cv->normalSwap();
                  else if (cv->state == ViewState::EDIT)
                        cv->editSwap();
                  }},
            {{"lyrics"}, [](ScoreView* cv, const QByteArray&) {
                  cv->score()->startCmd();
                  Lyrics* lyrics = cv->score()->addLyrics();
                  cv->score()->endCmd();
                  if (lyrics) {
                        cv->startEditMode(lyrics);
                        return;
                        }
                  }},
            {{"figured-bass"}, [](ScoreView* cv, const QByteArray&) {
                  FiguredBass* fb = cv->score()->addFiguredBass();
                  if (fb) {
                        cv->startEditMode(fb);
                        return;
                        }
                  }},
            {{"mag"}, /*[](ScoreView* cv, const QByteArray&)*/ {
                  // ??
                  }},
            {{"play"}, [](ScoreView* cv, const QByteArray&) {
                  if (seq && seq->canStart()) {
                        if (cv->state == ViewState::NORMAL || cv->state == ViewState::NOTE_ENTRY)
                              cv->changeState(ViewState::PLAY);
                        else if (cv->state == ViewState::PLAY)
                              cv->changeState(ViewState::NORMAL);
                        }
                  else
                        getAction("play")->setChecked(false);
                  }},
            {{"fotomode"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->state == ViewState::NORMAL)
                        cv->changeState(ViewState::FOTO);
                  else if (cv->fotoMode())
                        cv->changeState(ViewState::NORMAL);
                  }},
            {{"add-slur"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddSlur();
                  }},
            {{"add-hairpin"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddHairpin(HairpinType::CRESC_HAIRPIN);
                  }},
            {{"add-hairpin-reverse"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddHairpin(HairpinType::DECRESC_HAIRPIN);
                  }},
            {{"add-noteline"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddNoteLine();
                  }},
            {{"chord-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeState(ViewState::NORMAL);
                  cv->cmdAddChordName(HarmonyType::STANDARD);
                  }},
            {{"roman-numeral-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeState(ViewState::NORMAL);
                  cv->cmdAddChordName(HarmonyType::ROMAN);
                  }},
            {{"nashville-number-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeState(ViewState::NORMAL);
                  cv->cmdAddChordName(HarmonyType::NASHVILLE);
                  }},
            {{"title-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::TITLE);
                  }},
            {{"subtitle-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::SUBTITLE);
                  }},
            {{"composer-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::COMPOSER);
                  }},
            {{"poet-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::POET);
                  }},
            {{"part-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::INSTRUMENT_EXCERPT);
                  }},
            {{"system-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::SYSTEM);
                  }},
            {{"staff-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::STAFF);
                  }},
            {{"expression-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::EXPRESSION);
                  }},
            {{"rehearsalmark-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::REHEARSAL_MARK);
                  }},
            {{"instrument-change-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::INSTRUMENT_CHANGE);
                  }},
            {{"fingering-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::FINGERING);
                  }},
            {{"sticking-text"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddText(Tid::STICKING);
                  }},
            {{"edit-element"}, [](ScoreView* cv, const QByteArray&) {
                  Element* e = cv->score()->selection().element();
                  if (e && e->isEditable() && !cv->popupActive) {
                        cv->startEditMode(e);
                        }
                  }},
            {{"select-similar"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->score()->selection().isSingle()) {
                        Element* e = cv->score()->selection().element();
                        mscore->selectSimilar(e, false);
                        }
                  }},
            {{"select-similar-staff"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->score()->selection().isSingle()) {
                        Element* e = cv->score()->selection().element();
                        mscore->selectSimilar(e, true);
                        }
                  }},
            {{"select-dialog"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->score()->selection().isSingle()) {
                        Element* e = cv->score()->selection().element();
                        mscore->selectElementDialog(e);
                        }
                  }},
      //      {{"find"}, [](ScoreView* cv, const QByteArray&) {
      //            ; // TODO:state         sm->postEvent(new CommandEvent(cmd));
      //            }},
            {{"scr-prev"}, [](ScoreView* cv, const QByteArray&) {
                  cv->screenPrev();
                  }},
            {{"scr-next"}, [](ScoreView* cv, const QByteArray&) {
                  cv->screenNext();
                  }},
            {{"page-prev"}, [](ScoreView* cv, const QByteArray&) {
                  cv->pagePrev();
                  }},
            {{"page-next"}, [](ScoreView* cv, const QByteArray&) {
                  cv->pageNext();
                  }},
            {{"page-top"}, [](ScoreView* cv, const QByteArray&) {
                  cv->pageTop();
                  }},
            {{"page-end"}, [](ScoreView* cv, const QByteArray&) {
                  cv->pageEnd();
                  }},
            {{"select-next-chord",
              "select-prev-chord",
              "select-next-measure",
              "select-prev-measure",
              "select-begin-line",
              "select-end-line",
              "select-begin-score",
              "select-end-score",
              "select-staff-above",
              "select-staff-below"}, [](ScoreView* cv, const QByteArray& cmd) {
                  Element* el = cv->score()->selectMove(cmd);
                  if (el)
                        cv->adjustCanvasPosition(el, false);
                  cv->score()->setPlayChord(true);
                  cv->updateAll();
                  }},
            {{"next-chord",
              "prev-chord",
              "next-track",
              "prev-track",
              "next-measure",
              "prev-measure"}, [](ScoreView* cv, const QByteArray& cmd) {

                  if (cv->score()->selection().isLocked()) {
                        LOGW() << "unable exec cmd: " << cmd << ", selection locked, reason: " << cv->score()->selection().lockReason();
                        return;
                        }

                  Element* el = cv->score()->selection().element();
                  if (el && (el->isTextBase())) {
                        cv->score()->startCmd();
                        const PropertyFlags pf = PropertyFlags::UNSTYLED;
                        if (cmd == "prev-chord")
                              el->undoChangeProperty(Pid::OFFSET, el->offset() - QPointF (MScore::nudgeStep * el->spatium(), 0.0), pf);
                        else if (cmd == "next-chord")
                              el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF (MScore::nudgeStep * el->spatium(), 0.0), pf);
                        else if (cmd == "prev-measure")
                              el->undoChangeProperty(Pid::OFFSET, el->offset() - QPointF (MScore::nudgeStep10 * el->spatium(), 0.0), pf);
                        else if (cmd == "next-measure")
                              el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF (MScore::nudgeStep10 * el->spatium(), 0.0), pf);
                        cv->score()->endCmd();
                        }
                  else {
                        Element* ele = cv->score()->move(cmd);
                        if (ele)
                              cv->adjustCanvasPosition(ele, false);
                        cv->score()->setPlayChord(true);
                        cv->updateAll();
                        }
                  }},
            {{"pitch-up-diatonic"}, [](ScoreView* cv, const QByteArray&) {
                  cv->score()->upDown(true, UpDownMode::DIATONIC);
                  }},
            {{"pitch-down-diatonic"}, [](ScoreView* cv, const QByteArray&) {
                  cv->score()->upDown(false, UpDownMode::DIATONIC);
                  }},
            {{"move-up"}, [](ScoreView* cv, const QByteArray) {
                  QList<Element*> el = cv->score()->selection().uniqueElements();
                  foreach (Element* e, el) {
                        ChordRest* cr = nullptr;
                        if (e->type() == ElementType::NOTE)
                              cr = static_cast<Note*>(e)->chord();
                        else if (e->type() == ElementType::REST)
                              cr = static_cast<Rest*>(e);
                        if (cr)
                              cv->score()->moveUp(cr);
                        }
                  }},
            {{"move-down"}, [](ScoreView* cv, const QByteArray&) {
                  QList<Element*> el = cv->score()->selection().uniqueElements();
                  foreach (Element* e, el) {
                        ChordRest* cr = nullptr;
                        if (e->type() == ElementType::NOTE)
                              cr = static_cast<Note*>(e)->chord();
                        else if (e->type() == ElementType::REST)
                              cr = static_cast<Rest*>(e);
                        if (cr)
                              cv->score()->moveDown(cr);
                        }
                  }},
            {{"up-chord"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  Element* oel = el;
                  if (el && (el->isNote() || el->isRest()))
                        cv->cmdGotoElement(cv->score()->upAlt(el));
                  el = cv->score()->selection().element();
                  while (el && el->isRest() && toRest(el)->isGap()) {
                        if (cv->score()->upAlt(el) == el) {
                              cv->cmdGotoElement(oel);
                              break;
                              }
                        el = cv->score()->upAlt(el);
                        cv->cmdGotoElement(el);
                        }
                  }},
            {{"down-chord"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  Element* oel = el;
                  if (el && (el->isNote() || el->isRest()))
                        cv->cmdGotoElement(cv->score()->downAlt(el));
                  el = cv->score()->selection().element();
                  while (el && el->isRest() && toRest(el)->isGap()) {
                        if (cv->score()->downAlt(el) == el) {
                              cv->cmdGotoElement(oel);
                              break;
                              }
                        el = cv->score()->downAlt(el);
                        cv->cmdGotoElement(el);
                        }
                  }},
            {{"top-chord"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  if (el && el->type() == ElementType::NOTE)
                        cv->cmdGotoElement(cv->score()->upAltCtrl(static_cast<Note*>(el)));
                  }},
            {{"bottom-chord"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  if (el && el->type() == ElementType::NOTE)
                        cv->cmdGotoElement(cv->score()->downAltCtrl(static_cast<Note*>(el)));
                  }},
            {{"next-segment-element"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  if (!el && !cv->score()->selection().elements().isEmpty() )
                      el = cv->score()->selection().elements().first();

                  if (el)
                        cv->cmdGotoElement(el->nextSegmentElement());
                  else
                        cv->cmdGotoElement(cv->score()->firstElement());
                  }},
            {{"prev-segment-element"}, [](ScoreView* cv, const QByteArray&) {
                  Element* el = cv->score()->selection().element();
                  if (!el && !cv->score()->selection().elements().isEmpty())
                      el = cv->score()->selection().elements().last();

                  if (el)
                        cv->cmdGotoElement(el->prevSegmentElement());
                  else
                        cv->cmdGotoElement(cv->score()->lastElement());
                  }},
            {{"next-element"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editMode()) {
                        cv->textTab(false);
                        return;
                        }
                  Element* el = cv->score()->selection().element();
                  if (!el && !cv->score()->selection().elements().isEmpty() )
                      el = cv->score()->selection().elements().first();
                  if (!el) {
                        ChordRest* cr = cv->score()->selection().currentCR();
                        if (cr) {
                              if (cr->isChord())
                                    el = toChord(cr)->downNote();
                              else if (cr->isRest())
                                    el = cr;
                              cv->score()->select(el);
                              }
                        }

                  if (el)
                        cv->cmdGotoElement(cv->score()->nextElement());
                  else
                        cv->cmdGotoElement(cv->score()->firstElement());
                  }},
            {{"prev-element"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editMode()) {
                        cv->textTab(true);
                        return;
                        }
                  Element* el = cv->score()->selection().element();
                  if (!el && !cv->score()->selection().elements().isEmpty())
                      el = cv->score()->selection().elements().last();
                  if (!el) {
                        ChordRest* cr = cv->score()->selection().currentCR();
                        if (cr) {
                              if (cr->isChord())
                                    el = toChord(cr)->upNote();
                              else if (cr->isRest())
                                    el = cr;
                              cv->score()->select(el);
                              }
                        }

                  if (el)
                        cv->cmdGotoElement(cv->score()->prevElement());
                  else
                        cv->cmdGotoElement(cv->score()->lastElement());
                  }},
            {{"first-element"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdGotoElement(cv->score()->firstElement(false));
                  }},
            {{"last-element"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdGotoElement(cv->score()->lastElement(false));
                  }},
            {{"get-location"}, [](ScoreView* cv, const QByteArray&) {
                  // get current selection
                  Element* e = cv->score()->selection().element();
                  if (!e) {
                        // no current selection - restore lost selection
                        e = cv->score()->selection().currentCR();
                        if (e && e->isChord())
                              e = toChord(e)->upNote();
                        }
                  if (!e) {
                        // no current or last selection - fall back to first element
                        e = cv->score()->firstElement(false);
                        }
                  // TODO: find & read current key & time signatures
                  if (e) {
                        ScoreAccessibility::instance()->clearAccessibilityInfo();
                        cv->cmdGotoElement(e);
                        }
                  }},
            {{"rest", "rest-TAB"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdEnterRest();
                  }},
            {{"rest-1"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdEnterRest(TDuration(TDuration::DurationType::V_WHOLE));
                  }},
            {{"rest-2"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdEnterRest(TDuration(TDuration::DurationType::V_HALF));
                  }},
            {{"rest-4"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdEnterRest(TDuration(TDuration::DurationType::V_QUARTER));
                  }},
            {{"rest-8"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdEnterRest(TDuration(TDuration::DurationType::V_EIGHTH));
                  }},
            {{"interval1",
              "interval2", "interval-2",
              "interval3", "interval-3",
              "interval4", "interval-4",
              "interval5", "interval-5",
              "interval6", "interval-6",
              "interval7", "interval-7",
              "interval8", "interval-8",
              "interval9", "interval-9"}, [](ScoreView* cv, const QByteArray& cmd) {
                  int n = cmd.mid(8).toInt();
                  std::vector<Note*> nl = cv->score()->selection().noteList();
                  if (!nl.empty()) {
                        //if (!noteEntryMode())
                        //      ;     // TODO: state    sm->postEvent(new CommandEvent("note-input"));
                        cv->score()->cmdAddInterval(n, nl);
                        }
                  }},
            {{"tie"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->noteEntryMode()) {
                        cv->score()->cmdAddTie();
                        cv->moveCursor();
                        }
                  else
                        cv->score()->cmdToggleTie();
                  }},
            {{"chord-tie"}, [](ScoreView* cv, const QByteArray&) {
                  cv->score()->cmdAddTie(true);
                  cv->moveCursor();
                  }},
            {{"duplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(2);
                  }},
            {{"triplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(3);
                  }},
            {{"quadruplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(4);
                  }},
            {{"quintuplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(5);
                  }},
            {{"sextuplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(6);
                  }},
            {{"septuplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(7);
                  }},
            {{"octuplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(8);
                  }},
            {{"nonuplet"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdTuplet(9);
                  }},
            {{"tuplet-dialog"}, [](ScoreView* cv, const QByteArray&) {
                  cv->score()->startCmd();
                  Tuplet* tuplet = mscore->tupletDialog();
                  if (tuplet)
                        cv->cmdCreateTuplet(cv->score()->getSelectedChordRest(), tuplet);
                  cv->score()->endCmd();
                  cv->moveCursor();
                  }},
            {{"repeat-sel"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdRepeatSelection();
                  }},
            {{"voice-1"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeVoice(0);
                  }},
            {{"voice-2"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeVoice(1);
                  }},
            {{"voice-3"}, [](ScoreView* cv, const QByteArray) {
                  cv->changeVoice(2);
                  }},
            {{"voice-4"}, [](ScoreView* cv, const QByteArray&) {
                  cv->changeVoice(3);
                  }},
            {{"enh-both"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdChangeEnharmonic(true);
                  }},
            {{"enh-current"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdChangeEnharmonic(false);
                  }},
            {{"revision"}, [](ScoreView* cv, const QByteArray&) {
                  Score* sc = cv->score()->masterScore();
                  sc->createRevision();
                  }},
            {{"append-measure"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAppendMeasures(1, ElementType::MEASURE);
                  }},
            {{"insert-measure"}, [](ScoreView* cv, const QByteArray&) {
                cv->cmdInsertMeasures(1, ElementType::MEASURE);
                }},
            {{"insert-hbox"}, [](ScoreView* cv, const QByteArray&) {
                cv->cmdInsertMeasures(1, ElementType::HBOX);
                }},
            {{"insert-vbox"}, [](ScoreView* cv, const QByteArray&) {
                cv->cmdInsertMeasures(1, ElementType::VBOX);
                }},
            {{"append-hbox"}, [](ScoreView* cv, const QByteArray&) {
                MeasureBase* mb = cv->appendMeasure(ElementType::HBOX);
                  cv->score()->select(mb, SelectType::SINGLE, 0);
                  }},
            {{"append-vbox"}, [](ScoreView* cv, const QByteArray&) {
                MeasureBase* mb = cv->appendMeasure(ElementType::VBOX);
                  cv->score()->select(mb, SelectType::SINGLE, 0);
                  }},
            {{"insert-textframe"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdInsertMeasure(ElementType::TBOX);
                  }},
            {{"append-textframe"}, [](ScoreView* cv, const QByteArray&) {
                  MeasureBase* mb = cv->appendMeasure(ElementType::TBOX);
                  if (mb) {
                        TBox* tf = static_cast<TBox*>(mb);
                        Text* text = 0;
                        foreach(Element* e, tf->el()) {
                              if (e->type() == ElementType::TEXT) {
                                    text = static_cast<Text*>(e);
                                    break;
                                    }
                              }
                        if (text) {
                              cv->score()->select(text, SelectType::SINGLE, 0);
                              cv->startEditMode(text);
                              }
                        }
                  }},
            {{"insert-fretframe"}, [](ScoreView* cv, const QByteArray&) {
                  if (!enableExperimental)
                        return;
                  cv->cmdInsertMeasure(ElementType::FBOX);
                  }},
            {{"move-left"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdMoveCR(true);
                  }},
            {{"move-right"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdMoveCR(false);
                  }},
            {{"reset"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editMode()) {
                        cv->editData.element->reset();
                        cv->score()->update();
                        }
                  else {
                        cv->score()->startCmd();
                        for (Element* e : cv->score()->selection().elements()) {
                              e->reset();
                              if (e->isSpanner()) {
                                    Spanner* sp = toSpanner(e);
                                    for (SpannerSegment* ss : sp->spannerSegments())
                                          ss->reset();
                                    }
                              }
                        cv->score()->endCmd();
                        }
                  cv->updateGrips();
                  }},
      #ifdef OMR
            {{"show-omr"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->score()->masterScore()->omr())
                        showOmr(!_score->masterScore()->showOmr());
                  }},
      #endif
            {{"split-measure"}, [](ScoreView* cv, const QByteArray&) {
                  Element* e = cv->score()->selection().element();
                  if (!(e && (e->isNote() || e->isRest())))
                        MScore::setError(NO_CHORD_REST_SELECTED);
                  else {
                        if (e->isNote())
                              e = toNote(e)->chord();
                        ChordRest* cr = toChordRest(e);
                        cv->score()->cmdSplitMeasure(cr);
                        }
                  }},
            {{"join-measures"}, [](ScoreView* cv, const QByteArray&) {
                  Measure* m1;
                  Measure* m2;
                  if (!cv->score()->selection().measureRange(&m1, &m2) || m1 == m2) {
                        QMessageBox::warning(0, "MuseScore",
                           tr("No measures selected:\n"
                           "Please select a range of measures to join and try again"));
                        }
                  else {
                        cv->score()->cmdJoinMeasure(m1, m2);
                        }
                  }},
            {{"next-lyric", "prev-lyric"}, [](ScoreView* cv, const QByteArray& cmd) {
                  cv->editCmd(cmd);
                  }},
            {{"add-remove-breaks"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdAddRemoveBreaks();
                  }},
            {{"copy-lyrics-to-clipboard"}, [](ScoreView* cv, const QByteArray&) {
                  cv->cmdCopyLyricsToClipboard();
                  }},

            // STATE_NOTE_ENTRY_REALTIME actions (auto or manual)

            {{"realtime-advance"}, [](ScoreView* cv, const QByteArray&) {
                  cv->realtimeAdvance(true);
                  }},

            // STATE_HARMONY_FIGBASS_EDIT actions

            {{"advance-longa"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(4,1));
                  }},
            {{"advance-breve"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(2,1));
                  }},
            {{"advance-1"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,1));
                  }},
            {{"advance-2"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,2));
                  }},
            {{"advance-4"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,4));
                  }},
            {{"advance-8"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,8));
                  }},
            {{"advance-16"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,16));
                  }},
            {{"advance-32"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,32));
                  }},
            {{"advance-64"}, [](ScoreView* cv, const QByteArray&) {
                  cv->ticksTab(Fraction(1,64));
                  }},
            {{"prev-measure-TEXT"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editData.element->isHarmony())
                        cv->harmonyTab(true);
                  else if (cv->editData.element->isFiguredBass())
                        cv->figuredBassTab(true, true);
                  }},
            {{"next-measure-TEXT"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editData.element->isHarmony())
                        cv->harmonyTab(false);
                  else if (cv->editData.element->isFiguredBass())
                        cv->figuredBassTab(true, false);
                  }},
            {{"prev-beat-TEXT"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editData.element->isHarmony())
                        cv->harmonyBeatsTab(false, true);
                  }},
            {{"next-beat-TEXT"}, [](ScoreView* cv, const QByteArray&) {
                  if (cv->editData.element->isHarmony())
                        cv->harmonyBeatsTab(false, false);
                  }},

            // STATE_NOTE_ENTRY_TAB actions

            // move input state string up or down, within the number of strings of the instrument;
            // this may move the input state cursor outside of the tab line range to accommodate
            // instrument strings not represented in the tab (e.g.: lute bass strings):
            // the appropriate visual rendition of the input cursor in those cases will be managed by moveCursor()
            {{"string-above",
              "string-below"}, [](ScoreView* cv, const QByteArray& cmd) {
                  InputState& is          = cv->score()->inputState();
                  Staff*      staff       = cv->score()->staff(is.track() / VOICES);
                  int         instrStrgs  = staff->part()->instrument()->stringData()->strings();
                  // assume "string-below": if tab is upside-down, 'below' means toward instrument top (-1)
                  // if not, 'below' means toward instrument bottom (+1)
                  int         delta       = (staff->staffType(is.tick())->upsideDown() ? -1 : +1);
                  if (cmd == "string-above")                      // if "above", reverse delta
                        delta = -delta;
                  int         strg        = is.string() + delta;  // dest. physical string
                  if (strg >= 0 && strg < instrStrgs) {            // if dest. string within instrument limits
                        is.setString(strg);                       // update status
                        cv->moveCursor();
                        }
                  }},
            {{"text-word-left"}, [](ScoreView* cv, const QByteArray&) {
                  toTextBase(cv->editData.element)->movePosition(cv->editData, QTextCursor::WordLeft);
                  }},
            {{"text-word-right"}, [](ScoreView* cv, const QByteArray&) {
                  toTextBase(cv->editData.element)->movePosition(cv->editData, QTextCursor::NextWord);
                  }},
            {{"concert-pitch"}, [](ScoreView* cv, const QByteArray& cmd) {
                  QAction* a = getAction(cmd);
                  if (cv->score()->styleB(Sid::concertPitch) != a->isChecked()) {
                        cv->score()->startCmd();
                        cv->score()->cmdConcertPitchChanged(a->isChecked(), true);
                        cv->score()->endCmd();
                        }
                  }},
            };

      auto c = std::find_if(cmdList.begin(), cmdList.end(), [cmd](const ScoreViewCmd& cc) {
            for (auto& name : cc.commands) {
                  if (cmd == name)
                        return true;
                  }
            return false;
            });

      if (c != cmdList.end()) {
            c->exec(this, cmd);
            }
      else {
            editData.view = this;
            QAction* a = getAction(cmd);
            _score->cmd(a, editData);
            }
      if (_score->processMidiInput())
            mscore->endCmd();
      }

//---------------------------------------------------------
//   textTab
//---------------------------------------------------------

void ScoreView::textTab(bool back)
      {
      if (!editMode())
            return;
      Element* oe = editData.element;
      if (!oe || !oe->isTextBase())
            return;

      if (oe->isHarmony()) {
            harmonyBeatsTab(true, back);
            return;
            }
      else if (oe->isFiguredBass()) {
            figuredBassTab(false, back);
            return;
            }
      else if (oe->isLyrics()) {
            // not actually hit, left/right handled elsewhere
            lyricsTab(back, false, true);
            return;
            }

      Element* op = oe->parent();
      if (!(op->isSegment() || op->isNote()))
            return;

      TextBase* ot = toTextBase(oe);
      Tid defaultTid = Tid(ot->propertyDefault(Pid::SUB_STYLE).toInt());
      Tid tid = ot->tid();
      ElementType type = ot->type();
      int staffIdx = ot->staffIdx();

      // get prev/next element now, as current element may be deleted if empty
      Element* el = back ? score()->prevElement() : score()->nextElement();
      // end edit mode
      changeState(ViewState::NORMAL);

      // find new note to add text to
      bool here = false;      // prevent infinite loop (relevant if navigation is allowed to wrap around end of score)
      while (el) {
            if (el->isNote()) {
                  Note* n = toNote(el);
                  if (op->isNote() && n != op)
                        break;
                  else if (op->isSegment() && n->chord()->segment() != op)
                        break;
                  else if (here)
                        break;
                  here = true;
                  }
            else if (el->isRest() && op->isSegment()) {
                  // skip rests, but still check for infinite loop
                  Rest* r = toRest(el);
                  if (r->segment() != op)
                        ;
                  else if (here)
                        break;
                  here = true;
                  }
            // get prev/next note
            score()->select(el);
            Element* el2 = back ? score()->prevElement() : score()->nextElement();
            // start/end of score reached
            if (el2 == el)
                  break;
            el = el2;
            }
      if (!el || !el->isNote()) {
            // nothing found, exit cleanly
            if (op->selectable())
                  score()->select(op);
            else
                  score()->deselectAll();
            return;
            }
      Note* nn = toNote(el);

      // go to note
      cmdGotoElement(nn);

      // get existing text to edit
      el = nullptr;
      if (op->isNote()) {
            // check element list of new note
            for (Element* e : nn->el()) {
                  if (e->type() != type)
                        continue;
                  TextBase* nt = toTextBase(e);
                  if (nt->tid() == tid) {
                        el = e;
                        break;
                        }
                  }
            }
      else if (op->isSegment()) {
            // check annotation list of new segment
            Segment* ns = nn->chord()->segment();
            for (Element* e : ns->annotations()) {
                  if (e->staffIdx() != staffIdx || e->type() != type)
                        continue;
                  TextBase* nt = toTextBase(e);
                  if (nt->tid() == tid) {
                        el = e;
                        break;
                        }
                  }
            }

      if (el) {
            // edit existing text
            score()->select(el);
            startEditMode(el);
            }
      else {
            // add new text if no existing element to edit
            // TODO: for tempo text, mscore->addTempo() could be called
            // but it pre-fills the text
            // would be better to create empty tempo element
            if (type != ElementType::TEMPO_TEXT) {
                  PropertyFlags pf = oe ? oe->propertyFlags(Pid::PLACEMENT) : PropertyFlags::STYLED;
                  Placement oePlacement = oe ? oe->placement() : Placement::ABOVE;
                  cmdAddText(defaultTid, tid, pf, oePlacement);
                  }
            }
      }

//---------------------------------------------------------
//   editKeySticking
//---------------------------------------------------------

bool ScoreView::editKeySticking()
      {
      Q_ASSERT(editData.element->isSticking());

      switch (editData.key) {
            case Qt::Key_Space:
                  textTab(editData.modifiers & Qt::ShiftModifier);
                  return true;
            case Qt::Key_Left:
                  textTab(true);
                  return true;
            case Qt::Key_Right:
            case Qt::Key_Return:
                  textTab(false);
                  return true;
            default:
                  break;
            }

      return false;
      }

//---------------------------------------------------------
//   showOmr
//---------------------------------------------------------

void ScoreView::showOmr(bool flag)
      {
      _score->masterScore()->setShowOmr(flag);
      ScoreTab* t = mscore->getTab1();
      if (t->view() != this)
            t = mscore->getTab2();
      if (t->view() == this)
            t->setCurrentIndex(t->currentIndex());
      else
            qDebug("view not found");
      }

//---------------------------------------------------------
//   startNoteEntry
//---------------------------------------------------------

void ScoreView::startNoteEntry()
      {
      InputState& is = _score->inputState();

      is.setSegment(0);
      Note* note  = 0;

      if (_score->selection().isNone()) {
            // no selection
            // choose page in current view (favor top left quadrant if possible)
            // select first (top/left) chordrest of that page in current view
            // or, CR at last selected position if that is in view
            Page* p = nullptr;
            QList<QPointF> points;
            points.append(toLogical(QPoint(width() * 0.25, height() * 0.25)));
            points.append(toLogical(QPoint(0.0, 0.0)));
            points.append(toLogical(QPoint(0.0, height())));
            points.append(toLogical(QPoint(width(), 0.0)));
            points.append(toLogical(QPoint(width(), height())));
            int i = 0;
            while (!p && i < points.size()) {
                  p = point2page(points[i]);
                  i++;
                  }
            if (p) {
                  ChordRest* topLeft = nullptr;
                  qreal tlY = 0.0;
                  Fraction tlTick = Fraction(0,1);
                  QRectF viewRect  = toLogical(QRectF(0.0, 0.0, width(), height()));
                  QRectF pageRect  = p->bbox().translated(p->x(), p->y());
                  QRectF intersect = viewRect & pageRect;
                  intersect.translate(-p->x(), -p->y());
                  QList<Element*> el = p->items(intersect);
                  ChordRest* lastSelected = score()->selection().currentCR();
                  for (Element* e : el) {
                        // loop through visible elements
                        // looking for the CR in voice 1 with earliest tick and highest staff position
                        // but stop we find the last selected CR
                        ElementType et = e->type();
                        if (et == ElementType::NOTE || et == ElementType::REST) {
                              if (e->voice())
                                    continue;
                              ChordRest* cr;
                              if (et == ElementType::NOTE) {
                                    cr = static_cast<ChordRest*>(e->parent());
                                    if (!cr)
                                          continue;
                                    }
                              else {
                                    cr = static_cast<ChordRest*>(e);
                                    }
                              if (cr == lastSelected) {
                                    topLeft = cr;
                                    break;
                                    }
                              // compare ticks rather than x position
                              // to make sure we favor earlier rather than later systems
                              // even though later system might have note farther to left
                              Fraction crTick = Fraction(0,1);
                              if (cr->segment())
                                    crTick = cr->segment()->tick();
                              else
                                    continue;
                              // compare staff Y position rather than note Y position
                              // to be sure we do not reject earliest note
                              // just because it is lower in pitch than subsequent notes
                              qreal crY = 0.0;
                              if (cr->measure() && cr->measure()->system())
                                    crY = cr->measure()->system()->staffYpage(cr->staffIdx());
                              else
                                    continue;
                              if (topLeft) {
                                    if (crTick <= tlTick && crY <= tlY) {
                                          topLeft = cr;
                                          tlTick = crTick;
                                          tlY = crY;
                                          }
                                    }
                              else {
                                    topLeft = cr;
                                    tlTick = crTick;
                                    tlY = crY;
                                    }
                              }
                        }
                  if (topLeft)
                        _score->select(topLeft, SelectType::SINGLE);
                  }
            }

      Element* el = _score->selection().element();
      if (!el)
            el = _score->selection().firstChordRest();
      if (el == 0 || (el->type() != ElementType::CHORD && el->type() != ElementType::REST && el->type() != ElementType::NOTE)) {
            // if no note/rest is selected, start with voice 0
            int track = is.track() == -1 ? 0 : (is.track() / VOICES) * VOICES;
            // try to find an appropriate measure to start in
            Fraction tick = el ? el->tick() : Fraction(0,1);
            el = _score->searchNote(tick, track);
            if (!el)
                  el = _score->searchNote(Fraction(0,1), track);
            }
      if (!el)
            return;
      if (el->type() == ElementType::CHORD) {
            Chord* c = static_cast<Chord*>(el);
            note = c->selectedNote();
            if (note == 0)
                  note = c->upNote();
            el = note;
            }
      TDuration d(is.duration());
      if (!d.isValid() || d.isZero() || d.type() == TDuration::DurationType::V_MEASURE)
            is.setDuration(TDuration(TDuration::DurationType::V_QUARTER));
      is.setAccidentalType(AccidentalType::NONE);

      _score->select(el, SelectType::SINGLE, 0);
      is.setRest(false);
      is.setNoteEntryMode(true);
      adjustCanvasPosition(el, false);

      getAction("pad-rest")->setChecked(false);
      setMouseTracking(true);
      shadowNote->setVisible(true);
      _score->setUpdateAll();
      _score->update();

      Staff* staff = _score->staff(is.track() / VOICES);
      switch (staff->staffType(is.tick())->group()) {
            case StaffGroup::STANDARD:
                  break;
            case StaffGroup::TAB: {
                  int strg = 0;                 // assume topmost string as current string
                  // if entering note entry with a note selected and the note has a string
                  // set InputState::_string to note physical string
                  if (el->type() == ElementType::NOTE) {
                        strg = (static_cast<Note*>(el))->string();
                        }
                  is.setString(strg);
                  break;
                  }
            case StaffGroup::PERCUSSION:
                  break;
            }
      // set cursor after setting the stafftype-dependent state
      moveCursor();
      mscore->updateInputState(_score);
      shadowNote->setVisible(false);
      setCursorOn(true);
      }

//---------------------------------------------------------
//   endNoteEntry
//---------------------------------------------------------

void ScoreView::endNoteEntry()
      {
      InputState& is = _score->inputState();
      is.setNoteEntryMode(false);
      if (is.slur()) {
            const std::vector<SpannerSegment*>& el = is.slur()->spannerSegments();
            if (!el.empty())
                  el.front()->setSelected(false);
            is.setSlur(0);
            }
      setMouseTracking(false);
      shadowNote->setVisible(false);
      setCursorOn(false);
      _score->setUpdateAll();
      _score->update();
      }

//---------------------------------------------------------
//   dragScoreView
//---------------------------------------------------------

void ScoreView::dragScoreView(QMouseEvent* ev)
      {
      QPoint d = ev->pos() - _matrix.map(editData.startMove).toPoint();
      int dx   = d.x();
      int dy   = d.y();

      if (dx == 0 && dy == 0)
            return;

      constraintCanvas(&dx, &dy);

      TourHandler::startTour("navigate-tour");

      _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
         _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
      imatrix = _matrix.inverted();
      scroll(dx, dy, QRect(0, 0, width(), height()));
      // scroll schedules an update which is probably too small
      // hack around:
      if (dx > 0)
            update(-10, 0, dx + 50, height());
      else if (dx < 0)
            update(width() - 50 + dx, 0, width() + 10, height());
      emit offsetChanged(_matrix.dx(), _matrix.dy());
      emit viewRectChanged();
      }

//---------------------------------------------------------
//   doDragLasso
//---------------------------------------------------------

void ScoreView::doDragLasso(QMouseEvent* ev)
      {
      TourHandler::startTour("select-tour");
      QPointF p = toLogical(ev->pos());
      _score->addRefresh(lasso->canvasBoundingRect());
      QRectF r;
      r.setCoords(editData.startMove.x(), editData.startMove.y(), p.x(), p.y());
      lasso->setbbox(r.normalized());
      QRectF _lassoRect(lasso->bbox());
      r = _matrix.mapRect(_lassoRect);
      QSize sz(r.size().toSize());
      mscore->statusBar()->showMessage(QString("%1 x %2").arg(sz.width()).arg(sz.height()), 3000);
      _score->addRefresh(lasso->canvasBoundingRect());
      _score->lassoSelect(lasso->bbox());
      _score->update();
      }

//---------------------------------------------------------
//   endLasso
//---------------------------------------------------------

void ScoreView::endLasso()
      {
      _score->addRefresh(lasso->canvasBoundingRect());
      lasso->setbbox(QRectF());
      _score->lassoSelectEnd();
      _score->update();
      mscore->endCmd();
      }

//---------------------------------------------------------
//   deselectAll
//---------------------------------------------------------

void ScoreView::deselectAll()
      {
      _score->deselectAll();
      _score->update();
      mscore->endCmd();
      }

//---------------------------------------------------------
//   inputMethodQuery
//---------------------------------------------------------

QVariant ScoreView::inputMethodQuery(Qt::InputMethodQuery query) const
      {
//      qDebug("0x%x  %s", int(query), editData.element ? editData.element->name() : "-no element-");
      if (editData.element && editData.element->isTextBase()) {
            TextBase* text = toTextBase(editData.element);
            switch (query) {
                  case Qt::ImCursorRectangle: {
                        QRectF r;
                        if (editMode()) {
                              TextCursor* cursor = text->cursor(editData);
                              r = toPhysical(cursor->cursorRect().translated(text->canvasPos()));
                              r.setWidth(1); // InputMethod doesn't display properly if width left at 0
                              }
                        else
                              r = toPhysical(text->canvasBoundingRect());
                        r.setHeight(r.height() + 10); // add a little margin under the cursor
                        qDebug("ScoreView::inputMethodQuery() updating cursorRect to: (%3f, %3f) + (%3f, %3f)", r.x(), r.y(), r.width(), r.height());
                        return QVariant(r);
                        }
                  case Qt::ImEnabled:
                        return true; // TextBase will always accept input method input
                  case Qt::ImHints:
                        return Qt::ImhNone; // No hints for now, but maybe in future will give hints
                  case Qt::ImInputItemClipRectangle: // maybe give clip rect hint in future, but for now use default parent clipping rect
                  default:
                        return QWidget::inputMethodQuery(query); // fall back to QWidget's version as default
                  }
            }
      QVariant d = QWidget::inputMethodQuery(query); // fall back to QWidget's version as default
//      qDebug() << "   " << d;
      return d;
      }

//---------------------------------------------------------
//   lmag
//---------------------------------------------------------

qreal ScoreView::lmag() const
      {
      return _matrix.m11() / (mscore->physicalDotsPerInch() / DPI);
      }

//---------------------------------------------------------
//   mag
//---------------------------------------------------------

qreal ScoreView::mag() const
      {
      return _matrix.m11();
      }

//---------------------------------------------------------
//   setOffset
//---------------------------------------------------------

void ScoreView::setOffset(qreal x, qreal y)
      {
      _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
         _matrix.m22(), _matrix.m23(), x, y, _matrix.m33());
      imatrix = _matrix.inverted();
      emit viewRectChanged();
      emit offsetChanged(x, y);
      }

//---------------------------------------------------------
//   xoffset
//---------------------------------------------------------

qreal ScoreView::xoffset() const
      {
      return _matrix.dx();
      }

//---------------------------------------------------------
//   yoffset
//---------------------------------------------------------

qreal ScoreView::yoffset() const
      {
      return _matrix.dy();
      }

//---------------------------------------------------------
//   fsize
//---------------------------------------------------------

QSizeF ScoreView::fsize() const
      {
      QSize s = size();
      return QSizeF(s.width() * imatrix.m11(), s.height() * imatrix.m22());
      }

//---------------------------------------------------------
//   keyboard nav constants
//---------------------------------------------------------

constexpr qreal scrollStep   {   .8 };
constexpr qreal thinPadding  { 10.0 };
constexpr qreal thickPadding { 25.0 };

//---------------------------------------------------------
//   pageNext
//---------------------------------------------------------

void ScoreView::pageNext()
      {
      if (score()->pages().empty())
            return;
      if (score()->layoutMode() != LayoutMode::PAGE) {
            screenNext();
            return;
            }
      Page* page = score()->pages().back();
      qreal x, y;
      if (MScore::verticalOrientation()) {
            x        = thinPadding;
            y        = yoffset() - (page->height() + thickPadding) * mag();
            qreal ly = thinPadding - page->pos().y() * mag();
            if (y <= ly - height() * scrollStep) {
                  pageEnd();
                  return;
                  }
            }
      else {
            y        = thinPadding;
            x        = xoffset() - (page->width() + thickPadding) * mag();
            qreal lx = thinPadding - page->pos().x() * mag();
            if (x <= lx - width() * scrollStep) {
                  pageEnd();
                  return;
                  }
            }
      setOffset(x, y);
      update();
      }

//---------------------------------------------------------
//   screenNext
//---------------------------------------------------------

void ScoreView::screenNext()
      {
      if (score()->pages().empty())
            return;
      qreal x { xoffset() };
      qreal y { yoffset() };
      if (score()->layoutMode() == LayoutMode::LINE) {
            x -= width() * scrollStep;
            MeasureBase* lm = score()->lastMeasureMM();
            // Vertical frames aren't laid out in continuous view
            while (lm->isVBoxBase())
                  lm = lm->prev();
            qreal lx = (lm->pos().x() + lm->width()) * mag() - width() * scrollStep;
            if (x < -lx)
                  x = -lx;
            }
      else {
            y -= height() * scrollStep;
            MeasureBase* lm { score()->lastMeasureMM() };
            qreal ly { (lm->canvasPos().y() + lm->height()) * mag() - height() * scrollStep };
            // Special case to jump to top of next page in horizontal view.
            if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()
               && y <= -ly - height() * scrollStep) {
                  pageNext();
                  return;
                  }
            if (y < -ly)
                  y = -ly;
            }
      setOffset(x, y);
      update();
      }

//---------------------------------------------------------
//   pagePrev
//---------------------------------------------------------

void ScoreView::pagePrev()
      {
      if (score()->pages().empty())
            return;
      if (score()->layoutMode() != LayoutMode::PAGE) {
            screenPrev();
            return;
            }
      Page* page = score()->pages().front();
      qreal x, y;
      if (MScore::verticalOrientation()) {
            x  = thinPadding;
            y  = yoffset() + (page->height() + thickPadding) * mag();
            if (y > thinPadding)
                  y = thinPadding;
            }
      else {
            y  = thinPadding;
            x  = xoffset() + (page->width() + thickPadding) * mag();
            if (x > thinPadding)
                  x = thinPadding;
            }
      setOffset(x, y);
      update();
      }

//---------------------------------------------------------
//   screenPrev
//---------------------------------------------------------

void ScoreView::screenPrev()
      {
      if (score()->pages().empty())
            return;
      qreal x { xoffset() };
      qreal y { yoffset() };
      if (score()->layoutMode() == LayoutMode::LINE) {
            x += width() * scrollStep;
            if (x > thinPadding)
                  x = thinPadding;
            }
      else {
            y += height() * scrollStep;
            // Special casing for jumping to bottom of prev page in horizontal view
            if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()
               && y >= thinPadding + height() * scrollStep) {
                  Page* page { score()->pages().front() };
                  x += (page->width() + thickPadding) * mag();
                  // The condition prevents jumping to the bottom of the
                  // first page after reaching the top
                  if (x < thinPadding + (page->width() + thickPadding) * mag()) {
                        MeasureBase* lm { score()->lastMeasureMM() };
                        y = -(lm->canvasPos().y() + lm->height()) * mag() + height() * scrollStep;
                        }
                  if (x > thinPadding)
                        x = thinPadding;
                  }
            if (y > thinPadding)
                y = thinPadding;
            }
      setOffset(x, y);
      update();
      }

//---------------------------------------------------------
//   pageTop
//---------------------------------------------------------

void ScoreView::pageTop()
      {
      if (score()->layoutMode() == LayoutMode::LINE)
            setOffset(thinPadding, 0.0);
      else
            setOffset(thinPadding, thinPadding);
      update();
      }

//---------------------------------------------------------
//   pageEnd
//---------------------------------------------------------

void ScoreView::pageEnd()
      {
      if (score()->pages().empty())
            return;
      MeasureBase* lm = score()->lastMeasureMM();
      if (score()->layoutMode() == LayoutMode::LINE) {
            // Vertical frames aren't laid out in continuous view
            while (lm->isVBoxBase())
                  lm = lm->prev();
            qreal lx = (lm->pos().x() + lm->width()) * mag() - scrollStep * width();
            setOffset(-lx, yoffset());
            }
      else {
            qreal lx { -thinPadding };
            if (score()->layoutMode() == LayoutMode::PAGE && !MScore::verticalOrientation()) {
                  for (int i { 0 }; i < score()->npages() - 1; ++i)
                        lx += score()->pages().at(i)->width() * mag();
                  }
            if (lm->system() && lm->system()->page()->width() * mag() > width())
                  lx = (lm->canvasPos().x() + lm->width()) * mag() - width() * scrollStep;

            qreal ly { (lm->canvasPos().y() + lm->height()) * mag() - height() * scrollStep };
            setOffset(-lx, -ly);
            }
      update();
      }

//---------------------------------------------------------
//   adjustCanvasPosition
//---------------------------------------------------------

void ScoreView::adjustCanvasPosition(const Element* el, bool playBack, int staff )
      {
      if (this != mscore->currentScoreView() && !_moveWhenInactive)
            return;
      // TODO: change icon, or add panning options
      if (!mscore->panDuringPlayback())
            return;

      if (score()->layoutMode() == LayoutMode::LINE) {

            if (!el)
                  return;

            /* Not used, because impossible to get panel width beforehand
            const MeasureBase* m = 0;
            if (el->type() == ElementType::MEASURE)
                  m = static_cast<const MeasureBase*>(el);
            else
                  m = static_cast<const Measure*>(el->parent()->findMeasure());
            */

            qreal xo = 0.0;  // new x offset
            QRectF curPos = playBack ? _cursor->rect() : el->canvasBoundingRect();
            // keep current note in view as well if applicable (note input mode)
            Element* current = nullptr;
            if (noteEntryMode()) {
                  current = score()->selection().cr();
                  if (current && current != el)
                        curPos |= current->canvasBoundingRect();
                  }

            qreal curPosR = curPos.right();                    // Position on the canvas
            qreal curPosL = curPos.left();                     // Position on the canvas
            qreal curPosMagR = curPosR * mag() + xoffset(); // Position in the screen
            qreal curPosMagL = curPosL * mag() + xoffset(); // Position in the screen
            qreal marginLeft = width() * 0.05;
            qreal marginRight = width() * 0.05; // leaves 5% margin to the right

            if (_continuousPanel->active())
                  marginLeft += _continuousPanel->width() * mag();

            // this code implements "continuous" panning
            // it could potentially be enabled via more panning options
            //if (playBack && _cursor) {
            //      // keep playback cursor pinned at 25%
            //      xo = -curPosL * mag() + marginLeft + width() * 0.2;
            //      }
            //else
            if (round(curPosMagR) > round(width() - marginRight)) {
                  // focus in or beyond right margin
                  // pan to left margin in playback,
                  // most of the way left in note entry,
                  // otherwise just enforce right margin
                  if (playBack)
                        xo = -curPosL * mag() + marginLeft;
                  else if (noteEntryMode())
                        xo = -curPosL * mag() + marginLeft + width() * 0.2;
                  else
                        xo = -curPosR * mag() + width() - marginRight;
                  }
            else if (round(curPosMagL) < round(marginLeft) ) {
                  // focus in or beyond left margin
                  // enforce left margin
                  // (previously we moved canvas all the way right,
                  // but this made sense only when navigating right-to-left)
                  xo = -curPosL * mag() + marginLeft;
                  }
            else {
                  // focus is within margins, so do nothing
                  return;
                  }
            // avoid empty space on either side of "page"
            qreal scoreEnd = score()->pages().front()->width() * mag() + xo;
            if (xo > 10)
                  xo = 10;
            else if (scoreEnd < width())
                  xo += width() - scoreEnd;

            setOffset(xo, yoffset());
            update();
            return;
            }

      const MeasureBase* m;
      if (!el)
            return;
      else if (el->type() == ElementType::NOTE)
            m = static_cast<const Note*>(el)->chord()->measure();
      else if (el->type() == ElementType::REST)
            m = static_cast<const Rest*>(el)->measure();
      else if (el->type() == ElementType::CHORD)
            m = static_cast<const Chord*>(el)->measure();
      else if (el->type() == ElementType::SEGMENT)
            m = static_cast<const Segment*>(el)->measure();
      else if (el->type() == ElementType::LYRICS)
            m = static_cast<const Lyrics*>(el)->measure();
      else if ( (el->type() == ElementType::HARMONY || el->type() == ElementType::FIGURED_BASS)
         && el->parent()->type() == ElementType::SEGMENT)
            m = static_cast<const Segment*>(el->parent())->measure();
      else if (el->type() == ElementType::HARMONY && el->parent()->type() == ElementType::FRET_DIAGRAM
         && el->parent()->parent()->type() == ElementType::SEGMENT)
            m = static_cast<const Segment*>(el->parent()->parent())->measure();
      else if (el->isMeasureBase())
            m = static_cast<const MeasureBase*>(el);
      else if (el->isSpannerSegment()) {
            Element* se = static_cast<const SpannerSegment*>(el)->spanner()->startElement();
            m = static_cast<Measure*>(se->findMeasure());
            }
      else if (el->isSpanner()) {
            Element* se = static_cast<const Spanner*>(el)->startElement();
            m = static_cast<Measure*>(se->findMeasure());
            }
      else {
            // attempt to find measure
            Element* e = el->parent();
            while (e && !e->isMeasureBase())
                  e = e->parent();
            if (e)
                  m = toMeasureBase(e);
            else
                  return;
            }
      if (!m)
            return;

      int staffIdx = el->staffIdx();
      System* sys = m->system();
      if (!sys)
            return;

      QPointF p(el->canvasPos());
      QRectF r(canvasViewport());
      QRectF mRect(m->canvasBoundingRect());
      QRectF sysRect;
      if (staffIdx == -1)
            sysRect = sys->canvasBoundingRect();
      else
            sysRect = sys->staff(staffIdx)->bbox();

      // only try to track measure if not during playback
      if (!playBack)
            sysRect = mRect;

      double _spatium    = score()->spatium();
      const qreal border = _spatium * 3;
      QRectF showRect;
      if (staff == -1) {
            showRect = QRectF(mRect.x(), sysRect.y(), mRect.width(), sysRect.height())
                        .adjusted(-border, -border, border, border);
            }
      else {
            // find a box for the individual stave in a system
            QRectF stave = QRectF(sys->canvasBoundingRect().left(),
                                  sys->staffCanvasYpage(staff),
                                  sys->width(),
                                  sys->staff(staff)->bbox().height());
            showRect = mRect.intersected(stave).adjusted(-border, -border, border, border);
            }

/*printf("%f %f %f %f   %f %f %f %f  %d\n",
   showRect.x(), showRect.y(), showRect.width(), showRect.height(),
   r.x(), r.y(), r.width(), r.height(),
   r.contains(showRect)
   );
*/
      // canvas is not as wide as measure, track note instead
      if (r.width() < showRect.width()) {
            QRectF eRect(el->canvasBoundingRect());
            showRect.setX(eRect.x());
            showRect.setWidth(eRect.width());
            }

      // canvas is not as tall as system
      if (r.height() < showRect.height()) {
            if (sys->staves()->size() == 1 || !playBack) {
                  // track note if single staff
                  showRect.setY(p.y());
                  showRect.setHeight(el->height());
                  }
            else if (sys->page()->systems().size() == 1) {
                  // otherwise, just keep current vertical position if possible
                  // see issue #7724
                  showRect.setY(r.y());
                  showRect.setHeight(r.height());
                  }
            }
      if (shadowNote->visible())
            setShadowNote(p);

      if (r.contains(showRect))
            return;

      qreal x   = - xoffset() / mag();
      qreal y   = - yoffset() / mag();

      qreal oldX = x, oldY = y;

      if (showRect.left() < r.left())
            x = showRect.left() - border;
      else if (showRect.left() > r.right())
            x = showRect.right() - width() / mag() + border;
      else if (r.width() >= showRect.width() && showRect.right() > r.right())
            x = showRect.left() - border;
      if (showRect.top() < r.top() && showRect.bottom() < r.bottom())
            y = showRect.top() - border;
      else if (showRect.top() > r.bottom())
            y = showRect.bottom() - height() / mag() + border;
      else if (r.height() >= showRect.height() && showRect.bottom() > r.bottom())
            y = showRect.top() - border;

      // align to page borders if extends beyond
      Page* page = sys->page();
      if (x < page->x() || r.width() >= page->width())
            x = page->x();
      else if (r.width() < page->width() && r.width() + x > page->width() + page->x())
            x = (page->width() + page->x()) - r.width();
      if (y < page->y() || r.height() >= page->height())
            y = page->y();
      else if (r.height() < page->height() && r.height() + y > page->height() + page->y())
            y = (page->height() + page->y()) - r.height();

      // hack: don't update if we haven't changed the offset
      if (oldX == x && oldY == y)
            return;

      setOffset(-x * mag(), -y * mag());
      update();
      }

//---------------------------------------------------------
//   cmdEnterRest
//---------------------------------------------------------

void ScoreView::cmdEnterRest()
      {
      InputState& is = _score->inputState();
      if (is.track() == -1 || is.segment() == 0)          // invalid state
            return;
      if (!is.duration().isValid() || is.duration().isZero() || is.duration().isMeasure())
            is.setDuration(TDuration::DurationType::V_QUARTER);
      cmdEnterRest(is.duration());
      }

//---------------------------------------------------------
//   cmdEnterRest
//---------------------------------------------------------

void ScoreView::cmdEnterRest(const TDuration& d)
      {
      if (!noteEntryMode())
            cmd("note-input");
      if (_score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM))
            _score->cmd(getAction("pad-rest"), editData);
      else
            _score->cmdEnterRest(d);
#if 0
      expandVoice();
      if (_is.cr() == 0) {
            qDebug("cannot enter rest here");
            return;
            }

      int track = _is.track;
      Segment* seg  = setNoteRest(_is.cr(), track, -1, d.fraction(), 0, AUTO);
      ChordRest* cr = static_cast<ChordRest*>(seg->element(track));
      if (cr)
            nextInputPos(cr, false);
      _is.rest = false;  // continue with normal note entry
#endif
      }

//---------------------------------------------------------
//   mscoreState
//---------------------------------------------------------

ScoreState ScoreView::mscoreState() const
      {
      if (fotoMode())
            return STATE_FOTO;
      if (state == ViewState::NOTE_ENTRY) {
            const InputState is = _score->inputState();
            Staff* staff = _score->staff(is.track() / VOICES);
            switch( staff->staffType(is.tick())->group()) {
                  case StaffGroup::STANDARD:
                        return STATE_NOTE_ENTRY_STAFF_PITCHED;
                  case StaffGroup::TAB:
                        return STATE_NOTE_ENTRY_STAFF_TAB;
                  case StaffGroup::PERCUSSION:
                        return STATE_NOTE_ENTRY_STAFF_DRUM;
                  }
            }
      if (state == ViewState::EDIT || state == ViewState::DRAG_EDIT) {
            if (editData.element && editData.element->isLyrics())
                  return STATE_LYRICS_EDIT;
            else if (editData.element &&
                  ( (editData.element->isHarmony()) || editData.element->isFiguredBass()) )
                  return STATE_HARMONY_FIGBASS_EDIT;
            else if (editData.element && editData.element->isTextBase())
                  return STATE_TEXT_EDIT;
            return STATE_EDIT;
            }
      if (state == ViewState::PLAY)
            return STATE_PLAY;
      return STATE_NORMAL;
      }

//---------------------------------------------------------
//   startUndoRedo
//---------------------------------------------------------

void ScoreView::startUndoRedo(bool undo)
      {
      _score->undoRedo(undo, state == ViewState::EDIT ? &editData : 0);

      if (_score->inputState().segment())
            mscore->setPos(_score->inputState().tick());
      if (_score->noteEntryMode() && !noteEntryMode())
            cmd("note-input");    // enter note entry mode
      else if (!_score->noteEntryMode() && noteEntryMode())
            cmd("escape");        // leave note entry mode

      updateEditElement();
      }

//---------------------------------------------------------
//   cmdAddSlur
//    command invoked, or icon double clicked
//---------------------------------------------------------

void ScoreView::cmdAddSlur(const Slur* slurTemplate)
      {
      InputState& is = _score->inputState();
      if (noteEntryMode() && is.slur()) {
            const std::vector<SpannerSegment*>& el = is.slur()->spannerSegments();
            if (!el.empty()) {
                  el.front()->setSelected(false);
                  // Now make sure that the slur segment is redrawn so that it does not *look* selected
                  update();
                  }
            is.setSlur(nullptr);
            return;
            }

      _score->startCmd();

      ChordRest* cr1;
      ChordRest* cr2;
      const auto& sel = _score->selection();
      auto el         = sel.uniqueElements();

      if (sel.isRange()) {
            int startTrack = sel.staffStart() * VOICES;
            int endTrack   = sel.staffEnd() * VOICES;
            for (int track = startTrack; track < endTrack; ++track) {
                  cr1 = 0;
                  cr2 = 0;
                  for (Element* e : el) {
                        if (e->track() != track)
                              continue;
                        if (e->isNote())
                              e = toNote(e)->chord();
                        if (!e->isChord())
                              continue;
                        ChordRest* cr = toChordRest(e);
                        if (!cr1 || cr1->tick() > cr->tick())
                              cr1 = cr;
                        if (!cr2 || cr2->tick() < cr->tick())
                              cr2 = cr;
                        }
                  if (cr1 && (cr1 != cr2))
                        addSlur(cr1, cr2, slurTemplate);
                  }
            }
      else {
            cr1 = 0;
            cr2 = 0;
            for (Element* e : el) {
                  if (e->isNote())
                        e = toNote(e)->chord();
                  if (!e->isChord())
                        continue;
                  ChordRest* cr = toChordRest(e);
                  if (!cr1 || cr->isBefore(cr1))
                        cr1 = cr;
                  if (!cr2 || cr2->isBefore(cr))
                        cr2 = cr;
                  }
            if (cr1 == cr2)
                  cr2 = 0;
            if (cr1)
                  addSlur(cr1, cr2, slurTemplate);
            }
      _score->endCmd();
      }

//---------------------------------------------------------
//   addSlur
//---------------------------------------------------------

void ScoreView::addSlur(ChordRest* cr1, ChordRest* cr2, const Slur* slurTemplate)
      {
      bool switchToSlur = false;
      if (cr2 == 0) {
            cr2 = nextChordRest(cr1);
            if (cr2 == 0)
                  cr2 = cr1;
            switchToSlur = true; // select slur for editing if last chord is not given
            }

      Slur* slur = slurTemplate ? slurTemplate->clone() : new Slur(cr1->score());
      slur->setScore(cr1->score());
      slur->setTick(cr1->tick());
      slur->setTick2(cr2->tick());
      slur->setTrack(cr1->track());
      if (cr2->staff()->part() == cr1->staff()->part() && !cr2->staff()->isLinked(cr1->staff()))
            slur->setTrack2(cr2->track());
      else
            slur->setTrack2(cr1->track());
      slur->setStartElement(cr1);
      slur->setEndElement(cr2);

      cr1->score()->undoAddElement(slur);
      SlurSegment* ss = new SlurSegment(cr1->score());
      ss->setSpannerSegmentType(SpannerSegmentType::SINGLE);
      if (cr1 == cr2)
            ss->setSlurOffset(Grip::END, QPointF(3.0 * cr1->score()->spatium(), 0.0));
      slur->add(ss);

      if (noteEntryMode()) {
            _score->inputState().setSlur(slur);
            ss->setSelected(true);
            }
      else if (switchToSlur) {
            startEditMode(ss);
            }
      }

//---------------------------------------------------------
//   cmdAddHairpin
//    '<' typed on keyboard
//---------------------------------------------------------

void ScoreView::cmdAddHairpin(HairpinType type)
      {
      const Selection& selection = _score->selection();
      // special case for two selected chordrests on same staff
      bool twoNotesSameStaff = false;
      if (selection.isList() && selection.elements().size() == 2) {
            ChordRest* cr1 = selection.firstChordRest();
            ChordRest* cr2 = selection.lastChordRest();
            if (cr1 && cr2 && cr1 != cr2 && cr1->staffIdx() == cr2->staffIdx())
                  twoNotesSameStaff = true;
            }
      // add hairpin on each staff if possible
      if (selection.isRange() && selection.staffStart() != selection.staffEnd() - 1) {
            _score->startCmd();
            for (int staffIdx = selection.staffStart() ; staffIdx < selection.staffEnd(); ++staffIdx) {
                  ChordRest* cr1 = selection.firstChordRest(staffIdx * VOICES);
                  ChordRest* cr2 = selection.lastChordRest(staffIdx * VOICES);
                  _score->addHairpin(type, cr1, cr2, /* toCr2End */ true);
                  }
            _score->endCmd();
            }
      else if (selection.isRange() || selection.isSingle() || twoNotesSameStaff) {
            // for single staff range selection, or single selection,
            // find start & end elements elements
            ChordRest* cr1;
            ChordRest* cr2;
            _score->getSelectedChordRest2(&cr1, &cr2);
            _score->startCmd();
            Hairpin* pin = _score->addHairpin(type, cr1, cr2, /* toCr2End */ !twoNotesSameStaff);
            _score->endCmd();
            if (!pin)
                  return;

            const std::vector<SpannerSegment*>& el = pin->spannerSegments();
            if (!noteEntryMode()) {
                  if (!el.empty()) {
                        startEditMode(el.front());
                        }
                  }
            }
      else {
            // do not attempt for list selection
            // or we will keep adding hairpins to the same chordrests
            return;
            }
      }

//---------------------------------------------------------
//   cmdAddNoteLine
//---------------------------------------------------------

void ScoreView::cmdAddNoteLine()
      {
      Note* firstNote = 0;
      Note* lastNote  = 0;
      if (_score->selection().isRange()) {
            int startTrack = _score->selection().staffStart() * VOICES;
            int endTrack   = _score->selection().staffEnd() * VOICES;
            for (int track = startTrack; track < endTrack; ++track) {
                  for (Note* n : _score->selection().noteList(track)) {
                        if (firstNote == 0 || firstNote->chord()->tick() > n->chord()->tick())
                              firstNote = n;
                        if (lastNote == 0 || lastNote->chord()->tick() < n->chord()->tick())
                              lastNote = n;
                        }
                  }
            }
      else {
            for (Note* n : _score->selection().noteList()) {
                  if (firstNote == 0 || firstNote->chord()->tick() > n->chord()->tick())
                        firstNote = n;
                  if (lastNote == 0 || lastNote->chord()->tick() < n->chord()->tick())
                        lastNote = n;
                  }
            }
      if (!firstNote || !lastNote) {
            qDebug("addNoteLine: no note %p %p", firstNote, lastNote);
            return;
            }
      if (firstNote == lastNote) {
           qDebug("addNoteLine: no support for note to same note line %p", firstNote);
           return;
           }
      TextLine* tl = new TextLine(_score);
      tl->setParent(firstNote);
      tl->setStartElement(firstNote);
      tl->setEndElement(lastNote);
      tl->setDiagonal(true);
      tl->setAnchor(Spanner::Anchor::NOTE);
      tl->setTick(firstNote->chord()->tick());
      _score->startCmd();
      _score->undoAddElement(tl);
      _score->endCmd();
      }

//---------------------------------------------------------
//   cmdChangeEnharmonic
//---------------------------------------------------------

void ScoreView::cmdChangeEnharmonic(bool both)
      {
      _score->startCmd();
      QList<Note*> notes = _score->selection().uniqueNotes();
      for (Note* n : notes) {
            Staff* staff = n->staff();
            if (staff->part()->instrument()->useDrumset())
                  continue;
            if (staff->isTabStaff(n->tick())) {
                  int string = n->line() + (both ? 1 : -1);
                  int fret   = staff->part()->instrument()->stringData()->fret(n->pitch(), string, staff, n->chord()->tick());
                  if (fret != -1) {
                        n->undoChangeProperty(Pid::FRET, fret);
                        n->undoChangeProperty(Pid::STRING, string);
                        }
                  }
            else {
                  static const int tab[36] = {
                        26, 14,  2,  // 60  B#   C   Dbb
                        21, 21,  9,  // 61  C#   C#  Db
                        28, 16,  4,  // 62  C##  D   Ebb
                        23, 23, 11,  // 63  D#   D#  Eb
                        30, 18,  6,  // 64  D##  E   Fb
                        25, 13,  1,  // 65  E#   F   Gbb
                        20, 20,  8,  // 66  F#   F#  Gb
                        27, 15,  3,  // 67  F##  G   Abb
                        22, 22, 10,  // 68  G#   G#  Ab
                        29, 17,  5,  // 69  G##  A   Bbb
                        24, 24, 12,  // 70  A#   A#  Bb
                        31, 19,  7,  // 71  A##  B   Cb
                        };
                  int tpc = n->tpc();
                  int i;
                  for (i = 0; i < 36; ++i) {
                        if (tab[i] == tpc) {
                              if ((i % 3) < 2) {
                                    if (tab[i] == tab[i + 1])
                                          tpc = tab[i + 2];
                                    else
                                          tpc = tab[i + 1];
                                    }
                              else {
                                    tpc = tab[i - 2];
                                    }
                              break;
                              }
                        }
                  if (i == 36) {
                        qDebug("tpc %d not found", tpc);
                        }
                  else {
                        n->undoSetTpc(tpc);
                        if (both || staff->part()->instrument(n->chord()->tick())->transpose().isZero()) {
                              // change both spellings
                              int t = n->transposeTpc(tpc);
                              if (n->concertPitch())
                                    n->undoChangeProperty(Pid::TPC2, t);
                              else
                                    n->undoChangeProperty(Pid::TPC1, t);
                              }
                        }
                  }
            }

      Selection& selection = _score->selection();
      selection.clear();
      for (Note* n : notes)
            selection.add(n);
      _score->endCmd();
      }

//---------------------------------------------------------
//   cloneElement
//---------------------------------------------------------

void ScoreView::cloneElement(Element* e)
      {
      if (e->isMeasure() || e->isNote() || e->isVBox())
            return;
      QDrag* drag = new QDrag(this);
      QMimeData* mimeData = new QMimeData;
      if (e->isSpannerSegment())
            e = toSpannerSegment(e)->spanner();
      mimeData->setData(mimeSymbolFormat, e->mimeData(QPointF()));
      drag->setMimeData(mimeData);
      static QPixmap pixmap = QPixmap(2, 2);    // null or 1x1 crashes on Linux under ChromeOS?!
      pixmap.fill(Qt::white);
      drag->setPixmap(pixmap);
      drag->exec(Qt::CopyAction);
      }

//---------------------------------------------------------
//   changeEditElement
//---------------------------------------------------------

void ScoreView::changeEditElement(Element* e)
      {
      if (editData.element == e)
            return;

      const Grip grip = editData.curGrip;
      const bool dragEdit = (state == ViewState::DRAG_EDIT);

      if (editData.element && dragEdit)
            editData.element->endEditDrag(editData);

      endEdit();
      startEdit(e, grip);

      if (editData.element && dragEdit)
            editData.element->startEditDrag(editData);
      }

//---------------------------------------------------------
//   setCursorVisible
//---------------------------------------------------------

void ScoreView::setCursorVisible(bool v)
      {
      _cursor->setVisible(v);
      }

//---------------------------------------------------------
//   cmdTuplet
//---------------------------------------------------------

void ScoreView::cmdTuplet(int n, ChordRest* cr)
      {
      if (cr->durationType() < TDuration(TDuration::DurationType::V_512TH) && cr->durationType() != TDuration(TDuration::DurationType::V_MEASURE)) {
            mscore->noteTooShortForTupletDialog();
            return;
            }
      Measure* measure = cr->measure();
      if (measure && measure->isMMRest())
            return;

      Fraction f(cr->ticks());
      Tuplet* ot  = cr->tuplet();

      f.reduce();       //measure duration might not be reduced
      Fraction ratio(n, f.numerator());
      Fraction fr(1, f.denominator());
      while (ratio.numerator() >= ratio.denominator() * 2) {
            ratio.setDenominator(ratio.denominator() * 2);  // operator*= reduces, we don't want that here
            fr    *= Fraction(1,2);
            }

      Tuplet* tuplet = new Tuplet(_score);
      tuplet->setRatio(ratio);

      //
      // "fr" is the fraction value of one tuple element
      //
      // "tuplet time" is "normal time" / tuplet->ratio()
      //    Example: an 1/8 has 240 midi ticks, in an 1/8 triplet the note
      //             has a tick duration of 240 / (3/2) = 160 ticks
      //

      tuplet->setTicks(f);
      TDuration baseLen(fr);
      tuplet->setBaseLen(baseLen);

      tuplet->setTrack(cr->track());
      tuplet->setTick(cr->tick());
      tuplet->setParent(measure);

      if (ot)
            tuplet->setTuplet(ot);

      cmdCreateTuplet(cr, tuplet);
      }

//---------------------------------------------------------
//   cmdCreateTuplet
//---------------------------------------------------------

void ScoreView::cmdCreateTuplet(ChordRest* cr, Tuplet* tuplet)
      {
      _score->cmdCreateTuplet(cr, tuplet);

      const std::vector<DurationElement*>& cl = tuplet->elements();
      size_t ne = cl.size();
      DurationElement* el = 0;
      if (ne && cl[0]->type() == ElementType::REST)
            el  = cl[0];
      else if (ne > 1)
            el = cl[1];
      if (el) {
            _score->select(el, SelectType::SINGLE, 0);
            _score->inputState().setDuration(tuplet->baseLen());
            changeState(ViewState::NOTE_ENTRY);
            }
      }

//---------------------------------------------------------
//   changeVoice
//---------------------------------------------------------

void ScoreView::changeVoice(int voice)
      {
      InputState* is = &score()->inputState();
      int track = (is->track() / VOICES) * VOICES + voice;

      if (is->noteEntryMode()) {
            is->setTrack(track);
            if (is->segment()) { // can be null for eg repeatMeasure
                  is->setSegment(is->segment()->measure()->first(SegmentType::ChordRest));
                  moveCursor();
                  score()->setUpdateAll();
                  score()->update();
                  mscore->setPos(is->segment()->tick());
                  }
            }
      else {
            // treat as command to move notes to another voice
            score()->changeVoice(voice);
            // modify the input state only if the command was successful
            for (ChordRest* cr : score()->getSelectedChordRests())
                  if (cr->voice() == voice) {
                        is->setTrack(track);
                        break;
                        }
            }
      }

//---------------------------------------------------------
//   cmdTuplet
//---------------------------------------------------------

void ScoreView::cmdTuplet(int n)
      {
      _score->startCmd();
      if (noteEntryMode()) {
            _score->expandVoice();
            ChordRest* cr = _score->inputState().cr();
            if (cr) {
                  _score->changeCRlen(cr, _score->inputState().duration());
                  cmdTuplet(n, cr);
                  }
            }
      else {
            for (ChordRest* cr : _score->getSelectedChordRests()) {
                  if (!cr->isGrace()) {
                        cmdTuplet(n, cr);
                        }
                  }
            }
      _score->endCmd();
      moveCursor();     // do this after endCmd to make sure segment has been laid out
      }

//---------------------------------------------------------
//   midiNoteReceived
//---------------------------------------------------------

void ScoreView::midiNoteReceived(int pitch, bool chord, int velocity)
      {
      qDebug("midiNoteReceived: pitch %d, chord %d, velocity %d", pitch, chord, velocity);

      MidiInputEvent ev;
      ev.pitch = pitch;
      ev.chord = chord;
      ev.velocity = velocity;

      score()->masterScore()->enqueueMidiEvent(ev);

      if (!score()->undoStack()->active())
            cmd((const char*)0);

      if (!chord && velocity && !realtimeTimer->isActive() && score()->usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO)) {
            // First note pressed in automatic real-time mode.
            extendNoteTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY)); // set timer to trigger repeatedly
            triggerCmdRealtimeAdvance(); // also trigger once immediately
            }

      }

//---------------------------------------------------------
//   extendCurrentNote
//    Called after user has held down a midi key for a while.
//    TODO: adapt to allow calling from StepTime mode.
//---------------------------------------------------------

void ScoreView::extendCurrentNote()
      {
      if (!noteEntryMode() || realtimeTimer->isActive())
            return;

      allowRealtimeRests = false;
      realtimeTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY)); // set timer to trigger repeatedly
      triggerCmdRealtimeAdvance(); // also trigger once immediately
      }

//---------------------------------------------------------
//   realtimeAdvance
//---------------------------------------------------------

void ScoreView::realtimeAdvance(bool allowRests)
      {
      if (!noteEntryMode())
            return;
      InputState& is = score()->inputState();
      switch (is.noteEntryMethod()) {
            case NoteEntryMethod::REALTIME_MANUAL:
                  allowRealtimeRests = allowRests;
                  triggerCmdRealtimeAdvance();
                  break;
            case NoteEntryMethod::REALTIME_AUTO:
                  if (realtimeTimer->isActive())
                        realtimeTimer->stop();
                  else {
                        allowRealtimeRests = allowRests;
                        realtimeTimer->start(preferences.getInt(PREF_IO_MIDI_REALTIMEDELAY));
                        }
                  break;
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   triggerCmdRealtimeAdvance
//---------------------------------------------------------

void ScoreView::triggerCmdRealtimeAdvance()
      {
      InputState& is = score()->inputState();
      bool realtime = is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO) || is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_MANUAL);
      if (!realtime || !noteEntryMode() || (!allowRealtimeRests && score()->activeMidiPitches()->empty())) {
            if (realtimeTimer->isActive())
                  realtimeTimer->stop();
            allowRealtimeRests = true;
            return;
            }
      // give audible feedback immediately to indicate a beat, but don’t advance just yet.
      seq->playMetronomeBeat(_score->tick2beatType(is.tick()));
      // The user will want to press notes "on the beat" and not before the beat, so wait a
      // little in case midi input event is received just after realtime-advance was called.
      QTimer::singleShot(100, Qt::PreciseTimer, this, SLOT(cmdRealtimeAdvance()));
      }

//---------------------------------------------------------
//   cmdRealtimeAdvance
//    move input forwards and extend current chord/rest.
//---------------------------------------------------------

void ScoreView::cmdRealtimeAdvance()
      {
      InputState& is = _score->inputState();
      if (!is.noteEntryMode())
            return;
      _score->startCmd();
      Fraction ticks2measureEnd = is.segment()->measure()->ticks() - is.segment()->rtick();
      if (!is.cr() || (is.cr()->ticks() != is.duration().fraction() && is.duration() < ticks2measureEnd))
            _score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
      ChordRest* prevCR = toChordRest(is.cr());
      if (_score->inputState().endOfScore())
            _score->appendMeasures(1);
      is.moveToNextInputPos();
      if (_score->activeMidiPitches()->empty())
            _score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
      else {
            Chord* prevChord = prevCR->isChord() ? toChord(prevCR) : 0;
            bool partOfChord = false;
            for (const MidiInputEvent &ev : *_score->activeMidiPitches()) {
                  _score->addTiedMidiPitch(ev.pitch, partOfChord, prevChord);
                  partOfChord = true;
                  }
            }
      if (prevCR->measure() != is.segment()->measure()) {
            // just advanced across barline. Now simplify tied notes.
            score()->regroupNotesAndRests(prevCR->measure()->tick(), is.segment()->measure()->tick(), is.track());
            }
      _score->endCmd();
      }

//---------------------------------------------------------
//   cmdAddChordName
//---------------------------------------------------------

void ScoreView::cmdAddChordName(HarmonyType ht)
      {
      if (!_score->checkHasMeasures())
            return;

      int track = -1;
      Element* newParent = nullptr;
      Element* el = _score->selection().element();
      if (el && el->isFretDiagram()) {
            FretDiagram* fd = toFretDiagram(el);
            track = fd->track();
            newParent = fd;
            }
      else {
            ChordRest* cr = _score->getSelectedChordRest();
            if (cr) {
                  track = cr->track();
                  newParent = toElement(cr->segment());
                  }
            }
      if (track == -1 || !newParent)
            return;

      _score->startCmd();
      Harmony* harmony = new Harmony(_score);
      harmony->setTrack(track);
      harmony->setParent(newParent);
      harmony->setHarmonyType(ht);
      _score->undoAddElement(harmony);
      _score->endCmd();

      _score->select(harmony, SelectType::SINGLE, 0);
      startEditMode(harmony);
      _score->update();
      }

//---------------------------------------------------------
//   cmdAddText
//---------------------------------------------------------

void ScoreView::cmdAddText(Tid tid, Tid customTid, PropertyFlags pf, Placement p)
      {
      if (!_score->checkHasMeasures())
            return;
      if (noteEntryMode())          // force out of entry mode
            changeState(ViewState::NORMAL);

      TextBase* s = 0;
      _score->startCmd();
      if (tid == Tid::STAFF && customTid == Tid::EXPRESSION)
            tid = customTid;  // expression is not first class element, but treat as such
      switch (tid) {
            case Tid::TITLE:
            case Tid::SUBTITLE:
            case Tid::COMPOSER:
            case Tid::POET:
            case Tid::INSTRUMENT_EXCERPT:
                  {
                  MeasureBase* measure = _score->first();
                  if (!measure->isVBox()) {
                        _score->insertMeasure(ElementType::VBOX, measure);
                        measure = measure->prev();
                        }
                  s = new Text(_score, tid);
                  s->setParent(measure);
                  _score->undoAddElement(s);
                  }
                  break;

            case Tid::REHEARSAL_MARK:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new RehearsalMark(_score);
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::STAFF:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new StaffText(_score, Tid::STAFF);
                  // if the previous text is UNSTYLED or NOSTYLE
                  // and the next text is created using text tab command
                  // (only those created in this way can make pf not STYLED, see definition of ScoreView::textTab())
                  // it inherits the same PropertyFlags and Placement
                  // making it much easier to put all texts above/below staves
                  if (pf != PropertyFlags::STYLED) {
                        s->setPlacement(p);
                        s->setPropertyFlags(Pid::PLACEMENT, pf);
                        }
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::SYSTEM:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new SystemText(_score, Tid::SYSTEM);
                  if (pf != PropertyFlags::STYLED) {
                        s->setPlacement(p);
                        s->setPropertyFlags(Pid::PLACEMENT, pf);
                        }
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::EXPRESSION:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new StaffText(_score, Tid::EXPRESSION);
                  s->setPlacement(Placement::BELOW);
                  s->setPropertyFlags(Pid::PLACEMENT, PropertyFlags::UNSTYLED);
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::INSTRUMENT_CHANGE:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new InstrumentChange(_score);
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::STICKING:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new Sticking(_score);
                  cr->undoAddAnnotation(s);
                  }
                  break;
            case Tid::FINGERING:
            case Tid::LH_GUITAR_FINGERING:
            case Tid::RH_GUITAR_FINGERING:
            case Tid::STRING_NUMBER:
                  {
                  Element* e = _score->getSelectedElement();
                  if (!e || !e->isNote())
                        break;
                  bool isTablature = e->staff()->isTabStaff(e->tick());
                  bool tabFingering = e->staff()->staffType(e->tick())->showTabFingering();
                  if (isTablature && !tabFingering)
                        break;
                  s = new Fingering(_score, tid);
                  s->setTrack(e->track());
                  s->setParent(e);
                  if (pf != PropertyFlags::STYLED) {
                        s->setPlacement(p);
                        s->setPropertyFlags(Pid::PLACEMENT, pf);
                        }
                  _score->undoAddElement(s);
                  }
                  break;
            default:
                  break;
            }

      if (s) {
            if (customTid != Tid::DEFAULT && customTid != tid)
                  s->initTid(customTid);
            _score->select(s, SelectType::SINGLE, 0);
            _score->endCmd();
            Measure* m = s->findMeasure();
            if (m && m->hasMMRest() && s->links()) {
                  Measure* mmRest = m->mmRest();
                  for (ScoreElement* se : *s->links()) {
                        TextBase* s1 = toTextBase(se);
                        if (s != s1 && s1->findMeasure() == mmRest) {
                              s = s1;
                              break;
                              }
                        }
                  }
            adjustCanvasPosition(s, false);
            startEditMode(s);
            }
      else
            _score->endCmd();
      }

//---------------------------------------------------------
//   cmdAppendMeasures
///   Append \a n measures.
///
///   Keyboard callback, called from pulldown menu.
//
//    - called from pulldown menu
//---------------------------------------------------------

void ScoreView::cmdAppendMeasures(int n, ElementType type)
      {
      _score->startCmd();
      appendMeasures(n, type);
      _score->endCmd();
      }

//---------------------------------------------------------
//   appendMeasure
//---------------------------------------------------------

MeasureBase* ScoreView::appendMeasure(ElementType type)
      {
      _score->startCmd();
      _score->insertMeasure(type, 0);
      MeasureBase* mb = _score->lastMeasureMM();
      _score->endCmd();
      return mb;
      }

//---------------------------------------------------------
//   appendMeasures
//---------------------------------------------------------

void ScoreView::appendMeasures(int n, ElementType type)
      {
      if (_score->noStaves()) {
            QMessageBox::warning(0, "MuseScore",
               tr("No staves found:\n"
                  "please use the instruments dialog to\n"
                  "first create some staves"));
            return;
            }
      for (int i = 0; i < n; ++i)
            _score->insertMeasure(type, 0);
      }

//---------------------------------------------------------
//   cmdInsertMeasures
//---------------------------------------------------------

void ScoreView::cmdInsertMeasures(int n, ElementType type)
      {
      MeasureBase* mb = checkSelectionStateForInsertMeasure();
      if (!mb)
            return;
      _score->startCmd();
      for (int i = 0; i < n; ++i)
            _score->insertMeasure(type, mb);
      _score->endCmd();

      if (mb->type() == ElementType::MEASURE) {
            // re-select the original measure (which may now be covered by an mmrest)
            // do this after the layout so mmrests are updated
            Measure* m = _score->tick2measureMM(mb->tick());
            _score->select(m, SelectType::SINGLE, 0);
            }
      else {
            // original selection was not a measure, just re-select it
            _score->select(mb, SelectType::SINGLE, 0);
            }
      }

//---------------------------------------------------------
//   cmdInsertMeasure
//---------------------------------------------------------

void ScoreView::cmdInsertMeasure(ElementType type)
      {
      MeasureBase* mb = checkSelectionStateForInsertMeasure();
      if (!mb)
            return;
      _score->startCmd();
      _score->insertMeasure(type, mb);
      mb = mb->prev();
      if (mb->type() == ElementType::TBOX) {
            TBox* tbox = static_cast<TBox*>(mb);
            Text* s = tbox->text();
            _score->select(s, SelectType::SINGLE, 0);
            _score->endCmd();
            startEditMode(s);
            return;
            }
      if (mb)
           _score->select(mb, SelectType::SINGLE, 0);
      _score->endCmd();
      }

//---------------------------------------------------------
//   checkSelectionStateForInsertMeasure
//---------------------------------------------------------

MeasureBase* ScoreView::checkSelectionStateForInsertMeasure()
      {
      MeasureBase* mb = 0;
      if (_score->selection().isRange()) {
            mb = _score->selection().startSegment()->measure();
            return mb;
            }

      mb = _score->selection().findMeasure();
      if (mb)
            return mb;

      Element* e = _score->selection().element();
      if (e) {
            if (e->type() == ElementType::VBOX || e->type() == ElementType::TBOX || e->type() == ElementType::HBOX)
                  return static_cast<MeasureBase*>(e);
            }
      QMessageBox::warning(0, "MuseScore",
            tr("No measure selected:\n" "Please select a measure and try again"));
      return 0;
      }

//---------------------------------------------------------
//   cmdRepeatSelection
//---------------------------------------------------------

void ScoreView::cmdRepeatSelection()
      {
      const Selection& selection = _score->selection();

      if (noteEntryMode() && selection.isSingle()) {
            Element* el = _score->selection().element();
            if (el && el->type() == ElementType::NOTE) {
                  if (!_score->inputState().endOfScore()) {
                        _score->startCmd();
                        bool addTo = false;
                        Chord* c = static_cast<Note*>(el)->chord();
                        for (Note* note : c->notes()) {
                              NoteVal nval = note->noteVal();
                              _score->addPitch(nval, addTo);
                              addTo = true;
                              }
                        _score->endCmd();
                        }
                  }
            return;
            }
      if (!selection.isRange()) {
            qDebug("wrong selection type");
            return;
            }

      if (!checkCopyOrCut())
            return;

      QString mimeType = selection.mimeType();
      if (mimeType.isEmpty()) {
            qDebug("mime type is empty");
            return;
            }
      QMimeData* mimeData = new QMimeData;
      mimeData->setData(mimeType, selection.mimeData());
      if (MScore::debugMode)
            qDebug("cmdRepeatSelection: <%s>", mimeData->data(mimeType).data());
      QApplication::clipboard()->setMimeData(mimeData);

      QByteArray d(mimeData->data(mimeType));
      XmlReader xml(d);
      xml.setPasteMode(true);

      int dStaff = selection.staffStart();
      Segment* endSegment = selection.endSegment();

      if (endSegment && endSegment->segmentType() != SegmentType::ChordRest)
            endSegment = endSegment->next1(SegmentType::ChordRest);
      if (endSegment && endSegment->element(dStaff * VOICES)) {
            Element* e = endSegment->element(dStaff * VOICES);
            if (e) {
                  ChordRest* cr = toChordRest(e);
                  _score->startCmd();
                  _score->pasteStaff(xml, cr->segment(), cr->staffIdx());
                  _score->endCmd();
                  }
            else
                  qDebug("ScoreView::cmdRepeatSelection: cannot paste: %p <%s>", e, e ? e->name() : "");
            }
      else {
            qDebug("cmdRepeatSelection: cannot paste: endSegment: %p dStaff %d", endSegment, dStaff);
            }
      }

//---------------------------------------------------------
//   searchPage
//---------------------------------------------------------

bool ScoreView::searchPage(int n)
      {
      bool result = true;
      n -= score()->pageNumberOffset();
      if (n <= 0) {
            n = 1;
            result = false;
            }
      n--;
      if (n >= _score->npages()) {
            result = false;
            n = _score->npages() - 1;
            }
      const Page* page = _score->pages()[n];
      foreach (System* s, page->systems()) {
            if (s->firstMeasure()) {
                  gotoMeasure(s->firstMeasure());
                  break;
                  }
            }
      return result;
      }

//---------------------------------------------------------
//   searchMeasure
//---------------------------------------------------------

bool ScoreView::searchMeasure(int n)
      {
      if (n <= 0)
            return false;
      bool result = true;
      --n;
      int i = 0;
      Measure* measure;
      for (measure = _score->firstMeasureMM(); measure; measure = measure->nextMeasureMM()) {
            int nn = _score->styleB(Sid::createMultiMeasureRests) && measure->isMMRest()
               ? measure->mmRestCount() : 1;
            if (n >= i && n < (i+nn))
                  break;
            i += nn;
            }
      if (!measure) {
            measure = score()->lastMeasureMM();
            result = false;
            }
      gotoMeasure(measure);
      return result;
      }

//---------------------------------------------------------
//   searchRehearsalMark
//---------------------------------------------------------

bool ScoreView::searchRehearsalMark(const QString& s)
      {
      //search rehearsal marks
      QString ss = s.toLower();
      bool found = false;
      for (Segment* seg = score()->firstSegment(SegmentType::ChordRest); seg; seg = seg->next1(SegmentType::ChordRest)) {
            for (Element* e : seg->annotations()){
                  if (e->type() == ElementType::REHEARSAL_MARK) {
                        RehearsalMark* rm = static_cast<RehearsalMark*>(e);
                        QString rms = rm->plainText().toLower();
                        if (rms.startsWith(ss)) {
                              gotoMeasure(seg->measure());
                              found = true;
                              break;
                              }
                        }
                  }
            if (found)
                  break;
            }
      return found;
      }

//---------------------------------------------------------
//   gotoMeasure
//---------------------------------------------------------

void ScoreView::gotoMeasure(Measure* measure)
      {
      adjustCanvasPosition(measure, state != ViewState::NORMAL);
      int tracks = _score->nstaves() * VOICES;
      for (Segment* segment = measure->first(); segment; segment = segment->next()) {
            if (segment->segmentType() != SegmentType::ChordRest)
                  continue;
            int track;
            for (track = 0; track < tracks; ++track) {
                  ChordRest* cr = static_cast<ChordRest*>(segment->element(track));
                  if (cr) {
                        Element* e;
                        if (cr->type() == ElementType::CHORD)
                              e =  static_cast<Chord*>(cr)->upNote();
                        else //REST
                              e = cr;

                        _score->select(e, SelectType::SINGLE, 0);
                        break;
                        }
                  }
            if (track != tracks)
                  break;
            }
      _score->setUpdateAll();
      _score->update();
      }

//---------------------------------------------------------
//   layoutChanged
//---------------------------------------------------------

void ScoreView::layoutChanged()
      {
      if (mscore->navigator())
            mscore->navigator()->layoutChanged();
      _curLoopIn->move(_score->pos(POS::LEFT));
      Measure* lm = _score->lastMeasure();
      if (lm && _score->pos(POS::RIGHT) > lm->endTick())
            _score->setPos(POS::RIGHT, lm->endTick());
      _curLoopOut->move(_score->pos(POS::RIGHT));
      }

//---------------------------------------------------------
//   elementLower
//---------------------------------------------------------

static bool elementLower(const Element* e1, const Element* e2)
      {
      if (!e1->selectable())
            return false;
      if (!e2->selectable())
            return true;
      if (e1->isNote() && e2->isStem())
            return true;
      if (e2->isNote() && e1->isStem())
            return false;
      if (e1->z() == e2->z()) {
            // same stacking order, prefer non-hidden elements
            if (e1->type() == e2->type()) {
                  if (e1->type() == ElementType::NOTEDOT) {
                        const NoteDot* n1 = static_cast<const NoteDot*>(e1);
                        const NoteDot* n2 = static_cast<const NoteDot*>(e2);
                        if (n1->note() && n1->note()->hidden())
                              return false;
                        else if (n2->note() && n2->note()->hidden())
                              return true;
                        }
                  else if (e1->type() == ElementType::NOTE) {
                        const Note* n1 = static_cast<const Note*>(e1);
                        const Note* n2 = static_cast<const Note*>(e2);
                        if (n1->hidden())
                              return false;
                        else if (n2->hidden())
                              return true;
                        }
                  }
            // different types, or same type but nothing hidden - use track
            return e1->track() <= e2->track();
            }

      // default case, use stacking order
      return e1->z() <= e2->z();
      }

//---------------------------------------------------------
//   elementsNear
//---------------------------------------------------------

QList<Element*> ScoreView::elementsNear(QPointF p)
      {
      QList<Element*> ll;
      Page* page = point2page(p);
      if (!page)
            return ll;

      p       -= page->pos();
      double w = (preferences.getInt(PREF_UI_CANVAS_MISC_SELECTIONPROXIMITY) * .5) / matrix().m11();
      QRectF r(p.x() - w, p.y() - w, 3.0 * w, 3.0 * w);

      QList<Element*> el = page->items(r);
      for (Element* e : el) {
            e->itemDiscovered = 0;
            if (!e->selectable() || e->isPage())
                  continue;
            if (e->contains(p))
                  ll.append(e);
            }
      int n = ll.size();
      if ((n == 0) || ((n == 1) && (ll[0]->isMeasure()))) {
            //
            // if no relevant element hit, look nearby
            //
            for (Element* e : el) {
                  if (e->isPage() || !e->selectable())
                        continue;
                  if (e->intersects(r))
                        ll.append(e);
                  }
            }
      if (!ll.empty())
            qSort(ll.begin(), ll.end(), elementLower);
      return ll;
      }

//---------------------------------------------------------
//   elementNear
//---------------------------------------------------------

Element* ScoreView::elementNear(QPointF p)
      {
      QList<Element*> ll = elementsNear(p);
      if (ll.empty()) {
            // qDebug("  nothing found");
            return 0;
            }
#if 0
      qDebug("==");
      for (const Element* e : ll)
            qDebug("  %s selected %d z %d", e->name(), e->selected(), e->z());
#endif
      Element* e = ll.at(0);
      return e;
      }

//---------------------------------------------------------
//   posChanged
//---------------------------------------------------------

void ScoreView::posChanged(POS pos, unsigned tick)
      {
      switch (pos) {
            case POS::CURRENT:
                  // draw playback cursor only in the currently active view
                  if (this != mscore->currentScoreView() && !_moveWhenInactive)
                        return;
                  if (noteEntryMode())
                        moveCursor();     // update input cursor position
                  else
                        moveCursor(Fraction::fromTicks(tick)); // update play position
                  break;
            case POS::LEFT:
                  _curLoopIn->move(_score->pos(POS::LEFT));
                  break;
            case POS::RIGHT:
                  _curLoopOut->move(_score->pos(POS::RIGHT));
                  break;
            }
      }

//---------------------------------------------------------
//   loopToggled
//---------------------------------------------------------

void ScoreView::loopToggled(bool val)
      {
      if (_score->lastMeasure() == 0)
            return;
      if (_score->pos(POS::LEFT).isZero() && _score->pos(POS::RIGHT).isZero())
            _score->setPos(POS::RIGHT, _score->lastMeasure()->endTick());
      _curLoopIn->move(_score->loopInTick());
      _curLoopOut->move(_score->loopOutTick());
      _curLoopIn->setVisible(val);
      _curLoopOut->setVisible(val);
      update();
      }

//---------------------------------------------------------
//   cmdMoveCR
//    swap selected cr with cr to the left or right
//      - not across measure boundaries
//---------------------------------------------------------

void ScoreView::cmdMoveCR(bool left)
      {
      Element* e = _score->getSelectedElement();
      if (e && (e->type() == ElementType::NOTE || e->type() == ElementType::REST)) {
            if (e->type() == ElementType::NOTE)
                  e = e->parent();
            QList<ChordRest*> crl;
            if (e->links()) {
                  for (ScoreElement* cr : *e->links())
                        crl.append(static_cast<ChordRest*>(cr));
                  }
            else
                  crl.append(static_cast<ChordRest*>(e));

            bool cmdActive = false;
            for (ChordRest* cr1 : crl) {
                  if (cr1->type() == ElementType::REST) {
                        Rest* r = static_cast<Rest*>(cr1);
                        if (r->measure() && r->measure()->isMMRest())
                              break;
                        }
                  ChordRest* cr2 = left ? prevChordRest(cr1) : nextChordRest(cr1);
                  if (cr2 && cr1->measure() == cr2->measure() && !cr1->tuplet() && !cr2->tuplet()
                      && cr1->durationType() == cr2->durationType() && cr1->ticks() == cr2->ticks()) {
                        if (!cmdActive) {
                              _score->startCmd();
                              cmdActive = true;
                              }
                        _score->undo(new SwapCR(cr1, cr2));
                        }
                  }
            if (cmdActive)
                  _score->endCmd();
            }
      }

//---------------------------------------------------------
//   cmdAddRemoveBreaks
///   add or remove line breaks within a range selection
///   or, if nothing is selected, the entire score
//---------------------------------------------------------

void ScoreView::cmdAddRemoveBreaks()
      {
      bool noSelection = !_score->selection().isRange();

      if (noSelection)
            _score->cmdSelectAll();
      else if (!_score->selection().isRange())
            return;

      BreaksDialog bd;
      if (!bd.exec())
            return;

      int interval = bd.remove || bd.lock ? 0 : bd.interval;

      _score->addRemoveBreaks(interval, bd.lock);

      if (noSelection)
             _score->deselectAll();
      }

//---------------------------------------------------------
//   cmdCopyLyricsToClipboard
///   Copy the score lyrics into clipboard
//---------------------------------------------------------

void ScoreView::cmdCopyLyricsToClipboard()
      {
      QApplication::clipboard()->setText(_score->extractLyrics());
      }

//---------------------------------------------------------
//   updateContinuousPanel
//   slot triggered when moving around the score to keep the panel visible
//---------------------------------------------------------

void ScoreView::updateContinuousPanel()
      {
      if (_score->layoutMode() == LayoutMode::LINE)
            update();
      }

//---------------------------------------------------------
//   updateShadowNotes
//---------------------------------------------------------

void ScoreView::updateShadowNotes()
      {
      if (shadowNote->visible())
            setShadowNote(shadowNote->pos());
      }

//---------------------------------------------------------
//   getEditElement
//---------------------------------------------------------

Element* ScoreView::getEditElement()
      {
      return editData.element;
      }

//---------------------------------------------------------
//   onElementDestruction
//---------------------------------------------------------

void ScoreView::onElementDestruction(Element* e)
      {
      if (editData.element == e) {
            editData.element = nullptr;
            if (editMode())
                  changeState(ViewState::NORMAL);
            }
      }

//---------------------------------------------------------
//   startNoteEntryMode
//---------------------------------------------------------

void ScoreView::startNoteEntryMode()
      {
      changeState(ViewState::NOTE_ENTRY);
      }

//---------------------------------------------------------
//   fotoMode
//---------------------------------------------------------

bool ScoreView::fotoMode() const
      {
      switch (state) {
            case ViewState::NORMAL:
            case ViewState::DRAG:
            case ViewState::DRAG_OBJECT:
            case ViewState::EDIT:
            case ViewState::DRAG_EDIT:
            case ViewState::LASSO:
            case ViewState::NOTE_ENTRY:
            case ViewState::PLAY:
            case ViewState::ENTRY_PLAY:
                  break;

            case ViewState::FOTO:
            case ViewState::FOTO_DRAG:
            case ViewState::FOTO_DRAG_EDIT:
            case ViewState::FOTO_DRAG_OBJECT:
            case ViewState::FOTO_LASSO:
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   setEditElement
//---------------------------------------------------------

void ScoreView::setEditElement(Element* e)
      {
      if (editData.element == e)
            return;

      const bool normalState = (state == ViewState::NORMAL);

      if (normalState && editData.element && editData.element->normalModeEditBehavior() == Element::EditBehavior::Edit)
            endEdit();

      editData.element = e;

      if (normalState && e && e->normalModeEditBehavior() == Element::EditBehavior::Edit)
            startEdit(/* editMode */ false);
      else if (editData.grips) {
            editData.grips = 0;
            editData.curGrip = Grip::NO_GRIP;
            }
      }

//---------------------------------------------------------
//   updateEditElement
//---------------------------------------------------------

void ScoreView::updateEditElement()
      {
      if (state != ViewState::NORMAL)
            return;

      if (!_score) {
            setEditElement(nullptr);
            return;
            }

      const Selection& sel = _score->selection();

      switch (sel.state()) {
            case SelState::NONE:
            case SelState::RANGE:
                  setEditElement(nullptr);
                  break;
            case SelState::LIST:
                  if (Element* e = sel.element()) {
                        if (editData.element == e)
                              updateGrips();
                        else
                              setEditElement(e);
                        }
                  else {
                        setEditElement(nullptr);
                        }
                  break;
            }
      }

//---------------------------------------------------------
//   visibleElementInScore
//---------------------------------------------------------

static const Element* visibleElementInScore(const Element* orig, const Score* s)
      {
      if (!orig)
            return nullptr;
      if (orig->score() == s && orig->bbox().isValid())
            return orig;

      for (const ScoreElement* se : orig->linkList()) {
            const Element* e = toElement(se);
            if (e->score() == s && e->bbox().isValid()) // bbox check to ensure the element is indeed visible
                  return e;
            }

      return nullptr;
      }

//---------------------------------------------------------
//   needViewportMove
//---------------------------------------------------------

static bool needViewportMove(Score* cs, ScoreView* cv)
      {
      if (!cs || !cv)
            return false;

      const CmdState& state = cs->cmdState();

      if (state.startTick() < Fraction(0, 1))
            return false;

      const QRectF viewport = cv->canvasViewport(); // TODO: margins for intersection check?

      const Element* editElement = visibleElementInScore(state.element(), cs);
      if (editElement && editElement->bbox().isValid() && !editElement->isSpanner())
            return !viewport.intersects(editElement->canvasBoundingRect());

      if (state.startTick().isZero() && state.endTick() == cs->endTick())
            return false; // TODO: adjust staff perhaps?

      // Is any part of the range visible?
      Measure* mStart = cs->tick2measureMM(state.startTick());
      Measure* mEnd = cs->tick2measureMM(state.endTick());
      mEnd = mEnd ? mEnd->nextMeasureMM() : nullptr;

      const bool isExcerpt = !cs->isMaster();
      const int startStaff = (isExcerpt || state.startStaff() < 0) ? 0 : state.startStaff();
      const int endStaff = (isExcerpt || state.endStaff() < 0) ? (cs->nstaves() - 1) : state.endStaff();

      for (Measure* m = mStart; m && m != mEnd; m = m->nextMeasureMM()) {
            for (int st = startStaff; st <= endStaff; ++st) {
                  const StaffLines* l = m->staffLines(st);
                  if (l) {
                        const QRectF r = l->bbox().translated(l->canvasPos());
                        if (viewport.intersects(r))
                              return false;
                        }
                  }
            }

      // maybe viewport is at least near the desired position?
      const QPointF p = viewport.center();
      int staffIdx = 0;
      const Measure* m = cs->pos2measure(p, &staffIdx, nullptr, nullptr, nullptr);
      if (m && m->tick() >= state.startTick() && m->tick() <= state.endTick())
            return false;

      return true;
      }

//---------------------------------------------------------
//   moveViewportToLastEdit
//---------------------------------------------------------

void ScoreView::moveViewportToLastEdit()
      {
      if (_blockShowEdit)
            return;

      Score* sc = score();

      if (!needViewportMove(sc, this))
            return;

      const CmdState& st = sc->cmdState();
      const Element* editElement = visibleElementInScore(st.element(), sc);

      const MeasureBase* mb = nullptr;
      if (editElement) {
            if (editElement->isSpannerSegment()) {
                  const SpannerSegment* s = toSpannerSegment(editElement);
                  Fraction tick = s->tick();
                  if (System* sys = s->system()) {
                        Measure* fm = sys->firstMeasure();
                        if (fm)
                              tick = std::max(tick, fm->tick());
                        }
                  mb = sc->tick2measureMM(tick);
                  }
            else {
                  mb = editElement->findMeasureBase();
                  }
            }
      if (!mb)
            mb = sc->tick2measureMM(st.startTick());

      const Element* viewportElement = (editElement && editElement->bbox().isValid() && !mb->isMeasure()) ? editElement : mb;

      const int staff = sc->isMaster() && mb->isMeasure() ? st.startStaff() : -1; // TODO: choose the closest staff to the current viewport?
      adjustCanvasPosition(viewportElement, /* playback */ false, staff);
      }
}
