//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2009-2013 Werner Schweer
//
//  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 "pianoview.h"
#include "pianoruler.h"
#include "pianokeyboard.h"
#include "shortcut.h"
#include "musescore.h"
#include "scoreview.h"
#include "preferences.h"
#include "libmscore/part.h"
#include "libmscore/staff.h"
#include "libmscore/measure.h"
#include "libmscore/chord.h"
#include "libmscore/rest.h"
#include "libmscore/score.h"
#include "libmscore/note.h"
#include "libmscore/slur.h"
#include "libmscore/tie.h"
#include "libmscore/tuplet.h"
#include "libmscore/segment.h"
#include "libmscore/noteevent.h"

namespace Ms {

extern MuseScore* mscore;

static const qreal MIN_DRAG_DIST_SQ = 9;

const BarPattern PianoView::barPatterns[] = {
      {"C maj/A min",   {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1}},
      {"Db maj/Bb min", {1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0}},
      {"D maj/B min",   {0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}},
      {"Eb maj/C min",  {1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0}},
      {"E maj/Db min",  {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1}},
      {"F maj/D min",   {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0}},
      {"Gb maj/Eb min", {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}},
      {"G maj/E min",   {1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}},
      {"Ab maj/F min",  {1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0}},
      {"A maj/Gb min",  {0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1}},
      {"Bb maj/G min",  {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0}},
      {"B maj/Ab min",  {0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1}},
      {"C Diminished",  {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}},
      {"Db Diminished", {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}},
      {"D Diminished",  {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1}},
      {"C Half/Whole",  {1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}},
      {"Db Half/Whole", {0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1}},
      {"D Half/Whole",  {1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1}},
      {"C Whole tone",  {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}},
      {"Db Whole tone", {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}},
      {"C Augmented",   {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}},
      {"Db Augmented",  {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}},
      {"D Augmented",   {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0}},
      {"Eb Augmented",  {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}},
      {"",              {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}
};

//---------------------------------------------------------
//   PianoItem
//---------------------------------------------------------

PianoItem::PianoItem(Note* n, PianoView* pianoView)
   : _note(n), _pianoView(pianoView)
      {
      }


//---------------------------------------------------------
//   boundingRectTicks
//---------------------------------------------------------

QRect PianoItem::boundingRectTicks(NoteEvent* evt)
      {
      Chord* chord = _note->chord();
      int ticks = chord->duration().ticks();
      int tieLen = _note->playTicks() - ticks;
      int pitch = _note->pitch() + (evt ? evt->pitch() : 0);
      int len = (evt ? ticks * evt->len() / 1000 : ticks) + tieLen;

      int x1 = _note->chord()->tick()
            + (evt ? evt->ontime() * ticks / 1000 : 0);
      qreal y1 = pitch;

      QRect rect;
      rect.setRect(x1, y1, len, 1);
      return rect; 
      }

//---------------------------------------------------------
//   boundingRectPixels
//---------------------------------------------------------

QRect PianoItem::boundingRectPixels(NoteEvent* evt)
      {
      QRect rect = boundingRectTicks(evt);
      
      qreal tix2pix = _pianoView->xZoom();
      int noteHeight = _pianoView->noteHeight();
      
      rect.setRect(_pianoView->tickToPixelX(rect.x()), 
              (127 - rect.y()) * noteHeight,
              rect.width() * tix2pix,
              rect.height() * noteHeight
              );
      
      return rect;
      }

//---------------------------------------------------------
//   boundingRect
//---------------------------------------------------------

QRect PianoItem::boundingRect() {
      Chord* chord = _note->chord();
      int ticks = chord->duration().ticks();
      int tieLen = _note->playTicks() - ticks;
      int len = ticks + tieLen;
      int pitch = _note->pitch();

      qreal tix2pix = _pianoView->xZoom();
      int noteHeight = _pianoView->noteHeight();
      
      qreal x1 = _pianoView->tickToPixelX(_note->chord()->tick());
      qreal y1 = (127 - pitch) * noteHeight;

      QRect rect;      
      rect.setRect(x1, y1, len * tix2pix, noteHeight);
      return rect; 
      }



//---------------------------------------------------------
//   intersects
//---------------------------------------------------------

bool PianoItem::intersectsBlock(int startTick, int endTick, int highPitch, int lowPitch, NoteEvent* evt)
      {
      QRect r = boundingRectTicks(evt);
      int pitch = r.y();

      return r.right() >= startTick && r.left() <= endTick 
            && pitch >= lowPitch && pitch <= highPitch;
      }

//---------------------------------------------------------
//   intersects
//---------------------------------------------------------

bool PianoItem::intersects(int startTick, int endTick, int highPitch, int lowPitch)
      {
      if (_pianoView->playEventsView()) {
            for (NoteEvent& e : _note->playEvents())
                  if (intersectsBlock(startTick, endTick, highPitch, lowPitch, &e))
                        return true;
            return false;
            }
      else 
            return intersectsBlock(startTick, endTick, highPitch, lowPitch, 0);
      
      }


//---------------------------------------------------------
//   getTweakNoteEvent
//---------------------------------------------------------

NoteEvent* PianoItem::getTweakNoteEvent()
      {
      //Get topmost play event for note
      if (_note->playEvents().size() > 0)
            return &(_note->playEvents()[_note->playEvents().size() - 1]);
            
      return 0;
      }


//---------------------------------------------------------
//   paintNoteBlock
//---------------------------------------------------------

void PianoItem::paintNoteBlock(QPainter* painter, NoteEvent* evt)
      {
      int roundRadius = 3;

      QColor noteDeselected;
      QColor noteSelected;

      switch (preferences.globalStyle()) {
            case MuseScoreStyleType::DARK_FUSION:
                  noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_UNSEL_COLOR));
                  noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_NOTE_SEL_COLOR));
                  break;
            default:
                  noteDeselected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_UNSEL_COLOR));
                  noteSelected = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_NOTE_SEL_COLOR));
                  break;
            }

      QColor noteColor = _note->selected() ? noteSelected : noteDeselected;
      painter->setBrush(noteColor);
      
      painter->setPen(QPen(noteColor.darker(250)));
      QRectF bounds = boundingRectPixels(evt);
      painter->drawRoundedRect(bounds, roundRadius, roundRadius);

      //Pitch name
      if (bounds.width() >= 20 && bounds.height() >= 12) {
            QRectF textRect(bounds.x() + 2, bounds.y(), bounds.width() - 6, bounds.height() + 1);
            QRectF textHiliteRect(bounds.x() + 3, bounds.y() + 1, bounds.width() - 6, bounds.height());
            
            QFont f("FreeSans", 8);
            painter->setFont(f);

            //Note name
            QString name = tpc2name(_note->tpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false);
            painter->setPen(QPen(noteColor.lighter(130)));
            painter->drawText(textHiliteRect,
                  Qt::AlignLeft | Qt::AlignTop, name);

            painter->setPen(QPen(noteColor.darker(180)));
            painter->drawText(textRect,
                  Qt::AlignLeft | Qt::AlignTop, name);

            //Voice number
            if (bounds.width() >= 26) {
                  painter->setPen(QPen(noteColor.lighter(130)));
                  painter->drawText(textHiliteRect,
                        Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1));

                  painter->setPen(QPen(noteColor.darker(180)));
                  painter->drawText(textRect,
                        Qt::AlignRight | Qt::AlignTop, QString::number(_note->voice() + 1));
                  }
            }
      }

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

void PianoItem::paint(QPainter* painter)
      {
      painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);

      if (_pianoView->playEventsView()) {
            for (NoteEvent& e : _note->playEvents())
                  paintNoteBlock(painter, &e);
            }
      else 
            paintNoteBlock(painter, 0);
      }


//---------------------------------------------------------
//   PianoView
//---------------------------------------------------------

PianoView::PianoView()
   : QGraphicsView()
      {
      setFrameStyle(QFrame::NoFrame);
      setLineWidth(0);
      setMidLineWidth(0);
      setScene(new QGraphicsScene);
      setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
      setResizeAnchor(QGraphicsView::AnchorUnderMouse);
      setMouseTracking(true);
      _timeType = TType::TICKS;
      _playEventsView = true;
      _staff = 0;
      chord = 0;
      _barPattern = 0;
      _tuplet = 1;
      _subdiv = 0;
      _noteHeight = DEFAULT_KEY_HEIGHT;
      _xZoom = X_ZOOM_INITIAL;
      dragStarted = false;
      mouseDown = false;
      dragStyle = DragStyle::NONE;
      inProgressUndoEvent = false;
      }

//---------------------------------------------------------
//   ~PianoView
//---------------------------------------------------------

PianoView::~PianoView()
      {
      clearNoteData();
      }

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

void PianoView::drawBackground(QPainter* p, const QRectF& r)
      {
      if (_staff == 0)
            return;
      Score* _score = _staff->score();
      setFrameShape(QFrame::NoFrame);

      QColor colSelectionBox;

      QColor colWhiteKeyBg;
      QColor colGutter;
      QColor colBlackKeyBg;

      QColor colGridLine;

      switch (preferences.globalStyle()) {
            case MuseScoreStyleType::DARK_FUSION:
                  colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_WHITE_COLOR));

                  colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_WHITE_COLOR));
                  colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_BASE_COLOR));
                  colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_KEY_BLACK_COLOR));

                  colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_DARK_BG_GRIDLINE_COLOR));
                  break;
            default:
                  colSelectionBox = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_WHITE_COLOR));

                  colWhiteKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_WHITE_COLOR));
                  colGutter = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_BASE_COLOR));
                  colBlackKeyBg = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_KEY_BLACK_COLOR));

                  colGridLine = QColor(preferences.getColor(PREF_UI_PIANOROLL_LIGHT_BG_GRIDLINE_COLOR));
                  break;
            }

      const QColor colSelectionBoxFill = QColor(
                        colSelectionBox.red(), colSelectionBox.green(), colSelectionBox.blue(),
                        128);

      const QPen penLineMajor = QPen(colGridLine, 2.0, Qt::SolidLine);
      const QPen penLineMinor = QPen(colGridLine, 1.0, Qt::SolidLine);
      const QPen penLineSub = QPen(colGridLine, 1.0, Qt::DotLine);

      QRectF r1;
      r1.setCoords(-1000000.0, 0.0, tickToPixelX(0), 1000000.0);
      QRectF r2;
      r2.setCoords(tickToPixelX(ticks), 0.0, 1000000.0, 1000000.0);
      
      p->fillRect(r, colWhiteKeyBg);
      if (r.intersects(r1))
            p->fillRect(r.intersected(r1), colGutter);
      if (r.intersects(r2))
            p->fillRect(r.intersected(r2), colGutter);

      //
      // draw horizontal grid lines
      //
      qreal y1 = r.y();
      qreal y2 = y1 + r.height();
      qreal x1 = qMax(r.x(), (qreal)tickToPixelX(0));
      qreal x2 = qMin(x1 + r.width(), (qreal)tickToPixelX(ticks));

      int topPitch = ceil((_noteHeight * 128 - y1) / _noteHeight);
      int bmPitch = floor((_noteHeight * 128 - y2) / _noteHeight);
      
      Part* part = _staff->part();
      Interval transp = part->instrument()->transpose();

      //MIDI notes span [0, 127] and map to pitches starting at C-1
      for (int pitch = bmPitch; pitch <= topPitch; ++pitch) {
            int y = (127 - pitch) * _noteHeight;

            int degree = (pitch - transp.chromatic + 60) % 12;
            const BarPattern& pat = barPatterns[_barPattern];
//            if (degree == 1 || degree == 3 || degree == 6 || degree == 8 || degree == 10) {
              if (!pat.isWhiteKey[degree]) {
                  qreal px0 = qMax(r.x(), (qreal)tickToPixelX(0));
                  qreal px1 = qMin(r.x() + r.width(), (qreal)tickToPixelX(ticks));
                  QRectF hbar;
                  
                  hbar.setCoords(px0, y, px1, y + _noteHeight);
                  p->fillRect(hbar, colBlackKeyBg);
            }
            
            //Lines between rows
            p->setPen(degree == 0 ? penLineMajor : penLineMinor);
            p->drawLine(QLineF(x1, y + _noteHeight, x2, y + _noteHeight));
            }

      //
      // draw vertical grid lines
      //
      Pos pos1(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x1), 0), TType::TICKS);
      Pos pos2(_score->tempomap(), _score->sigmap(), qMax(pixelXToTick(x2), 0), TType::TICKS);
      
      int bar1, bar2, beat, tick;
      pos1.mbt(&bar1, &beat, &tick);
      pos2.mbt(&bar2, &beat, &tick);
      
      //Draw bar lines
      const int minBeatGap = 20;

      for (int bar = bar1; bar <= bar2; ++bar) {
            Pos barPos(_score->tempomap(), _score->sigmap(), bar, 0, 0);

            //Beat lines
            int beatsInBar = barPos.timesig().timesig().numerator();
            int ticksPerBeat = barPos.timesig().timesig().beatTicks();
            double pixPerBeat = ticksPerBeat * _xZoom;
            int beatSkip = ceil(minBeatGap / pixPerBeat);


//            int subExp = qMin((int)floor(log2(pixPerBeat / minBeatGap)), _subBeats);
//            int numSubBeats = pow(2, subExp);

            //Round up to next power of 2
            beatSkip = (int)pow(2, ceil(log(beatSkip)/log(2)));
            
            for (int beat1 = 0; beat1 < beatsInBar; beat1 += beatSkip) {
                  Pos beatPos(_score->tempomap(), _score->sigmap(), bar, beat1, 0);
                  double x = tickToPixelX(beatPos.time(TType::TICKS));
                  p->setPen(penLineMinor);
                  p->drawLine(x, y1, x, y2);

                  int subbeats = _tuplet * (1 << _subdiv);

                  for (int sub = 1; sub < subbeats; ++sub) {
                      Pos subBeatPos(_score->tempomap(), _score->sigmap(), bar, beat1, sub * MScore::division / subbeats);
                      x = tickToPixelX(subBeatPos.time(TType::TICKS));

                      p->setPen(penLineSub);
                      p->drawLine(x, y1, x, y2);
                      }

                  }
            
            //Bar line
            double x = tickToPixelX(barPos.time(TType::TICKS));
            p->setPen(x > 0 ? penLineMajor : QPen(Qt::black, 2.0));
            p->drawLine(x, y1, x, y2);
            }
      
      //Draw notes
      for (int i = 0; i < noteList.size(); ++i)
            noteList[i]->paint(p);

      //Draw locators
      for (int i = 0; i < 3; ++i) {
            if (_locator[i].valid())
                  {
                  p->setPen(QPen(i == 0 ? Qt::red : Qt::blue, 2));
                  qreal x = tickToPixelX(_locator[i].time(TType::TICKS));
                  p->drawLine(x, y1, x, y2);
                  }
            }
      
      //Draw drag selection box
      if (dragStarted && dragStyle == DragStyle::SELECTION_RECT) {
            int minX = qMin(mouseDownPos.x(), lastMousePos.x());
            int minY = qMin(mouseDownPos.y(), lastMousePos.y());
            int maxX = qMax(mouseDownPos.x(), lastMousePos.x());
            int maxY = qMax(mouseDownPos.y(), lastMousePos.y());
            QRectF rect(minX, minY, maxX - minX + 1, maxY - minY + 1);
            
            p->setPen(QPen(colSelectionBox, 2));
            p->setBrush(QBrush(colSelectionBoxFill, Qt::SolidPattern));
            p->drawRect(rect);
            }
      }


//---------------------------------------------------------
//   moveLocator
//---------------------------------------------------------

void PianoView::moveLocator(int /*i*/)
      {
      scene()->update();
      }


//---------------------------------------------------------
//   pixelXToTick
//---------------------------------------------------------

int PianoView::pixelXToTick(int pixX) {
      return (int)(pixX / _xZoom) - MAP_OFFSET; 
      }


//---------------------------------------------------------
//   tickToPixelX
//---------------------------------------------------------

int PianoView::tickToPixelX(int tick) { 
      return (int)(tick + MAP_OFFSET) * _xZoom;
      }

//---------------------------------------------------------
//   wheelEvent
//---------------------------------------------------------

void PianoView::wheelEvent(QWheelEvent* event)
      {
      int step = event->delta() / 120;

      if (event->modifiers() == Qt::ControlModifier) {
            //Horizontal zoom
            
            QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
            
            int mouseXTick = pixelXToTick(event->x() + (int)viewRect.x());
            
            _xZoom *= pow(X_ZOOM_RATIO, step);
            emit xZoomChanged(_xZoom);
            
            updateBoundingSize();
            updateNotes();
            
            int mousePixX = tickToPixelX(mouseXTick);
            horizontalScrollBar()->setValue(mousePixX - event->x());

            scene()->update();
            }
      else if (event->modifiers() == Qt::ShiftModifier) {
            //Horizontal scroll
            QWheelEvent we(event->pos(), event->delta(), event->buttons(), 0, Qt::Horizontal);
            QGraphicsView::wheelEvent(&we);
            }
      else if (event->modifiers() == 0) {
            //Vertical scroll
            QGraphicsView::wheelEvent(event);
            }
      else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
            //Vertical zoom
            QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
            qreal mouseYNote = (event->y() + (int)viewRect.y()) / (qreal)_noteHeight;
            
            _noteHeight = qMax(qMin(_noteHeight + step, MAX_KEY_HEIGHT), MIN_KEY_HEIGHT);
            emit noteHeightChanged(_noteHeight);
            
            updateBoundingSize();
            updateNotes();
            
            int mousePixY = (int)(mouseYNote * _noteHeight);
            verticalScrollBar()->setValue(mousePixY - event->y());
            
            scene()->update();
            }
      }


//---------------------------------------------------------
//   showPopupMenu
//---------------------------------------------------------

void PianoView::showPopupMenu(const QPoint& pos)
      {
      QMenu popup(this);
//      popup.addAction(getAction("cut"));
//      popup.addAction(getAction("copy"));
      popup.addAction(getAction("paste"));
//      popup.addAction(getAction("swap"));
      popup.addAction(getAction("delete"));

      popup.exec(pos);
      }

//---------------------------------------------------------
//   contextMenuEvent
//---------------------------------------------------------

void PianoView::contextMenuEvent(QContextMenuEvent *event)
      {
      showPopupMenu(event->globalPos());
      }


//---------------------------------------------------------
//   mousePressEvent
//---------------------------------------------------------

void PianoView::mousePressEvent(QMouseEvent* event)
      {
      bool rightBn = event->button() == Qt::RightButton;
      if (!rightBn) {
            mouseDown = true;
            mouseDownPos = mapToScene(event->pos());
            lastMousePos = mouseDownPos;
            }
      }

//---------------------------------------------------------
//   mouseReleaseEvent
//---------------------------------------------------------

void PianoView::mouseReleaseEvent(QMouseEvent* event)
      {
      int modifiers = QGuiApplication::keyboardModifiers();
      bool bnShift = modifiers & Qt::ShiftModifier;
      bool bnCtrl = modifiers & Qt::ControlModifier;
      
      bool rightBn = event->button() == Qt::RightButton;
      if (rightBn) {
            //Right clicks have been handled as popup menu
            return;
            }


      NoteSelectType selType = bnShift ? (bnCtrl ? NoteSelectType::SUBTRACT : NoteSelectType::XOR)
              : (bnCtrl ? NoteSelectType::ADD : NoteSelectType::REPLACE);

      if (dragStarted) {
            if (dragStyle == DragStyle::SELECTION_RECT) {
                  //Update selection
                  qreal minX = qMin(mouseDownPos.x(), lastMousePos.x());
                  qreal minY = qMin(mouseDownPos.y(), lastMousePos.y());
                  qreal maxX = qMax(mouseDownPos.x(), lastMousePos.x());
                  qreal maxY = qMax(mouseDownPos.y(), lastMousePos.y());

                  int startTick = pixelXToTick((int)minX);
                  int endTick = pixelXToTick((int)maxX);
                  int lowPitch = (int)floor(128 - maxY / noteHeight());
                  int highPitch = (int)ceil(128 - minY / noteHeight());

                  selectNotes(startTick, endTick, lowPitch, highPitch, selType);
                  }
            else if (dragStyle == DragStyle::MOVE_NOTES) {
                  //Keep last note drag event, if any
                  if (inProgressUndoEvent)
                        inProgressUndoEvent = false;
                  }

            dragStarted = false;
            }
      else {
            Score* score = _staff->score();

            int pickTick = pixelXToTick((int)mouseDownPos.x());
            int pickPitch = pixelYToPitch(mouseDownPos.y());

            PianoItem *pn = pickNote(pickTick, pickPitch);
            if (pn) {
                  if (selType == NoteSelectType::REPLACE)
                        selType = NoteSelectType::FIRST;

                  mscore->play(pn->note());
                  score->setPlayNote(false);

                  selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType);
                  }
            else {
                  if (!bnShift && !bnCtrl) {
                        //Select an empty pixel - should clear selection
                        selectNotes(pickTick, pickTick + 1, pickPitch, pickPitch, selType);
                        }
                  else if (!bnShift && bnCtrl) {

                        //Insert a new note at nearest subbeat
                        int subbeats = _tuplet * (1 << _subdiv);
                        int subbeatTicks = MScore::division / subbeats;
                        int roundedTick = (pickTick / subbeatTicks) * subbeatTicks;

                        InputState& is = score->inputState();
                        int voice = score->inputState().voice();
                        int track = _staff->idx() * VOICES + voice;

                        NoteVal nv(pickPitch);


                        ChordRest* e = score->findCR(roundedTick, track);
                        if (!e->tuplet() && _tuplet == 1) {
                              //Ignore tuplets
                              score->startCmd();
                              score->expandVoice(e->segment(), track);

                              ChordRest* cr0;
                              ChordRest* cr1;
                              Fraction frac = is.duration().fraction();
                              if (cutChordRest(e, track, roundedTick, cr0, cr1)) {
                                    score->setNoteRest(cr1->segment(), track, nv, frac);
                                    }
                              else {
                                    if (cr0->isChord() && cr0->duration().ticks() == frac.ticks()) {
                                          Chord* ch = toChord(cr0);
                                          score->addNote(ch, nv);
                                          }
                                    else {
                                          score->setNoteRest(cr0->segment(), track, nv, frac);
                                          }
                                    }

                              score->endCmd();
                              }

                        }
                  else if (bnShift && !bnCtrl) {
                        //Append a pitch to our curent chord/rest
                        int voice = score->inputState().voice();

                        //Find best chord to add to
                        int track = _staff->idx() * VOICES + voice;

                        ChordRest* e = score->findCR(pickTick, track);

                        if (e && e->isChord()) {
                              Chord* ch = toChord(e);

                              if (pickTick >= e->tick() && pickTick < ch->tick() + ch->duration().ticks()) {
                                    NoteVal nv(pickPitch);
                                    score->startCmd();
                                    score->addNote(ch, nv);
                                    score->endCmd();
                                    }

                              }
                        else if (e && e->isRest()) {
                              Rest* r = toRest(e);
                              NoteVal nv(pickPitch);
                              score->startCmd();
                              score->setNoteRest(r->segment(), track, nv, r->duration());
                              score->endCmd();
                              }
                        }
                  else if (bnShift && bnCtrl) {
                        //Cut the chord/rest at the nearest subbeat
                        int voice = score->inputState().voice();

                        //Find best chord to add to
                        int track = _staff->idx() * VOICES + voice;

                        int subbeats = _tuplet * (1 << _subdiv);

                        int subbeatTicks = MScore::division / subbeats;
                        int roundedTick = (pickTick / subbeatTicks) * subbeatTicks;

                        ChordRest* e = score->findCR(roundedTick, track);
                        if (!e->tuplet() && _tuplet == 1) {
                              score->startCmd();
                              score->expandVoice(e->segment(), track);
                              int startTick = e->tick();

                              if (roundedTick != startTick) {
                                    ChordRest* cr0;
                                    ChordRest* cr1;
                                    cutChordRest(e, track, roundedTick, cr0, cr1);
                                    }
                              score->endCmd();
                              }
                        }
                  }
            }
      
      
      dragStyle = DragStyle::NONE;
      mouseDown = false;
      scene()->update();
      }



//---------------------------------------------------------
//   mouseMoveEvent
//---------------------------------------------------------

void PianoView::mouseMoveEvent(QMouseEvent* event)
      {
      lastMousePos = mapToScene(event->pos());

      if (mouseDown && !dragStarted) {
            qreal dx = lastMousePos.x() - mouseDownPos.x();
            qreal dy = lastMousePos.y() - mouseDownPos.y();

            if (dx * dx + dy * dy >= MIN_DRAG_DIST_SQ) {
                  //Start dragging
                  dragStarted = true;
                  
                  //Check for move note
                  int tick = pixelXToTick(mouseDownPos.x());
                  int mouseDownPitch = pixelYToPitch(mouseDownPos.y());
                  PianoItem* pi = pickNote(tick, mouseDownPitch);
                  if (pi) {
                        if (!pi->note()->selected()) {
                              selectNotes(tick, tick, mouseDownPitch, mouseDownPitch, NoteSelectType::REPLACE);
                              }
                        dragStyle = DragStyle::MOVE_NOTES;
                        lastDragPitch = mouseDownPitch;
                        }
                  else {
                        dragStyle = DragStyle::SELECTION_RECT;
                        }
                  }
            }

      if (dragStarted) {
            if (dragStyle == DragStyle::MOVE_NOTES) {
                  int curPitch = pixelYToPitch(lastMousePos.y());
                  if (curPitch != lastDragPitch) {
                        int pitchDelta = curPitch - lastDragPitch;

                        Score* score = _staff->score();
                        if (inProgressUndoEvent) {
//                              score->undoRedo(true, 0, false);
                              inProgressUndoEvent = false;
                              }
                        
                        score->startCmd();
                        score->upDownDelta(pitchDelta, false);
                        score->endCmd();
                        
                        inProgressUndoEvent = true;
                        lastDragPitch = curPitch;
                        }
                  }
            
            scene()->update();
            }
      

      //Update mouse tracker      
      QPointF p(mapToScene(event->pos()));
      int pitch = (int)((_noteHeight * 128 - p.y()) / _noteHeight);
      emit pitchChanged(pitch);

      int tick = pixelXToTick(p.x());
      if (tick < 0) {
            tick = 0;
            trackingPos.setTick(tick);
            trackingPos.setInvalid();
            }
      else
            trackingPos.setTick(tick);
      emit trackingPosChanged(trackingPos);
      }

//---------------------------------------------------------
//   cutChordRest
//---------------------------------------------------------

bool PianoView::cutChordRest(ChordRest* e, int track, int cutTick, ChordRest*& cr0, ChordRest*& cr1)
      {
      int startTick = e->segment()->tick();
      int tcks = e->duration().ticks();
      if (cutTick <= startTick || cutTick > startTick + tcks) {
            cr0 = e;
            cr1 = 0;
            return false;
            }

      //Deselect note being cut
      if (e->isChord()) {
            Chord* ch = toChord(e);
            for (Note* n: ch->notes()) {
                  n->setSelected(false);
                  }
            }
      else if (e->isRest()) {
            Rest* r = toRest(e);
            r->setSelected(false);
            }


      //Subdivide at the cut tick
      NoteVal nv(-1);

      Score* score = _staff->score();
      score->setNoteRest(e->segment(), track, nv, Fraction::fromTicks(cutTick - e->tick()));
      ChordRest *nextCR = score->findCR(cutTick, track);

//      nextCR->segment()->setTick(cutTick);
      Chord* ch0 = 0;

      if (nextCR->isChord()) {
            //Copy chord into initial segment
            Chord* ch1 = toChord(nextCR);

            for (Note* n: ch1->notes()) {
                  NoteVal nx = n->noteVal();
                  if (!ch0) {
                        ChordRest* cr = score->findCR(startTick, track);
                        score->setNoteRest(cr->segment(), track, nx, cr->duration());
                        ch0 = toChord(score->findCR(startTick, track));
                        }
                  else {
                        score->addNote(ch0, nx);
                        }
                  }
            }

      cr0 = ch0;
      cr1 = nextCR;
      return true;
      }

//---------------------------------------------------------
//   selectNotes
//---------------------------------------------------------

PianoItem* PianoView::pickNote(int tick, int pitch)
      {
      for (int i = 0; i < noteList.size(); ++i) {
            PianoItem* pi = noteList[i];
            
            if (pi->intersects(tick, tick, pitch, pitch))
                  return pi;
            }
      
      return 0;
      }

//---------------------------------------------------------
//   selectNotes
//---------------------------------------------------------

void PianoView::selectNotes(int startTick, int endTick, int lowPitch, int highPitch, NoteSelectType selType)
      {
      Score* score = _staff->score();
      //score->masterScore()->cmdState().reset();      // DEBUG: should not be necessary
      score->startCmd();

      Selection selection(score);

      for (int i = 0; i < noteList.size(); ++i) {
            PianoItem* pi = noteList[i];
            bool inBounds = pi->intersects(startTick, endTick, highPitch, lowPitch);
            
            bool sel;
            switch (selType) {
                  default:
                  case NoteSelectType::REPLACE:
                        sel = inBounds;
                        break;
                  case NoteSelectType::XOR:
                        sel = inBounds != pi->note()->selected();
                        break;
                  case NoteSelectType::ADD:
                        sel = inBounds || pi->note()->selected();
                        break;
                  case NoteSelectType::SUBTRACT:
                        sel = !inBounds && pi->note()->selected();
                        break;
                  case NoteSelectType::FIRST:
                        sel = inBounds && selection.elements().empty();
                        break;
                  }
            
            if (sel)
                  selection.add(pi->note());
            }

      score->setSelection(selection);
      for (MuseScoreView* view : score->getViewer())
            view->updateAll();
      
//      _selection.setActiveSegment(0);
//      _selection.setActiveTrack(0);

//      _selection.setState(selState);

      scene()->update();
      score->setUpdateAll();
      score->update();
      score->endCmd();

      emit selectionChanged();
      }

//---------------------------------------------------------
//   leaveEvent
//---------------------------------------------------------

void PianoView::leaveEvent(QEvent* event)
      {
      emit pitchChanged(-1);
      trackingPos.setInvalid();
      emit trackingPosChanged(trackingPos);
      QGraphicsView::leaveEvent(event);
      }

//---------------------------------------------------------
//   ensureVisible
//---------------------------------------------------------

void PianoView::ensureVisible(int tick)
      {
      QRectF rect = mapToScene(viewport()->geometry()).boundingRect();

      qreal xpos = tickToPixelX(tick);
      qreal margin = rect.width() / 2;
      if (xpos < rect.x() + margin)
            horizontalScrollBar()->setValue(qMax(xpos - margin, 0.0));
      else if (xpos >= rect.x() + rect.width() - margin)
            horizontalScrollBar()->setValue(qMax(xpos - rect.width() + margin, 0.0));
      }

//---------------------------------------------------------
//   updateBoundingSize
//---------------------------------------------------------
void PianoView::updateBoundingSize()
      {
      Measure* lm = _staff->score()->lastMeasure();
      ticks       = lm->tick() + lm->ticks();
      scene()->setSceneRect(0.0, 0.0, 
              double((ticks + MAP_OFFSET * 2) * _xZoom),
              _noteHeight * 128);
      }

//---------------------------------------------------------
//   setStaff
//---------------------------------------------------------

void PianoView::setStaff(Staff* s, Pos* l)
      {
      _locator = l;
      
      if (_staff == s)
            return;
      
      _staff    = s;
      setEnabled(_staff != nullptr);
      if (!_staff) {
            scene()->blockSignals(true);  // block changeSelection()
            scene()->clear();
            clearNoteData();
            scene()->blockSignals(false);
            return;
            }

      trackingPos.setContext(_staff->score()->tempomap(), _staff->score()->sigmap());
      updateBoundingSize();

      updateNotes();
      
      QRectF boundingRect;
      bool brInit = false;
      QRectF boundingRectSel;
      bool brsInit = false;
      
      foreach (PianoItem* item, noteList) {
            if (!brInit) {
                  boundingRect = item->boundingRect();
                  brInit = true;
                  }
            else
                  boundingRect |= item->boundingRect();

            if (item->note()->selected()) {
                  if (!brsInit) {
                        boundingRectSel = item->boundingRect();
                        brsInit = true;
                        }
                  else
                        boundingRectSel |= item->boundingRect();
                  }
                  
            }

      QRectF viewRect = mapToScene(viewport()->geometry()).boundingRect();
      
      if (brsInit) {
            horizontalScrollBar()->setValue(boundingRectSel.x());
            verticalScrollBar()->setValue(qMax(boundingRectSel.y() + (boundingRectSel.height() - viewRect.height()) / 2, 0.0));
            }
      else if (brInit) {
            horizontalScrollBar()->setValue(boundingRect.x());
            verticalScrollBar()->setValue(qMax(boundingRect.y() - (boundingRectSel.height() - viewRect.height()) / 2, 0.0));
            }
      else {
            horizontalScrollBar()->setValue(0);
            verticalScrollBar()->setValue(qMax(viewRect.y() - viewRect.height() / 2, 0.0));
            }
      }

//---------------------------------------------------------
//   addChord
//---------------------------------------------------------

void PianoView::addChord(Chord* chrd, int voice)
      {
      for (Chord* c : chrd->graceNotes())
            addChord(c, voice);
      for (Note* note : chrd->notes()) {
            if (note->tieBack())
                  continue;
            noteList.append(new PianoItem(note, this));
            }
      }

//---------------------------------------------------------
//   updateNotes
//---------------------------------------------------------

void PianoView::updateNotes()
      {
      scene()->blockSignals(true);  // block changeSelection()
      scene()->clearFocus();
      scene()->clear();
      clearNoteData();

      int staffIdx   = _staff->idx();

      SegmentType st = SegmentType::ChordRest;
      for (Segment* s = _staff->score()->firstSegment(st); s; s = s->next1(st)) {
            for (int voice = 0; voice < VOICES; ++voice) {
                  int track = voice + staffIdx * VOICES;
                  Element* e = s->element(track);
                  if (e && e->isChord())
                        addChord(toChord(e), voice);
                  }
            }
      for (int i = 0; i < 3; ++i)
            moveLocator(i);
      scene()->blockSignals(false);
      
      scene()->update(sceneRect());
      }

//---------------------------------------------------------
//   updateNotes
//---------------------------------------------------------

void PianoView::clearNoteData()
      {
      for (int i = 0; i < noteList.size(); ++i)
            delete noteList[i];
      
      noteList.clear();
      }


//---------------------------------------------------------
//   getSelectedItems
//---------------------------------------------------------

QList<PianoItem*> PianoView::getSelectedItems()
      {
      QList<PianoItem*> list;
      for (int i = 0; i < noteList.size(); ++i) {
            if (noteList[i]->note()->selected())
                  list.append(noteList[i]);
            }
      return list;
      }

//---------------------------------------------------------
//   getItems
//---------------------------------------------------------

QList<PianoItem*> PianoView::getItems()
      {
      QList<PianoItem*> list;
      for (int i = 0; i < noteList.size(); ++i)
            list.append(noteList[i]);
      return list;
      }

//---------------------------------------------------------
//   getAction
//    returns action for shortcut
//---------------------------------------------------------

QAction* PianoView::getAction(const char* id)
      {
      Shortcut* s = Shortcut::getShortcut(id);
      return s ? s->action() : 0;
      }

//---------------------------------------------------------
//   setXZoom
//---------------------------------------------------------

void PianoView::setXZoom(int value)
      {
      if (_xZoom != value) {
            _xZoom = value;
            scene()->update();
            emit xZoomChanged(_xZoom);
            }
      }

//---------------------------------------------------------
//   setBarPattern
//---------------------------------------------------------

void PianoView::setBarPattern(int value)
      {
      if (_barPattern != value) {
            _barPattern = value;
            scene()->update();
            emit barPatternChanged(_barPattern);
            }
      }

//---------------------------------------------------------
//   setSubBeats
//---------------------------------------------------------

void PianoView::setTuplet(int value)
      {
      if (_tuplet != value) {
            _tuplet = value;
            scene()->update();
            emit tupletChanged(_tuplet);
            }
      }

//---------------------------------------------------------
//   setSubdiv
//---------------------------------------------------------

void PianoView::setSubdiv(int value)
      {
      if (_subdiv != value) {
            _subdiv = value;
            scene()->update();
            emit subdivChanged(_subdiv);
            }
      }

}
