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

#include <stdio.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qpainter.h>
#include <qdragobject.h>
#include <math.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include "midithread.h"
#include "xml.h"
#include "prcanvas.h"
#include "midiport.h"
#include "event.h"
#include "mpevent.h"
#include "globals.h"

#include "cmd.h"
#include "gatetime.h"
#include "velocity.h"
#include "song.h"

//---------------------------------------------------------
//   NEvent
//---------------------------------------------------------

NEvent::NEvent(Event* e, Part* p, int y) : CItem(e, p)
      {
      y = y - KH/4;
      int tick = e->posTick() + p->posTick();
      setPos(QPoint(tick, y));
      setBBox(QRect(tick, y, e->lenTick(), KH/2));
      }

//---------------------------------------------------------
//   addItem
//---------------------------------------------------------

void PianoCanvas::addItem(Part* part, Event* event)
      {
      NEvent* ev = new NEvent(event, part, pitch2y(((MidiEvent*)event)->pitch()));
      items.add(ev);
      }

//---------------------------------------------------------
//   PianoCanvas
//---------------------------------------------------------

PianoCanvas::PianoCanvas(MidiEditor* pr, QWidget* parent, int sx, int sy)
   : EventCanvas(pr, parent, sx, sy)
      {
      colorMode = 0;
      cmdRange  = 0;     // all Events
      playedPitch = -1;

      songChanged(SC_TRACK_INSERTED);
      connect(midiThread, SIGNAL(midiNote(int, int)), SLOT(midiNote(int,int)));
      }

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

int PianoCanvas::pitch2y(int pitch) const
      {
      int tt[] = {
            5, 12, 19, 26, 33, 44, 51, 58, 64, 71, 78, 85
            };
      int y = (75 * KH) - (tt[pitch%12] + (7 * KH) * (pitch/12));
      if (y < 0)
            y = 0;
      return y;
      }

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

int PianoCanvas::y2pitch(int y) const
      {
      const int total = (10 * 7 + 5) * KH;       // 75 Ganztonschritte
      y = total - y;
      int oct = (y / (7 * KH)) * 12;
      char kt[] = {
            0, 0, 0, 0, 0,  0,   0, 0, 0, 0,          // 5
            1, 1, 1,      1,   1, 1, 1,               // 13
            2, 2,         2,   2, 2, 2,               // 19
            3, 3, 3,      3,   3, 3, 3,               // 26
            4, 4, 4, 4,   4,   4, 4, 4, 4,            // 34
            5, 5, 5, 5,   5,   5, 5, 5, 5, 5,         // 43
            6, 6, 6,      6,   6, 6, 6,               // 52
            7, 7,         7,   7, 7, 7,               // 58
            8, 8, 8,      8,   8, 8, 8,               // 65
            9, 9,         9,   9, 9, 9,               // 71
            10, 10, 10,  10,   10, 10, 10,            // 78
            11, 11, 11, 11, 11,   11, 11, 11, 11, 11  // 87
            };
      return kt[y % 91] + oct;
      }

//---------------------------------------------------------
//   drawEvent
//    draws a note
//---------------------------------------------------------

void PianoCanvas::drawItem(QPainter& p, const CItem* item,
   const QRect&) const
      {
      p.setPen(black);
      QRect r = item->bbox();

      struct Triple {
            int r, g, b;
            };
      static Triple color1[12] = {
            { 0xff, 0x3d, 0x39 },
            { 0x39, 0xff, 0x39 },
            { 0x39, 0x3d, 0xff },
            { 0xff, 0xff, 0x39 },
            { 0xff, 0x3d, 0xff },
            { 0x39, 0xff, 0xff },
            { 0xff, 0x7e, 0x7a },
            { 0x7a, 0x7e, 0xff },
            { 0x7a, 0xff, 0x7a },
            { 0xff, 0x7e, 0xbf },
            { 0x7a, 0xbf, 0xff },
            { 0xff, 0xbf, 0x7a }
            };

      NEvent* nevent   = (NEvent*) item;
      MidiEvent* event = (MidiEvent*)nevent->event();

      QColor color;
      if (nevent->part() != curPart)
            p.setBrush(lightGray);
      else {
            if (item->isMoving()) {
                  p.setBrush(gray);
                  p.drawRect(r);
                  p.setBrush(NoBrush);
                  p.drawRect(item->mp().x(), item->mp().y() - item->height()/2, item->width(), item->height());
                  return;
                  }
            else if (item->isSelected()) {
                  p.setBrush(black);
                  }
            else {
                  color.setRgb(0, 0, 255);
                  switch(colorMode) {
                        case 0:
                              break;
                        case 1:     // pitch
                              {
                              Triple* c = &color1[event->pitch() % 12];
                              color.setRgb(c->r, c->g, c->b);
                              }
                              break;
                        case 2:     // velocity
                              {
                              int velo = event->velo();
                              if (velo < 64)
                                    color.setRgb(velo*4, 0, 0xff);
                              else
                                    color.setRgb(0xff, 0, (127-velo) * 4);
                              }
                              break;
                        }
                  p.setBrush(color);
                  }
            }
      p.drawRect(r);
      }

//---------------------------------------------------------
//   viewMouseDoubleClickEvent
//---------------------------------------------------------

void PianoCanvas::viewMouseDoubleClickEvent(QMouseEvent* event)
      {
      if ((_tool != PointerTool) && (event->button() != QMouseEvent::LeftButton)) {
            mousePress(event);
            return;
            }
      }

//---------------------------------------------------------
//   moveItem
//    called after moving an object
//---------------------------------------------------------

bool PianoCanvas::moveItem(CItem* item, const QPoint& pos, DragType dtype)
      {
      NEvent* nevent      = (NEvent*) item;
      MidiEvent* event    = (MidiEvent*)nevent->event();
      int npitch          = y2pitch(pos.y());
      MidiEvent* newEvent = new MidiEvent(*event);

      if (event->pitch() != npitch && _playEvents) {
            int port    = track()->outPort();
            int channel = track()->outChannel();
            // release note:
            MidiPlayEvent ev1(port, channel, 0x90, event->pitch(), 0);
            midiThread->playMidiEvent(&ev1);
            MidiPlayEvent ev2(port, channel, 0x90, npitch, event->velo());
            midiThread->playMidiEvent(&ev2);
            }
      Part* part = nevent->part();
      newEvent->setPitch(npitch);
      int ntick = editor->rasterVal(pos.x()) - part->posTick();
      newEvent->setPosTick(ntick);
      newEvent->setLenTick(event->lenTick());

      if (dtype == MOVE_COPY || dtype == MOVE_CLONE)
            midiThread->msgAddEvent(newEvent, part, false);
      else
            midiThread->msgChangeEvent(event, newEvent, part, false);
      return true;
      }

//---------------------------------------------------------
//   newItem(p, state)
//---------------------------------------------------------

CItem* PianoCanvas::newItem(const QPoint& p, int)
      {
      int pitch = y2pitch(p.y());
      int tick  = editor->rasterVal1(p.x());
      int len   = p.x() - tick;
      tick     -= curPart->posTick();
      MidiEvent* e = new MidiEvent(tick, MidiEvent::Note,
         pitch, curVelo, 0, len);
      return new NEvent(e, curPart, pitch2y(pitch));
      }

void PianoCanvas::newItem(CItem* item, bool noSnap)
      {
      NEvent* nevent = (NEvent*) item;
      MidiEvent* event = (MidiEvent*)nevent->event();
      int x = item->x();
      int w = item->width();

      if (!noSnap) {
            x = editor->rasterVal1(x); //round down
            w = editor->quantVal(w);
            }
      Part* part = nevent->part();
      event->setPosTick(x - part->posTick());
      event->setLenTick(w);
      event->setPitch(y2pitch(item->y()));
      midiThread->msgAddEvent(event, part);
      }

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

void PianoCanvas::resizeItem(CItem* item, bool noSnap)
      {
      NEvent* nevent = (NEvent*) item;
      MidiEvent* event = (MidiEvent*)(nevent->event());
      MidiEvent* newEvent = new MidiEvent(*event);
      int len;
      if (noSnap)
            len = nevent->width();
      else
            len = editor->quantVal(nevent->width());
      newEvent->setLenTick(len);
      midiThread->msgChangeEvent(event, newEvent, nevent->part());
      }

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

bool PianoCanvas::deleteItem(CItem* item)
      {
      NEvent* nevent = (NEvent*) item;
      if (nevent->part() == curPart) {
            midiThread->msgDeleteEvent(((NEvent*)item)->event(), ((NEvent*)item)->part());
            return true;
            }
      return false;
      }

//---------------------------------------------------------
//   pianoCmd
//---------------------------------------------------------

void PianoCanvas::pianoCmd(int cmd)
      {
      switch(cmd) {
            case CMD_LEFT:
                  song->setPos(0, pos[0] - editor->rasterStep(pos[0]),
                     true, false, true); //CDW
                  break;
            case CMD_RIGHT:
                  song->setPos(0, pos[0] + editor->rasterStep(pos[0]),
                     true, false, true); //CDW
                  break;
            case CMD_INSERT:
                  if (pos[0] < start() || pos[0] >= end())
                        break;
                  {
                  MidiPart* part = (MidiPart*)curPart;
                  if (part == 0)
                        break;
                  song->startUndo();
                  EventList* el = part->events();

                  std::list <MidiEvent*> elist;
                  for (iEvent e = el->lower_bound(pos[0]); e != el->end(); ++e)
                        elist.push_back((MidiEvent*)e->second);
                  for (std::list<MidiEvent*>::iterator i = elist.begin(); i != elist.end(); ++i) {
                        MidiEvent* event = *i;
                        MidiEvent* newEvent = new MidiEvent(*event);
                        newEvent->setPosTick(event->posTick() + editor->raster() - part->posTick());
                        midiThread->msgChangeEvent(event, newEvent, part, false);
                        }
                  song->endUndo(SC_EVENT_MODIFIED);
                  song->setPos(0, editor->rasterVal(pos[0] + editor->rasterStep(pos[0])),
                     true, false, true);
                  }
                  return;
            case CMD_DELETE:
                  if (pos[0] < start() || pos[0] >= end())
                        break;
                  {
                  MidiPart* part = (MidiPart*)curPart;
                  if (part == 0)
                        break;
                  song->startUndo();
                  EventList* el = part->events();

                  std::list <MidiEvent*> elist;
                  for (iEvent e = el->lower_bound(pos[0]); e != el->end(); ++e)
                        elist.push_back((MidiEvent*)e->second);
                  for (std::list<MidiEvent*>::iterator i = elist.begin(); i != elist.end(); ++i) {
                        MidiEvent* event = *i;
                        MidiEvent* newEvent = new MidiEvent(*event);
                        newEvent->setPosTick(event->posTick() - editor->raster() - part->posTick());
                        midiThread->msgChangeEvent(event, newEvent, part, false);
                        }
                  song->endUndo(SC_EVENT_MODIFIED);
                  song->setPos(0, editor->rasterVal(pos[0] - editor->rasterStep(pos[0])),
                     true, false, true);
                  }
                  break;
            }
      }

//---------------------------------------------------------
//   pianoPressed
//---------------------------------------------------------

void PianoCanvas::pianoPressed(int pitch, bool shift)
      {
      int port      = track()->outPort();
      int channel   = track()->outChannel();
      pitch        += track()->transposition;

      // play note:
      MidiPlayEvent e(port, channel, 0x90, pitch, 127);
      midiThread->playMidiEvent(&e);

      if (_steprec && pos[0] >= start_tick && pos[0] < end_tick) {
            if (curPart == 0)
                  return;
            int len  = editor->quant();
            int tick = pos[0] - curPart->posTick(); //CDW
            if (shift)
                  tick -= editor->rasterStep(tick);
            MidiEvent* e = new MidiEvent(
               tick, MidiEvent::Note, pitch, 127, 0, len);
            midiThread->msgAddEvent(e, curPart);
            tick += editor->rasterStep(tick);
            if (tick != song->cpos())
                  song->setPos(0, tick, true, false, true);
            }
      }

//---------------------------------------------------------
//   pianoReleased
//---------------------------------------------------------

void PianoCanvas::pianoReleased(int pitch, bool)
      {
      int port    = track()->outPort();
      int channel = track()->outChannel();
      pitch      += track()->transposition;

      // release key:
      MidiPlayEvent e(port, channel, 0x90, pitch, 0);
      midiThread->playMidiEvent(&e);
      }

//---------------------------------------------------------
//   drawTickRaster
//---------------------------------------------------------

void drawTickRaster(QPainter& p, int x, int y, int w, int h, int quant)
      {
      int bar1, bar2, beat, tick;
      sigmap.tickValues(x, &bar1, &beat, &tick);
      sigmap.tickValues(x+w, &bar2, &beat, &tick);
      ++bar2;
      int y2 = y + h;
      for (int bar = bar1; bar < bar2; ++bar) {
            int x = sigmap.bar2tick(bar, 0, 0);
            p.setPen(Qt::black);
            p.drawLine(x, y, x, y2);
            p.setPen(Qt::gray);
            int z, n;
            sigmap.timesig(x, z, n);
            int q = p.xForm(QPoint(quant, 0)).x() - p.xForm(QPoint(0, 0)).x();
            int qq = quant;
            if (q < 8)        // grid too dense
                  qq *= 2;
            switch (quant) {
                  case 32:
                  case 48:
                  case 64:
                  case 96:
                  case 192:         // 8tel
                  case 128:         // 8tel Triolen
                  case 288:
                        {
                        int xx = x + qq;
                        for (int beat = 1; beat <= z; beat++) {
                              int xxx = sigmap.bar2tick(bar, beat, 0);
                              while (xx <= xxx) {
                                    p.drawLine(xx, y, xx, y2);
                                    xx += qq;
                                    }
                              xx = xxx;
                              }
                        }
                        break;
                  default:
                        for (int beat = 1; beat < z; beat++) {
                              int xx = sigmap.bar2tick(bar, beat, 0);
                              p.drawLine(xx, y, xx, y2);
                              }
                        break;
                  }
            }
      }

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

void PianoCanvas::drawCanvas(QPainter& p, const QRect& rect)
      {
      int x = rect.x();
      int y = rect.y();
      int w = rect.width();
      int h = rect.height();

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

      int yy  = ((y-1) / KH) * KH + KH;
      int key = 75 - (yy / KH);
      for (; yy < y + h; yy += KH) {
            switch (key % 7) {
                  case 0:
                  case 3:
                        p.setPen(black);
                        break;
                  default:
                        p.setPen(gray);
                        break;
                  }
            p.drawLine(x, yy, x + w, yy);
            --key;
            }

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

      drawTickRaster(p, x, y, w, h, editor->quant());
      }

//---------------------------------------------------------
//   cmd
//    pulldown menu commands
//---------------------------------------------------------

void PianoCanvas::cmd(int cmd, int quantStrength,
   int quantLimit, bool quantLen, int range)
      {
      cmdRange = range;
      switch (cmd) {
            case CMD_CUT:
                  copy();
                  song->startUndo();
                  for (iCItem i = items.begin(); i != items.end(); ++i) {
                        if (!(i->second->isSelected()))
                              continue;
                        NEvent* e = (NEvent*)(i->second);
                        midiThread->msgDeleteEvent(e->event(), e->part(), false);
                        }
                  song->endUndo(SC_EVENT_REMOVED);
                  break;
            case CMD_COPY:
                  copy();
                  break;
            case CMD_PASTE:
                  paste();
                  break;
            case CMD_DEL:
                  if (selectionSize()) {
                        song->startUndo();
                        for (iCItem i = items.begin(); i != items.end(); ++i) {
                              if (!i->second->isSelected())
                                    continue;
                              midiThread->msgDeleteEvent(i->second->event(), i->second->part(), false);
                              }
                        song->endUndo(SC_EVENT_REMOVED);
                        }
                  return;
            case CMD_OVER_QUANTIZE:     // over quantize
                  quantize(100, 1, quantLen);
                  break;
            case CMD_ON_QUANTIZE:     // note on quantize
                  quantize(50, 1, false);
                  break;
            case CMD_ONOFF_QUANTIZE:     // note on/off quantize
                  quantize(50, 1, true);
                  break;
            case CMD_ITERATIVE_QUANTIZE:     // Iterative Quantize
                  quantize(quantStrength, quantLimit, quantLen);
                  break;
            case CMD_SELECT_ALL:     // select all
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        if (!k->second->isSelected())
                              selectItem(k->second, true);
                        }
                  break;
            case CMD_SELECT_NONE:     // select none
                  deselectAll();
                  break;
            case CMD_SELECT_INVERT:     // invert selection
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        selectItem(k->second, !k->second->isSelected());
                        }
                  break;
            case CMD_SELECT_ILOOP:     // select inside loop
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        NEvent* nevent =(NEvent*)(k->second);
                        Event* event = nevent->event();
                        int tick  = event->posTick();
                        if (tick < song->lpos() || tick >= song->rpos())
                              selectItem(k->second, false);
                        else
                              selectItem(k->second, true);
                        }
                  break;
            case CMD_SELECT_OLOOP:     // select outside loop
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        NEvent* nevent =(NEvent*)(k->second);
                        Event* event = nevent->event();
                        int tick  = event->posTick();
                        if (tick < song->lpos() || tick >= song->rpos())
                              selectItem(k->second, true);
                        else
                              selectItem(k->second, false);
                        }
                  break;
            case CMD_MODIFY_GATE_TIME:
                  {
                  GateTime w(this);
                  w.setRange(range);
                  if (!w.exec())
                        break;
                  int range  = w.range();        // all, selected, looped, sel+loop
                  int rate   = w.rateVal();
                  int offset = w.offsetVal();

                  song->startUndo();
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        NEvent* nevent =(NEvent*)(k->second);
                        MidiEvent* event = (MidiEvent*)nevent->event();
                        if (event->type() != MidiEvent::Note)
                              continue;
                        int tick      = event->posTick();
                        bool selected = k->second->isSelected();
                        bool inLoop   = (tick >= song->lpos()) && (tick < song->rpos());

                        if ((range == 0)
                           || (range == 1 && selected)
                           || (range == 2 && inLoop)
                           || (range == 3 && selected && inLoop)) {
                              int len   = event->lenTick();

                              len = rate ? (len * 100) / rate : 1;
                              len += offset;

                              if (event->lenTick() != len) {
                                    MidiEvent* newEvent = new MidiEvent(*event);
                                    newEvent->setLenTick(len);
                                    midiThread->msgChangeEvent(event, newEvent, nevent->part(), false);
                                    }
                              }
                        }
                  song->endUndo(SC_EVENT_MODIFIED);
                  }
                  break;

            case CMD_MODIFY_VELOCITY:
                  {
                  Velocity w(this);
                  w.setRange(range);
                  if (!w.exec())
                        break;
                  int range  = w.range();        // all, selected, looped, sel+loop
                  int rate   = w.rateVal();
                  int offset = w.offsetVal();

                  song->startUndo();
                  for (iCItem k = items.begin(); k != items.end(); ++k) {
                        NEvent* nevent =(NEvent*)(k->second);
                        MidiEvent* event = (MidiEvent*)nevent->event();
                        if (event->type() != MidiEvent::Note)
                              continue;
                        int tick      = event->posTick();
                        bool selected = k->second->isSelected();
                        bool inLoop   = (tick >= song->lpos()) && (tick < song->rpos());

                        if ((range == 0)
                           || (range == 1 && selected)
                           || (range == 2 && inLoop)
                           || (range == 3 && selected && inLoop)) {
                              int velo = event->velo();

                              velo = rate ? (velo * 100) / rate : 64;
                              velo += offset;

                              if (velo <= 0)
                                    velo = 1;
                              if (velo > 127)
                                    velo = 127;
                              if (event->velo() != velo) {
                                    MidiEvent* newEvent = new MidiEvent(*event);
                                    newEvent->setVelo(velo);
                                    midiThread->msgChangeEvent(event, newEvent, nevent->part(), false);
                                    }
                              }
                        }
                  song->endUndo(SC_EVENT_MODIFIED);
                  }
                  break;

            case CMD_CRESCENDO:
            case CMD_TRANSPOSE:
            case CMD_THIN_OUT:
            case CMD_ERASE_EVENT:
            case CMD_NOTE_SHIFT:
            case CMD_MOVE_CLOCK:
            case CMD_COPY_MEASURE:
            case CMD_ERASE_MEASURE:
            case CMD_DELETE_MEASURE:
            case CMD_CREATE_MEASURE:
                  break;
            default:
//                  printf("unknown ecanvas cmd %d\n", cmd);
                  break;
            }
      updateSelection();
      redraw();
      }

//---------------------------------------------------------
//   quantize
//---------------------------------------------------------

void PianoCanvas::quantize(int strength, int limit, bool quantLen)
      {
      song->startUndo();
      for (iCItem k = items.begin(); k != items.end(); ++k) {
            NEvent* nevent =(NEvent*)(k->second);
            MidiEvent* event = (MidiEvent*)nevent->event();
            Part* part = nevent->part();
            if (event->type() != MidiEvent::Note)
                  continue;

            if ((cmdRange & CMD_RANGE_SELECTED) && !k->second->isSelected())
                  continue;

            int tick = event->posTick() + part->posTick();

            if ((cmdRange & CMD_RANGE_LOOP)
               && ((tick < song->lpos() || tick >= song->rpos())))
                  continue;

            int len   = event->lenTick();
            int tick2 = tick + len;

            // quant start position
            int diff  = editor->rasterVal(tick) - tick;
            if (abs(diff) > limit)
                  tick += ((diff * strength) / 100);

            // quant len
            diff = editor->rasterVal(tick2) - tick2;
            if (quantLen && (abs(diff) > limit))
                  len += ((diff * strength) / 100);

            // something changed?
            if (((event->posTick() + part->posTick()) != tick) || (event->lenTick() != len)) {
                  MidiEvent* newEvent = new MidiEvent(*event);
                  newEvent->setPosTick(tick - part->posTick());
                  newEvent->setLenTick(len);
                  midiThread->msgChangeEvent(event, newEvent, part, false);
                  }
            }
      song->endUndo(SC_EVENT_MODIFIED);
      }

//---------------------------------------------------------
//   midiNote
//---------------------------------------------------------

void PianoCanvas::midiNote(int pitch, int velo)
      {
      if (_midiin && _steprec && curPart
         && !song->play() && velo && pos[0] >= start_tick
         && pos[0] < end_tick
         && !(globalKeyState & AltButton)) {
            int len  = editor->quant();
            int tick = pos[0]; //CDW
            int starttick = tick;
            if (globalKeyState & ShiftButton)
                  tick -= editor->rasterStep(tick);

            //
            // extend len of last note?
            //
            EventList* events = curPart->events();
            if (globalKeyState & ControlButton) {
                  for (iEvent i = events->begin(); i != events->end(); ++i) {
                        MidiEvent* ev = (MidiEvent*)i->second;
                        if (!ev->isNote())
                              continue;
                        if (ev->pitch() == pitch && ((ev->posTick()+ev->lenTick()) == starttick)) {
                              MidiEvent* e = new MidiEvent(*ev);
                              e->setLenTick(ev->lenTick() + editor->rasterStep(starttick));
                              midiThread->msgChangeEvent(ev, e, curPart);
                              tick += editor->rasterStep(tick);
                              if (tick != song->cpos())
                                    song->setPos(0, tick, true, false, true);
                              return;
                              }
                        }
                  }

            //
            // if we already entered the note, delete it
            //
            EventRange range = events->equal_range(tick);
            for (iEvent i = range.first; i != range.second; ++i) {
                  MidiEvent* ev = (MidiEvent*)i->second;
                  if (ev->isNote() && ev->pitch() == pitch) {
                        midiThread->msgDeleteEvent(ev, curPart);
                        if (globalKeyState & ShiftButton)
                              tick += editor->rasterStep(tick);
                        return;
                        }
                  }
            MidiEvent* e = new MidiEvent(
               tick - curPart->posTick(), MidiEvent::Note, pitch, velo, 0, len);
            midiThread->msgAddEvent(e, curPart);
            tick += editor->rasterStep(tick);
            if (tick != song->cpos())
                  song->setPos(0, tick, true, false, true);
            }
      }

//---------------------------------------------------------
//   getTextDrag
//---------------------------------------------------------

QTextDrag* PianoCanvas::getTextDrag(QWidget* parent)
      {
      //---------------------------------------------------
      //   generate event list from selected events
      //---------------------------------------------------

      EventList el;
      int startTick = -1;
      for (iCItem i = items.begin(); i != items.end(); ++i) {
            if (!i->second->isSelected())
                  continue;
            NEvent* ne = (NEvent*)(i->second);
            MidiEvent* e = (MidiEvent*)ne->event();
            if (startTick == -1)
                  startTick = e->posTick();
            el.add(e);
            }

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

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

      int level = 0;
      xml.tag(level++, "eventlist");
      for (ciEvent e = el.begin(); e != el.end(); ++e)
            e->second->write(level, xml, -startTick);
      xml.etag(--level, "eventlist");

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

      fflush(tmp);
      struct stat f_stat;
      if (fstat(fileno(tmp), &f_stat) == -1) {
            fprintf(stderr, "PianoCanvas::copy() fstat failes:<%s>\n",
               strerror(errno));
            fclose(tmp);
            return 0;
            }
      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), parent);
      drag->setSubtype("eventlist");
      munmap(fbuf, n);
      fclose(tmp);
      return drag;
      }

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

void PianoCanvas::copy()
      {
      QTextDrag* drag = getTextDrag(0);
      if (drag)
            QApplication::clipboard()->setData(drag, QClipboard::Clipboard);
      }

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

void PianoCanvas::pasteAt(const QString& pt, 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;
                  case Xml::TagStart:
                        if (tag == "eventlist") {
                              song->startUndo();
                              EventList* el = new EventList();
                              el->read(xml, "eventlist");
                              for (iEvent i = el->begin(); i != el->end(); ++i) {
                                    MidiEvent* e = (MidiEvent*)(i->second);
                                    e->setPosTick(e->posTick() + pos - curPart->posTick());
                                    midiThread->msgAddEvent(e, curPart, false);
                                    }
                              song->endUndo(SC_EVENT_INSERTED);
                              delete el;
                              return;
                              }
                        else
                              xml.unknown("pasteAt");
                        break;
                  case Xml::Attribut:
                  case Xml::TagEnd:
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   paste
//    paste events
//---------------------------------------------------------

void PianoCanvas::paste()
      {
      QCString subtype("eventlist");
      QMimeSource* ms = QApplication::clipboard()->data(QClipboard::Clipboard);
      QString pt;
      if (!QTextDrag::decode(ms, pt, subtype)) {
            printf("cannot paste: bad data type\n");
            return;
            }
      pasteAt(pt, song->cpos());
      }

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

void PianoCanvas::startDrag(CItem* /* item*/, bool copymode)
      {
      QTextDrag* drag = getTextDrag(this);
      if (drag) {
//            QApplication::clipboard()->setData(drag, QClipboard::Clipboard);

            if (copymode)
                  drag->dragCopy();
            else
                  drag->dragMove();
            }
      }

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

void PianoCanvas::dragEnterEvent(QDragEnterEvent* event)
      {
      event->accept(QTextDrag::canDecode(event));
      }

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

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

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

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

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

void PianoCanvas::viewDropEvent(QDropEvent* event)
      {
      QString text;
      if (event->source() == this) {
            printf("local DROP\n");
            return;
            }
      if (QTextDrag::decode(event, text)) {
            int x = editor->rasterVal(event->pos().x());
            if (x < 0)
                  x = 0;
            pasteAt(text, x);
            }
      else {
            printf("cannot decode drop\n");
            }
      }

//---------------------------------------------------------
//   itemPressed
//---------------------------------------------------------

void PianoCanvas::itemPressed(const CItem* item)
      {
      if (!_playEvents)
            return;

      int port         = track()->outPort();
      int channel      = track()->outChannel();
      NEvent* nevent   = (NEvent*) item;
      MidiEvent* event = (MidiEvent*)nevent->event();
      playedPitch      = event->pitch();
      int velo         = event->velo();

      // play note:
      MidiPlayEvent e(port, channel, 0x90, playedPitch, velo);
      midiThread->playMidiEvent(&e);
      }

//---------------------------------------------------------
//   itemReleased
//---------------------------------------------------------

void PianoCanvas::itemReleased(const CItem*, const QPoint&)
      {
      if (!_playEvents)
            return;
      int port    = track()->outPort();
      int channel = track()->outChannel();

      // release note:
      MidiPlayEvent ev(port, channel, 0x90, playedPitch, 0);
      midiThread->playMidiEvent(&ev);
      playedPitch = -1;
      }

//---------------------------------------------------------
//   itemMoved
//---------------------------------------------------------

void PianoCanvas::itemMoved(const CItem* item, const QPoint& pos)
      {
      int npitch = y2pitch(pos.y());
      if ((playedPitch != -1) && (playedPitch != npitch)) {
            int port         = track()->outPort();
            int channel      = track()->outChannel();
            NEvent* nevent   = (NEvent*) item;
            MidiEvent* event = (MidiEvent*)nevent->event();

            // release note:
            MidiPlayEvent ev1(port, channel, 0x90, playedPitch, 0);
            midiThread->playMidiEvent(&ev1);
            // play note:
            MidiPlayEvent e2(port, channel, 0x90, npitch, event->velo());
            midiThread->playMidiEvent(&e2);
            playedPitch = npitch;
            }
      }

//---------------------------------------------------------
//   curPartChanged
//---------------------------------------------------------

void PianoCanvas::curPartChanged()
      {
      editor->setCaption(getCaption());
      }

