//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: items.cpp,v 1.1.1.1 2003/10/29 10:05:26 wschweer Exp $
//  (C) Copyright 1999,2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <stdio.h>
#include <assert.h>
#include <qpainter.h>

#include "globals.h"
#include "items.h"
#include "layout.h"
#include "symbols.h"
#include "event.h"
#include "song.h"
#include "midithread.h"

#define A_LYRICS 0

//---------------------------------------------------------
//   ScoreItem
//---------------------------------------------------------

ScoreItem::ScoreItem(int t)
      {
      _tick   = t;
      _state  = Normal;
      sr      = 0;
      }

ScoreItem::ScoreItem(int t, const QPoint& p)
   : pt(p)
      {
      _tick   = t;
      _state  = Normal;
      sr      = 0;
      }

ScoreItem::ScoreItem(int t, StaveRow* _sr)
      {
      _tick   = t;
      _state  = Normal;
      sr      = _sr;
      }

void Symbol::remove()
      {
      midiThread->msgDeleteEvent(_event, _part);
      }

void QuantItem::remove()
      {
      midiThread->msgDeleteEvent(_event, _part);
      }

//---------------------------------------------------------
//   LyricsItem::remove
//---------------------------------------------------------

void LyricsItem::remove()
      {
      MidiEvent* ev = event();
      MidiEvent* newEvent = new MidiEvent(*ev);

      iAttribute i = newEvent->beginAttr();
      for (; i != newEvent->endAttr(); ++i) {
            if (i->type() == A_LYRICS) {
                  newEvent->eraseAttr(i);
                  break;
                  }
            }
//TODO      midiThread->msgChangeEvent(ev, newEvent, part());
      }

//---------------------------------------------------------
//   moveX
//---------------------------------------------------------

void ScoreItem::moveX(int dx)
      {
      pt.setX(pt.x() + dx);
      r.moveBy(dx, 0);
      }

//---------------------------------------------------------
//   ScoreList::find
//---------------------------------------------------------

ScoreItem* ScoreList::find(const QPoint& pos) const
      {
      for (ciScore s = begin(); s != end(); ++s) {
            if (s->second->contains(pos))
                  return s->second;
            }
      return 0;
      }


//---------------------------------------------------------
//   Symbol
//---------------------------------------------------------

Symbol::Symbol(const QPoint& p, int tick,
   MidiEvent* ev, MidiPart* part) : PixmapItem(tick, p)
      {
      _part  = part;
      _event = ev;
      QPixmap* pix = dynBM[_event->dataA()];
      po.setX(-10);
      po.setY(-12);
      setPixmap(pix);
      }

//---------------------------------------------------------
//   draw
//---------------------------------------------------------

void SystemItem::draw(QPainter& p) const
      {
      for (int k = 0; k < lines; ++k)
            p.drawLine(0, k*6, width, k*6);     // lineD = 6
      }

PixmapItem::PixmapItem(int tick, const QPoint& p, const QPixmap* pix)
   : ScoreItem(tick, p)
      {
      setPixmap(pix);
      }

PixmapItem::PixmapItem(int tick, const QPoint& p)
   : ScoreItem(tick, p)
      {
      }

void PixmapItem::setPixmap(const QPixmap* pix)
      {
      pm = pix;
      r  = pm->rect();
      r.moveTopLeft(pt+po);
      }

//---------------------------------------------------------
//   PixmapItem::draw
//---------------------------------------------------------

void PixmapItem::draw(QPainter& p) const
      {
      switch (_state) {
            case Moving:
                  {
                  QRect rr = r;
                  rr.moveTopLeft(_moving+po);
                  p.fillRect(rr, Qt::red);
                  p.drawPixmap(_moving+po, *pm);
                  }
                  break;

            case Selected:
                  p.fillRect(r, Qt::red);
                  // fall through

            case Normal:
                  p.drawPixmap(pt+po, *pm);
                  break;
            }
      }

RestItem::RestItem(int tick, const QPoint& p, int len, int ticksBar)
   : PixmapItem(tick, p)
      {
      if (len == 0)
            return;
      _len = len;
      QPixmap* pix;
      if (len == ticksBar)
            pix = restBM[0];
      else {
            int nn = len/48;        // 1/32
            switch (nn) {
                  case 1:
                        pix = restBM[5];
                        break;
                  case 2:
                        pix = restBM[4];
                        break;
                  case 4:
                        pix = restBM[3];
                        break;
                  case 8:
                        pix = restBM[2];
                        break;
                  case 16:
                        pix = restBM[1];
                        break;
                  case 32:    // ganze note
                        pix = restBM[0];
                        break;
                  default:
                        pix = restBM[5];
                        printf("kann Pausenlänge %d->%d nicht verarbeiten\n", len, nn);
                        break;
                  }
            }
      pt -= QPoint(pix->width()/2, -1);
      setPixmap(pix);
      }

//---------------------------------------------------------
//   Tie::draw
//---------------------------------------------------------

void Tie::draw(QPainter& p) const
      {
      QPoint p1 = note1->pos();
      QPoint p2 = note2->pos();
      if (note2->staveRow() != note1->staveRow()) {
            if (note2->staveRow() != sr) {
                  System* s = sr->system;
                  int x = (s->endBar-s->startBar)*s->measureWidth+s->xoffset;
                  p2 = QPoint(x+5, p1.y());
                  }
            else
                  p1 = p2 + QPoint(-15, 0);
            }

      QPointArray pa(4);
      int y1, y2;
      int yo = note1->hline() * 3;
      if (_up) {
            y1 = yo - 5;
            y2 = y1 - 6;
            }
      else {
            y1 = yo + 5;
            y2 = y1 + 6;
            }
      int x1 = p1.x();
      int x4 = p2.x();
      int x2 = x1 + (x4-x1)/4;
      int x3 = x4 - (x4-x1)/4;
      pa.setPoint(0, x1, y1);
      pa.setPoint(1, x2, y2);
      pa.setPoint(2, x3, y2);
      pa.setPoint(3, x4, y1);
      p.drawCubicBezier(pa);
      }

//---------------------------------------------------------
//   MeasureItem:draw
//---------------------------------------------------------

void MeasureItem::draw(QPainter& p) const
      {
      int x = pt.x();
      int y = pt.y();
      p.save();
      p.setPen(QPen((_state == Selected) ? Qt::red : Qt::black, 2));
      switch(type) {
            case 1:     // end bar
                  p.setPen(QPen((_state == Selected) ? Qt::red : Qt::black, 1));
                  p.drawLine(x-5, y, x-5, y + h);
                  p.fillRect(x-3, y, 4, y + h, (_state == Selected) ? Qt::red : Qt::black);
                  break;
            default:
            case 0:     // normal bar
                  p.drawLine(x, y, x, y + h);
                  break;
            }
      p.restore();
      }

//---------------------------------------------------------
//   Text::draw
//---------------------------------------------------------

void Text::draw(QPainter& p) const
      {
      p.save();
      p.setPen(QPen(color));
      p.setFont(font);
// printf("draw %d %d:%d <%s>\n", _state, pt.x(), pt.y(), s.latin1());
      switch (_state) {
            case Moving:
                  {
                  QRect rr = p.boundingRect(_moving.x(), _moving.y(), 0, 0,
                     align | QPainter::DontClip, s);
                  p.fillRect(rr, Qt::red);
                  }
                  p.drawText(_moving.x(), _moving.y(), 0, 0,
                        align | QPainter::DontClip, s);
                  break;

            case Selected:
                  r = p.boundingRect(pt.x(), pt.y(), 0, 0,
                     align | QPainter::DontClip, s);
                  p.fillRect(r, Qt::red);
                  // fall through

            case Normal:
                  p.drawText(pt.x(), pt.y(), 0, 0,
                        align | QPainter::DontClip, s, -1, &r);
                  break;
            }
      p.restore();
      }

//---------------------------------------------------------
//   QuantItem
//---------------------------------------------------------

QuantItem::QuantItem(int t, MidiEvent* e, MidiPart* p, int n, int r)
  : Text(t, QFont(QString("Carter"), 12, QFont::Bold), Qt::blue)
      {
      _part = p;
      _event = e;
      nq = n;
      rq = r;
      switch (nq) {
            case 1536:  setText(QString("Q1")); break;
            case  768:  setText(QString("Q2")); break;
            case  384:  setText(QString("Q4")); break;
            case  192:  setText(QString("Q8")); break;
            case   96:  setText(QString("Q16")); break;
            case   48:  setText(QString("Q32")); break;
            case   24:  setText(QString("Q64")); break;
            default:    setText(QString("Q")); break;
            }
      setAlign(QPainter::AlignHCenter|QPainter::AlignBottom);
      }

//---------------------------------------------------------
//   KeyItem::draw
//---------------------------------------------------------

void KeyItem::draw(QPainter& p) const
      {
      if (_state == Selected)
            p.fillRect(r, Qt::red);
      key->draw(p, pt);
      }

//---------------------------------------------------------
//   Clef::draw
//---------------------------------------------------------

void Clef::draw(QPainter& p) const
      {
      if (_state == Selected)
            p.fillRect(pt.x(), pt.y(), 20, 20, Qt::red);
      scale->draw(*key, p, pt);
      }

//---------------------------------------------------------
//   TimesigItem::draw
//---------------------------------------------------------

void TimesigItem::draw(QPainter& p) const
      {
      if (_state == Selected)
            p.fillRect(pt.x(), pt.y(), 20, 20, Qt::red);
      switch(type) {
            case 0:
                  p.drawPixmap(pt-QPoint(10,0), *ts44);
                  break;
            case 1:
                  p.drawPixmap(pt-QPoint(10,0), *ts22);
                  break;
            case 2:
            default:
                  {
                  p.setFont(font);
                  QString nomTxt;
                  QString denTxt;
                  denTxt.setNum(denom);
                  nomTxt.setNum(nom1+nom2+nom3);
                  p.drawText(pt.x(), 6*-1, 0, 0, QPainter::AlignHCenter|QPainter::DontClip, nomTxt);
                  p.drawText(pt.x(), 6*1,  0, 0, QPainter::AlignHCenter|QPainter::DontClip, denTxt);
                  }
                  break;
            }
      }

//---------------------------------------------------------
//   LyricsItem::LyricsItem
//---------------------------------------------------------

LyricsItem::LyricsItem(const QPoint& p, Attribute* a, NoteItem* ni, const QFont& font)
   : Text(ni->tick(), a->text(), font)
      {
      pt = p;
      align = QPainter::AlignHCenter|QPainter::AlignBottom;
      _note = ni;
      _attribute = a;
      editState = false;
      cursorVisible = false;
      }

//---------------------------------------------------------
//   LyricsItem::draw
//---------------------------------------------------------

void LyricsItem::draw(QPainter& p) const
      {
      p.save();
      p.setPen(QPen(color));
      p.setFont(font);

      QPoint rp = pos() + _note->pos();

      switch (_state) {
            case Moving:
                  {
                  int x = _moving.x()+ _note->pos().x();
                  int y = _moving.y()+ _note->pos().y();
                  QRect rr = p.boundingRect(x, y, 0, 0,
                     align | QPainter::DontClip, s);
                  p.fillRect(rr, Qt::red);
                  p.drawText(x, y, 0, 0, align | QPainter::DontClip, s);
                  }
                  break;

            case Selected:
                  r = p.boundingRect(rp.x(), rp.y(), 0, 0,
                     align | QPainter::DontClip, s);
                  p.fillRect(r, Qt::red);
                  // fall through

            case Normal:
                  if (editState) {
                        if (s.isEmpty()) {
                              r = p.boundingRect(rp.x(), rp.y(), 0, 0,
                                 align | QPainter::DontClip, QString(" "));
                              }
                        else {
                              r = p.boundingRect(rp.x(), rp.y(), 0, 0,
                                 align | QPainter::DontClip, s);
                              }
                        p.fillRect(r, Qt::yellow);
                        if (cursorVisible) {
                              int x = r.right();
                              p.drawLine(x+2, r.y(), x+2, r.bottom());
                              }
                        }
                  p.drawText(rp.x(), rp.y(), 0, 0,
                        align | QPainter::DontClip, s, -1, &r);
                  break;
            }
      p.restore();
      }

void LyricsItem::blink()
      {
      cursorVisible = !cursorVisible;
      }

void LyricsItem::setEditState()
      {
      editState = true;
      cursorVisible = true;
      }
void LyricsItem::resetEditState()
      {
      editState = false;
      cursorVisible = false;
      }

void LyricsItem::add(const QString& str)
      {
      s += str;
      }

//---------------------------------------------------------
//   clear
//---------------------------------------------------------

void ScoreList::clear()
      {
/*    Objekte werden in mehreren Listen gehalten
      moving + selected und dürfen dort nicht gelöscht werden

      for (iScore i = begin(); i != end(); ++i)
            delete i->second;
 */
      std::multimap<int, ScoreItem*, std::less<int> >::clear();
      }


//---------------------------------------------------------
//   len2beams
//    1 - 8tel
//    2 - 16tel
//---------------------------------------------------------

int len2beams(int len)
      {
      switch (len/12) {
            case 1:  return 5;  // 128
            case 2:  return 4;  // 64
            case 4:  return 3;  // 32
            case 8:  return 2;  // 16
            case 16: return 1;  // 8
            default:
                  return 0;
            }
      }

//---------------------------------------------------------
//   Beam::draw
//---------------------------------------------------------

void Beam::draw(QPainter& p) const
      {
      int minbeams = 5;
      int maxbeams = 0;
      for (ciNote i = notes.begin(); i != notes.end(); ++i) {
            int b = len2beams((*i)->len());
            if (b < minbeams)
                  minbeams = b;
            if (b > maxbeams)
                  maxbeams = b;
            }
      int bh = 20 + (maxbeams-1)*(3+2);
      NoteItem* firstItem = notes.front();
      NoteItem* lastItem = notes.back();

      int x1 = lastItem->pos().x();
      int x2 = firstItem->pos().x();

      bool up = firstItem->hline() > lastItem->hline();

      enum BeamPos { ABOVE, BELOW };
      BeamPos beamPos = ((min+max)/2) <= 4 ? BELOW : ABOVE;

      int minX = min * 3;
      int maxX = max * 3;
      int y1, y2;
      if (beamPos == BELOW) {
            x1 += -4;
            x2 += -4;
            y1 = y2 = bh;
            }
      else {
            x1 += 4;
            x2 += 4;
            y1 = y2 = -bh;
            }
      if (up) {
            y1 += maxX;
            y2 += minX;
            }
      else {
            y1 += minX;
            y2 += maxX;
            }

      int dx1 = x2-x1;
      int dy1 = y2-y1;

      p.save();
      p.setPen(QPen(Qt::black, 1));
      for (ciNote i = notes.begin(); i != notes.end(); ++i) {
            int x3 = (*i)->pos().x();
            int y3 = (*i)->hline()*3;
            if (beamPos == BELOW)
                  x3 -= 4;
            else
                  x3 += 4;
            int y4 = y1 + ((x2-x3)*(dy1)+dx1/2)/dx1;
            p.drawLine(x3, y3, x3, y4);
            int b = len2beams((*i)->len());
            //
            // draw partial beams
            //
            if (minbeams < b)
                  fprintf(stderr, "sorry, not partial beams implemented\n");
            }
      p.setPen(QPen(Qt::black, 3));
      for (int i = 0; i < minbeams; ++i) {
            int hoff = (beamPos == BELOW) ? -(i*5) : (i*5);
            p.drawLine(x2, y1+hoff, x1, y2+hoff);
            }
      p.restore();
      }

//---------------------------------------------------------
//   move
//---------------------------------------------------------

void Symbol::move(MidiEditor*, SystemLayout* system, bool copyflag)
      {
      setState(ScoreItem::Selected);
      MidiEvent* eventX = event();
      QPoint npos  = offset() + moving();

      StaveRow* sr;
      int ny;
      int ntick = system->xy2tick(npos, &ny, &sr);

      if (ntick == -1)
            return;
      if (sr->stave->mode == LOWER)
            ny += system->lineHeight1;
      MidiEvent* newEvent = new MidiEvent(*eventX);
      newEvent->setPosTick(ntick);
      newEvent->setB(ny);
      newEvent->setC(0);
      MidiTrack* track = sr->stave->track;
      MidiPart* part  = (MidiPart*)(track->findPart(ntick));

      if (!copyflag)
            midiThread->msgDeleteEvent(eventX, _part, false);
      midiThread->msgAddEvent(newEvent, part, false);
      setEvent(newEvent);  // damit selektion funktioniert
      }

void TitleItem::move(MidiEditor*, SystemLayout*, bool)
      {
      printf("not impl.: move title\n");
      }

void LyricsItem::move(MidiEditor*, SystemLayout* system, bool copyflag)
      {
      setState(ScoreItem::Selected);
      NoteItem* ni = noteItem();
      MidiEvent* event = ni->event();
      QPoint npos  = offset() + moving();

      StaveRow* sr;
      int ny;
      int ntick = system->xy2tick(npos, &ny, &sr);

      if (ntick == -1)
            return;

      if (sr->stave->mode == LOWER)
            ny += system->lineHeight1;
      MidiEvent* newEvent = new MidiEvent(*event);
      //
      // index des Attributs imt original Event
      // feststellen:
      //
      Attribute* a = attribute();
      int idx = 0;
      iAttribute ia = event->beginAttr();
      for (; ia != event->endAttr(); ++ia, ++idx) {
            if (&*ia == a)
                  break;
            }
      assert(ia != event->endAttr());
      //
      //  pointer auf zu änderndes Attribut in
      //  Event-Kopie legen
      //
      ia = newEvent->beginAttr();
      while (idx--)
            ++ia;
      a = &*ia;

      a->setPos(QPoint(0, ny));
      MidiPart* part = ni->part();

      if (!copyflag)
            midiThread->msgDeleteEvent(event, noteItem()->part());
      midiThread->msgAddEvent(newEvent, part);
      ni->setEvent(newEvent);
      }


