//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: confmport.cpp,v 1.3 2003/10/30 12:11:50 wschweer Exp $
//
//  (C) Copyright 2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <list>
#include <termios.h>

#include <qlistview.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qlineedit.h>
#include <qcombobox.h>
#include <qlabel.h>
#include <qbuttongroup.h>
#include <stdio.h>
#include <qpopupmenu.h>
#include <qgroupbox.h>
#include <qradiobutton.h>
#include <qspinbox.h>
#include <qcheckbox.h>
#include <qsignalmapper.h>
#include <qtooltip.h>
#include <qfiledialog.h>
#include <qtoolbutton.h>

#include "confmport.h"
#include "app.h"
#include "icons.h"
#include "globals.h"
#include "transport.h"
#include "arranger.h"
#include "seq.h"
#include "midiport.h"
#include "driver/mididev.h"
#include "driver/midiserial.h"
#include "xml.h"
#include "midisyncimpl.h"
#include "midifilterimpl.h"
#include "ctrlcombo.h"
#include "minstrument.h"
#include "synth.h"
#include "midithread.h"
#include "mess.h"
#include "audio.h"

enum { DEVCOL_NO = 0, DEVCOL_GUI, DEVCOL_REC, DEVCOL_INSTR, DEVCOL_NAME,
       DEVCOL_STATE };

//---------------------------------------------------------
//   speed2int
//---------------------------------------------------------

static int speed2int(const QString& s)
      {
      if (s == "9600")
            return B9600;
      if (s == "19200")
            return B19200;
      if (s == "38400")
            return B38400;
      return 0;
      }

//---------------------------------------------------------
//   speed2idx
//---------------------------------------------------------

static int speed2idx(const QString& s)
      {
      if (s == "9600")
            return 0;
      if (s == "19200")
            return 1;
      if (s == "38400")
            return 2;
      return 0;
      }

//---------------------------------------------------------
//   speed2str
//---------------------------------------------------------

static QString speed2str(int s)
      {
      switch(s) {
            case B9600:   return QString("9600");
            case B19200:  return QString("19200");
            case B38400:  return QString("38400");
            }
      return QString("?");
      }


//---------------------------------------------------------
//   handshaking2int
//---------------------------------------------------------

static int handshaking2int(const QString& s)
      {
      if (s == "None")
            return 0;
      if (s == "RTS/CTS")
            return 1;
      if (s == "Xon/Xoff")
            return 2;
      return 0;
      }

//---------------------------------------------------------
//   handshaking2str
//---------------------------------------------------------

static QString handshaking2str(int v)
      {
      switch(v) {
            case 0:  return QString("None");
            case 1:  return QString("RTS/CTS");
            case 2:  return QString("Xon/Xoff");
            }
      return QString("?");
      }

//---------------------------------------------------------
//   type2int
//---------------------------------------------------------

static int type2int(const QString& s)
      {
      if (s == "Raw Device")
            return 0;
      if (s == "Serial Port")
            return 1;
      if (s == "Pipe")
            return 2;
      return 0;
      }

//---------------------------------------------------------
//   type2str
//---------------------------------------------------------

static QString type2str(int t)
      {
      switch(t) {
            case 0:  return QString("Raw Device");
            case 1:  return QString("Serial Port");
            case 2:  return QString("Pipe");
            }
      return QString("?");
      }

//---------------------------------------------------------
//   MidiRawDeviceDialog
//---------------------------------------------------------

MidiRawDeviceDialog::MidiRawDeviceDialog(QWidget* parent, const char* name,
   bool modal, WFlags fl)
   : MidiRawDeviceDialogBase(parent, name, modal, fl)
      {
      connect(buttonDeviceNew, SIGNAL(clicked()), SLOT(deviceNew()));
      connect(buttonDeviceDelete, SIGNAL(clicked()), SLOT(deviceDelete()));
      connect(buttonDevicePath, SIGNAL(clicked()), SLOT(devicePath()));
      connect(editName, SIGNAL(textChanged(const QString&)), SLOT(editNameChanged(const QString&)));
      connect(editPath, SIGNAL(textChanged(const QString&)), SLOT(editPathChanged(const QString&)));
      connect(virtPorts, SIGNAL(valueChanged(int)), SLOT(virtPortsChanged(int)));
      connect(speed, SIGNAL(activated(const QString&)), SLOT(speedChanged(const QString&)));
      connect(handshaking, SIGNAL(activated(const QString&)), SLOT(handshakingChanged(const QString&)));
      connect(type, SIGNAL(activated(const QString&)), SLOT(typeChanged(const QString&)));
      connect(listDevices, SIGNAL(selectionChanged(QListViewItem*)),
         SLOT(devicesSelectionChanged(QListViewItem*)));
      listDevices->setColumnAlignment(3, AlignHCenter);
      }

//---------------------------------------------------------
//   populate
//---------------------------------------------------------

void MidiRawDeviceDialog::populate()
      {
      QString vp;

      listDevices->clear();
      for (iSerialDevice i = serialDevices.begin(); i != serialDevices.end(); ++i) {
            vp.setNum((*i)->vports());
            new QListViewItem(listDevices,
               (*i)->name(),
               (*i)->path(),
               type2str((*i)->type()),
               vp,
               speed2str((*i)->speed()),
               handshaking2str((*i)->handshaking())
               );
            }
      listDevices->setCurrentItem(listDevices->firstChild());
      if (listDevices->childCount() == 0) {
            buttonDeviceDelete->setEnabled(false);
            editName->setEnabled(false);
            editPath->setEnabled(false);
            virtPorts->setEnabled(false);
            speed->setEnabled(false);
            handshaking->setEnabled(false);
            type->setEnabled(false);
            }
      }

//---------------------------------------------------------
//   deviceSelectionChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::devicesSelectionChanged(QListViewItem* item)
      {
      if (item) {
            editName->setText(item->text(0));
            editPath->setText(item->text(1));
            virtPorts->setValue(item->text(3).toInt());
            speed->setCurrentItem(speed2idx(item->text(4)));
            handshaking->setCurrentItem(handshaking2int(item->text(5)));
            type->setCurrentItem(type2int(item->text(2)));
            }
      else {
            editName->setText(QString(""));
            editPath->setText(QString(""));
            virtPorts->setValue(0);
            speed->setCurrentItem(2);
            handshaking->setCurrentItem(0);
            type->setCurrentItem(1);
            }
      if (item->text(2) == "Serial Port") {
            speed->setEnabled(true);
            handshaking->setEnabled(true);
            }
      else {
            speed->setEnabled(false);
            handshaking->setEnabled(false);
            }
      }

//---------------------------------------------------------
//   editNameChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::editNameChanged(const QString& text)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      item->setText(0, text);
      }

//---------------------------------------------------------
//   editPathChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::editPathChanged(const QString& text)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      item->setText(1, text);
      }

//---------------------------------------------------------
//   typeChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::typeChanged(const QString& text)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      if (text == "Serial Port") {
            speed->setEnabled(true);
            item->setText(4, speed->currentText());
            handshaking->setEnabled(true);
            item->setText(5, handshaking->currentText());
            }
      else {
            speed->setEnabled(false);
            item->setText(4, QString("---"));
            handshaking->setEnabled(false);
            item->setText(5, QString("---"));
            }
      item->setText(2, text);
      }

//---------------------------------------------------------
//   virtPortsChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::virtPortsChanged(int val)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      QString s;
      s.setNum(val);
      item->setText(3, s);
      }

//---------------------------------------------------------
//   speedChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::speedChanged(const QString& text)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      item->setText(4, text);
      }

//---------------------------------------------------------
//   handshakingChanged
//---------------------------------------------------------

void MidiRawDeviceDialog::handshakingChanged(const QString& text)
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      item->setText(5, text);
      }

//---------------------------------------------------------
//   deviceNew
//---------------------------------------------------------

void MidiRawDeviceDialog::deviceNew()
      {
      QListViewItem* item = new QListViewItem(listDevices,
         QString(""), QString(""), QString("Serial Port"), QString("1"),
         QString("38400"), QString("None"));
      listDevices->setCurrentItem(item);
      editName->setText(QString(""));
      editPath->setText(QString(""));
      virtPorts->setValue(0);
      speed->setCurrentItem(2);
      handshaking->setCurrentItem(0);
      type->setCurrentItem(1);

      buttonDeviceDelete->setEnabled(true);
      editName->setEnabled(true);
      editPath->setEnabled(true);
      virtPorts->setEnabled(true);
      speed->setEnabled(true);
      handshaking->setEnabled(true);
      type->setEnabled(true);
      }

//---------------------------------------------------------
//   deviceDelete
//---------------------------------------------------------

void MidiRawDeviceDialog::deviceDelete()
      {
      QListViewItem* item = listDevices->currentItem();
      if (item == 0)
            return;
      if (deleteSerialDevice(item->text(1))) {
            printf("CANNOT DELETE DEVICE<%s>: device busy\n",
               item->text(0).latin1());
            }
      else {
            delete item;
            }
      if (listDevices->childCount() == 0) {
            buttonDeviceDelete->setEnabled(false);
            editName->setEnabled(false);
            editPath->setEnabled(false);
            virtPorts->setEnabled(false);
            speed->setEnabled(false);
            handshaking->setEnabled(false);
            type->setEnabled(false);
            }
      }

//---------------------------------------------------------
//   accept
//---------------------------------------------------------

void MidiRawDeviceDialog::accept()
      {
      QListViewItem* item = listDevices->firstChild();

      while (item) {
            iSerialDevice i;
            for (i = serialDevices.begin(); i != serialDevices.end(); ++i) {
                  if ((*i)->path() == item->text(1)) {
                        break;
                        }
                  }
            if (i != serialDevices.end()) {
                  (*i)->setName(item->text(0));
                  (*i)->setType(SerialType(type2int(item->text(2))));
                  (*i)->setVports(item->text(3).toInt());
                  (*i)->setSpeed(speed2int(item->text(4)));
                  (*i)->setHandshaking(handshaking2int(item->text(5)));
                  }
            else {
                  SerialDevice* port = new SerialDevice;
                  port->setName(item->text(0));
                  port->setPath(item->text(1));
                  port->setType(SerialType(type2int(item->text(2))));
                  port->setVports(item->text(3).toInt());
                  port->setSpeed(speed2int(item->text(4)));
                  port->setHandshaking(handshaking2int(item->text(5)));
                  serialDevices.push_back(port);
                  for (int i = 0; i < port->vports(); ++i) {
                        QString name = port->name();
                        if (port->vports() > 1) {
                              QString pn;
                              pn.setNum(i+1);
                              name += "-" + pn;
                              }
                        MidiSerialDevice* dev = new MidiSerialDevice(port, i, name);
                        dev->setrwFlags(0x3);
                        midiDevices.add(dev);
                        }
                  }
            item = item->nextSibling();
            }
      close();
      }

//---------------------------------------------------------
//   devicePath
//---------------------------------------------------------

void MidiRawDeviceDialog::devicePath()
      {
      QString s;
      if (editPath->text().isEmpty())
            s = QString("/dev");
      else
            s = editPath->text();
      s = QFileDialog::getOpenFileName(s, QString::null, this, "midiRawDevicePathDialog");
      if (!s.isEmpty())
            editPath->setText(s);
      }

//---------------------------------------------------------
//   rbClicked
//---------------------------------------------------------

void MPConfig::rbClicked(QListViewItem* item, const QPoint&, int col)
      {
      if (item == 0)
            return;
      QString id = item->text(DEVCOL_NO);
      int no = atoi(id.latin1()) - 1;
      if (no < 0 || no >= MIDI_PORTS)
            return;

      int n;
      MidiPort* port      = &midiPorts[no];
      int flags           = port->rwFlags();
      MidiDevice* dev     = port->device();
      QListView* listView = item->listView();
      QPoint ppt = listView->itemRect(item).bottomLeft();
      ppt += QPoint(listView->header()->sectionPos(col), listView->header()->height());
      ppt  = listView->mapToGlobal(ppt);

      switch (col) {
            case DEVCOL_GUI:
                  if (dev == 0)
                        break;
                  if (port->hasGui()) {
                        port->showGui(!port->guiVisible());
                        item->setPixmap(DEVCOL_GUI, port->guiVisible() ? *dotIcon : *dothIcon);
                        }
                  break;

            case DEVCOL_REC:
                  if (dev == 0)
                        break;
                  flags ^= 0x2;
                  port->setrwFlags(flags);
                  item->setPixmap(DEVCOL_REC, (port->rwFlags() & 0x2) ? *dotIcon : QPixmap());
                  midiThread->setMidiDevice(port, dev);
                  break;
            case DEVCOL_NAME:
                  {
                  if (popup == 0)
                        popup = new QPopupMenu(this);
                  popup->clear();
                  popup->insertItem(tr("none"), 0);
                  int id = 1;
                  for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end(); ++i) {
                        const QString s = (*i)->name();
                        popup->insertItem(s, id);
                        for (int k = 0; k < MIDI_PORTS; ++k) {
                              MidiDevice* dev = midiPorts[k].device();
                              if (dev && s == dev->name()) {
                                    popup->setItemEnabled(id, false);
                                    break;
                                    }
                              }
                        ++id;
                        }
                  popup->insertItem(tr("other raw ..."), 999);
                  n = popup->exec(ppt, 0);
                  if (n == -1)
                        break;
                  if (n == 999) {
                        //-------------------------------------------
                        // other raw device
                        //-------------------------------------------
                        if (rdDialog == 0) {
                              rdDialog = new MidiRawDeviceDialog();
                              }
                        rdDialog->populate();
                        rdDialog->show();
                        break;
                        }
                  QString s = popup->text(n);
                  item->setText(DEVCOL_NAME, s);
                  if (n > 0) {
                        MidiDevice* dev = midiDevices.find(s);
                        if (dev) {
                              port->setrwFlags(dev->rwFlags());
                              item->setPixmap(DEVCOL_REC, (port->rwFlags() & 0x2) ? *dotIcon : QPixmap());
                              item->setText(DEVCOL_STATE, port->state());
                              }
                        midiThread->setMidiDevice(port, dev);
                        iSynthI ii;
                        for (ii = synthiInstances.begin(); ii != synthiInstances.end(); ++ii) {
                              if ((*ii)->iname() == dev->name()) {
//                                    midiInstruments.push_back(*ii);
                                    port->setInstrument(*ii);
                                    (*ii)->setDevice(dev);
                                    break;
                                    }
                              }
                        }
                  else {
                        midiThread->setMidiDevice(port, 0);
                        item->setPixmap(DEVCOL_REC, QPixmap());
                        }
                  item->setText(DEVCOL_STATE, port->state());
                  song->update();
                  }
                  break;

            case DEVCOL_INSTR:
                  {
                  if (instrPopup == 0)
                        instrPopup = new QPopupMenu(this);
                  instrPopup->clear();
                  for (iMidiInstrument i = midiInstruments.begin(); i
                     != midiInstruments.end(); ++i) {
                        instrPopup->insertItem((*i)->iname());
                        }
                  n = instrPopup->exec(ppt, 0);
                  if (n == -1)
                        break;
                  QString s = instrPopup->text(n);
                  item->setText(DEVCOL_INSTR, s);
                  for (iMidiInstrument i = midiInstruments.begin(); i
                     != midiInstruments.end(); ++i) {
                        if ((*i)->iname() == s) {
                              port->setInstrument(*i);
                              break;
                              }
                        }
                  song->update();
                  }
                  break;

            }
      }

//---------------------------------------------------------
//   MPWhatsThis::text
//---------------------------------------------------------

QString MPWhatsThis::text(const QPoint& pos)
      {
      int n = header->cellAt(pos.x());
      if (n == -1)
            return QString::null;
      switch (header->mapToLogical(n)) {
            case DEVCOL_NO:
                  return QHeader::tr("Port Number");
            case DEVCOL_GUI:
                  return QHeader::tr("enable gui for device");
            case DEVCOL_REC:
                  return QHeader::tr("enables recording from the device");
            case DEVCOL_NAME:
                  return QHeader::tr("Name of the midi device associated with"
                       " this port number");
            case DEVCOL_INSTR:
                  return QHeader::tr("Instrument connected to port");
            case DEVCOL_STATE:
                  return QHeader::tr("State: result of opening the device");
            default:
                  break;
            }
      return QString::null;
      }

//---------------------------------------------------------
//   MPConfig
//    Midi Port Config
//---------------------------------------------------------

MPConfig::MPConfig(QWidget* parent, char* name)
   : SynthConfigBase(parent, name)
      {
      popup      = 0;
      instrPopup = 0;
      rdDialog   = 0;

      mdevView->setSorting(-1);
      mdevView->setAllColumnsShowFocus(true);
      mdevView->setColumnWidthMode(3, QListView::Maximum);
      mdevView->setColumnAlignment(0, AlignCenter);
      mdevView->setFocusPolicy(NoFocus);

      mdevView->setColumnAlignment(DEVCOL_NO, AlignRight);
      mdevView->setColumnAlignment(DEVCOL_GUI, AlignCenter);
      mdevView->setColumnAlignment(DEVCOL_REC, AlignCenter);
      mdevView->header()->setResizeEnabled(false, DEVCOL_NO);
      mdevView->header()->setResizeEnabled(false, DEVCOL_REC);
      mdevView->header()->setResizeEnabled(false, DEVCOL_GUI);
      mdevView->setResizeMode(QListView::LastColumn);

      new MPWhatsThis(mdevView, mdevView->header());

      connect(mdevView, SIGNAL(pressed(QListViewItem*,const QPoint&,int)),
         this, SLOT(rbClicked(QListViewItem*,const QPoint&,int)));
      connect(song, SIGNAL(songChanged(int)), SLOT(songChanged(int)));
      connect(song, SIGNAL(songChanged(int)), SLOT(songChanged(int)));
      connect(addInstance, SIGNAL(clicked()), SLOT(addInstanceClicked()));
      connect(removeInstance, SIGNAL(clicked()), SLOT(removeInstanceClicked()));
      songChanged(0);
      }

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

void MPConfig::songChanged(int)
      {
      mdevView->clear();
      for (int i = MIDI_PORTS-1; i >= 0; --i) {
            MidiPort* port  = &midiPorts[i];
            MidiDevice* dev = port->device();
            QString s;
            s.setNum(i+1);
            QListViewItem* item = new QListViewItem(mdevView);
            item->setText(DEVCOL_NO, s);
            item->setText(DEVCOL_STATE, port->state());
            if (port->instrument())
                  item->setText(DEVCOL_INSTR, port->instrument()->iname());
            else
                  item->setText(DEVCOL_INSTR, tr("unknown"));
            if (dev) {
                  item->setText(DEVCOL_NAME, dev->name());
                  if (port->rwFlags() & 0x2)
                        item->setPixmap(DEVCOL_REC, *dotIcon);
                  }
            else {
                  item->setText(DEVCOL_NAME, tr("none"));
                  item->setPixmap(DEVCOL_GUI, *dothIcon);
                  }
            if (port->hasGui()) {
                  item->setPixmap(DEVCOL_GUI, port->guiVisible() ? *dotIcon : *dothIcon);
                  }
            else {
                  item->setPixmap(DEVCOL_GUI, QPixmap());
                  }
            item->setPixmap(DEVCOL_INSTR, *buttondownIcon);
            item->setPixmap(DEVCOL_NAME, *buttondownIcon);
            mdevView->insertItem(item);
            }
      synthList->clear();
      for (int i = 0; i < nsynthis; ++i) {
            if (synthis[i] == 0)    //DEBUG
                  continue;
            QString s = synthis[i]->baseName();
            QListViewItem* item = new QListViewItem(synthList);
            item->setText(0, s);
            s.setNum(synthis[i]->instances());
            item->setText(1, s);
            item->setText(2, QString(synthis[i]->label()));
            }
      instanceList->clear();
      for (iSynthI si = synthiInstances.begin(); si != synthiInstances.end(); ++si) {
            QListViewItem* iitem = new QListViewItem(instanceList);
            iitem->setText(0, (*si)->iname());
            QString port;
            port.sprintf("%d:%d",
               (*si)->mess()->alsaClient(),
               (*si)->mess()->alsaPort());
            iitem->setText(1, port);
            }
      }

//---------------------------------------------------------
//   addInstanceClicked
//---------------------------------------------------------

void MPConfig::addInstanceClicked()
      {
      QListViewItem* item = synthList->selectedItem();
      if (item == 0)
            return;
      SynthI* si = song->createSynthI(item->text(2));
      audio->msgAddSynthI(si);      // add to instance list & connect audio
      midiThread->msgAddSynthI(si); // add to midi poll

      QString name(si->iname());
      QListViewItem* iitem = new QListViewItem(instanceList);
      iitem->setText(0, name);
      QString port;
      port.sprintf("%d:%d", si->mess()->alsaClient(), si->mess()->alsaPort());

      iitem->setText(1, port);

      song->updateAudioMixer();

      QString inst;
      inst.setNum(si->synth()->instances());
      item->setText(1, inst);
      }

//---------------------------------------------------------
//   removeInstanceClicked
//---------------------------------------------------------

void MPConfig::removeInstanceClicked()
      {
      QListViewItem* item = instanceList->selectedItem();
      if (item == 0)
            return;
      iSynthI ii;
      for (ii = synthiInstances.begin(); ii != synthiInstances.end(); ++ii) {
            if ((*ii)->iname() == item->text(0))
                  break;
            }
      if (ii == synthiInstances.end()) {
            printf("synthesizerConfig::removeInstanceClicked(): synthi not found\n");
            return;
            }
      int port;
      for (port = 0; port < MIDI_PORTS; ++port) {
            if (midiPorts[port].instrument() == *ii)
                  break;
            }
      if (port != MIDI_PORTS) {
            // synthi is attached
            midiThread->setMidiDevice(&midiPorts[port], 0);
            midiPorts[port].setInstrument(genericMidiInstrument);
            }
      audio->msgRemoveSynthI(*ii);
      song->update();
      }

//---------------------------------------------------------
//   configMidiPorts
//---------------------------------------------------------

void MusE::configMidiPorts()
      {
      if (!midiPortConfig)
            midiPortConfig = new MPConfig(0, "midiPortConfig");
      midiPortConfig->show();
      }

