//=============================================================================
//  MusE Score
//  Linux Music Score Editor
//  $Id:$
//
//  Copyright (C) 2010 Werner Schweer and 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.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
 
#include "drumview.h"
#include "libmscore/staff.h"
#include "piano.h"
#include "libmscore/measure.h"
#include "libmscore/chord.h"
#include "libmscore/score.h"
#include "libmscore/note.h"
#include "libmscore/slur.h"
#include "libmscore/segment.h"
 
namespace Ms {
 
static const int MAP_OFFSET = 480;
 
//---------------------------------------------------------
//   pitch2y
//---------------------------------------------------------
 
static int pitch2y(int pitch)
      {
      static int tt[] = {
            12, 19, 25, 32, 38, 51, 58, 64, 71, 77, 84, 90
            };
      int y = (75 * keyHeight) - (tt[pitch % 12] + (7 * keyHeight) * (pitch / 12));
      if (y < 0)
            y = 0;
      return y;
      }
 
//---------------------------------------------------------
//   DrumItem
//---------------------------------------------------------
 
DrumItem::DrumItem(Note* n)
   : QGraphicsPolygonItem(), _note(n)
      {
      setFlags(flags() | QGraphicsItem::ItemIsSelectable);
      int pitch = _note->pitch();
      QPolygonF p;
      double h2 = keyHeight/2;
      p << QPointF(0, -h2) << QPointF(h2, 0.0) << QPointF(0.0, h2) << QPointF(-h2, 0.0);
      setPolygon(p);
      setBrush(QBrush());
      setSelected(_note->selected());
      setData(0, QVariant::fromValue<void*>(_note));
 
      setPos(_note->chord()->tick() + 480, pitch2y(pitch) + keyHeight / 4);
      setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
      }
 
//---------------------------------------------------------
//   paint
//---------------------------------------------------------
 
void DrumItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
      {
//      Chord* chord = note->chord();
      int x1       = 0; // note->onTimeOffset() + note->onTimeUserOffset();
      painter->setPen(pen());
      painter->setBrush(isSelected() ? Qt::yellow : Qt::blue);
      painter->drawPolygon(polygon().translated(x1, 0.0));
      }
 
//---------------------------------------------------------
//   pix2pos
//---------------------------------------------------------
 
Pos DrumView::pix2pos(int x) const
      {
      x -= MAP_OFFSET;
      if (x < 0)
            x = 0;
      return Pos(staff->score()->tempomap(), staff->score()->sigmap(), x, _timeType);
      }
 
//---------------------------------------------------------
//   pos2pix
//---------------------------------------------------------
 
int DrumView::pos2pix(const Pos& p) const
      {
      return p.time(_timeType) + MAP_OFFSET;
      }
 
//---------------------------------------------------------
//   drawBackground
//---------------------------------------------------------
 
void DrumView::drawBackground(QPainter* p, const QRectF& r)
      {
      if (staff == 0)
            return;
      Score* _score = staff->score();
 
      QRectF r1;
      r1.setCoords(-1000000.0, 0.0, 480.0, 1000000.0);
      QRectF r2;
      r2.setCoords(ticks + 480, 0.0, 1000000.0, 1000000.0);
      QColor bg(0x71, 0x8d, 0xbe);
 
      p->fillRect(r, bg);
      if (r.intersects(r1))
            p->fillRect(r.intersected(r1), bg.darker(150));
      if (r.intersects(r2))
            p->fillRect(r.intersected(r2), bg.darker(150));
 
      //
      // draw horizontal grid lines
      //
      qreal y1 = r.y();
      qreal y2 = y1 + r.height();
      qreal kh = 13.0;
      qreal x1 = r.x();
      qreal x2 = x1 + r.width();
 
      int key = floor(y1 / 75);
      qreal y = key * kh;
 
      for (; key < 75; ++key, y += kh) {
            if (y < y1)
                  continue;
            if (y > y2)
                  break;
            p->setPen(QPen((key % 7) == 5 ? Qt::lightGray : Qt::gray));
            p->drawLine(QLineF(x1, y, x2, y));
            }
 
      //
      // draw vertical grid lines
      //
      static const int mag[7] = {
            1, 1, 2, 5, 10, 20, 50
            };
 
      Pos pos1 = pix2pos(x1);
      Pos pos2 = pix2pos(x2);
 
      //---------------------------------------------------
      //    draw raster
      //---------------------------------------------------
 
      int bar1, bar2, beat, tick;
      pos1.mbt(&bar1, &beat, &tick);
      pos2.mbt(&bar2, &beat, &tick);
 
      int n = mag[magStep < 0 ? 0 : magStep];
 
      bar1 = (bar1 / n) * n;           // round down
      if (bar1 && n >= 2)
            bar1 -= 1;
      bar2 = ((bar2 + n - 1) / n) * n; // round up
 
      for (int bar = bar1; bar <= bar2;) {
            Pos stick(_score->tempomap(), _score->sigmap(), bar, 0, 0);
            if (magStep > 0) {
                  double x = double(pos2pix(stick));
                  if (x > 0) {
                        p->setPen(Qt::lightGray);
                        p->drawLine(x, y1, x, y2);
                        }
                  else {
                        p->setPen(Qt::black);
                        p->drawLine(x, y1, x, y1);
                        }
                  }
            else {
                  int z = stick.timesig().timesig().numerator();
                  for (int beat = 0; beat < z; beat++) {
                        if (magStep == 0) {
                              Pos xx(_score->tempomap(), _score->sigmap(), bar, beat, 0);
                              int xp = pos2pix(xx);
                              if (xp < 0)
                                    continue;
                              if (xp > 0) {
                                    p->setPen(beat == 0 ? Qt::lightGray : Qt::gray);
                                    p->drawLine(xp, y1, xp, y2);
                                    }
                              else {
                                    p->setPen(Qt::black);
                                    p->drawLine(xp, y1, xp, y2);
                                    }
                              }
                        else {
                              int k;
                              if (magStep == -1)
                                    k = 2;
                              else if (magStep == -2)
                                    k = 4;
                              else if (magStep == -3)
                                    k = 8;
                              else if (magStep == -4)
                                    k = 16;
                              else
                                    k = 32;
 
                              int n = (MScore::division * 4) / stick.timesig().timesig().denominator();
                              for (int i = 0; i < k; ++i) {
                                    Pos xx(_score->tempomap(), _score->sigmap(), bar, beat, (n * i)/ k);
                                    int xp = pos2pix(xx);
                                    if (xp < 0)
                                          continue;
                                    if (xp > 0) {
                                          p->setPen(i == 0 && beat == 0 ? Qt::lightGray : Qt::gray);
                                          p->drawLine(xp, y1, xp, y2);
                                          }
                                    else {
                                          p->setPen(Qt::black);
                                          p->drawLine(xp, y1, xp, y2);
                                          }
                                    }
                              }
                        }
                  }
            if (bar == 0 && n >= 2)
                  bar += (n-1);
            else
                  bar += n;
            }
      }
 
//---------------------------------------------------------
//   DrumView
//---------------------------------------------------------
 
DrumView::DrumView()
   : QGraphicsView()
      {
      setScene(new QGraphicsScene);
      setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
      setResizeAnchor(QGraphicsView::AnchorUnderMouse);
      setMouseTracking(true);
      setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect);
      setDragMode(QGraphicsView::RubberBandDrag);
      _timeType = TType::TICKS;
      magStep   = 0;
      }
 
//---------------------------------------------------------
//   setStaff
//---------------------------------------------------------
 
void DrumView::setStaff(Staff* s, Pos* l)
      {
      static const QColor lcColors[3] = { Qt::red, Qt::blue, Qt::blue };
 
      staff    = s;
      _locator = l;
      pos.setContext(s->score()->tempomap(), s->score()->sigmap());
 
      scene()->blockSignals(true);
 
      scene()->clear();
      for (int i = 0; i < 3; ++i) {
            locatorLines[i] = new QGraphicsLineItem(QLineF(0.0, 0.0, 0.0, keyHeight * 75.0 * 5));
            QPen pen(lcColors[i]);
            pen.setWidth(2);
            locatorLines[i]->setPen(pen);
            locatorLines[i]->setZValue(1000+i);       // set stacking order
            locatorLines[i]->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
            scene()->addItem(locatorLines[i]);
            }
 
      int staffIdx = staff->idx();
      int startTrack = staffIdx * VOICES;
      int endTrack   = startTrack + VOICES;
      for (Segment* s = staff->score()->firstSegment(SegmentType::All); s; s = s->next1()) {
            for (int track = startTrack; track < endTrack; ++track) {
                  Element* e = s->element(track);
                  if (e == 0 || e->type() != ElementType::CHORD)
                        continue;
                  Chord* chord = static_cast<Chord*>(e);
                  foreach(Note* n, chord->notes()) {
                        if (n->tieBack())
                              continue;
                        scene()->addItem(new DrumItem(n));
                        }
                  }
            }
      scene()->blockSignals(false);
 
      Measure* lm = staff->score()->lastMeasure();
      ticks       = lm->tick() + lm->ticks();
      scene()->setSceneRect(0.0, 0.0, double(ticks + 960), keyHeight * 75);
 
      for (int i = 0; i < 3; ++i)
            moveLocator(i);
      //
      // move to something interesting
      //
      QList<QGraphicsItem*> items = scene()->selectedItems();
      QRectF boundingRect;
      foreach(QGraphicsItem* item, items) {
            Note* note = static_cast<Note*>(item->data(0).value<void*>());
            if (note)
                  boundingRect |= item->mapToScene(item->boundingRect()).boundingRect();
            }
      centerOn(boundingRect.center());
      }
 
//---------------------------------------------------------
//   moveLocator
//---------------------------------------------------------
 
void DrumView::moveLocator(int i)
      {
      if (_locator[i].valid()) {
            locatorLines[i]->setVisible(true);
            qreal x = qreal(pos2pix(_locator[i]));
            locatorLines[i]->setPos(QPointF(x, 0.0));
            }
      else
            locatorLines[i]->setVisible(false);
      }
 
//---------------------------------------------------------
//   wheelEvent
//---------------------------------------------------------
 
void DrumView::wheelEvent(QWheelEvent* event)
      {
      int step = event->delta() / 120;
      double xmag = transform().m11();
      double ymag = transform().m22();
 
      if (event->modifiers() == Qt::ControlModifier) {
            if (step > 0) {
                  for (int i = 0; i < step; ++i) {
                        if (xmag > 10.0)
                              break;
                        scale(1.1, 1.0);
                        xmag *= 1.1;
                        }
                  }
            else {
                  for (int i = 0; i < -step; ++i) {
                        if (xmag < 0.001)
                              break;
                        scale(.9, 1.0);
                        xmag *= .9;
                        }
                  }
            emit magChanged(xmag, ymag);
 
            int tpix  = (480 * 4) * xmag;
            magStep = -5;
            if (tpix <= 4000)
                  magStep = -4;
            if (tpix <= 2000)
                  magStep = -3;
            if (tpix <= 1000)
                  magStep = -2;
            if (tpix <= 500)
                  magStep = -1;
            if (tpix <= 128)
                  magStep = 0;
            if (tpix <= 64)
                  magStep = 1;
            if (tpix <= 32)
                  magStep = 2;
            if (tpix <= 16)
                  magStep = 3;
            if (tpix <= 8)
                  magStep = 4;
            if (tpix <= 4)
                  magStep = 5;
            if (tpix <= 2)
                  magStep = 6;
 
            //
            // if xpos <= 0, then the scene is centered
            // there is no scroll bar anymore sending
            // change signals, so we have to do it here:
            //
            double xpos = -(mapFromScene(QPointF()).x());
            if (xpos <= 0)
                  emit xposChanged(xpos);
            }
      else if (event->modifiers() == Qt::ShiftModifier) {
            QWheelEvent we(event->pos(), event->delta(), event->buttons(), 0, Qt::Horizontal);
            QGraphicsView::wheelEvent(&we);
            }
      else if (event->modifiers() == 0) {
            QGraphicsView::wheelEvent(event);
            }
      else if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
            if (step > 0) {
                  for (int i = 0; i < step; ++i) {
                        if (ymag > 3.0)
                              break;
                        scale(1.0, 1.1);
                        ymag *= 1.1;
                        }
                  }
            else {
                  for (int i = 0; i < -step; ++i) {
                        if (ymag < 0.4)
                              break;
                        scale(1.0, .9);
                        ymag *= .9;
                        }
                  }
            emit magChanged(xmag, ymag);
            }
      }
 
//---------------------------------------------------------
//   y2pitch
//---------------------------------------------------------
 
int DrumView::y2pitch(int y) const
      {
      int pitch;
      const int total = (10 * 7 + 5) * keyHeight;       // 75 Ganztonschritte
      y = total - y;
      int oct = (y / (7 * keyHeight)) * 12;
      static const char kt[] = {
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            1, 1, 1, 1, 1, 1, 1,
            2, 2, 2, 2, 2, 2,
            3, 3, 3, 3, 3, 3, 3,
            4, 4, 4, 4, 4, 4, 4, 4, 4,
            5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
            6, 6, 6, 6, 6, 6, 6,
            7, 7, 7, 7, 7, 7,
            8, 8, 8, 8, 8, 8, 8,
            9, 9, 9, 9, 9, 9,
            10, 10, 10, 10, 10, 10, 10,
            11, 11, 11, 11, 11, 11, 11, 11, 11, 11
            };
      pitch = kt[y % 91] + oct;
      if (pitch < 0 || pitch > 127)
            pitch = -1;
      return pitch;
      }
 
//---------------------------------------------------------
//   mouseMoveEvent
//---------------------------------------------------------
 
void DrumView::mouseMoveEvent(QMouseEvent* event)
      {
      QPointF p(mapToScene(event->pos()));
      int pitch = y2pitch(int(p.y()));
      emit pitchChanged(pitch);
      int tick = int(p.x()) -480;
      if (tick < 0) {
            tick = 0;
            pos.setTick(tick);
            pos.setInvalid();
            }
      else
            pos.setTick(tick);
      emit posChanged(pos);
      QGraphicsView::mouseMoveEvent(event);
      }
 
//---------------------------------------------------------
//   leaveEvent
//---------------------------------------------------------
 
void DrumView::leaveEvent(QEvent* event)
      {
      emit pitchChanged(-1);
      pos.setInvalid();
      emit posChanged(pos);
      QGraphicsView::leaveEvent(event);
      }
 
//---------------------------------------------------------
//   ensureVisible
//---------------------------------------------------------
 
void DrumView::ensureVisible(int tick)
      {
      tick += MAP_OFFSET;
      QPointF pt = mapToScene(0, height() / 2);
      QGraphicsView::ensureVisible(qreal(tick), pt.y(), 240.0, 1.0);
      }
}
 

V636 The 'keyHeight / 2' expression was implicitly cast from 'int' type to 'double' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: staff, _locator, locatorLines, ticks.