//=============================================================================
//  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 "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 "scoretab.h"
#include "seq.h"
#include "splitstaff.h"
#include "textcursor.h"
#include "textpalette.h"
#include "texttools.h"
#include "fotomode.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/stafftext.h"
#include "libmscore/stafftype.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"
 
namespace Ms {
 
extern QErrorMessage* errorMessage;
 
//---------------------------------------------------------
//   ScoreView
//---------------------------------------------------------
 
ScoreView::ScoreView(QWidget* parent)
   : QWidget(parent), editData(this)
      {
      setStatusTip("scoreview");
      setAcceptDrops(true);
#ifndef Q_OS_MAC
      setAttribute(Qt::WA_OpaquePaintEvent);
#endif
      setAttribute(Qt::WA_NoSystemBackground);
      setFocusPolicy(Qt::ClickFocus);
      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.mag * (mscore->physicalDotsPerInch() / DPI);
      _matrix     = QTransform(mag, 0.0, 0.0, mag, 0.0, 0.0);
      imatrix     = _matrix.inverted();
      _magIdx     = preferences.mag == 1.0 ? MagIdx::MAG_100 : MagIdx::MAG_FREE;
      focusFrame  = 0;
//      dragElement = 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.bgUseColor)
            setBackground(MScore::bgColor);
      else {
            QPixmap* pm = new QPixmap(preferences.bgWallpaper);
            setBackground(pm);
            }
      if (preferences.fgUseColor)
            setForeground(preferences.fgColor);
      else {
            QPixmap* pm = new QPixmap(preferences.fgWallpaper);
            if (pm == 0 || pm->isNull())
                  qDebug("no valid pixmap %s", qPrintable(preferences.fgWallpaper));
            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->type() == ElementType::TEXT && obj->parent() && obj->parent()->type() == ElementType::TUPLET) {
            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
 
      a = popup->exec(pos);
      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);
            _score->endCmd();
            }
      }
 
//---------------------------------------------------------
//   measurePopup
//---------------------------------------------------------
 
void ScoreView::measurePopup(const QPoint& gpos, Measure* obj)
      {
      int staffIdx;
      int pitch;
      Segment* seg;
 
      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);
 
      if (staff->part()->instrument()->drumset()) {
            a = popup->addAction(tr("Drumroll Editor..."));
            a->setData("drumroll");
            }
      else {
            a = popup->addAction(tr("Pianoroll Editor..."));
            a->setData("pianoroll");
            }
 
      a = popup->addAction(tr("Staff 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"));
      popup->addAction(getAction("insert-measure"));
      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();
            mscore->editInPianoroll(staff);
            }
      else if (cmd == "staff-properties") {
            int tick = obj ? obj->tick() : -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();
            }
      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(int tick)
      {
      Measure* measure = score()->tick2measureMM(tick);
      if (measure == 0)
            return;
 
      qreal x;
      Segment* s;
      for (s = measure->first(SegmentType::ChordRest); s;) {
            int t1 = s->tick();
            int x1 = s->canvasPos().x();
            qreal x2;
            int t2;
            Segment* ns = s->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) {
                  int   dt = t2 - t1;
                  qreal dx = x2 - x1;
                  x = x1 + dx * (tick-t1) / dt;
                  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(StyleIdx::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(StyleIdx::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);
      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
//---------------------------------------------------------
 
int 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, int tick, bool isInPos)
      {
      //
      // set mark height for whole system
      //
      Measure* measure = score()->tick2measure(tick);
      if (measure == 0)
            return;
      qreal x;
 
      Segment* s;
      for (s = measure->first(SegmentType::ChordRest); s;) {
            int t1 = s->tick();
            int x1 = s->canvasPos().x();
            qreal x2;
            int 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) {
                  int   dt = t2 - t1;
                  qreal dx = x2 - x1;
                  x = x1 + dx * (tick-t1) / dt;
                  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(0);
      qreal relX    = pos.pos.x() - pos.segment->measure()->canvasPos().x();
      pos.pos.rx() -= qMin(relX - score()->styleP(StyleIdx::barNoteDistance) * mag, 0.0);
 
      shadowNote->setVisible(true);
      Staff* staff = score()->staff(pos.staffIdx);
      shadowNote->setMag(staff->mag(0));
      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.setDuration(d.fraction());
            symNotehead = rest.getSymbol(is.duration().type(), 0, staff->lines(pos.segment->tick()), &yo);
            shadowNote->setState(symNotehead, voice, d, true);
            }
      else {
            symNotehead = Note::noteHead(0, noteheadGroup, noteHead);
            shadowNote->setState(symNotehead, voice, d);
            }
 
      shadowNote->layout();
      shadowNote->setPos(pos.pos);
      }
 
//---------------------------------------------------------
//   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.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);
 
      lasso->draw(&vp);
      shadowNote->draw(&vp);
 
      if (!dropAnchor.isNull()) {
            QPen pen(QBrush(QColor(80, 0, 0)), 2.0 / vp.worldTransform().m11(), Qt::DotLine);
            vp.setPen(pen);
            vp.drawLine(dropAnchor);
 
            qreal d = 4.0 / vp.worldTransform().m11();
            QRectF r(-d, -d, 2 * d, 2 * d);
 
            vp.setBrush(QBrush(QColor(80, 0, 0)));
            vp.setPen(Qt::NoPen);
            r.moveCenter(dropAnchor.p1());
            vp.drawEllipse(r);
            r.moveCenter(dropAnchor.p2());
            vp.drawEllipse(r);
            }
      }
 
//---------------------------------------------------------
//   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()) {
                  QRectF f(page->canvasBoundingRect());
                  p.drawLine(f.right(), 0.0, f.right(), f.bottom());
                  }
            }
      }
 
//---------------------------------------------------------
//   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));
 
      switch (state) {
            case ViewState::NORMAL:
            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)
                        editData.element->drawEditMode(&p, editData);
                  break;
            }
 
      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);
                  qStableSort(ell.begin(), ell.end(), elementLessThan);
                  drawElements(p, ell);
                  }
            }
      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()));
                  qStableSort(ell.begin(), ell.end(), elementLessThan);
                  QPointF pos(page->pos());
                  p.translate(pos);
                  drawElements(p, ell);
 
#ifndef NDEBUG
                  if (!score()->printing()) {
                        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).draw(&p);
                                                            p.translate(-pt);
                                                            }
                                                      }
                                                }
                                          }
                                    }
                              }
                        if (MScore::showMeasureShapes) {
                              for (const System* system : page->systems()) {
                                    for (const MeasureBase* mb : system->measures()) {
                                          if (mb->isMeasure()) {
                                                const Measure* m = toMeasure(mb);
                                                p.setPen(Qt::NoPen);
                                                p.setBrush(QBrush(QColor(0, 0, 255, 60)));
                                                for (int staffIdx = 0; staffIdx < score()->nstaves(); ++staffIdx) {
                                                      Staff* staff = score()->staff(staffIdx);
//                                                      if (staff->show() && staff->part()->show()) {
                                                      if (staff->show()) {
                                                            QPointF pt(m->pos().x() + system->pos().x(), 0);
                                                            p.translate(pt);
                                                            QPointF o(0.0, m->system()->staffYpage(staffIdx));
                                                            m->staffShape(staffIdx).translated(o).draw(&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->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(0) + y;
            double y2 = ss2->y() + ss2->bbox().height() + 2 * score()->staff(lastStaff)->spatium(0) + 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->next1MM();
                  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()));
            }
      p.setMatrixEnabled(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);
            qreal hmargin = this->width() * 0.75;
            qreal vmargin = this->height() * 0.75;
            pagesRect.adjust(-hmargin, -vmargin, hmargin, vmargin);
            QRectF toPagesRect = pagesRect.translated(dx, dy);
 
            // move right
            if (dx > 0) {
                  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();
                              }
                        }
                  }
 
            // move down
            if (dy > 0) {
                  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;
      }
 
#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, const QList<Element*>& el)
      {
      for (const Element* e : el) {
            e->itemDiscovered = 0;
            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
            }
      }
 
//---------------------------------------------------------
//   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));
      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()
      {
      if (editData.element)
            editData.element->editCut(editData);
      }
 
//---------------------------------------------------------
//   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 (editData.element && editData.element->isText())
            toText(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
            int tickLen = 0, staves = 0;
            QByteArray data(ms->data(mimeStaffListFormat));
            XmlReader e(_score, data);
            e.readNextStartElement();
            if (e.name() == "StaffList") {
                  tickLen         = e.intAttribute("len", 0);
                  staves          = e.intAttribute("staves", 0);
                  }
            if (tickLen > 0) { // attempt to extend selection to match clipboard size
                  Segment* seg = _score->selection().startSegment();
                  int 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();
                  int 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 data(_score->selection().mimeData());
      if (this->normalPaste()) {
            QMimeData* mimeData = new QMimeData;
            mimeData->setData(mimeType, data);
            QApplication::clipboard()->setMimeData(mimeData);
            }
      }
 
//---------------------------------------------------------
//   normalPaste
//---------------------------------------------------------
 
bool ScoreView::normalPaste()
      {
      _score->startCmd();
      const QMimeData* ms = QApplication::clipboard()->mimeData();
      _score->cmdPaste(ms, this);
      bool rv = MScore::_error == MS_NO_ERROR;
      _score->endCmd();
      return rv;
      }
 
//---------------------------------------------------------
//   cmdGotoElement
//---------------------------------------------------------
 
void ScoreView::cmdGotoElement(Element* e)
      {
      if (e) {
            if (e->type() == ElementType::NOTE)
                  score()->setPlayNote(true);
            score()->select(e, SelectType::SINGLE, 0);
            if (e)
                  adjustCanvasPosition(e, false);
            moveCursor();
            }
      }
 
//---------------------------------------------------------
//   ticksTab
//---------------------------------------------------------
 
void ScoreView::ticksTab(int 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);
      }
 
void ScoreView::cmd(const char* s)
      {
      const QByteArray cmd(s);
 
      if (MScore::debugMode)
            qDebug("ScoreView::cmd <%s>", s);
 
      if (cmd == "escape")
            escapeCmd();
      else if (cmd == "note-input") {
            if (state == ViewState::NORMAL)
                  changeState(ViewState::NOTE_ENTRY);
            else if (state == ViewState::NOTE_ENTRY)
                  changeState(ViewState::NORMAL);
            }
      else if (cmd == "copy") {
            if (fotoMode())
                  fotoModeCopy();
            else if (state == ViewState::NORMAL)
                  normalCopy();
            else if (state == ViewState::EDIT)
                  editCopy();
            }
      else if (cmd == "cut") {
            if (state == ViewState::NORMAL)
                  normalCut();
            else if (state == ViewState::EDIT)
                  editCut();
            }
      else if (cmd == "paste") {
            if (state == ViewState::NORMAL)
                  normalPaste();
            else if (state == ViewState::EDIT)
                  editPaste();
            }
      else if (cmd == "swap") {
            if (state == ViewState::NORMAL)
                  normalSwap();
            else if (state == ViewState::EDIT)
                  editSwap();
            }
      else if (cmd == "lyrics") {
            Lyrics* lyrics = _score->addLyrics();
            if (lyrics) {
                  startEditMode(lyrics);
                  return;
                  }
            }
      else if (cmd == "figured-bass") {
            changeState(ViewState::NOTE_ENTRY);
            FiguredBass* fb = _score->addFiguredBass();
            if (fb) {
                  startEditMode(fb);
                  return;
                  }
            }
      else if (cmd == "mag") {
            // ??
            }
      else if (cmd == "play") {
            if (state == ViewState::NORMAL || state == ViewState::NOTE_ENTRY)
                  changeState(ViewState::PLAY);
            else if (state == ViewState::PLAY)
                  changeState(ViewState::NORMAL);
            }
      else if (cmd == "fotomode") {
            if (state == ViewState::NORMAL)
                  changeState(ViewState::FOTO);
            else if (fotoMode())
                  changeState(ViewState::NORMAL);
            }
      else if (cmd == "add-slur")
            addSlur();
      else if (cmd == "add-hairpin")
            cmdAddHairpin(HairpinType::CRESC_HAIRPIN);
      else if (cmd == "add-hairpin-reverse")
            cmdAddHairpin(HairpinType::DECRESC_HAIRPIN);
      else if (cmd == "add-noteline")
            cmdAddNoteLine();
      else if (cmd == "chord-text") {
            changeState(ViewState::NORMAL);
            cmdAddChordName();
            }
      else if (cmd == "title-text")
            cmdAddText(TEXT::TITLE);
      else if (cmd == "subtitle-text")
            cmdAddText(TEXT::SUBTITLE);
      else if (cmd == "composer-text")
            cmdAddText(TEXT::COMPOSER);
      else if (cmd == "poet-text")
            cmdAddText(TEXT::POET);
      else if (cmd == "part-text")
            cmdAddText(TEXT::PART);
      else if (cmd == "system-text")
            cmdAddText(TEXT::SYSTEM);
      else if (cmd == "staff-text")
            cmdAddText(TEXT::STAFF);
      else if (cmd == "expression-text")
            cmdAddText(TEXT::EXPRESSION);
      else if (cmd == "rehearsalmark-text")
            cmdAddText(TEXT::REHEARSAL_MARK);
      else if (cmd == "instrument-change-text")
            cmdAddText(TEXT::INSTRUMENT_CHANGE);
      else if (cmd == "fingering-text")
            cmdAddText(TEXT::FINGERING);
 
      else if (cmd == "edit-element") {
            Element* e = _score->selection().element();
            if (e && e->isEditable()) {
                  startEditMode(e);
                  }
            }
      else if (cmd == "select-similar") {
            if (_score->selection().isSingle()) {
                  Element* e = _score->selection().element();
                  mscore->selectSimilar(e, false);
                  }
            }
      else if (cmd == "select-similar-staff") {
            if (_score->selection().isSingle()) {
                  Element* e = _score->selection().element();
                  mscore->selectSimilar(e, true);
                  }
            }
      else if (cmd == "select-dialog") {
            if (_score->selection().isSingle()) {
                  Element* e = _score->selection().element();
                  mscore->selectElementDialog(e);
                  }
            }
      else if (cmd == "play") {
            if (seq && seq->canStart())
                  changeState(ViewState::PLAY);
            else
                  getAction("play")->setChecked(false);
            }
//      else if (cmd == "find")
//            ; // TODO:state         sm->postEvent(new CommandEvent(cmd));
      else if (cmd == "page-prev")
            pagePrev();
      else if (cmd == "page-next")
            pageNext();
      else if (cmd == "page-top")
            pageTop();
      else if (cmd == "page-end")
            pageEnd();
      else if (cmd == "select-next-chord"
         || cmd == "select-prev-chord"
         || cmd == "select-next-measure"
         || cmd == "select-prev-measure"
         || cmd == "select-begin-line"
         || cmd == "select-end-line"
         || cmd == "select-begin-score"
         || cmd == "select-end-score"
         || cmd == "select-staff-above"
         || cmd == "select-staff-below") {
            Element* el = _score->selectMove(cmd);
            if (el)
                  adjustCanvasPosition(el, false);
            updateAll();
            }
      else if (cmd == "next-chord"
         || cmd == "prev-chord"
         || cmd == "next-track"
         || cmd == "prev-track"
         || cmd == "next-measure"
         || cmd == "prev-measure") {
            Element* el = score()->selection().element();
            if (el && (el->isText())) {
                  score()->startCmd();
                  if (cmd == "prev-chord")
                        el->undoChangeProperty(P_ID::USER_OFF, el->userOff() - QPointF (MScore::nudgeStep * el->spatium(), 0.0));
                  else if (cmd == "next-chord")
                        el->undoChangeProperty(P_ID::USER_OFF, el->userOff() + QPointF (MScore::nudgeStep * el->spatium(), 0.0));
                  else if (cmd == "prev-measure")
                        el->undoChangeProperty(P_ID::USER_OFF, el->userOff() - QPointF (MScore::nudgeStep10 * el->spatium(), 0.0));
                  else if (cmd == "next-measure")
                        el->undoChangeProperty(P_ID::USER_OFF, el->userOff() + QPointF (MScore::nudgeStep10 * el->spatium(), 0.0));
                  score()->endCmd();
                  }
            else {
                  Element* el = _score->move(cmd);
                  if (el)
                        adjustCanvasPosition(el, false);
                  updateAll();
                  }
            }
      else if (cmd == "pitch-up-diatonic")
            score()->upDown(true, UpDownMode::DIATONIC);
      else if (cmd == "pitch-down-diatonic")
            score()->upDown(false, UpDownMode::DIATONIC);
      else if (cmd == "move-up") {
            QList<Element*> el = 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)
                        score()->moveUp(cr);
                  }
            }
      else if (cmd == "move-down") {
            QList<Element*> el = 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)
                        score()->moveDown(cr);
                  }
            }
      else if (cmd == "up-chord") {
            Element* el = score()->selection().element();
            if (el && (el->isNote() || el->isRest()))
                  cmdGotoElement(score()->upAlt(el));
            el = score()->selection().element();
            while (el->isRest() && toRest(el)->isGap() && el->voice() != 0) {
                  el = score()->upAlt(el);
                  cmdGotoElement(el);
                  }
            }
      else if (cmd == "down-chord") {
            Element* el = score()->selection().element();
            Element* oel = el;
            if (el && (el->isNote() || el->isRest()))
                  cmdGotoElement(score()->downAlt(el));
            el = score()->selection().element();
            while (el->isRest() && toRest(el)->isGap() && el->voice() != 3) {
                  if (score()->downAlt(el) == el) {
                        cmdGotoElement(oel);
                        break;
                        }
                  el = score()->downAlt(el);
                  cmdGotoElement(el);
                  }
            }
      else if (cmd == "top-chord" ) {
            Element* el = score()->selection().element();
            if (el && el->type() == ElementType::NOTE)
                  cmdGotoElement(score()->upAltCtrl(static_cast<Note*>(el)));
            }
      else if (cmd == "bottom-chord") {
            Element* el = score()->selection().element();
            if (el && el->type() == ElementType::NOTE)
                  cmdGotoElement(score()->downAltCtrl(static_cast<Note*>(el)));
            }
      else if (cmd == "next-segment-element") {
            Element* el = score()->selection().element();
            if (!el && !score()->selection().elements().isEmpty() )
                el = score()->selection().elements().first();
 
            if (el)
                  cmdGotoElement(el->nextSegmentElement());
            else
                  cmdGotoElement(score()->firstElement());
            }
      else if (cmd == "prev-segment-element") {
            Element* el = score()->selection().element();
            if (!el && !score()->selection().elements().isEmpty())
                el = score()->selection().elements().last();
 
            if (el)
                  cmdGotoElement(el->prevSegmentElement());
            else
                  cmdGotoElement(score()->lastElement());
            }
      else if (cmd == "next-element") {
            Element* el = score()->selection().element();
            if (!el && !score()->selection().elements().isEmpty() )
                el = score()->selection().elements().first();
 
            if (el)
                  cmdGotoElement(score()->nextElement());
            else
                  cmdGotoElement(score()->firstElement());
            }
      else if (cmd == "prev-element") {
            Element* el = score()->selection().element();
            if (!el && !score()->selection().elements().isEmpty())
                el = score()->selection().elements().last();
 
            if (el)
                  cmdGotoElement(score()->prevElement());
            else
                  cmdGotoElement(score()->lastElement());
            }
      else if (cmd == "first-element") {
            cmdGotoElement(score()->firstElement());
            }
      else if (cmd == "last-element") {
            cmdGotoElement(score()->lastElement());
            }
      else if (cmd == "rest" || cmd == "rest-TAB")
            cmdEnterRest();
      else if (cmd == "rest-1")
            cmdEnterRest(TDuration(TDuration::DurationType::V_WHOLE));
      else if (cmd == "rest-2")
            cmdEnterRest(TDuration(TDuration::DurationType::V_HALF));
      else if (cmd == "rest-4")
            cmdEnterRest(TDuration(TDuration::DurationType::V_QUARTER));
      else if (cmd == "rest-8")
            cmdEnterRest(TDuration(TDuration::DurationType::V_EIGHTH));
      else if (cmd.startsWith("interval")) {
            int n = cmd.mid(8).toInt();
            std::vector<Note*> nl = _score->selection().noteList();
            if (!nl.empty()) {
                  //if (!noteEntryMode())
                  //      ;     // TODO: state    sm->postEvent(new CommandEvent("note-input"));
                  _score->cmdAddInterval(n, nl);
                  }
            }
      else if (cmd == "tie") {
            _score->cmdAddTie();
            moveCursor();
            }
      else if (cmd == "duplet")
            cmdTuplet(2);
      else if (cmd == "triplet")
            cmdTuplet(3);
      else if (cmd == "quadruplet")
            cmdTuplet(4);
      else if (cmd == "quintuplet")
            cmdTuplet(5);
      else if (cmd == "sextuplet")
            cmdTuplet(6);
      else if (cmd == "septuplet")
            cmdTuplet(7);
      else if (cmd == "octuplet")
            cmdTuplet(8);
      else if (cmd == "nonuplet")
            cmdTuplet(9);
      else if (cmd == "tuplet-dialog") {
            _score->startCmd();
            Tuplet* tuplet = mscore->tupletDialog();
            if (tuplet)
                  cmdCreateTuplet(_score->getSelectedChordRest(), tuplet);
            _score->endCmd();
            moveCursor();
            }
      else if (cmd == "repeat-sel")
            cmdRepeatSelection();
      else if (cmd == "voice-1")
            changeVoice(0);
      else if (cmd == "voice-2")
            changeVoice(1);
      else if (cmd == "voice-3")
            changeVoice(2);
      else if (cmd == "voice-4")
            changeVoice(3);
      else if (cmd == "enh-both")
            cmdChangeEnharmonic(true);
      else if (cmd == "enh-current")
            cmdChangeEnharmonic(false);
      else if (cmd == "revision") {
            Score* s = _score->masterScore();
            s->createRevision();
            }
      else if (cmd == "append-measure")
            cmdAppendMeasures(1, ElementType::MEASURE);
      else if (cmd == "insert-measure")
          cmdInsertMeasures(1, ElementType::MEASURE);
      else if (cmd == "insert-hbox")
          cmdInsertMeasures(1, ElementType::HBOX);
      else if (cmd == "insert-vbox")
          cmdInsertMeasures(1, ElementType::VBOX);
      else if (cmd == "append-hbox") {
          MeasureBase* mb = appendMeasure(ElementType::HBOX);
            _score->select(mb, SelectType::SINGLE, 0);
            }
      else if (cmd == "append-vbox") {
          MeasureBase* mb = appendMeasure(ElementType::VBOX);
            _score->select(mb, SelectType::SINGLE, 0);
            }
      else if (cmd == "insert-textframe")
            cmdInsertMeasure(ElementType::TBOX);
      else if (cmd == "append-textframe") {
            MeasureBase* mb = 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) {
                        _score->select(text, SelectType::SINGLE, 0);
                        startEditMode(text);
                        }
                  }
            }
      else if (cmd == "insert-fretframe")
            cmdInsertMeasure(ElementType::FBOX);
      else if (cmd == "move-left")
            cmdMoveCR(true);
      else if (cmd == "move-right")
            cmdMoveCR(false);
      else if (cmd == "reset") {
            if (editMode()) {
                  editData.element->reset();
                  updateGrips();
                  _score->update();
                  }
            else {
                  _score->startCmd();
                  for (Element* e : _score->selection().elements()) {
                        e->reset();
                        if (e->isSpanner()) {
                              Spanner* sp = toSpanner(e);
                              for (SpannerSegment* ss : sp->spannerSegments())
                                    ss->reset();
                              }
                        }
                  _score->endCmd();
                  }
            }
#ifdef OMR
      else if (cmd == "show-omr") {
            if (_score->masterScore()->omr())
                  showOmr(!_score->masterScore()->showOmr());
            }
#endif
      else if (cmd == "split-measure") {
            Element* e = _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);
                  _score->cmdSplitMeasure(cr);
                  }
            }
      else if (cmd == "join-measures") {
            Measure* m1;
            Measure* m2;
            if (!_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 {
                  _score->cmdJoinMeasure(m1, m2);
                  }
            }
      else if (cmd == "next-lyric" || cmd == "prev-lyric")
            editCmd(cmd);
      else if (cmd == "add-remove-breaks")
            cmdAddRemoveBreaks();
      else if (cmd == "copy-lyrics-to-clipboard")
            cmdCopyLyricsToClipboard();
 
      // STATE_NOTE_ENTRY_REALTIME actions (auto or manual)
 
      else if (cmd == "realtime-advance")
            realtimeAdvance(true);
 
      // STATE_HARMONY_FIGBASS_EDIT actions
 
      else if (cmd == "advance-longa")
            ticksTab(MScore::division << 4);
      else if (cmd == "advance-breve")
            ticksTab(MScore::division << 3);
      else if (cmd == "advance-1")
            ticksTab(MScore::division << 2);
      else if (cmd == "advance-2")
            ticksTab(MScore::division << 1);
      else if (cmd == "advance-4")
            ticksTab(MScore::division);
      else if (cmd == "advance-8")
            ticksTab(MScore::division >> 1);
      else if (cmd == "advance-16")
            ticksTab(MScore::division >> 2);
      else if (cmd == "advance-32")
            ticksTab(MScore::division >> 3);
      else if (cmd == "advance-64")
            ticksTab(MScore::division >> 4);
      else if (cmd == "prev-measure-TEXT") {
            if (editData.element->isHarmony())
                  harmonyTab(true);
            else if (editData.element->isFiguredBass())
                  figuredBassTab(true, true);
            }
      else if (cmd == "next-measure-TEXT") {
            if (editData.element->isHarmony())
                  harmonyTab(false);
            else if (editData.element->isFiguredBass())
                  figuredBassTab(true, false);
            }
      else if (cmd == "prev-beat-TEXT") {
            if (editData.element->isHarmony())
                  harmonyBeatsTab(false, true);
            }
      else if (cmd == "next-beat-TEXT") {
            if (editData.element->isHarmony())
                  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()
      else if(cmd == "string-above" || cmd == "string-below") {
            InputState& is          = _score->inputState();
            Staff*      staff       = _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
                  moveCursor();
                  }
            }
#if 0       // TODO
      else if (cmd == "text-word-left")
            static_cast<Text*>(editData.element)->movePosition(QTextCursor::WordLeft);
      else if (cmd == "text-word-right")
            static_cast<Text*>(editData.element)->movePosition(QTextCursor::NextWord);
#endif
      else if (cmd == "concert-pitch") {
            QAction* a = getAction(cmd);
            if (_score->styleB(StyleIdx::concertPitch) != a->isChecked()) {
                  _score->startCmd();
                  _score->cmdConcertPitchChanged(a->isChecked(), true);
                  _score->endCmd();
                  }
            }
      else {
            editData.view = this;
            QAction* a = getAction(cmd);
            _score->cmd(a, editData);
            }
      if (_score->processMidiInput())
            mscore->endCmd();
      }
 
//---------------------------------------------------------
//   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->setCurrent(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
            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;
                  int tlTick = 0;
                  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);
                  for (Element* e : el) {
                        // loop through visible elements
                        // looking for the CR in voice 1 with earliest tick and highest staff position
                        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);
                                    }
                              // 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
                              int crTick = 0;
                              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().activeCR() ? _score->selection().activeCR() : _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
            while (el && el->type() != ElementType::MEASURE)
                  el = el->parent();
            int tick = el ? static_cast<Measure*>(el)->tick() : 0;
            el = _score->searchNote(tick, track);
            if (!el)
                  el = _score->searchNote(0, 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));
 
      _score->select(el, SelectType::SINGLE, 0);
      is.update(el);
      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 QList<SpannerSegment*>& el = is.slur()->spannerSegments();
            if (!el.isEmpty())
                  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);
 
      _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();
      }
 
#if 0
//---------------------------------------------------------
//   onEditPasteTransition
//---------------------------------------------------------
 
void ScoreView::onEditPasteTransition(QMouseEvent* ev)
      {
      editData.startMove = imatrix.map(QPointF(ev->pos()));
      Element* e = elementNear(editData.startMove);
      if (e == editData.element && editData.element->mousePress(editData)) {
            _score->addRefresh(editData.element->canvasBoundingRect());
            _score->update();
            }
      }
#endif
 
//---------------------------------------------------------
//   editScoreViewDragTransition
//    Check for mouse click outside of editData.element.
//---------------------------------------------------------
 
bool ScoreView::editScoreViewDragTransition(QMouseEvent* ev)
      {
      QPointF p = toLogical(ev->pos());
      Element* e = elementNear(p);
 
      if (e == 0 || e->type() == ElementType::MEASURE) {
            editData.startMove   = p;
//TODOxxx            dragElement = e;
            return true;
            }
      return false;
      }
 
//---------------------------------------------------------
//   editSelectTransition
//    Check for mouse click outside of editData.element.
//---------------------------------------------------------
 
bool ScoreView::editSelectTransition(QMouseEvent* ev)
      {
      QPointF p = toLogical(ev->pos());
      Element* e = elementNear(p);
 
      if (e != editData.element) {
            editData.startMove   = p;
//TODOxxx            dragElement = e;
            return true;
            }
      return false;
      }
 
//---------------------------------------------------------
//   doDragLasso
//---------------------------------------------------------
 
void ScoreView::doDragLasso(QMouseEvent* ev)
      {
      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();
      }
 
//---------------------------------------------------------
//   editInputTransition
//---------------------------------------------------------
 
#if 0
void ScoreView::editInputTransition(QInputMethodEvent* ie)
      {
      if (editData.element->isText()) {
            toText(editData.element)->inputTransition(ie);
            QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
            }
      }
#endif
 
//---------------------------------------------------------
//   inputMethodQuery
//---------------------------------------------------------
 
QVariant ScoreView::inputMethodQuery(Qt::InputMethodQuery query) const
      {
#if 0       // TODO
      // if editing a text object, place the InputMethod popup window just below the text
      if ((query & Qt::ImCursorRectangle) && editData.element && editData.element->isText()) {
            Text* text = toText(editData.element);
            if (text->cursor()) {
                  QRectF cursorRect = toPhysical(text->cursorRect().translated(text->canvasPos()));
                  cursorRect.setWidth(1.0); // InputMethod doesn't display properly if width left at 0
                  cursorRect.setHeight(cursorRect.height() + 5.0); // add a little margin under the cursor
                  qDebug("cursorRect: [%3f,%3f,%3f,%3f]", cursorRect.x(), cursorRect.y(), cursorRect.width(), cursorRect.height());
                  return QVariant(cursorRect);
                  }
            else
                  return QVariant(toPhysical(text->canvasBoundingRect()));
            }
#endif
      return QWidget::inputMethodQuery(query); // fall back to QWidget's version as default
      }
 
//---------------------------------------------------------
//   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());
      }
 
//---------------------------------------------------------
//   pageNext
//---------------------------------------------------------
 
void ScoreView::pageNext()
      {
      if (score()->pages().empty())
            return;
      if (score()->layoutMode() == LayoutMode::LINE) {
            qreal x = xoffset() - width() * .8;
            MeasureBase* lm = score()->last();
            qreal lx = (lm->pos().x() + lm->width()) * mag() - width() * .8;
            if (x < -lx)
                  x = -lx;
            setOffset(x, yoffset());
            }
      else {
            Page* page = score()->pages().back();
            qreal x, y;
            if (MScore::verticalOrientation()) {
                  x        = 10.0;
                  y        = yoffset() - (page->height() + 25.0) * mag();
                  qreal ly = 10.0 - page->pos().y() * mag();
                  if (y < ly)
                        y = ly;
                  }
            else {
                  y        = 10.0;
                  x        = xoffset() - (page->width() + 25.0) * mag();
                  qreal lx = 10.0 - page->pos().x() * mag();
                  if (x < lx)
                        x = lx;
                  }
            setOffset(x, y);
            }
      update();
      }
 
//---------------------------------------------------------
//   pagePrev
//---------------------------------------------------------
 
void ScoreView::pagePrev()
      {
      if (score()->pages().empty())
            return;
      if (score()->layoutMode() == LayoutMode::LINE) {
            qreal x = xoffset() + width() * .8;
            if (x > 10.0)
                  x = 10.0;
            setOffset(x, yoffset());
            }
      else {
            Page* page = score()->pages().front();
            qreal x, y;
            if (MScore::verticalOrientation()) {
                  x  = 10.0;
                  y  = yoffset() + (page->height() + 25.0) * mag();
                  if (y > 10.0)
                        y = 10.0;
                  }
            else {
                  y  = 10.0;
                  x    = xoffset() + (page->width() + 25.0) * mag();
                  if (x > 10.0)
                        x = 10.0;
                  }
            setOffset(x, y);
            }
      update();
      }
 
//---------------------------------------------------------
//   pageTop
//---------------------------------------------------------
 
void ScoreView::pageTop()
      {
      if (score()->layoutMode() == LayoutMode::LINE)
            setOffset(10.0, 0.0);
      else
            setOffset(10.0, 10.0);
      update();
      }
 
//---------------------------------------------------------
//   pageEnd
//---------------------------------------------------------
 
void ScoreView::pageEnd()
      {
      if (score()->pages().empty())
            return;
      if (score()->layoutMode() == LayoutMode::LINE) {
            MeasureBase* lm = score()->last();
            qreal lx = (lm->canvasPos().x() + lm->width()) * mag();
            lx -= width() * .8;
            setOffset(-lx, yoffset());
            }
      else {
            Page* lastPage = score()->pages().back();
            QPointF p(lastPage->pos());
            if (MScore::verticalOrientation()) {
                  setOffset(25.0, 25 - p.y() * mag());
                  }
            else {
                  setOffset(25.0 - p.x() * mag(), 25.0);
                  }
            }
      update();
      }
 
//---------------------------------------------------------
//   adjustCanvasPosition
//---------------------------------------------------------
 
void ScoreView::adjustCanvasPosition(const Element* el, bool playBack, int staff )
      {
      if (this != mscore->currentScoreView())
            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
            // at one point, the code used _cursor->rect(), but this only works during note entry or playback
            // see issue #33391
            QRectF curPos = el->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();
 
            if (round(curPosMagR) > round(width() - marginRight)) {
                  xo = -curPosL * mag() + marginLeft;
 
                  // Keeps the score up to the right to avoid blank gap on the right.
                  qreal scoreEnd = score()->pages().front()->width() * mag() + xo;
                  if (scoreEnd < width())
                        xo += width() - scoreEnd;
 
                  setOffset(xo, yoffset());
                  update();
                  }
            else if (round(curPosMagL) < round(marginLeft) ) {
                  xo = -curPosR * mag() + width() - marginRight;
 
                  // Bring back the score to the left to avoid blank gap on the left.
                  if (xo > 10)
                        xo = 10;
 
                  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->type() == ElementType::MEASURE || el->type() == ElementType::VBOX)
            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->type() != ElementType::MEASURE)
                  e = e->parent();
            if (e)
                  m = static_cast<Measure*>(e);
            else
                  return;
            }
      if (!m)
            return;
 
      int staffIdx = el->staffIdx();
      System* sys = m->system();
      if (!sys)
            return;
 
      QPointF p(el->canvasPos());
      QRectF r(imatrix.mapRect(geometry()));
      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()) {
            showRect.setX(p.x());
            showRect.setWidth(el->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 (mscore->state() & ScoreState::STATE_NOTE_ENTRY) {
            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 (!MScore::verticalOrientation()) {
            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();
                  }
      else {
            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()
      {
      const InputState& is = _score->inputState();
      if (is.track() == -1 || is.segment() == 0)          // invalid state
            return;
      cmdEnterRest(is.duration());
      }
 
//---------------------------------------------------------
//   cmdEnterRest
//---------------------------------------------------------
 
void ScoreView::cmdEnterRest(const TDuration& d)
      {
//TODO      if (!noteEntryMode())
//            sm->postEvent(new CommandEvent("note-input"));
//      if (_score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM))
//            _score->cmd(getAction("pad-rest"));
//      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->type() == ElementType::LYRICS))
                  return STATE_LYRICS_EDIT;
            else if (editData.element &&
                  ( (editData.element->type() == ElementType::HARMONY) || editData.element->type() == ElementType::FIGURED_BASS) )
                  return STATE_HARMONY_FIGBASS_EDIT;
            else if (editData.element && editData.element->isText())
                  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
      }
 
//---------------------------------------------------------
//   addSlur
//    command invoked, or icon double clicked
//---------------------------------------------------------
 
void ScoreView::addSlur()
      {
      InputState& is = _score->inputState();
      if (noteEntryMode() && is.slur()) {
            const QList<SpannerSegment*>& el = is.slur()->spannerSegments();
            if (!el.isEmpty())
                  el.front()->setSelected(false);
            is.setSlur(nullptr);
            return;
            }
      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->isChordRest())
                              continue;
                        ChordRest* cr = toChordRest(e);
                        if (!cr1 || cr1->tick() > cr->tick())
                              cr1 = cr;
                        if (!cr2 || cr2->tick() < cr->tick())
                              cr2 = cr;
                        }
                  if (cr1)
                        cmdAddSlur(cr1, cr2);
                  }
            }
      else {
            cr1 = 0;
            cr2 = 0;
            for (Element* e : el) {
                  if (e->isNote())
                        e = toNote(e)->chord();
                  if (!e->isChordRest())
                        continue;
                  ChordRest* cr = toChordRest(e);
                  if (!cr1 || cr1->tick() > cr->tick())
                        cr1 = cr;
                  if (!cr2 || cr2->tick() < cr->tick() || (cr1 && cr1->parent() == cr))
                        cr2 = cr;
                  }
            if (cr1 == cr2)
                  cr2 = 0;
            if (cr1)
                  cmdAddSlur(cr1, cr2);
            }
      }
 
//---------------------------------------------------------
//   cmdAddSlur
//---------------------------------------------------------
 
void ScoreView::cmdAddSlur(ChordRest* cr1, ChordRest* cr2)
      {
      bool startEditMode = false;
      if (cr2 == 0) {
            cr2 = nextChordRest(cr1);
            if (cr2 == 0)
                  cr2 = cr1;
            startEditMode = true;      // start slur in edit mode if last chord is not given
            }
 
      _score->startCmd();
 
      Slur* slur = new Slur(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));
      ss->setAutoplace(false);
      slur->add(ss);
 
      _score->endCmd();
 
      if (noteEntryMode()) {
            _score->inputState().setSlur(slur);
            ss->setSelected(true);
            }
      else if (startEditMode) {
            editData.element = ss;
            changeState(ViewState::EDIT);
            }
      }
 
//---------------------------------------------------------
//   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);
                  if (!cr1)
                       continue;
                  if (cr2 == 0)
                       cr2 = cr1;
                  _score->addHairpin(type, cr1->tick(), cr2->tick() + cr2->actualTicks(), cr1->track());
                  }
            _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);
            if (!cr1)
                  return;
            if (cr2 == 0)
                  cr2 = cr1;
 
            _score->startCmd();
            int tick2 = twoNotesSameStaff ? cr2->tick() : cr2->tick() + cr2->actualTicks();
            Hairpin* pin = _score->addHairpin(type, cr1->tick(), tick2, cr1->track());
            pin->layout();
            _score->endCmd();
 
            const QList<SpannerSegment*>& el = pin->spannerSegments();
            if (!noteEntryMode()) {
                  if (!el.isEmpty()) {
                        editData.element = el.front();
                        changeState(ViewState::EDIT);
                        }
                  }
            }
      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();
      Selection selection = _score->selection();
      QList<Note*> notes = 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) {
                        score()->undoChangeProperty(n, P_ID::FRET, fret);
                        score()->undoChangeProperty(n, P_ID::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(P_ID::TPC2, t);
                              else
                                    n->undoChangeProperty(P_ID::TPC1, t);
                              }
                        }
                  }
            }
 
      selection.clear();
      for (Note* n : notes)
            selection.add(n);
      _score->endCmd();
      }
 
//---------------------------------------------------------
//   cloneElement
//---------------------------------------------------------
 
void ScoreView::cloneElement(Element* e)
      {
      if (e->isMeasure() || e->isNote() || e->isVBox() || e->isSpacer())
            return;
      QDrag* drag = new QDrag(this);
      QMimeData* mimeData = new QMimeData;
      if (e->isSegment())
            e = static_cast<SpannerSegment*>(e)->spanner();
      mimeData->setData(mimeSymbolFormat, e->mimeData(QPointF()));
      drag->setMimeData(mimeData);
      drag->setPixmap(QPixmap());
      drag->start(Qt::CopyAction);
      }
 
//---------------------------------------------------------
//   changeEditElement
//---------------------------------------------------------
 
void ScoreView::changeEditElement(Element* e)
      {
      Grip grip = editData.curGrip;
      endEdit();
      startEdit(e, grip);
      }
 
//---------------------------------------------------------
//   setCursorVisible
//---------------------------------------------------------
 
void ScoreView::setCursorVisible(bool v)
      {
      _cursor->setVisible(v);
      }
 
//---------------------------------------------------------
//   cmdTuplet
//---------------------------------------------------------
 
void ScoreView::cmdTuplet(int n, ChordRest* cr)
      {
      if (cr->durationType() < TDuration(TDuration::DurationType::V_128TH) && cr->durationType() != TDuration(TDuration::DurationType::V_MEASURE)) {
            mscore->noteTooShortForTupletDialog();
            return;
            }
      Measure* measure = cr->measure();
      if (measure && measure->isMMRest())
            return;
 
      Fraction f(cr->duration());
      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 /= 2;
            fr    /= 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->setDuration(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();
      int 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;
      is->setTrack(track);
 
      if (is->noteEntryMode()) {
            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);
            }
      }
 
//---------------------------------------------------------
//   modifyElement
//---------------------------------------------------------
 
void ScoreView::modifyElement(Element* el)
      {
      if (el == 0) {
            qDebug("modifyElement: el==0");
            return;
            }
      Score* cs = el->score();
      if (!cs->selection().isSingle()) {
            qDebug("modifyElement: cs->selection().state() != SelState::SINGLE");
            delete el;
            return;
            }
      Element* e = cs->selection().element();
      Chord* chord;
      if (e->type() == ElementType::CHORD)
            chord = static_cast<Chord*>(e);
      else if (e->type() == ElementType::NOTE)
            chord = static_cast<Note*>(e)->chord();
      else {
            qDebug("modifyElement: no note/Chord selected:");
            e->dump();
            delete el;
            return;
            }
      switch (el->type()) {
            case ElementType::ARTICULATION:
                  chord->add(static_cast<Articulation*>(el));
                  break;
            default:
                  qDebug("modifyElement: %s not ARTICULATION", el->name());
                  delete el;
                  return;
            }
      }
 
//---------------------------------------------------------
//   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 {
            QSet<ChordRest*> set;
            for (Element* e : _score->selection().elements()) {
                  if (e->type() == ElementType::NOTE) {
                        Note* note = static_cast<Note*>(e);
                        if (note->noteType() != NoteType::NORMAL) { //no tuplet on grace notes
                              _score->endCmd();
                              return;
                              }
                        e = note->chord();
                        }
                  if (e->isChordRest()) {
                        ChordRest* cr = static_cast<ChordRest*>(e);
                        if (!set.contains(cr)) {
                              cmdTuplet(n, cr);
                              set.insert(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 %d chord %d", pitch, chord);
 
      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.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.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.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 dont 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();
      if (!is.cr() || is.cr()->duration() != is.duration().fraction())
            _score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
      ChordRest* prevCR = toChordRest(is.cr());
      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()
      {
      if (!_score->checkHasMeasures())
            return;
 
      int track = -1;
      Segment* segment = nullptr;
      Element* el = _score->selection().element();
      if (el && el->type() == ElementType::FRET_DIAGRAM) {
            FretDiagram* fd = toFretDiagram(el);
            track = fd->track();
            while (el && !el->isSegment())
                  el = el->parent();
            if (el)
                  segment = toSegment(el);
            }
      else {
            ChordRest* cr = _score->getSelectedChordRest();
            if (cr) {
                  track = cr->track();
                  segment = cr->segment();
                  }
            }
      if (track == -1 || !segment)
            return;
 
      _score->startCmd();
      Harmony* harmony = new Harmony(_score);
      harmony->setTrack(track);
      harmony->setParent(segment);
      _score->undoAddElement(harmony);
 
      _score->select(harmony, SelectType::SINGLE, 0);
      startEditMode(harmony);
      _score->update();
      }
 
//---------------------------------------------------------
//   cmdAddText
//---------------------------------------------------------
 
void ScoreView::cmdAddText(TEXT type)
      {
      if (!_score->checkHasMeasures())
            return;
      if (noteEntryMode())          // force out of entry mode
            changeState(ViewState::NORMAL);
 
      Text* s = 0;
      _score->startCmd();
      switch(type) {
            case TEXT::TITLE:
            case TEXT::SUBTITLE:
            case TEXT::COMPOSER:
            case TEXT::POET:
            case TEXT::PART:
                  {
                  MeasureBase* measure = _score->first();
                  if (!measure->isVBox()) {
                        _score->insertMeasure(ElementType::VBOX, measure);
                        measure = measure->prev();
                        }
                  s = new Text(_score);
                  switch(type) {
                        case TEXT::TITLE:    s->initSubStyle(SubStyle::TITLE);    break;
                        case TEXT::SUBTITLE: s->initSubStyle(SubStyle::SUBTITLE); break;
                        case TEXT::COMPOSER: s->initSubStyle(SubStyle::COMPOSER); break;
                        case TEXT::POET:     s->initSubStyle(SubStyle::POET);     break;
                        case TEXT::PART:     s->initSubStyle(SubStyle::INSTRUMENT_EXCERPT); break;
                        default: /* can't happen, but need to keep compiler happy */ break;
                        }
                  s->setParent(measure);
                  adjustCanvasPosition(measure, false);
                  }
                  break;
 
            case TEXT::REHEARSAL_MARK:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new RehearsalMark(_score);
                  s->setTrack(0);
                  s->setParent(cr->segment());
                  }
                  break;
            case TEXT::STAFF:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new StaffText(_score);
                  s->setTrack(cr->track());
                  s->initSubStyle(SubStyle::STAFF);
 
                  s->setParent(cr->segment());
                  }
                  break;
            case TEXT::SYSTEM:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new SystemText(_score);
                  s->setTrack(0);
                  s->initSubStyle(SubStyle::SYSTEM);
                  s->setParent(cr->segment());
                  }
                  break;
            case TEXT::EXPRESSION:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new StaffText(_score);
                  s->setTrack(cr->track());
                  s->initSubStyle(SubStyle::EXPRESSION);
                  s->setPlacement(Element::Placement::BELOW);
                  s->setParent(cr->segment());
                  }
                  break;
            case TEXT::INSTRUMENT_CHANGE:
                  {
                  ChordRest* cr = _score->getSelectedChordRest();
                  if (!cr)
                        break;
                  s = new InstrumentChange(_score);
                  s->setTrack(cr->track());
                  s->setParent(cr->segment());
                  }
                  break;
            case TEXT::FINGERING:
                  {
                  Element* e = _score->getSelectedElement();
                  if (!e || !e->isNote()
                     || !e->staff()->isPitchedStaff(e->tick()))
                        break;
                  s = new Fingering(_score);
                  s->setTrack(e->track());
                  s->setParent(e);
                  }
                  break;
            }
 
      if (s) {
            _score->undoAddElement(s);
            _score->select(s, SelectType::SINGLE, 0);
            _score->endCmd();
            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->last();
      _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;
            }
 
      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 data(mimeData->data(mimeType));
      XmlReader xml(_score, data);
      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(StyleIdx::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, true);
      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 (e1->z() == e2->z()) {
            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()->hidden())
                              return n2;
                        else
                              return n1;
                        }
                  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 n2;
                        else
                              return n1;
                        }
                  }
            }
      return e1->z() < e2->z();
      }
 
//---------------------------------------------------------
//   elementNear
//---------------------------------------------------------
 
Element* ScoreView::elementNear(QPointF p)
      {
      Page* page = point2page(p);
      if (!page)
            return 0;
 
      p       -= page->pos();
      double w = (preferences.proximity * .5) / matrix().m11();
      QRectF r(p.x() - w, p.y() - w, 3.0 * w, 3.0 * w);
 
      QList<Element*> el = page->items(r);
      QList<Element*> ll;
      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()) {
            // qDebug("  nothing found");
            return 0;
            }
      qSort(ll.begin(), ll.end(), elementLower);
 
#if 0
      qDebug("elementNear");
      foreach(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)
      {
      if (this != mscore->currentScoreView())
            return;
      switch (pos) {
            case POS::CURRENT:
                  if (noteEntryMode())
                        moveCursor();     // update input cursor position
                  else
                        moveCursor(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) == 0 && _score->pos(POS::RIGHT) == 0)
            _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->duration() == cr2->duration()) {
                        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()
      {
      setShadowNote(shadowNote->pos());
      }
 
//---------------------------------------------------------
//   getEditElement
//---------------------------------------------------------
 
Element* ScoreView::getEditElement()
      {
      return editData.element;
      }
 
//---------------------------------------------------------
//   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;
      }
}

V668 There is no sense in testing the 'pm' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error.

V547 Expression 'e' is always true.

V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1740, 1811.

V595 The 'mb' pointer was utilized before it was verified against nullptr. Check lines: 3923, 3931.