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

#include "config.h"

#include <stdio.h>

#include <qlayout.h>
#include <qcombobox.h>
#include <qtoolbutton.h>
#include <qbuttongroup.h>
#include <qsizegrip.h>
#include <qlabel.h>
#include <qaccel.h>
#include <qfontmetrics.h>
#include <qcombobox.h>
#include <qwhatsthis.h>
#include <qtoolbar.h>
#include <qtooltip.h>
#include <qpopupmenu.h>
#include <qhbox.h>
#include <qstringlist.h>
#include <qfiledialog.h>
#include <qcheckbox.h>
#include <qspinbox.h>
#include <qpushbutton.h>
#include <qstyle.h>
#include <qmainwindow.h>

#include "arranger.h"
#include "song.h"
#include "mtscale.h"
#include "scrollscale.h"
#include "pcanvas.h"
#include "poslabel.h"
#include "intlabel.h"
#include "xml.h"
#include "splitter.h"
#include "lcombo.h"
#include "midiport.h"
#include "../driver/mididev.h"
#include "utils.h"
#include "globals.h"
#include "midictrl.h"
#include "tlist.h"
#include "minstrument.h"
#include "icons.h"
#include "mtrackinfobase.h"
#include "wtrackinfobase.h"
#include "header.h"
#include "utils.h"
#include "midithread.h"
#include "alayout.h"
#include "audio.h"
#include "mixer/amixer.h"
#include "wave.h"

//---------------------------------------------------------
//   MidiTrackInfo
//---------------------------------------------------------

class MidiTrackInfo : public MidiTrackInfoBase {

      virtual void fontChange(const QFont&) {
            setFixedWidth(10*fontMetrics().width('m'));
            }
   public:
      MidiTrackInfo(QWidget* parent) : MidiTrackInfoBase(parent) {}
      };

//---------------------------------------------------------
//   WaveTrackInfo
//---------------------------------------------------------

class WaveTrackInfo : public WaveTrackInfoBase {

      virtual void fontChange(const QFont&) {
            setFixedWidth(10*fontMetrics().width('m'));
            }
   public:
      WaveTrackInfo(QWidget* parent) : WaveTrackInfoBase(parent) {}
      };

//---------------------------------------------------------
//   TWhatsThis::text
//---------------------------------------------------------

QString TWhatsThis::text(const QPoint& pos)
      {
      int section = header->sectionAt(pos.x());
      if (section == -1)
            return QString::null;
      switch(section) {
            case COL_RECORD:   return QHeader::tr("Enable Recording"); break;
            case COL_ACTIVITY: return QHeader::tr("Track Activity"); break;
            case COL_MUTE:     return QHeader::tr("Mute Indicator"); break;
            case COL_SOLO:     return QHeader::tr("Solo Indicator"); break;
            case COL_CLASS:    return QHeader::tr("Track Type"); break;
            case COL_NAME:     return QHeader::tr("Track Name"); break;
            case COL_OCHANNEL: return QHeader::tr("Output Channel Number"); break;
            case COL_OPORT:    return QHeader::tr("Output Port"); break;
            case COL_TIMELOCK: return QHeader::tr("Time Lock"); break;
            default: break;
            }
      return QString::null;
      }

//---------------------------------------------------------
//   Arranger
//    is the central widget in app
//---------------------------------------------------------

Arranger::Arranger(QMainWindow* parent, const char* name)
   : QWidget(parent, name)
      {
      _bgPixmap = "";
      _raster  = 0;      // measure
      selected = 0;
      setMinimumSize(600, 50);
      showTrackinfo = false;

      trackInfoSize = 11 * fontMetrics().width('m');

      //---------------------------------------------------
      //  ToolBar
      //    create toolbar in toplevel widget
      //---------------------------------------------------

      QToolBar* toolbar = new QToolBar(tr("Arranger"), parent);

      QLabel* label = new QLabel(tr("Cursor"), toolbar, "Cursor");
      label->setAlignment(AlignRight|AlignVCenter);
      label->setIndent(3);
      cursorPos = new PosLabel(toolbar);
      cursorPos->setEnabled(false);

      const char* rastval[] = {
            QT_TR_NOOP("Off"), QT_TR_NOOP("Bar"), "1/2", "1/4", "1/8", "1/16"
            };
      label = new QLabel(tr("Snap"), toolbar, "Snap");
      label->setAlignment(AlignRight|AlignVCenter);
      label->setIndent(3);
      QComboBox* raster = new QComboBox(toolbar);
      for (int i = 0; i < 6; i++)
            raster->insertItem(tr(rastval[i]), i);
      raster->setCurrentItem(1);
      connect(raster, SIGNAL(activated(int)), SLOT(_setRaster(int)));

      // Song len
      label = new QLabel(tr("Len"), toolbar, "Len");
      label->setAlignment(AlignRight|AlignVCenter);
      label->setIndent(3);
      lenEntry = new QSpinBox(1, 50000, 1, toolbar);
      lenEntry->setValue(song->len());
      connect(lenEntry, SIGNAL(valueChanged(int)), SLOT(songlenChanged(int)));

      typeBox = new LabelCombo(tr("Type"), toolbar);
      typeBox->insertItem(tr("NO"), 0);
      typeBox->insertItem(tr("GM"), 1);
      typeBox->insertItem(tr("GS"), 2);
      typeBox->insertItem(tr("XG"), 3);
      typeBox->setCurrentItem(0);
      connect(typeBox, SIGNAL(activated(int)), SLOT(modeChange(int)));
      QToolTip::add(typeBox, tr("midi song type"));
      QWhatsThis::add(typeBox, tr("midi song type"));

      label = new QLabel(tr("Pitch"), toolbar, "Pitch");
      label->setAlignment(AlignRight|AlignVCenter);
      label->setIndent(3);
      QSpinBox* globalPitchSpinBox = new QSpinBox(-127, 127, 1, toolbar);
      globalPitchSpinBox->setValue(song->globalPitchShift());
      QToolTip::add(globalPitchSpinBox, tr("midi pitch"));
      QWhatsThis::add(globalPitchSpinBox, tr("global midi pitch shift"));
      connect(globalPitchSpinBox, SIGNAL(valueChanged(int)), SLOT(globalPitchChanged(int)));

      label = new QLabel(tr("Tempo"), toolbar, "Tempo");
      label->setAlignment(AlignRight|AlignVCenter);
      label->setIndent(3);
      globalTempoSpinBox = new QSpinBox(50, 200, 1, toolbar);
      globalTempoSpinBox->setSuffix(QString("%"));
      globalTempoSpinBox->setValue(tempomap.globalTempo());
      QToolTip::add(globalTempoSpinBox, tr("midi tempo"));
      QWhatsThis::add(globalTempoSpinBox, tr("midi tempo"));
      connect(globalTempoSpinBox, SIGNAL(valueChanged(int)), SLOT(globalTempoChanged(int)));

      QToolButton* tempo50  = new QToolButton(toolbar, "tempo50");
      tempo50->setText(QString("50%"));
      connect(tempo50, SIGNAL(clicked()), SLOT(setTempo50()));
      QToolButton* tempo100 = new QToolButton(toolbar, "tempo100");
      tempo100->setText(tr("N"));
      connect(tempo100, SIGNAL(clicked()), SLOT(setTempo100()));
      QToolButton* tempo200 = new QToolButton(toolbar, "tempo200");
      tempo200->setText(QString("200%"));
      connect(tempo200, SIGNAL(clicked()), SLOT(setTempo200()));

      QVBoxLayout* box  = new QVBoxLayout(this);
      box->addWidget(hLine(this), AlignTop);

      //---------------------------------------------------
      //  Tracklist
      //---------------------------------------------------

      int xscale = -100;
      int yscale = 1;

      split  = new Splitter(Horizontal, this, "split");
      box->addWidget(split, 1000);

      QWidget* tracklist = new QWidget(split);
      split->setResizeMode(tracklist, QSplitter::Stretch);

      //---------------------------------------------------
      //    Track Info
      //---------------------------------------------------

      genTrackInfo(tracklist);

      // Track-Info Button
      ib  = new QToolButton(tracklist);
      ib->setText(tr("TrackInfo"));
      ib->setToggleButton(true);
      ib->setOn(showTrackinfo);

      connect(ib, SIGNAL(toggled(bool)), SLOT(showInspector(bool)));

      header = new Header(tracklist, "header");
      header->setFixedHeight(30);

      QFontMetrics fm1(header->font());
      int fw = 8;

      header->addLabel(tr("R"), fm1.width('R')+fw);
      header->addLabel(tr("A"), fm1.width('A')+fw);
      header->addLabel(tr("M"), fm1.width('M')+fw);
      header->addLabel(tr("S"), fm1.width('S')+fw);
      header->addLabel(tr("C"), fm1.width('C')+fw);
      header->addLabel(tr("Track"), 100);
      header->addLabel(tr("O-Port"), 60);
      header->addLabel(tr("Ch"), 30);
      header->addLabel(tr("T"), fm1.width('T')+fw);
      header->setResizeEnabled(false, COL_RECORD);
      header->setResizeEnabled(false, COL_MUTE);
      header->setResizeEnabled(false, COL_SOLO);
      header->setResizeEnabled(false, COL_CLASS);
      header->setResizeEnabled(false, COL_OCHANNEL);
      header->setResizeEnabled(false, COL_TIMELOCK);

//      header->setStretchEnabled(true, COL_NAME);
      header->setTracking(true);

      new THeaderTip(header);
      new TWhatsThis(header, header);

      list = new TList(header, tracklist, "tracklist");

      connect(list, SIGNAL(selectionChanged()), SLOT(trackSelectionChanged()));
      connect(header, SIGNAL(sizeChange(int,int,int)), list, SLOT(redraw()));
      connect(header, SIGNAL(moved(int,int)), list, SLOT(redraw()));
      connect(header, SIGNAL(moved(int,int)), this, SLOT(headerMoved()));

      //  tracklist:
      //
      //         0         1         2
      //   +-----------+--------+---------+
      //   | Trackinfo | vline  | Header  | 0
      //   |           |        +---------+
      //   |           |        | TList   | 1
      //   +-----------+--------+---------+
      //   |             hline            | 2
      //   +-----+------------------------+
      //   | ib  |                        | 3
      //   +-----+------------------------+

      TLLayout* tgrid = new TLLayout(tracklist); // layout manager for this
      tgrid->wadd(0, midiTrackInfo);             // beast
      tgrid->wadd(1, waveTrackInfo);
      tgrid->wadd(2, noTrackInfo);
      tgrid->wadd(3, vLine(tracklist));
      tgrid->wadd(4, header);
      tgrid->wadd(5, list);
      tgrid->wadd(6, hLine(tracklist));
      tgrid->wadd(7, ib);

      //---------------------------------------------------
      //    Editor
      //---------------------------------------------------

      QWidget* editor = new QWidget(split);
      split->setResizeMode(editor, QSplitter::Stretch);
      int offset = sigmap.ticksMeasure(0);
      hscroll = new ScrollScale(-1000, -10, xscale, song->len(), Horizontal, editor, -offset);
      ib->setFixedHeight(hscroll->sizeHint().height());

      vscroll = new QScrollBar(1, 20*20, 1, 5, 0, Vertical, editor);
      list->setScroll(vscroll);

      QValueList<int> vallist;
      vallist.append(tgrid->maximumSize().width());
      split->setSizes(vallist);

      QGridLayout* egrid  = new QGridLayout(editor);
      egrid->setColStretch(0, 100);
      egrid->setRowStretch(2, 100);

      time = new MTScale(&_raster, editor, xscale);
      time->setOrigin(-offset, 0);
      canvas = new PartCanvas(&_raster, editor, xscale, yscale);
      canvas->setCanvasTools(arrangerTools);
      canvas->setOrigin(-offset, 0);
      canvas->setFocus();

      egrid->addMultiCellWidget(time,           0, 0, 0, 1);
      egrid->addMultiCellWidget(hLine(editor),  1, 1, 0, 1);
      egrid->addWidget(canvas,  2, 0);
      egrid->addWidget(vscroll, 2, 1);
      egrid->addWidget(hscroll, 3, 0, AlignBottom);

      connect(vscroll, SIGNAL(valueChanged(int)), canvas, SLOT(setYPos(int)));
      connect(hscroll, SIGNAL(scrollChanged(int)), canvas, SLOT(setXPos(int)));
      connect(hscroll, SIGNAL(scaleChanged(int)),  canvas, SLOT(setXMag(int)));
      connect(vscroll, SIGNAL(valueChanged(int)), list,   SLOT(setYPos(int)));
      connect(hscroll, SIGNAL(scrollChanged(int)), time,   SLOT(setXPos(int)));
      connect(hscroll, SIGNAL(scaleChanged(int)),  time,   SLOT(setXMag(int)));

      connect(canvas,  SIGNAL(timeChanged(int)),   SLOT(setTime(int)));
      connect(time,    SIGNAL(timeChanged(int)),   SLOT(setTime(int)));

      connect(canvas, SIGNAL(tracklistChanged()), list, SLOT(tracklistChanged()));
      connect(canvas, SIGNAL(dclickPart(Track*)), SIGNAL(editPart(Track*)));
      connect(canvas, SIGNAL(startEditor(PartList*,int)),   SIGNAL(startEditor(PartList*, int)));

      connect(song, SIGNAL(songChanged(int)), SLOT(songChanged(int)));
      connect(canvas, SIGNAL(followEvent(int)), hscroll, SLOT(setOffset(int)));
      connect(canvas, SIGNAL(selectionChanged()), SIGNAL(selectionChanged()));
      connect(canvas, SIGNAL(dropFile(const QString&)), SIGNAL(dropFile(const QString&)));

      connect(canvas, SIGNAL(toolChanged(int)), SIGNAL(toolChanged(int)));
      }

//---------------------------------------------------------
//   headerMoved
//---------------------------------------------------------

void Arranger::headerMoved()
      {
      header->setStretchEnabled(true, COL_NAME);
      }

//---------------------------------------------------------
//   setTime
//---------------------------------------------------------

void Arranger::setTime(int tick)
      {
      cursorPos->setEnabled(tick != -1);
      cursorPos->setValue(tick);
      time->setPos(3, tick, false);
      }

//---------------------------------------------------------
//   toolChange
//---------------------------------------------------------

void Arranger::setTool(int t)
      {
      canvas->setTool(t);
      }

//---------------------------------------------------------
//   dclickPart
//---------------------------------------------------------

void Arranger::dclickPart(Track* t)
      {
      emit editPart(t);
      }

//---------------------------------------------------------
//   setBgPixmap
//---------------------------------------------------------

void Arranger::setBgPixmap(const QString& bg)
      {
      _bgPixmap = bg;
      QPixmap pm(bg);
      canvas->setBg(pm);
      }

//---------------------------------------------------------
//   songlenChanged
//---------------------------------------------------------

void Arranger::songlenChanged(int n)
      {
      int newLen = sigmap.bar2tick(n, 0, 0);
      song->setLen(newLen);
      lenEntry->clearFocus();  // ??
      }

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

void Arranger::songChanged(int type)
      {
// printf("song changed %08x tracks %d\n", type, song->tracks()->size());
//      vscroll->setRange(0, 100);
      selected = 0;
      int endTick = song->len();
      int offset  = sigmap.ticksMeasure(0);
      hscroll->setRange(-offset, endTick + offset);  //DEBUG
      canvas->setOrigin(-offset, 0);
      time->setOrigin(-offset, 0);

      int bar, beat, tick;
      sigmap.tickValues(endTick, &bar, &beat, &tick);
      if (tick || beat)
            ++bar;
      lenEntry->blockSignals(true);
      lenEntry->setValue(bar);
      lenEntry->blockSignals(false);

      trackSelectionChanged();
      canvas->partsChanged();
      typeBox->setCurrentItem(int(song->mtype()));
      if (type & SC_SIG)
            time->redraw();
      if (type & SC_TEMPO)
            setGlobalTempo(tempomap.globalTempo());
      }

//---------------------------------------------------------
//   showInspector
//---------------------------------------------------------

void Arranger::showInspector(bool flag)
      {
      if (flag) {
            showTrackinfo = true;
            if (selected == 0) {
                  updateNoTrackInfo();
                  return;
                  }
            switch(selected->type()) {
                  case Track::MIDI:
                  case Track::DRUM:
                        updateMidiTrackInfo();
                        break;
                  case Track::WAVE:
                        updateWaveTrackInfo();
                        break;
                  }
            }
      else  {
            showTrackinfo = false;
            midiTrackInfo->hide();
            waveTrackInfo->hide();
            noTrackInfo->hide();
            }
      }

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

void Arranger::trackSelectionChanged()
      {
      TrackList* tracks = song->tracks();
      Track* track = 0;
      for (iTrack t = tracks->begin(); t != tracks->end(); ++t) {
            if ((*t)->selected()) {
                  track = *t;
                  break;
                  }
            }
      if (track == 0) {
            selected = 0;
            updateInspector();
            return;
            }
      if (track == selected)
            return;
      selected = track;
      updateInspector();
      }

//---------------------------------------------------------
//   updateInspector
//---------------------------------------------------------

void Arranger::updateInspector()
      {
      if (!showTrackinfo)
            return;
      if (!selected) {
            updateNoTrackInfo();
            return;
            }
      switch(selected->type()) {
            case Track::MIDI:
            case Track::DRUM:
                  updateMidiTrackInfo();
                  break;
            case Track::WAVE:
                  updateWaveTrackInfo();
                  break;
            }
      }

//---------------------------------------------------------
//   updateNoTrackInfo
//---------------------------------------------------------

void Arranger::updateNoTrackInfo()
      {
      waveTrackInfo->hide();
      midiTrackInfo->hide();
      noTrackInfo->show();
      }

//---------------------------------------------------------
//   iNameChanged
//---------------------------------------------------------

void Arranger::iNameChanged()
      {
      QString txt = midiTrackInfo->iName->text();
      nameChanged(txt);
      }

//---------------------------------------------------------
//   iwNameChanged
//---------------------------------------------------------

void Arranger::iwNameChanged()
      {
      nameChanged(waveTrackInfo->iName->text());
      }

//---------------------------------------------------------
//   nameChanged
//---------------------------------------------------------

void Arranger::nameChanged(const QString& txt)
      {
      if (txt == selected->name())
            return;
      Track* track = selected->clone();
      track->setName(txt);
      midiThread->msgChangeTrack(selected, track);
      }

//---------------------------------------------------------
//   iOutputChannelChanged
//---------------------------------------------------------

void Arranger::iOutputChannelChanged(int channel)
      {
      --channel;
      MidiTrack* track = (MidiTrack*)selected;
      if (channel != track->outChannel()) {
            track->setOutChannel(channel);
            list->redraw();
            }
      }

//---------------------------------------------------------
//   iKanalChanged
//---------------------------------------------------------

void Arranger::iInputChannelChanged(const QString& s)
      {
      MidiTrack* track = (MidiTrack*)selected;
      int val = string2bitmap(s);
      if (val != track->inChannelMask()) {
            track->setInChannelMask(val);
            list->redraw();
            }
      }

//---------------------------------------------------------
//   iOutputPortChanged
//---------------------------------------------------------

void Arranger::iOutputPortChanged(int index)
      {
      MidiTrack* track = (MidiTrack*)selected;
      if (index == track->outPort())
            return;
      track->setOutPort(index);
      list->redraw();
      }

//---------------------------------------------------------
//   iInputPortChanged
//---------------------------------------------------------

void Arranger::iInputPortChanged(const QString& s)
      {
      int val = string2bitmap(s);
      MidiTrack* track = (MidiTrack*)selected;
      if (val == track->inPortMask())
            return;
      track->setInPortMask(val);
      list->redraw();
      }

//---------------------------------------------------------
//   iHBankChanged
//---------------------------------------------------------

void Arranger::iHBankChanged(int hbank)
      {
      hbank -= 1;
      MidiPort* port = &midiPorts[selected->outPort()];
      int channel = ((MidiTrack*)selected)->outChannel();

      int prg   = port->iState(channel)->program;
      int lbank = port->iState(channel)->controller[CTRL_LBANK];
      port->programChange(channel, hbank, lbank, prg);
      updateInspector();
      }

//---------------------------------------------------------
//   iLBankChanged
//---------------------------------------------------------

void Arranger::iLBankChanged(int lbank)
      {
      lbank -= 1;
      MidiPort* port = &midiPorts[selected->outPort()];
      int channel = ((MidiTrack*)selected)->outChannel();

      int prg   = port->iState(channel)->program;
      int hbank = port->iState(channel)->controller[CTRL_HBANK];
      port->programChange(channel, hbank, lbank, prg);
      updateInspector();
      }

//---------------------------------------------------------
//   iProgramChanged
//---------------------------------------------------------

void Arranger::iProgramChanged(int prg)
      {
      prg -= 1;

      MidiPort* port = &midiPorts[selected->outPort()];
      int channel = selected->outChannel();

      int lbank = port->iState(channel)->controller[CTRL_LBANK];
      int hbank = port->iState(channel)->controller[CTRL_HBANK];
      port->programChange(channel, hbank, lbank, prg);
      updateInspector();
      }

//---------------------------------------------------------
//   iLautstChanged
//---------------------------------------------------------

void Arranger::iLautstChanged(int val)
      {
      MidiPort* port = &midiPorts[selected->outPort()];
      int channel = ((MidiTrack*)selected)->outChannel();
      port->iState(channel)->controller[CTRL_VOLUME] = val;

      // Realtime Change:
      port->iState(channel)->controller[CTRL_VOLUME] = val;
      port->setCtrl(channel, CTRL_VOLUME, val);
      updateInspector();
      }

//---------------------------------------------------------
//   iTranspChanged
//---------------------------------------------------------

void Arranger::iTranspChanged(int val)
      {
      MidiTrack* track = (MidiTrack*)selected;
      track->transposition = val;
      }

//---------------------------------------------------------
//   iAnschlChanged
//---------------------------------------------------------

void Arranger::iAnschlChanged(int val)
      {
      MidiTrack* track = (MidiTrack*)selected;
      track->velocity = val;
      }

//---------------------------------------------------------
//   iVerzChanged
//---------------------------------------------------------

void Arranger::iVerzChanged(int val)
      {
      MidiTrack* track = (MidiTrack*)selected;
      track->delay = val;
      }

//---------------------------------------------------------
//   iLenChanged
//---------------------------------------------------------

void Arranger::iLenChanged(int val)
      {
      MidiTrack* track = (MidiTrack*)selected;
      track->len = val;
      }

//---------------------------------------------------------
//   iKomprChanged
//---------------------------------------------------------

void Arranger::iKomprChanged(int val)
      {
      MidiTrack* track = (MidiTrack*)selected;
      track->compression = val;
      }

//---------------------------------------------------------
//   iPanChanged
//---------------------------------------------------------

void Arranger::iPanChanged(int val)
      {
      val -= 1;
      MidiPort* port = &midiPorts[selected->outPort()];
      int channel = selected->outChannel();
      port->iState(channel)->controller[CTRL_PANPOT] = val;

      // Realtime Change:
      port->setCtrl(channel, CTRL_PANPOT, val);
      }

//---------------------------------------------------------
//   modeChange
//---------------------------------------------------------

void Arranger::modeChange(int mode)
      {
      song->setMType(MType(mode));
      updateInspector();
      }

//---------------------------------------------------------
//   setMode
//---------------------------------------------------------

void Arranger::setMode(int mode)
      {
      typeBox->setCurrentItem(mode);
      }

//---------------------------------------------------------
//   genTrackInfo
//---------------------------------------------------------

void Arranger::genTrackInfo(QWidget* parent)
      {
      noTrackInfo   = new QWidget(parent);
      midiTrackInfo = new MidiTrackInfo(parent);
      waveTrackInfo = new WaveTrackInfo(parent);

      midiTrackInfo->setFixedWidth(trackInfoSize);
      noTrackInfo->setFixedWidth(trackInfoSize);
      waveTrackInfo->setFixedWidth(trackInfoSize);

      midiTrackInfo->hide();
      waveTrackInfo->hide();
      if (!showTrackinfo)
            noTrackInfo->hide();
      genWaveTrackInfo();
      genMidiTrackInfo();
      }

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

void Arranger::writeStatus(int level, Xml& xml)
      {
      xml.tag(level++, "arranger");
      xml.intTag(level, "info", ib->isOn());
      split->writeStatus(level, xml);
      list->writeStatus(level, xml, "list");

      xml.intTag(level, "part_type",  canvas->showPartType());
      xml.intTag(level, "show_events", canvas->showPartEvent());
      xml.strTag(level, "image", _bgPixmap.latin1());
      xml.intTag(level, "xpos", hscroll->pos());
      xml.intTag(level, "xmag", hscroll->mag());
      xml.intTag(level, "ypos", vscroll->value());
//      xml.intTag(level, "ymag", vscroll->mag());
      xml.intTag(level, "grid", hasGrid()?1:0);
      xml.etag(level, "arranger");
      }

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

void Arranger::readStatus(Xml& xml)
      {
      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 == "info")
                              showTrackinfo = xml.parseInt();
                        else if (tag == split->name())
                              split->readStatus(xml);
                        else if (tag == "part_type")
                              canvas->setShowPartType(xml.parseInt());
                        else if (tag == "show_events")
                              canvas->setShowPartEvent(xml.parseInt());
                        else if (tag == "list")
                              list->readStatus(xml, "list");
                        else if (tag == "image")
                              setBgPixmap(xml.parse1());
                        else if (tag == "xmag")
                              hscroll->setMag(xml.parseInt());
                        else if (tag == "xpos") {
                              int hpos = xml.parseInt();
                              hscroll->setPos(hpos);
                              }
                        else if (tag == "ymag")
                              xml.parseInt();   // obsolete
                        else if (tag == "ypos")
                              vscroll->setValue(xml.parseInt());
                        else if (tag == "grid")
                              setGrid((xml.parseInt() == 1)?true:false);
                        else
                              xml.unknown("Arranger");
                        break;
                  case Xml::TagEnd:
                        if (tag == "arranger") {
                              ib->setOn(showTrackinfo);
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   setRaster
//---------------------------------------------------------

void Arranger::_setRaster(int index)
      {
      static int rasterTable[] = {
            1, 0, 768, 384, 192, 96
            };
      _raster = rasterTable[index];
      canvas->redraw();
      }

//---------------------------------------------------------
//   instrPopup
//---------------------------------------------------------

void Arranger::instrPopup()
      {
      int channel = ((MidiTrack*)selected)->outChannel();
      int port = selected->outPort();
      MidiInstrument* instr = midiPorts[port].instrument();
      instr->populatePatchPopup(pop, channel, song->mtype());

      int rv = pop->exec(midiTrackInfo->iPatch->mapToGlobal(QPoint(10,5)));
      if (rv != -1) {
            //
            // TODO: songtype berücksichtigen; Drumtrack?
            //       bei GM hbank und lbank nicht setzen
            //       bei GS lbank nicht setzen

            int hb   = (signed char)((rv >> 16) & 0xff);
            int lb   = (signed char)((rv >> 8)  & 0xff);
            int prog = (signed char)(rv & 0xff);

            MidiPort* mp = &midiPorts[port];
            mp->programChange(channel, hb, lb, prog);
            updateInspector();
            }
      }

//---------------------------------------------------------
//   genMidiTrackInfo
//---------------------------------------------------------

void Arranger::genMidiTrackInfo()
      {
      connect(midiTrackInfo->midiThru, SIGNAL(toggled(bool)), SLOT(midiThruChanged(bool)));
      connect(midiTrackInfo->iPatch, SIGNAL(released()), SLOT(instrPopup()));

      pop = new QPopupMenu(midiTrackInfo->iPatch);
      pop->setCheckable(false);

      connect(midiTrackInfo->iName, SIGNAL(returnPressed()), SLOT(iNameChanged()));
      connect(midiTrackInfo->iOutputChannel, SIGNAL(valueChanged(int)), SLOT(iOutputChannelChanged(int)));
      connect(midiTrackInfo->iInputChannel, SIGNAL(textChanged(const QString&)), SLOT(iInputChannelChanged(const QString&)));
      connect(midiTrackInfo->iHBank, SIGNAL(valueChanged(int)), SLOT(iHBankChanged(int)));
      connect(midiTrackInfo->iLBank, SIGNAL(valueChanged(int)), SLOT(iLBankChanged(int)));
      connect(midiTrackInfo->iProgram, SIGNAL(valueChanged(int)), SLOT(iProgramChanged(int)));
      connect(midiTrackInfo->iLautst, SIGNAL(valueChanged(int)), SLOT(iLautstChanged(int)));
      connect(midiTrackInfo->iTransp, SIGNAL(valueChanged(int)), SLOT(iTranspChanged(int)));
      connect(midiTrackInfo->iAnschl, SIGNAL(valueChanged(int)), SLOT(iAnschlChanged(int)));
      connect(midiTrackInfo->iVerz, SIGNAL(valueChanged(int)), SLOT(iVerzChanged(int)));
      connect(midiTrackInfo->iLen, SIGNAL(valueChanged(int)), SLOT(iLenChanged(int)));
      connect(midiTrackInfo->iKompr, SIGNAL(valueChanged(int)), SLOT(iKomprChanged(int)));
      connect(midiTrackInfo->iPan, SIGNAL(valueChanged(int)), SLOT(iPanChanged(int)));
      connect(midiTrackInfo->iOutput, SIGNAL(activated(int)), SLOT(iOutputPortChanged(int)));
      connect(midiTrackInfo->iInput, SIGNAL(textChanged(const QString&)), SLOT(iInputPortChanged(const QString&)));
      }

//---------------------------------------------------------
//   genWaveTrackInfo
//---------------------------------------------------------

void Arranger::genWaveTrackInfo()
      {
      connect(waveTrackInfo->ports, SIGNAL(activated(int)), SLOT(iChannelsChanged(int)));
      connect(waveTrackInfo->iName, SIGNAL(returnPressed()), SLOT(iwNameChanged()));
      connect(waveTrackInfo->outputRoute, SIGNAL(activated(const QString&)),
         SLOT(setOutputRoute(const QString&)));
      }

//---------------------------------------------------------
//   updateMidiTrackInfo
//---------------------------------------------------------

void Arranger::updateMidiTrackInfo()
      {
      waveTrackInfo->hide();
      noTrackInfo->hide();
      midiTrackInfo->show();

      int outChannel = selected->outChannel();
      int inChannel  = selected->inChannelMask();
      int outPort    = selected->outPort();
      int inPort     = selected->inPortMask();

      midiTrackInfo->iInput->clear();
      midiTrackInfo->iOutput->clear();

      midiTrackInfo->midiThru->setChecked(((MidiTrack*)selected)->midiThruFlag());

      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port  = &midiPorts[i];
            MidiDevice* dev = port->device();
            QString name;
            name.sprintf("%d(%s)", i+1, dev ? dev->name().latin1() : "none");
            midiTrackInfo->iOutput->insertItem(name, i);
            if (i == outPort)
                  midiTrackInfo->iOutput->setCurrentItem(i);
            if (!(port->rwFlags() & 0x1)) {
                  // disable entry
                  }
            }
      midiTrackInfo->iInput->setText(bitmap2String(inPort));
      midiTrackInfo->iInputChannel->setText(bitmap2String(inChannel));

      ChannelState* istate = midiPorts[outPort].iState(outChannel);

      if (midiTrackInfo->iName->text() != selected->name()) {
            midiTrackInfo->iName->setText(selected->name());
            midiTrackInfo->iName->home(false);
            }

      midiTrackInfo->iOutputChannel->setValue(outChannel+1);
      midiTrackInfo->iInputChannel->setText(bitmap2String(inChannel));

      MidiInstrument* instr = midiPorts[outPort].instrument();
      const char* name = instr->getPatchName(outChannel, istate->controller[CTRL_HBANK],
         istate->controller[CTRL_LBANK], istate->program, song->mtype());

      midiTrackInfo->iPatch->setText(QString(name));

      midiTrackInfo->iHBank->blockSignals(true);
      midiTrackInfo->iLBank->blockSignals(true);
      midiTrackInfo->iProgram->blockSignals(true);
      midiTrackInfo->iLautst->blockSignals(true);
      midiTrackInfo->iPan->blockSignals(true);

      midiTrackInfo->iHBank->setValue(istate->controller[CTRL_HBANK] + 1);
      midiTrackInfo->iLBank->setValue(istate->controller[CTRL_LBANK] + 1);
      midiTrackInfo->iProgram->setValue(istate->program + 1);
      midiTrackInfo->iLautst->setValue(istate->controller[CTRL_VOLUME]);
      midiTrackInfo->iPan->setValue(istate->controller[CTRL_PANPOT] + 1);

      midiTrackInfo->iHBank->blockSignals(false);
      midiTrackInfo->iLBank->blockSignals(false);
      midiTrackInfo->iProgram->blockSignals(false);
      midiTrackInfo->iLautst->blockSignals(false);
      midiTrackInfo->iPan->blockSignals(false);

      midiTrackInfo->iTransp->setValue(((MidiTrack*)selected)->transposition);
      midiTrackInfo->iAnschl->setValue(((MidiTrack*)selected)->velocity);
      midiTrackInfo->iVerz->setValue(((MidiTrack*)selected)->delay);
      midiTrackInfo->iLen->setValue(((MidiTrack*)selected)->len);
      midiTrackInfo->iKompr->setValue(((MidiTrack*)selected)->compression);
      switch(song->mtype()) {
            case MT_GM:
                  midiTrackInfo->iHBank->setEnabled(false);
                  midiTrackInfo->iLBank->setEnabled(false);
                  break;
            case MT_GS:
                  midiTrackInfo->iHBank->setEnabled(true);
                  midiTrackInfo->iLBank->setEnabled(false);
                  break;
            case MT_UNKNOWN:
            case MT_XG:
                  midiTrackInfo->iHBank->setEnabled(true);
                  midiTrackInfo->iLBank->setEnabled(true);
                  break;
            }
      }

//---------------------------------------------------------
//   updateWaveTrackInfo
//---------------------------------------------------------

void Arranger::updateWaveTrackInfo()
      {
      noTrackInfo->hide();
      midiTrackInfo->hide();
      waveTrackInfo->show();

      WaveTrack* track = (WaveTrack*)selected;
      if (waveTrackInfo->iName->text() != track->name()) {
            waveTrackInfo->iName->setText(track->name());
            waveTrackInfo->iName->home(false);
            }
      AudioNode* route = track->route();
      int currentRoute = 0;
      waveTrackInfo->outputRoute->clear();
      waveTrackInfo->outputRoute->insertItem(tr("Master"));
      waveTrackInfo->outputRoute->insertItem(tr("Group A"));
      waveTrackInfo->outputRoute->insertItem(tr("Group B"));
      waveTrackInfo->outputRoute->insertItem(tr("Group C"));
      waveTrackInfo->outputRoute->insertItem(tr("Group D"));

      if (route == &audioOutput)
            currentRoute = 0;
      else if (route == &audioGroups[0])
            currentRoute = 1;
      else if (route == &audioGroups[1])
            currentRoute = 2;
      else if (route == &audioGroups[2])
            currentRoute = 3;
      else if (route == &audioGroups[3])
            currentRoute = 4;

      waveTrackInfo->outputRoute->setCurrentItem(currentRoute);

      int curItem = 0;
      switch(track->ports()) {
            case 1: curItem = 0; break;
            case 2: curItem = 1; break;
            default:
                  fprintf(stderr, "internal error: illegal channel number %d\n",
                     track->ports());
                  break;
            }
      waveTrackInfo->ports->setCurrentItem(curItem);
      }

//---------------------------------------------------------
//   setOutputRoute
//---------------------------------------------------------

void Arranger::setOutputRoute(const QString& r)
      {
      AudioNode* dst = name2Node(r);
      WaveTrack* src = (WaveTrack*)selected;
      audio->msgSetRoute(src, dst);
      }

//---------------------------------------------------------
//   midiThruChanged
//---------------------------------------------------------

void Arranger::midiThruChanged(bool flag)
      {
      ((MidiTrack*)selected)->setMidiThruFlag(flag);
      }

//---------------------------------------------------------
//   reset
//---------------------------------------------------------

void Arranger::reset()
      {
      canvas->setXPos(0);
      canvas->setYPos(0);
      hscroll->setPos(0);
      vscroll->setValue(0);
      time->setXPos(0);
      time->setYPos(0);
      }

//---------------------------------------------------------
//   iChannelsChanged
//---------------------------------------------------------

void Arranger::iChannelsChanged(int i)
      {
      int ch = 0;
      switch(i) {
            case 0:    ch = 1; break;  // mono
            case 1:    ch = 2; break;  // stereo
            }
      WaveTrack* track = (WaveTrack*)selected;
      audio->msgSetChannels(track, ch);
      if (audioMixer)
            audioMixer->updateMixer();
      }

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

void Arranger::cmd(int cmd)
      {
      int ncmd;
      switch (cmd) {
            case CMD_CUT_PART:
                  ncmd = PartCanvas::CMD_CUT_PART;
                  break;
            case CMD_COPY_PART:
                  ncmd = PartCanvas::CMD_COPY_PART;
                  break;
            case CMD_PASTE_PART:
                  ncmd = PartCanvas::CMD_PASTE_PART;
                  break;
            default:
                  return;
            }
      canvas->cmd(ncmd);
      }

int Arranger::showPartType() const
      {
      return canvas->showPartType();
      }
int Arranger::showPartEvent() const
      {
      return canvas->showPartEvent();
      }
void Arranger::setShowPartType(int val)
      {
      canvas->setShowPartType(val);
      }
void Arranger::setShowPartEvent(int val)
      {
      canvas->setShowPartEvent(val);
      }
void Arranger::setActivityMode(int i) {
	list->setActivityMode(i);
      }

void Arranger::setActivityColor(QColor c) {
	list->setActivityColor(c);
      }

//void Arranger::setSelectedTrackColor(QColor c)
//      {
//	list->setSelectedTrackColor(c);
//      }

int Arranger::getActivityMode() const
      {
      return list->getActivityMode();
      }
QColor Arranger::getActivityColor() const
      {
      return list->getActivityColor();
      }
//QColor Arranger::getSelectedTrackColor() const
//      {
//      return list->getSelectedTrackColor();
//      }

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

void Arranger::setGrid(bool b)
      {
      canvas->setGrid(b);
      }

//---------------------------------------------------------
//   hasGrid
//---------------------------------------------------------

bool Arranger::hasGrid()
      {
      return canvas->hasGrid();
      }

//---------------------------------------------------------
//   globalPitchChanged
//---------------------------------------------------------

void Arranger::globalPitchChanged(int val)
      {
      song->setGlobalPitchShift(val);
      }

//---------------------------------------------------------
//   globalTempoChanged
//---------------------------------------------------------

void Arranger::globalTempoChanged(int val)
      {
      midiThread->msgSetGlobalTempo(val);
      }

//---------------------------------------------------------
//   setTempo50
//---------------------------------------------------------

void Arranger::setTempo50()
      {
      setGlobalTempo(50);
      }

//---------------------------------------------------------
//   setTempo100
//---------------------------------------------------------

void Arranger::setTempo100()
      {
      setGlobalTempo(100);
      }

//---------------------------------------------------------
//   setTempo200
//---------------------------------------------------------

void Arranger::setTempo200()
      {
      setGlobalTempo(200);
      }

//---------------------------------------------------------
//   setGlobalTempo
//---------------------------------------------------------

void Arranger::setGlobalTempo(int val)
      {
      globalTempoSpinBox->setValue(val);
      }


