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

#include "listedit.h"
#include "mtscale.h"
#include "scrollscale.h"
#include "intlabel.h"
#include "globals.h"
#include "icons.h"
#include "editevent.h"
#include "xml.h"
#include "midictrl.h"
#include "midi.h"
#include "pitchedit.h"

#include "event.h"
#include "song.h"
#include "midithread.h"

#include <qtoolbar.h>
#include <qtooltip.h>
#include <qtoolbutton.h>
#include <qaccel.h>
#include <qlayout.h>
#include <qhbox.h>
#include <qsizegrip.h>
#include <qscrollbar.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qradiobutton.h>
#include <qbuttongroup.h>
#include <qlistbox.h>
#include <qlistview.h>
#include <qcombobox.h>
#include <qheader.h>
#include <qpopupmenu.h>
#include <qmenubar.h>
#include <qaction.h>

//---------------------------------------------------------
//   EventListItem
//---------------------------------------------------------

class EventListItem : public QListViewItem {
   public:
      MidiEvent* event;
      MidiPart* part;

      EventListItem(QListView* parent, MidiEvent* ev, MidiPart* p)
         : QListViewItem(parent) {
            event = ev;
            part  = p;
            }
      virtual QString text(int col) const;
      virtual int compare(QListViewItem*i, int, bool) const {
            return event->posTick() - ((EventListItem*)i)->event->posTick();
            }
      };

/*---------------------------------------------------------
 *    midi_meta_name
 *---------------------------------------------------------*/

static QString midiMetaComment(const MidiEvent* ev)
      {
      int meta  = ev->dataA();
      QString s = midiMetaName(meta);

      switch (meta) {
            case 0:
            case 0x2f:
            case 0x51:
            case 0x54:
            case 0x58:
            case 0x59:
            case 0x74:
            case 0x7f:  return s;

            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 0x0a:
            case 0x0b:
            case 0x0c:
            case 0x0d:
            case 0x0e:
            case 0x0f:
                  {
                  s += QString(": ");
                  const char* txt = (char*)(ev->data());
                  int len = ev->dataLen();
                  char buffer[len+1];
                  memcpy(buffer, txt, len);
                  buffer[len] = 0;

                  for (int i = 0; i < len; ++i) {
                        if (buffer[i] == '\n' || buffer[i] == '\r')
                        buffer[i] = ' ';
                        }
                  return s + QString(buffer);
                  }

            case 0x20:
            case 0x21:
            default:
                  {
                  s += QString(": ");
                  int i;
                  int len = ev->lenTick();
                  int n = len > 10 ? 10 : len;
                  for (i = 0; i < n; ++i) {
                        if (i >= ev->dataLen())
                              break;
                        s += QString(" 0x");
                        QString k;
                        k.setNum(ev->data()[i] & 0xff, 16);
                        s += k;
                        }
                  if (i == 10)
                        s += QString("...");
                  return s;
                  }
            }
      }

//---------------------------------------------------------
//   closeEvent
//---------------------------------------------------------

void ListEdit::closeEvent(QCloseEvent* e)
      {
      emit deleted((int)this);
      e->accept();
      }

//---------------------------------------------------------
//   songChanged
//---------------------------------------------------------

void ListEdit::songChanged(int type)
      {
      if (type == 0)
            return;
      if (type & (SC_PART_REMOVED | SC_PART_MODIFIED
         | SC_PART_INSERTED | SC_EVENT_REMOVED | SC_EVENT_MODIFIED
         | SC_EVENT_INSERTED | SC_SELECTION)) {
            if (type & (SC_PART_REMOVED | SC_PART_INSERTED))
                  genPartlist();
            // close window if editor has no parts anymore
            if (parts()->empty()) {
                  close(false);
                  return;
                  }
            if (type == SC_SELECTION) {
                  bool update = false;
                  QListViewItem* ci = 0;
                  for (QListViewItem* i = liste->firstChild(); i; i = i->nextSibling()) {
                        if (i->isSelected() ^ ((EventListItem*)i)->event->selected()) {
                              i->setSelected(((EventListItem*)i)->event->selected());
                              if (i->isSelected())
                                    ci = i;
                              update = true;
                              }
                        }
                  if (update) {
                        if (ci)
                              liste->setCurrentItem(ci);
                        liste->triggerUpdate();
                        }
                  }
            else {
                  curPart = 0;
                  curTrack = 0;
                  liste->clear();
                  for (iPart p = parts()->begin(); p != parts()->end(); ++p) {
                        MidiPart* part = (MidiPart*) (p->second);
                        if (part->sn() == curPartId)
                              curPart  = part;
                        EventList* el = part->events();
                        for (iEvent i = el->begin(); i != el->end(); ++i) {
                              EventListItem* item = new EventListItem(liste, (MidiEvent*)(i->second), part);
                              item->setSelected(i->second->selected());
                              }
                        }
                  }
            if (curPart == 0)
                  curPart  = (MidiPart*)(parts()->begin()->second);
            curTrack = curPart->track();
            }
      }

//---------------------------------------------------------
//   addEvent
//---------------------------------------------------------

QString EventListItem::text(int col) const
      {
      QString s;
      QString commentLabel;
      switch(col) {
            case 0:
                  s.setNum(event->posTick());
                  break;
            case 1:
                  {
                  int t = event->posTick() + part->posTick();
                  int bar, beat, tick;
                  sigmap.tickValues(t, &bar, &beat, &tick);
                  s.sprintf("%04d.%02d.%03d", bar+1, beat+1, tick);
                  }
                  break;
            case 2:
                  switch(event->type()) {
                        case MidiEvent::Note:
                              s = QString("Note");
                              break;
                        case MidiEvent::NoteOff:
                              s = QString("Noff");
                              break;
                        case MidiEvent::Program:
                              s = QString("Pgrm");
                              break;
                        case MidiEvent::Ctrl7:
                              commentLabel = midiCtrl7Name(event->dataA());
                              s = QString("Ctrl7");
                              break;
                        case MidiEvent::Ctrl14:
                              commentLabel = midiCtrl14Name(event->dataA(), event->dataC());
                              s = QString("Ctrl14");
                              break;
                        case MidiEvent::Sysex:
                              {
                              commentLabel = QString("len ");
                              QString k;
                              k.setNum(event->dataLen());
                              commentLabel += k;
                              commentLabel += QString(" ");

                              commentLabel += nameSysex(event->dataLen(), event->data());
                              int i;
                              for (i = 0; i < 10; ++i) {
                                    if (i >= event->dataLen())
                                          break;
                                    commentLabel += QString(" 0x");
                                    QString k;
                                    k.setNum(event->data()[i] & 0xff, 16);
                                    commentLabel += k;
                                    }
                              if (i == 10)
                                    commentLabel += QString("...");
                              }
                              s = QString("SysEx");
                              break;
                        case MidiEvent::PAfter:
                              s = QString("PoAT");
                              break;
                        case MidiEvent::CAfter:
                              s = QString("ChAT");
                              break;
                        case MidiEvent::Pitch:
                              s = QString("Pitch");
                              break;
                        case MidiEvent::Meta:
                              commentLabel = midiMetaComment(event);
                              s = QString("Meta");
                              break;
                        case MidiEvent::NRPN:
                              s = QString("NRPN");
                              break;
                        case MidiEvent::RPN:
                              s = QString("RPN");
                              break;
                        case MidiEvent::NoEvent:
                        case MidiEvent::Symbol:
                        case MidiEvent::Quantize:
                        case MidiEvent::Clock:
                              s = QString("???");
                              break;
                        }
                  break;
            case 3:
                  s.setNum(part->track()->outChannel() + 1);
                  break;
            case 4:
                  if (event->type() == MidiEvent::Note || event->type() == MidiEvent::NoteOff) {
                        s = pitch2string(event->dataA());
                        }
                  else {
                        s.setNum(event->dataA());
                        }
                  break;
            case 5:
                  s.setNum(event->dataB());
                  break;
            case 6:
                  s.setNum(event->dataC());
                  break;
            case 7:
                  s.setNum(event->lenTick());
                  break;
            case 8:
                  switch(event->type()) {
                        case MidiEvent::Ctrl7:
                              s = midiCtrl7Name(event->dataA());
                              break;
                        case MidiEvent::Ctrl14:
                              s = midiCtrl14Name(event->dataA(), event->dataC());
                              break;
                        case MidiEvent::Sysex:
                              {
                              s = QString("len ");
                              QString k;
                              k.setNum(event->dataLen());
                              s += k;
                              s += QString(" ");

                              commentLabel += nameSysex(event->dataLen(), event->data());
                              int i;
                              for (i = 0; i < 10; ++i) {
                                    if (i >= event->dataLen())
                                          break;
                                    s += QString(" 0x");
                                    QString k;
                                    k.setNum(event->data()[i] & 0xff, 16);
                                    s += k;
                                    }
                              if (i == 10)
                                    s += QString("...");
                              }
                              break;
                        case MidiEvent::Meta:
                              s = midiMetaComment(event);
                              break;
                        default:
                              break;
                        }
                  break;

            }
      return s;
      }

//---------------------------------------------------------
//   ListEdit
//---------------------------------------------------------

ListEdit::ListEdit(PartList* pl)
   : MidiEditor(0, 0, pl)
      {
      insertItems = new QActionGroup(this, "InsertItems", false);
      QAction* insertNote = new QAction(tr("Insert Note"), QIconSet(*note1Icon), tr("insert Note"),
        CTRL+Key_N, insertItems);
      QAction* insertSysEx = new QAction(tr("Insert SysEx"), QIconSet(*sysexIcon), tr("insert SysEx"),
        CTRL+Key_S, insertItems);
      QAction* insertCtrl = new QAction(tr("Insert Ctrl"), QIconSet(*ctrlIcon), tr("insert Ctrl"),
        CTRL+Key_T, insertItems);
      QAction* insertMeta = new QAction(tr("Insert Meta"), QIconSet(*metaIcon), tr("insert Meta"),
        0, insertItems);
      QAction* insertPitch = new QAction(tr("Insert Pitch"), QIconSet(*pitchIcon), tr("insert Pitch"),
        CTRL+Key_E, insertItems);
      QAction* insertCAfter = new QAction(tr("Insert Channel Aftertouch"), QIconSet(*cafterIcon), tr("insert Channel Aftertouch"),
        CTRL+Key_A, insertItems);
      QAction* insertPAfter = new QAction(tr("Insert Key Aftertouch"), QIconSet(*pafterIcon), tr("insert Poly Aftertouch"),
        CTRL+Key_P, insertItems);

      connect(insertNote,    SIGNAL(activated()), SLOT(editInsertNote()));
      connect(insertSysEx,   SIGNAL(activated()), SLOT(editInsertSysEx()));
      connect(insertCtrl,    SIGNAL(activated()), SLOT(editInsertCtrl7()));
      connect(insertMeta,    SIGNAL(activated()), SLOT(editInsertMeta()));
      connect(insertPitch,   SIGNAL(activated()), SLOT(editInsertPitch()));
      connect(insertCAfter,  SIGNAL(activated()), SLOT(editInsertCAfter()));
      connect(insertPAfter,  SIGNAL(activated()), SLOT(editInsertPAfter()));

      //---------Pulldown Menu----------------------------
      menuEdit = new QPopupMenu(this);
      menuBar()->insertItem(tr("&Edit"), menuEdit);
      undoRedo->addTo(menuEdit);
      menuEdit->insertSeparator();
#if 0
      menuEdit->insertItem(tr("Cut"),   EList::CMD_CUT);
      menuEdit->setAccel(CTRL+Key_X, EList::CMD_CUT);
      menuEdit->insertItem(tr("Copy"),  EList::CMD_COPY);
      menuEdit->setAccel(CTRL+Key_C, EList::CMD_COPY);
      menuEdit->insertItem(tr("Paste"), EList::CMD_PASTE);
      menuEdit->setAccel(CTRL+Key_V, EList::CMD_PASTE);
      menuEdit->insertSeparator();
#endif
      menuEdit->insertItem(tr("Delete Events"), CMD_DELETE);
      menuEdit->setAccel(Key_Delete, CMD_DELETE);
      menuEdit->insertSeparator();

      insertItems->addTo(menuEdit);

      connect(menuEdit, SIGNAL(activated(int)), SLOT(cmd(int)));

      //---------ToolBar----------------------------------
      listTools = new QToolBar(this, "list-tools");
      listTools->setLabel(tr("List Tools"));
      undoRedo->addTo(listTools);
      QToolBar* insertTools = new QToolBar(this, "insert");
      insertTools->setLabel(tr("Insert Tools"));
      insertItems->addTo(insertTools);

      //
      //---------------------------------------------------
      //    liste
      //---------------------------------------------------
      //

      liste = new QListView(mainw);
      QFontMetrics fm(liste->font());
      int n = fm.width('9');
      int b = 8;
      int c = fm.width(QString("Val B"));
      liste->setAllColumnsShowFocus(true);
      liste->setSorting(0);
      liste->setSelectionMode(QListView::Extended);

      liste->addColumn(tr("Tick"),  n * 6 + b);
      liste->addColumn(tr("Bar"),   fm.width(QString("9999.99.999")) + b);
      liste->addColumn(tr("Typ"),   fm.width(QString("SysEx")) + b);
      liste->addColumn(tr("Ch"),    n * 2 + b);
      liste->addColumn(tr("Val A"), c + b);
      liste->addColumn(tr("Val B"), c + b);
      liste->addColumn(tr("Val C"), c + b);
      liste->addColumn(tr("Len"),   n * 4 + b);
      liste->addColumn(tr("Comment"));
      liste->setResizeMode(QListView::LastColumn);
      connect(liste, SIGNAL(selectionChanged()), SLOT(selectionChanged()));
      connect(liste, SIGNAL(doubleClicked(QListViewItem*)), SLOT(doubleClicked(QListViewItem*)));

      //---------------------------------------------------
      //    Rest
      //---------------------------------------------------

      mainGrid->setRowStretch(1, 100);
      mainGrid->setColStretch(0, 100);
      mainGrid->addMultiCellWidget(liste, 1, 2, 0, 0);
      connect(song, SIGNAL(songChanged(int)), SLOT(songChanged(int)));
      songChanged(-1);

      curPart   = (MidiPart*)(pl->begin()->second);
      curPartId = curPart->sn();
      }

//---------------------------------------------------------
//   ~ListEdit
//---------------------------------------------------------

ListEdit::~ListEdit()
      {
      undoRedo->removeFrom(listTools);
      }

//---------------------------------------------------------
//   editInsertNote
//---------------------------------------------------------

void ListEdit::editInsertNote()
      {
      MidiEvent* event = EditNoteDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertSysEx
//---------------------------------------------------------

void ListEdit::editInsertSysEx()
      {
      MidiEvent* event = EditSysexDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertCtrl7
//---------------------------------------------------------

void ListEdit::editInsertCtrl7()
      {
      MidiEvent* event = EditCtrl7Dialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertCtrl14
//---------------------------------------------------------

void ListEdit::editInsertCtrl14()
      {
      MidiEvent* event = EditCtrl14Dialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertMeta
//---------------------------------------------------------

void ListEdit::editInsertMeta()
      {
      MidiEvent* event = EditMetaDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertPitch
//---------------------------------------------------------

void ListEdit::editInsertPitch()
      {
      MidiEvent* event = EditPitchDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertCAfter
//---------------------------------------------------------

void ListEdit::editInsertCAfter()
      {
      MidiEvent* event = EditCAfterDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editInsertPAfter
//---------------------------------------------------------

void ListEdit::editInsertPAfter()
      {
      MidiEvent* event = EditPAfterDialog::getEvent(0, 0, this);
      if (event) {
            midiThread->msgAddEvent(event, curPart);
            }
      }

//---------------------------------------------------------
//   editEvent
//---------------------------------------------------------

void ListEdit::editEvent(MidiEvent* event, MidiPart* part)
      {
      int tick = event->posTick();
      MidiEvent* nevent = 0;
      switch(event->type()) {
            case MidiEvent::Note:
                  nevent = EditNoteDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Ctrl7:
                  nevent = EditCtrl7Dialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Ctrl14:
                  nevent = EditCtrl14Dialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Sysex:
                  nevent = EditSysexDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::PAfter:
                  nevent = EditPAfterDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::CAfter:
                  nevent = EditCAfterDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Pitch:
                  nevent = EditPitchDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Meta:
                  nevent = EditMetaDialog::getEvent(tick, event, this);
                  break;
            case MidiEvent::Program:
            default:
                  return;
            }
      if (nevent) {
            // TODO: check for event != nevent
            midiThread->msgChangeEvent(event, nevent, part);
            }
      }

//---------------------------------------------------------
//   readStatus
//---------------------------------------------------------

void ListEdit::readStatus(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            if (token == Xml::Error || token == Xml::End)
                  break;
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "midieditor")
                              MidiEditor::readStatus(xml);
                        else
                              xml.unknown("ListEdit");
                        break;
                  case Xml::TagEnd:
                        if (tag == "listeditor")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   writeStatus
//---------------------------------------------------------

void ListEdit::writeStatus(int level, Xml& xml) const
      {
      writePartList(level, xml);
      xml.tag(level++, "listeditor");
      MidiEditor::writeStatus(level, xml);
      xml.tag(level, "/listeditor");
      }

//---------------------------------------------------------
//   selectionChanged
//---------------------------------------------------------

void ListEdit::selectionChanged()
      {
      bool update = false;
      for (QListViewItem* i = liste->firstChild(); i; i = i->nextSibling()) {
            if (i->isSelected() ^ ((EventListItem*)i)->event->selected()) {
                  ((EventListItem*)i)->event->setSelected(i->isSelected());
                  update = true;
                  }
            }
      if (update)
            song->update(SC_SELECTION);
      }

//---------------------------------------------------------
//   doubleClicked
//---------------------------------------------------------

void ListEdit::doubleClicked(QListViewItem* item)
      {
      EventListItem* ev = (EventListItem*) item;
      editEvent(ev->event, ev->part);
      }

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

void  ListEdit::cmd(int cmd)
       {
         switch(cmd) {
           case CMD_DELETE:
             song->startUndo();
             for (QListViewItem* i = liste->firstChild(); i; i = i->nextSibling()) {
               EventListItem *item = (EventListItem *) i;

               if (i->isSelected() || item->event->selected())
                 midiThread->msgDeleteEvent(item->event, item->part, false);

             }
             song->endUndo(SC_EVENT_MODIFIED);
             break;
         }
       }

