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

#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>

#include <qapplication.h>
#include <qclipboard.h>
#include <qpainter.h>
#include <qpoint.h>
#include <qlineedit.h>
#include <qmessagebox.h>
#include <qdragobject.h>
#include <qpopupmenu.h>
#include <qurl.h>
#include <qmenudata.h>

#include "widgets/tools.h"
#include "midithread.h"
#include "pcanvas.h"
#include "midieditor.h"
#include "globals.h"
#include "icons.h"
#include "event.h"
#include "xml.h"
#include "wave.h"
#include "audio.h"

//---------------------------------------------------------
//   ColorListItem
//---------------------------------------------------------

class ColorListItem : public QCustomMenuItem {
      int r, g, b;
      int h;
      int fontheight;
      QString label;
      virtual QSize sizeHint() { return QSize(80, h); }
      virtual void paint(QPainter* p, const QColorGroup&, bool /*act*/, bool /*enabled*/, int x, int y, int /*w*/, int h)
            {
            p->fillRect(x+5, y+2, h-4, h-4, QBrush(QColor(r,g,b)));
            p->drawText(x+5 + h - 4 + 3, y+(fontheight * 3) / 4, label);
            }

   public:
      ColorListItem(int _r, int _g, int _b, int _h,  int _fh, const char* txt) {
            r = _r;
            g = _g;
            b = _b;
            h = _h;
            label = txt;
	      fontheight = _fh;
            }
      QString text() const { return QString("PartColor"); }
      };

//---------------------------------------------------------
//   NPart
//---------------------------------------------------------

NPart::NPart(Part* e) : CItem((Event*)0, e)
      {
      int th = track()->height();
      int y  = track()->y();
      setPos(QPoint(e->posTick(), y + 1));
      setBBox(QRect(e->posTick(), y + 1, e->lenTick(), th));
      }

//---------------------------------------------------------
//   PartCanvas
//---------------------------------------------------------

PartCanvas::PartCanvas(int* r, QWidget* parent, int sx, int sy)
   : Canvas(parent, sx, sy)
      {
      setBg(QColor(192, 208, 255));

      setAcceptDrops(true);
      _raster = r;
      _showPartType  = 1;      // 1 - names + (2 events or 4 cakewalk style)
      _showPartEvent = 5;      // noteOn + controller

      setFocusPolicy(StrongFocus);
      // Defaults:
      lineEditor = 0;
      showGrid = false;

      tracks = song->tracks();
      setMouseTracking(true);
      drag  = DRAG_OFF;
      curColorIndex = 0;
      partsChanged();
      }

//---------------------------------------------------------
//   y2pitch
//---------------------------------------------------------

int PartCanvas::y2pitch(int y) const
      {
      TrackList* tl = song->tracks();
      int yy  = 0;
      int idx = 0;
      for (iTrack it = tl->begin(); it != tl->end(); ++it, ++idx) {
            int h = (*it)->height();
            if ((y >= yy) && (y < yy+h)) {
                  return idx;
                  }
            yy += h;
            }
      printf("y2pitch  y%d not found\n", y);
      return -1;
      }

//---------------------------------------------------------
//   pitch2y
//---------------------------------------------------------

int PartCanvas::pitch2y(int p) const
      {
      TrackList* tl = song->tracks();
      int yy = 0;
      int idx = 0;
      for (iTrack it = tl->begin(); it != tl->end(); ++it, ++idx) {
            if (idx == p) {
                  return yy;
                  }
            yy += (*it)->height();
            }
      printf("pitch2y: index %d not found\n", p);
      return 0;
      }

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

void PartCanvas::leaveEvent(QEvent*)
      {
      emit timeChanged(-1);
      }

//---------------------------------------------------------
//   returnPressed
//---------------------------------------------------------

void PartCanvas::returnPressed()
      {
      lineEditor->hide();
      Part* oldPart = editPart->part();
      Part* newPart = oldPart->clone();
      newPart->setName(lineEditor->text());
      midiThread->msgChangePart(oldPart, newPart);
      }

//---------------------------------------------------------
//   viewMouseDoubleClick
//---------------------------------------------------------

void PartCanvas::viewMouseDoubleClickEvent(QMouseEvent* event)
      {
      if (_tool != PointerTool) {
            viewMousePressEvent(event);
            return;
            }
      QPoint cpos = event->pos();
      curItem = items.find(cpos);
      bool shift = event->state() & ShiftButton;
      if (curItem) {
            if (event->button() == QMouseEvent::LeftButton && shift) {
                  editPart = (NPart*)curItem;
                  QRect r = map(curItem->bbox());
                  if (lineEditor == 0) {
                        lineEditor = new QLineEdit(this);
                        connect(lineEditor, SIGNAL(returnPressed()),
                           SLOT(returnPressed()));
                        lineEditor->setFrame(true);
                        }
                  lineEditor->setText(editPart->name());
                  lineEditor->setFocus();
                  lineEditor->show();
                  lineEditor->setGeometry(r);
                  }
            else if (event->button() == QMouseEvent::LeftButton) {
                  deselectAll();
                  selectItem(curItem, true);
                  emit dclickPart(((NPart*)(curItem))->track());
                  }
            }
      //
      // double click creates new part between left and
      // right mark

      else {
            TrackList* tl = song->tracks();
            iTrack it;
            int yy = 0;
            int y = event->y();
            for (it = tl->begin(); it != tl->end(); ++it) {
                  int h = (*it)->height();
                  if (y >= yy && y < (yy + h))
                        break;
                  yy += h;
                  }
            if (pos[2] - pos[1] > 0 && it != tl->end()) {
                  Track* track = *it;
                  switch(track->type()) {
                        case Track::MIDI:
                        case Track::DRUM:
                              {
                              MidiPart* part = new MidiPart((MidiTrack*)track);
                              part->setPosTick(pos[1]);
                              part->setLenTick(pos[2]-pos[1]);
                              part->setName(track->name());
                              NPart* np = new NPart(part);
                              items.add(np);
                              deselectAll();
                              part->setSelected(true);
                              midiThread->msgAddPart(part);
                              }
                              break;
                        case Track::WAVE:
                              break;
                        }
                  }
            }
      }

//---------------------------------------------------------
//   startUndo
//---------------------------------------------------------

void PartCanvas::startUndo(DragType)
      {
      song->startUndo();
      }

//---------------------------------------------------------
//   endUndo
//---------------------------------------------------------

void PartCanvas::endUndo(DragType t)
      {
      song->endUndo((t == MOVE_COPY || t == MOVE_CLONE)
         ? SC_PART_INSERTED : SC_PART_MODIFIED);
      }

//---------------------------------------------------------
//   moveItem
//    return false, if copy/move not allowed
//---------------------------------------------------------

bool PartCanvas::moveItem(CItem* item, const QPoint& newpos, DragType t)
      {
      NPart* npart    = (NPart*) item;
      Part* spart     = npart->part();
      Track* track    = npart->track();
      int dtick       = newpos.x();
      unsigned ntrack = y2pitch(item->mp().y());
      Track::TrackType type = track->type();

      if (tracks->index(track) == ntrack && (dtick == spart->posTick())) {
            return false;
            }
      if (ntrack == -1) {
            ntrack = tracks->size();
            song->addTrack(type, track);
            emit tracklistChanged();
            }
      Track* dtrack = tracks->findIdx(ntrack);

      if (dtrack->type() != type) {
            QMessageBox::critical(this, QString("MusE"),
               tr("Cannot copy/move/clone to different Track-Type"));
            return false;
            }

      Part* dpart;
      bool clone = (t == MOVE_CLONE) || (spart->events()->arefCount() > 1);
      dpart = dtrack->newPart(spart, clone);
      dpart->setPosTick(dtick);

      if (!clone) {
            //
            // Copy Events
            //
            EventList* se = spart->events();
            EventList* de = dpart->events();
            for (iEvent i = se->begin(); i != se->end(); ++i) {
                  Event* oldEvent = i->second;
                  Event* ev = oldEvent->clone();
                  de->add(ev);
                  }
            }
      if (t == MOVE_COPY || t == MOVE_CLONE) {
            if (dtrack->type() == Track::WAVE)
                  audio->msgAddPart((WavePart*)dpart);
            else
                  midiThread->msgAddPart(dpart);
            }
      else if (t == MOVE_MOVE) {
            dpart->setSelected(spart->selected());
            if (dtrack->type() == Track::WAVE)
                  audio->msgChangePart((WavePart*)spart, (WavePart*)dpart);
            else
                  midiThread->msgChangePart(spart, dpart, false);
            }
      if (song->len() < (dpart->lenTick() + dpart->posTick()))
            song->setLen(dpart->lenTick() + dpart->posTick());
      return true;
      }

//---------------------------------------------------------
//   raster
//---------------------------------------------------------

QPoint PartCanvas::raster(const QPoint& p) const
      {
      int y = pitch2y(y2pitch(p.y()));
      int x = sigmap.raster(p.x(), *_raster);
      if (x < 0)
            x = 0;
      return QPoint(x, y);
      }

//---------------------------------------------------------
//   partsChanged
//---------------------------------------------------------

void PartCanvas::partsChanged()
      {
      items.clear();
      int idx = 0;
      for (iTrack t = tracks->begin(); t != tracks->end(); ++t) {
            PartList* pl = (*t)->parts();
            for (iPart i = pl->begin(); i != pl->end(); ++i) {
                  NPart* np = new NPart(i->second);
                  items.add(np);
                  if (i->second->selected()) {
                        selectItem(np, true);
                        }
                  }
            ++idx;
            }
      redraw();
      }

//---------------------------------------------------------
//   updateSelection
//---------------------------------------------------------

void PartCanvas::updateSelection()
      {
      for (iCItem i = items.begin(); i != items.end(); ++i) {
            NPart* part = (NPart*)(i->second);
            part->part()->setSelected(i->second->isSelected());
            }
      emit selectionChanged();
      redraw();
      }

//---------------------------------------------------------
//   resizeItem
//---------------------------------------------------------

void PartCanvas::resizeItem(CItem* i, bool noSnap)
      {
      Track* t = ((NPart*)(i))->track();
      Part*  p = ((NPart*)(i))->part();
      int w = i->width();
      if (!noSnap)
            w = sigmap.raster(w, *_raster);
      if (w == 0)
            w = sigmap.rasterStep(p->posTick(), *_raster);
      song->cmdResizePart(t, p, w);
      }

//---------------------------------------------------------
//   newItem
//    first create local Item
//---------------------------------------------------------

CItem* PartCanvas::newItem(const QPoint& pos, int)
      {
      int x = sigmap.raster(pos.x(), *_raster);
      unsigned trackIndex = y2pitch(pos.y());
      if (trackIndex == -1)
            return 0;
      Track* track = tracks->findIdx(trackIndex);
      Part* pa  = 0;
      NPart* np = 0;
      switch(track->type()) {
            case Track::MIDI:
            case Track::DRUM:
                  pa = new MidiPart((MidiTrack*)track);
                  pa->setPosTick(x);
                  pa->setLenTick(0);
                  break;
            case Track::WAVE:
                  pa = new WavePart((WaveTrack*)track);
                  pa->setPosTick(x);
                  pa->setLenTick(0);
                  break;
            }
      pa->setName(track->name());
      pa->setColorIndex(curColorIndex);
      np = new NPart(pa);
      return np;
      }

//---------------------------------------------------------
//   newItem
//---------------------------------------------------------

void PartCanvas::newItem(CItem* i, bool noSnap)
      {
      Part*  p = ((NPart*)(i))->part();

      int len = i->width();
      if (!noSnap)
            len = sigmap.raster(len, *_raster);
      if (len == 0)
            len = sigmap.rasterStep(p->posTick(), *_raster);
      p->setLenTick(len);
      p->setSelected(true);
      midiThread->msgAddPart(p);
      }

//---------------------------------------------------------
//   deleteItem
//---------------------------------------------------------

bool PartCanvas::deleteItem(CItem* i)
      {
      Part*  p = ((NPart*)(i))->part();
      midiThread->msgRemovePart(p);
      return true;
      }

//---------------------------------------------------------
//   splitItem
//---------------------------------------------------------

void PartCanvas::splitItem(CItem* item, const QPoint& pt)
      {
      NPart* np = (NPart*) item;
      Track* t = np->track();
      Part*  p = np->part();
      song->cmdSplitPart(t, p, sigmap.raster(pt.x(), *_raster));
      }

//---------------------------------------------------------
//   glueItem
//---------------------------------------------------------

void PartCanvas::glueItem(CItem* item)
      {
      NPart* np = (NPart*) item;
      Track* t = np->track();
      Part*  p = np->part();
      song->cmdGluePart(t, p);
      }

//---------------------------------------------------------
//   genItemPopup
//---------------------------------------------------------

QPopupMenu* PartCanvas::genItemPopup(CItem* item)
      {
      NPart* npart = (NPart*) item;
      Track::TrackType trackType = npart->track()->type();

      QPopupMenu* partPopup = new QPopupMenu(this);

      partPopup->insertItem(*editcutIconSet, tr("C&ut"), 4);
      partPopup->setAccel(CTRL+Key_X, 4);

      partPopup->insertItem(*editcopyIconSet, tr("&Copy"), 5);
      partPopup->setAccel(CTRL+Key_C, 5);

      partPopup->insertSeparator();
      partPopup->insertItem(tr("rename"), 0);
      QPopupMenu* colorPopup = new QPopupMenu(this);
      partPopup->insertItem(tr("color"), colorPopup);

      // part color selection
      const QFontMetrics& fm = colorPopup->fontMetrics();
      int h = fm.lineSpacing();

      PartColor* p = partColors;
      for (int i = 0; i < NUM_PARTCOLORS; ++i, ++p) {
            ColorListItem* item = new ColorListItem(p->r, p->g, p->b, h, fontMetrics().height(), p->name);
            colorPopup->insertItem(item, 20+i);
            }

      partPopup->insertItem(*deleteIcon, tr("delete"), 1);
      partPopup->insertItem(*cutIcon, tr("split"),  2);
      partPopup->insertItem(*glueIcon, tr("glue"),   3);
      partPopup->insertItem(tr("de-clone"), 15);

      partPopup->insertSeparator();
      switch(trackType) {
            case Track::MIDI:
                  partPopup->insertItem(*pianoIconSet, tr("pianoroll"), 10);
                  partPopup->insertItem(*scoreIconSet, tr("score"), 11);
                  partPopup->insertItem(tr("list"), 12);
                  break;
            case Track::DRUM:
                  partPopup->insertItem(tr("list"), 12);
                  partPopup->insertItem(tr("drums"), 13);
                  break;
            case Track::WAVE:
                  partPopup->insertItem(tr("wave edit"), 14);
                  break;
            }

      partPopup->setItemEnabled(1, true);
      partPopup->setItemEnabled(4, true);
      bool isClone = npart->part()->events()->arefCount() > 1;
      partPopup->setItemEnabled(15, isClone);
      return partPopup;
      }

//---------------------------------------------------------
//   itemPopup
//---------------------------------------------------------

void PartCanvas::itemPopup(CItem* item, int n, const QPoint& pt)
      {
      PartList* pl = new PartList;
      NPart* npart = (NPart*)(item);
      pl->add(npart->part());
      switch(n) {
            case 0:     // rename
                  {
                  editPart = npart;
                  QRect r = map(curItem->bbox());
                  if (lineEditor == 0) {
                        lineEditor = new QLineEdit(this);
                        connect(lineEditor, SIGNAL(returnPressed()),
                        SLOT(returnPressed()));
                        lineEditor->setFrame(true);
                        }
                  lineEditor->setText(editPart->name());
                  lineEditor->setFocus();
                  lineEditor->show();
                  lineEditor->setGeometry(r);
                  }
                  break;
            case 1:     // delete
                  deleteItem(item);
                  break;
            case 2:     // split
                  splitItem(item, pt);
                  break;
            case 3:     // glue
                  glueItem(item);
                  break;
            case 4:
                  copy(pl);
                  midiThread->msgRemovePart(npart->part());
                  break;
            case 5:
                  copy(pl);
                  break;
            case 10:    // pianoroll edit
                  emit startEditor(pl, 0);
                  return;
            case 11:    // score edit
                  {
                  Track* track = npart->part()->track();
                  if (!track->selected()) {
                        song->tracks()->deselect();
                        track->setSelected(true);
                        emit tracklistChanged();
                        }
                  emit startEditor(pl, 2);
                  }
                  return;
            case 12:    // list edit
                  emit startEditor(pl, 1);
                  return;
            case 13:    // drum edit
                  emit startEditor(pl, 3);
                  return;
            case 14:
                  emit startEditor(pl, 4);
                  return;
            case 15:    // declone
                  {
                  Part* spart  = npart->part();
                  Track* track = npart->track();
                  Part* dpart  = track->newPart(spart, false);

                  EventList* se = spart->events();
                  EventList* de = dpart->events();
                  for (iEvent i = se->begin(); i != se->end(); ++i) {
                        Event* oldEvent = i->second;
                        Event* ev = oldEvent->clone();
                        de->add(ev);
                        }
                  song->startUndo();
                  midiThread->msgChangePart(spart, dpart, false);
                  song->endUndo(SC_PART_MODIFIED);
                  }
            case 20 ... NUM_PARTCOLORS+20:
                  curColorIndex = n - 20;
                  npart->part()->setColorIndex(curColorIndex);
                  redraw();
                  break;

            default:
                  printf("unknown action %d\n", n);
                  break;
            }
      delete pl;
      }

//---------------------------------------------------------
//   viewMousePressEvent
//---------------------------------------------------------

void PartCanvas::mousePress(QMouseEvent* event)
      {
      if (event->state() & ShiftButton)
            return;
      QPoint pt = event->pos();
      CItem* item = items.find(pt);
      if (item == 0)
            return;
      switch (_tool) {
            default: break;
            case CutTool:
                  splitItem(item, pt);
                  break;
            case GlueTool:
                  glueItem(item);
                  break;
            case MuteTool:
                  {
                  NPart* np = (NPart*) item;
                  Part*  p = np->part();
                  p->setMute(!p->mute());
                  redraw();
                  break;
                  }
            }
      }

//---------------------------------------------------------
//   viewMouseReleaseEvent
//---------------------------------------------------------

void PartCanvas::mouseRelease(const QPoint&)
      {
      }

//---------------------------------------------------------
//   viewMouseMoveEvent
//---------------------------------------------------------

void PartCanvas::mouseMove(const QPoint& pos)
      {
//      emit timeChanged(sigmap.raster1(pos.x(), *_raster));
      emit timeChanged(sigmap.raster(pos.x(), *_raster));
      }

//---------------------------------------------------------
//   keyPress
//---------------------------------------------------------

void PartCanvas::keyPress(QKeyEvent* event)
      {
      int key = event->key();
      if (key == Key_Delete) {
            song->startUndo();
            song->msgRemoveParts();
            song->endUndo(SC_PART_REMOVED);
            return;
            }
      if (!isSingleSelection()) {
            event->ignore();  // give global accelerators a chance
            return;
            }
      CItem* item = 0;
      for (iCItem i = items.begin(); i != items.end(); ++i) {
            if (i->second->isSelected()) {
                  item = i->second;
                  break;
                  }
            }
      Part* part   = ((NPart*)item)->part();
      int tick     = part->posTick();
      Track* track = part->track();

      CItem* newItem = 0;
      Part* newPart = 0;

      bool alt = event->state() & AltButton;

      switch(key) {
            case Key_Return:
                  if (!item)
                        break;
                  else {
                        PartList* pl = new PartList;
                        NPart* npart = (NPart*)(item);
                        pl->add(npart->part());
                        int type = 0; 
                        switch (track->type()) { //Check if track is wave or drum, else track is midi 
                              case Track::DRUM: 
                              type = 3; 
                              break; 
 
                              case Track::WAVE: 
                              type = 4; 
                              break; 
 
                              default: 
                              break; 
                        } 
 
                        emit startEditor(pl, type); 
                        }
                  break;

            case Key_Right:
                  for (iCItem i = items.begin(); i != items.end(); ++i) {
                        NPart* npart = (NPart*)(i->second);
                        Part* ipart = npart->part();
                        if (ipart->track() != track)
                              continue;
                        if (ipart->posTick() < tick)
                              continue;
                        if (ipart == part)
                              continue;
                        if (newItem) {
                              if (ipart->posTick() < newPart->posTick()) {
                                    newItem = i->second;
                                    newPart = ipart;
                                    }
                              }
                        else {
                              newItem = i->second;
                              newPart = ipart;
                              }

                        }
                  break;
            case Key_Left:
                  for (iCItem i = items.begin(); i != items.end(); ++i) {
                        NPart* npart = (NPart*)(i->second);
                        Part* ipart = npart->part();
                        if (ipart->track() != track)
                              continue;
                        if (ipart->posTick() > tick)
                              continue;
                        if (ipart == part)
                              continue;
                        if (newItem) {
                              if (ipart->posTick() > newPart->posTick()) {
                                    newItem = i->second;
                                    newPart = ipart;
                                    }
                              }
                        else {
                              newItem = i->second;
                              newPart = ipart;
                              }
                        }
                  break;

            case Key_P:
                  if (alt) {
                        song->setPos(1,part->posTick());
                        song->setPos(2,part->posTick() + part->lenTick());
                        }
                  break;

            case Key_Up:
                  if(!item)
                        break;
                  else {
                        //To get an idea of which track is above us:
                        int stepsize = rmapxDev(1);
                        CItem *aboveL = 0, *aboveR = 0;
                        int y = item->y() - 1;
                        int x = item->x();
                        int middle = (x + item->width()/2);

                        //Get upper and lower limit of what is shown in the window
                        int ulimit  = this->mapxDev(this->width());
                        int llimit  = this->mapxDev(0);
                        int xoffset = 0;
                        int xleft   = middle - xoffset;
                        int xright  = middle + xoffset;
                        while ((xleft > llimit || xright < ulimit)  && (aboveL == 0) && (aboveR == 0)) {
                              xoffset += stepsize;
                              xleft  = middle - xoffset;
                              xright = middle + xoffset;
                              if (xleft >= 0)
                                    aboveL = items.find(QPoint(xleft,y));
                              if (xright <= ulimit)
                                    aboveR = items.find(QPoint(xright,y));
                              }

                        if ((aboveL || aboveR) != 0) { //We've hit something
                              CItem* above  = 0;
                              above = (aboveL !=0) ? aboveL : aboveR;
                              newItem = above;
                              }
                        }
                  break;

            case Key_Down:
                  if(!item)
                        break;
                  else {
                        //To get an idea of which track is below us:
                        int stepsize = rmapxDev(1);
                        CItem *belowL = 0, *belowR = 0;
                        int y = item->y() + item->height() + 1;
                        int x = item->x();
                        int middle = (x + item->width()/2);

                        //Get upper and lower limit of what is shown in the window
                        int ulimit  = this->mapxDev(this->width());
                        int llimit  = this->mapxDev(0);
                        int xoffset = 0;
                        int xleft   = middle - xoffset;
                        int xright  = middle + xoffset;
                        while ((xleft > llimit || xright < ulimit)  && (belowL == 0) && (belowR == 0)) {
                              xoffset += stepsize;
                              xleft  = middle - xoffset;
                              xright = middle + xoffset;
                              if (xleft >= 0)
                                    belowL = items.find(QPoint(xleft,y));
                              if (xright <= ulimit)
                                    belowR = items.find(QPoint(xright,y));
                              }

                        if ((belowL || belowR) != 0) { //We've hit something
                              CItem* below = 0;
                              below = (belowL !=0) ? belowL : belowR;
                              newItem = below;
                              }
                        }
                  break;

            default:
                  event->ignore();  // give global accelerators a chance
                  break;
            }
      if (newItem) {
            selectItem(item, false);
            selectItem(newItem, true);
            //Check if we've hit the upper or lower boundaries of the window. If so, set a new position
            if (newItem->x() < mapxDev(0)) {
                  int curpos = pos[0];
                  setPos(0,newItem->x(),true);
                  setPos(0,curpos,false); //Dummy to put the current position back once we've scrolled
                  }
            else if (newItem->x() > mapxDev(width())) {
                  int curpos = pos[0];
                  setPos(0,newItem->x(),true);
                  setPos(0,curpos,false); //Dummy to put the current position back once we've scrolled
                  }
            redraw();
            }
      }

//---------------------------------------------------------
//   drawPart
//    draws a part
//---------------------------------------------------------

void PartCanvas::drawItem(QPainter& p, const CItem* item, const QRect& rect) const
      {
      int from   = rect.x();
      int to     = from + rect.width();

      Part* part = ((NPart*)item)->part();
      int pTick  = part->posTick();
      from      -= pTick;
      to        -= pTick;

      QRect r    = item->bbox();
      int i      = part->colorIndex();

      p.setPen(black);
      if (part->mute()) {
            p.setBrush(gray);
            p.drawRect(r);
            }
      if (item->isMoving()) {
            p.setBrush(gray);
            p.drawRect(r);
            p.setBrush(NoBrush);
            p.drawRect(item->mp().x(), item->mp().y()+1, item->width(), item->height());
            }
      else if (part->mute())
            return;
      else if (part->selected()) {
            p.setBrush(black);
            p.setPen(QColor(partColors[i].r, partColors[i].g, partColors[i].b));
            p.drawRect(r);
            }
      else {
            bool clone = part->events()->arefCount() > 1;
            p.setPen(QPen(black, 2, clone ? DashLine : SolidLine));
            p.setBrush(QColor(partColors[i].r, partColors[i].g, partColors[i].b));
            p.drawRect(r);
            }

      bool waveType;
      MidiPart* mp = 0;
      WavePart* wp = 0;
      Track::TrackType type = part->track()->type();
      if (type == Track::WAVE) {
            waveType = true;
            wp =(WavePart*)part;
            }
      else {
            mp = (MidiPart*)part;
            waveType = false;
            }

      if (_showPartType & 2) {      // show events
            if (mp) {
                  p.setPen(darkGray);
                  EventList* events = mp->events();
                  iEvent ito(events->lower_bound(to));
                  for (iEvent i = events->lower_bound(from); i != ito; ++i) {
                        MidiEvent::EventType type = ((MidiEvent*)i->second)->type();
                        if (
                           ((_showPartEvent & 1) && (type == MidiEvent::Note))
                           || ((_showPartEvent & 2) && (type == MidiEvent::PAfter))
                           || ((_showPartEvent & 4) &&
                                (type == MidiEvent::Ctrl7
                                || type == MidiEvent::Ctrl14
                                || type == MidiEvent::NRPN || type == MidiEvent::RPN))
                           || ((_showPartEvent & 8) && (type == MidiEvent::Program))
                           || ((_showPartEvent &16) && (type == MidiEvent::CAfter))
                           || ((_showPartEvent &32) && (type == MidiEvent::Pitch))
                           || ((_showPartEvent &64) && (type == MidiEvent::Sysex || type == MidiEvent::Meta))
                           ) {
                              int t = i->first + pTick;
                              int th = part->track()->height();
                              p.drawLine(t, r.y()+2, t, r.y()+th-4);
                              }
                        }
                  }
            else if (wp)
                  drawWavePart(p, rect, wp, r);
            }

      else {      // show Cakewalk Style
            if (mp) {
                  p.setPen(darkGray);
                  EventList* events = mp->events();
                  iEvent ito(events->lower_bound(to));
                  for (iEvent i = events->begin(); i != ito; ++i) {
                        int t  = i->first + pTick;
                        int te = t + i->second->lenTick();

                        if (t > (to + pTick))
                              break;

                        if (te < (from + pTick))
                              continue;

                        if (te > (to + pTick))
                              te = to + pTick;

                        MidiEvent::EventType type = ((MidiEvent*)i->second)->type();
                        if (type == MidiEvent::Note) {
                              int pitch = ((MidiEvent*)i->second)->pitch();
                              int th = part->track()->height();
                              int y     = r.y() + th - (pitch * (th-6) / 127);
                              p.drawLine(t, y, te, y);
                              }
                        }
                  }
            else if (wp)
                  drawWavePart(p, rect, wp, r);
            }
      if (_showPartType & 1) {     // show names
            // draw name
	      // FN: Set text color depending on part color (black / white)
		int brightness;
		brightness =  partColors[i].r*29 + partColors[i].g*59 + partColors[i].b*12;
		if (brightness < 12000 || part->selected())
		      p.setPen(white);   /* too dark: use white for text color */
		else
			p.setPen(black);  /* otherwise use black */
            QRect rr = map(r);
            rr.setX(rr.x() + 3);
            p.save();
            p.setFont(font1);
            p.setWorldXForm(false);
            p.drawText(rr, AlignTop|AlignLeft, part->name());
            p.restore();
            }
      }

//---------------------------------------------------------
//   drawWavePart
//    bb - bounding box of paint area
//    pr - part rectangle
//---------------------------------------------------------

void PartCanvas::drawWavePart(QPainter& p,
   const QRect& bb, WavePart* wp, const QRect& _pr) const
      {
      QRect rr = p.worldMatrix().map(bb);
      QRect pr = p.worldMatrix().map(_pr);

      p.save();
      p.resetXForm();

      int x2 = 1;
      int x1  = rr.x() > pr.x() ? rr.x() : pr.x();
      x2 += rr.right() < pr.right() ? rr.right() : pr.right();

      if (x1 < 0)
            x1 = 0;
      if (x2 > width())
            x2 = width();
      int hh = pr.height();
      int h  = hh/2;
      int y  = pr.y() + h;

      EventList* el = wp->events();
      for (iEvent e = el->begin(); e != el->end(); ++e) {
            int cc = hh % 2 ? 0 : 1;
            WaveEvent* event  = (WaveEvent*)e->second;
            Clip* clip        = event->clip();
            if (!clip)
                  continue;
            const SndFile* f  = clip->file1();
            if (!f)
                  continue;
            int samples = event->lenSample();

            int w       = rmapx(event->lenTick());
            int xScale  = (samples + w/2)/w;
            int pos     = (x1 - pr.x()) * xScale + clip->spos();
            if (h < 20) {
                  //
                  //    combine multi channels into one waveform
                  //
                  for (int i = x1; i < x2; i++) {
                        SampleV sa[f->channels()];
                        const_cast<SndFile*>(f)->read(sa, xScale, pos);
                        pos += xScale;
                        int peak = 0;
                        int rms  = 0;
                        for (unsigned k = 0; k < f->channels(); ++k) {
                              if (sa[k].peak > peak)
                                    peak = sa[k].peak;
                              rms += sa[k].rms;
                              }
                        rms /= f->channels();
                        peak = (peak * (hh-2)) >> 9;
                        rms  = (rms  * (hh-2)) >> 9;
                        p.setPen(QColor(darkGray));
                        p.drawLine(i, y - peak - cc, i, y + peak);
                        p.setPen(QColor(black));
                        p.drawLine(i, y - rms - cc, i, y + rms);
                        }
                  }
            else {
                  //
                  //  multi channel display
                  //
                  h = hh / (f->channels() * 2);
                  int cc = hh % (f->channels() * 2) ? 0 : 1;
                  for (int i = x1; i < x2; i++) {
                        y  = pr.y() + h;
                        SampleV sa[f->channels()];
                        const_cast<SndFile*>(f)->read(sa, xScale, pos);
                        pos += xScale;
                        for (unsigned k = 0; k < f->channels(); ++k) {
                              int peak = (sa[k].peak * (h - 1)) >> 8;
                              int rms  = (sa[k].rms  * (h - 1)) >> 8;
                              p.setPen(QColor(darkGray));
                              p.drawLine(i, y - peak - cc, i, y + peak);
                              p.setPen(QColor(black));
                              p.drawLine(i, y - rms - cc, i, y + rms);
                              y  += 2 * h;
                              }
                        }
                  }
            }
      p.restore();
      }

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

void PartCanvas::cmd(int cmd)
      {
      PartList pl;
      for (iCItem i = items.begin(); i != items.end(); ++i) {
            if (!i->second->isSelected())
                  continue;
            NPart* npart = (NPart*)(i->second);
            pl.add(npart->part());
            }
      switch (cmd) {
            case CMD_CUT_PART:
                  copy(&pl);
                  song->startUndo();
                  for (iCItem i = items.begin(); i != items.end(); ++i) {
                        if (!i->second->isSelected())
                              continue;
                        NPart* p = (NPart*)(i->second);
                        Part* part = p->part();
                        midiThread->msgRemovePart(part);
                        }
                  song->endUndo(SC_PART_REMOVED);
                  break;
            case CMD_COPY_PART:
                  copy(&pl);
                  break;
            case CMD_PASTE_PART:
                  paste();
                  break;
            }
      }

//---------------------------------------------------------
//   copy
//    cut copy paste
//---------------------------------------------------------

void PartCanvas::copy(PartList* pl)
      {
      if (pl->empty())
            return;
      bool isWave = pl->begin()->second->track()->type() == Track::WAVE;

      //---------------------------------------------------
      //    write parts as XML into tmp file
      //---------------------------------------------------

      FILE* tmp = tmpfile();
      if (tmp == 0) {
            fprintf(stderr, "ParCanvas::copy() fopen failed: %s\n",
               strerror(errno));
            return;
            }
      Xml xml(tmp);

      int level = 0;
      int tick = 0;
      for (ciPart p = pl->begin(); p != pl->end(); ++p) {
            p->second->write(level, xml);
            int endTick = p->second->endTick();
            if (endTick > tick)
                  tick = endTick;
            }
      song->setPos(0, tick);

      //---------------------------------------------------
      //    read tmp file into QTextDrag Object
      //---------------------------------------------------

      fflush(tmp);
      struct stat f_stat;
      if (fstat(fileno(tmp), &f_stat) == -1) {
            fprintf(stderr, "PartCanvas::copy() fstat failes:<%s>\n",
               strerror(errno));
            fclose(tmp);
            return;
            }
      int n = f_stat.st_size;
      char* fbuf  = (char*)mmap(0, n+1, PROT_READ|PROT_WRITE,
         MAP_PRIVATE, fileno(tmp), 0);
      fbuf[n] = 0;
      QTextDrag* drag = new QTextDrag(QString(fbuf));
      drag->setSubtype(QCString(isWave ? "wavepartlist" : "midipartlist"));
      QApplication::clipboard()->setData(drag, QClipboard::Clipboard);
      munmap(fbuf, n);
      fclose(tmp);
      }

//---------------------------------------------------------
//   paste
//---------------------------------------------------------

int PartCanvas::pasteAt(const QString& pt, Track* track, int pos)
      {
      const char* p = pt.latin1();
      Xml xml(p);

      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return pos;
                  case Xml::TagStart:
                        if (tag == "part") {
                              Part* p;
                              if (track->type() == Track::MIDI || track->type() == Track::DRUM)
                                    p = new MidiPart((MidiTrack*)track);
                              else if (track->type() == Track::WAVE)
                                    p = new WavePart((WaveTrack*)track);
                              else
                                    break;
                              p->read(xml);
                              p->setPosTick(pos);
                              midiThread->msgAddPart(p);
                              pos += p->lenTick();
                              return pos;
                              }
                        else
                              xml.unknown("Arranger::paste");
                        break;
                  case Xml::TagEnd:
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   paste
//    paste part to current selected track at cpos
//---------------------------------------------------------

void PartCanvas::paste()
      {
      TrackList* tl = song->tracks();
      Track* track = 0;
      for (iTrack i = tl->begin(); i != tl->end(); ++i) {
            if ((*i)->selected()) {
                  if (track) {
                        QMessageBox::critical(this, QString("MusE"),
                          tr("Cannot paste: multiple tracks selected"));
                        return;
                        }
                  else
                        track = *i;
                  }
            }
      if (track == 0) {
            QMessageBox::critical(this, QString("MusE"),
              tr("Cannot paste: no track selected"));
            return;
            }

      QClipboard* cb  = QApplication::clipboard();
      QMimeSource* ms = cb->data(QClipboard::Clipboard);

      bool midiPart = false;
      bool wavePart = false;

      for (int i = 0; const char* format = ms->format(i); ++i) {
            format = ms->format(i);
            if (strcmp(format, "text/midipartlist") == 0) {
                  if (track->type() == Track::WAVE) {
                        QMessageBox::critical(this, QString("MusE"),
                              tr("Cannot paste midi parts to wave track"));
                        return;
                        }
                  midiPart = true;
                  }
            else if (strcmp(format, "text/wavepartlist") == 0) {
                  if (track->type() != Track::WAVE) {
                        QMessageBox::critical(this, QString("MusE"),
                        tr("Cannot paste wave parts to midi track"));
                        return;
                        }
                  wavePart = true;
                  }
            }

      if (!(midiPart || wavePart)) {
            QMessageBox::critical(this, QString("MusE"),
               tr("Cannot paste: wrong data type"));
            return;
            }
      QCString subtype = 0;
      QString txt = cb->text(subtype);
      if (!txt.isEmpty()) {
            int tick = pasteAt(txt, track, song->cpos());
            song->setPos(0, tick);
            }
      }

//---------------------------------------------------------
//   startDrag
//---------------------------------------------------------

void PartCanvas::startDrag(CItem* item, DragType t)
      {
      NPart* p = (NPart*)(item);
      Part* part = p->part();

      //---------------------------------------------------
      //    write part as XML into tmp file
      //---------------------------------------------------

      FILE* tmp = tmpfile();
      if (tmp == 0) {
            fprintf(stderr, "ParCanvas::copy() fopen failed: %s\n",
               strerror(errno));
            return;
            }
      Xml xml(tmp);
      int level = 0;
      part->write(level, xml);

      //---------------------------------------------------
      //    read tmp file into QTextDrag Object
      //---------------------------------------------------

      fflush(tmp);
      struct stat f_stat;
      if (fstat(fileno(tmp), &f_stat) == -1) {
            fprintf(stderr, "PartCanvas::copy() fstat failes:<%s>\n",
               strerror(errno));
            fclose(tmp);
            return;
            }
      int n = f_stat.st_size + 1;
      char* fbuf  = (char*)mmap(0, n, PROT_READ|PROT_WRITE,
         MAP_PRIVATE, fileno(tmp), 0);
      fbuf[n] = 0;
      QTextDrag* drag = new QTextDrag(QString(fbuf), this);
      drag->setSubtype("partlist");
      if (t == MOVE_COPY || t == MOVE_CLONE)
            drag->dragCopy();
      else
            drag->dragMove();
      munmap(fbuf, n);
      fclose(tmp);
      }

//---------------------------------------------------------
//   dragEnterEvent
//---------------------------------------------------------

void PartCanvas::dragEnterEvent(QDragEnterEvent* event)
      {
      printf("enter drag\n");
      event->accept(QTextDrag::canDecode(event));
      }

//---------------------------------------------------------
//   dragMoveEvent
//---------------------------------------------------------

void PartCanvas::dragMoveEvent(QDragMoveEvent*)
      {
//      printf("drag move %x\n", this);
      }

//---------------------------------------------------------
//   dragLeaveEvent
//---------------------------------------------------------

void PartCanvas::dragLeaveEvent(QDragLeaveEvent*)
      {
//      printf("drag leave\n");
      }

//---------------------------------------------------------
//   dropEvent
//---------------------------------------------------------

void PartCanvas::viewDropEvent(QDropEvent* event)
      {
      if (event->source() == this) {
            printf("no local DROP\n");
            return;
            }
      int type = 0;     // 0 = unknown, 1 = partlist, 2 = uri-list
      QString text;
      for (int i = 0; ; ++i) {
            const char* p= event->format(i);
            if (p == 0)
                  break;
            if (strncmp(p, "text/partlist", 13) == 0) {
                  type = 1;
                  break;
                  }
            else if (strcmp(p, "text/uri-list") == 0) {
                  type = 2;
                  break;
                  }
            else
                  printf("unknown drop format <%s>\n", p);
            }
      if (type == 0)
            return;
      if (QTextDrag::decode(event, text)) {
            if (type == 1) {
                  int x = sigmap.raster(event->pos().x(), *_raster);
                  if (x < 0)
                        x = 0;
                  unsigned trackNo = y2pitch(event->pos().y());
                  Track* track = 0;
                  if (trackNo < tracks->size())
                        track = tracks->findIdx(trackNo);
                  if (track)
                        pasteAt(text, track, x);
                  }
            else if (type == 2) {
                  QUrl url(text);
                  emit dropFile(url.path());
                  }
            }
      }

//---------------------------------------------------------
//   setShowPartType
//---------------------------------------------------------

void PartCanvas::setShowPartType(int val)
      {
      _showPartType = val;
      redraw();
      }

//---------------------------------------------------------
//   showPartType
//---------------------------------------------------------

int PartCanvas::showPartEvent() const
      {
      return _showPartEvent;
      }

//---------------------------------------------------------
//   setShowPartEvent
//---------------------------------------------------------

void PartCanvas::setShowPartEvent(int val)
      {
      _showPartEvent = val;
      redraw();
      }

//---------------------------------------------------------
//   drawCanvas
//---------------------------------------------------------

void PartCanvas::drawCanvas(QPainter& p, const QRect& rect)
      {
      if (!showGrid)
            return;

      int x = rect.x();
      int y = rect.y();
      int w = rect.width();
      int h = rect.height();

      //////////
      // GRID //
      //////////

      p.setPen(gray);

      //--------------------------------
      // horizontal lines
      //--------------------------------

      TrackList* tl = song->tracks();
      int yy = 0;
      for (iTrack it = tl->begin(); it != tl->end(); ++it) {
            int h = (*it)->height();
            if (yy > y+h)
                  break;
	      p.drawLine(x, yy, x + w, yy);
            yy += h;
            }

      //--------------------------------
      // vertical lines
      //--------------------------------

      int bar, beat, tick;
      switch (*_raster) {
            case 0:     // measure
                  sigmap.tickValues(x, &bar, &beat, &tick);
                  for (;;) {
                        int xt = sigmap.bar2tick(bar++, 0, 0);
                        if (xt >= x + w)
                              break;
                        p.drawLine(xt, y, xt, y+h);
                        }
                  break;
            case 1:           // no raster
                  break;
            case 768:         // 1/2
            case 384:         // 1/4
            case 192:         // 1/8
            case 96:          // 1/16
                  {
                  int r = *_raster;
                  int rr = rmapx(r);
                  while (rr < 4) {
                        r *= 2;
                        rr = rmapx(r);
                        }

                  for (int xt = x; xt < (x + w); xt += r)
                        p.drawLine(xt, y, xt+1, y+h);
                  }
                  break;
            }
      }

//---------------------------------------------------------
//   setGrid
//---------------------------------------------------------

void PartCanvas::setGrid(bool b)
      {
      showGrid = b;
      redraw();
      }

