// rdsound_panel.cpp
//
// The sound panel widget for RDAirPlay
//
//   (C) Copyright 2002-2021 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public
//   License along with this program; if not, write to the Free Software
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include "rdapplication.h"
#include "rdbutton_dialog.h"
#include "rdcut.h"
#include "rdconf.h"
#include "rddb.h"
#include "rdedit_panel_name.h"
#include "rdescape_string.h"
#include "rdlog_line.h"
#include "rdmacro.h"
#include "rdsound_panel.h"

RDSoundPanel::RDSoundPanel(int station_panels,int user_panels,bool flash,
			   const QString &caption,const QString &label_template,
			   bool extended,RDEventPlayer *player,
			   RDCartDialog *cart_dialog,QWidget *parent)
  : RDWidget(parent)
{
  panel_playmode_box=NULL;
  panel_button_columns=PANEL_MAX_BUTTON_COLUMNS;
  panel_button_rows=PANEL_MAX_BUTTON_ROWS;
  panel_cue_port=-1;
  panel_caption=caption;
  if(extended) {
    panel_tablename="`EXTENDED_PANELS`";
    panel_name_tablename="`EXTENDED_PANEL_NAMES`";
  }
  else {
    panel_tablename="`PANELS`";
    panel_name_tablename="`PANEL_NAMES`";
  }
  panel_label_template=label_template;

  panel_type=RDAirPlayConf::StationPanel;
  panel_number=0;
  panel_setup_mode=false;
  panel_reset_mode=false;
  panel_parent=parent;
  panel_station_panels=station_panels;
  panel_user_panels=user_panels;
  panel_event_player=player;
  panel_action_mode=RDAirPlayConf::Normal;
  for(int i=0;i<RD_MAX_STREAMS;i++) {
    panel_active_buttons[i]=NULL;
  }
  panel_flash=flash;
  panel_flash_count=0;
  panel_flash_state=false;
  panel_config_panels=false;
  panel_pause_enabled=false;
  for(unsigned i=0;i<RD_SOUNDPANEL_MAX_OUTPUTS;i++) {
    panel_card[i]=-1;
    panel_port[i]=-1;
  }
  panel_cart_dialog=cart_dialog;
  for(int i=0;i<RD_SOUNDPANEL_MAX_OUTPUTS;i++) {
    panel_timescaling_supported[i]=false;
  }
  panel_onair_flag=false;

  //
  // Load Buttons
  //
  panel_mapper=new QSignalMapper(this);
  connect(panel_mapper,SIGNAL(mapped(int)),this,SLOT(buttonMapperData(int)));

  LoadPanels();

  //
  // Panel Selector
  //
  panel_selector_box=new RDComboBox(this);
  panel_selector_box->setFont(buttonFont());
  panel_selector_box->addIgnoredKey(Qt::Key_Space);
  connect(panel_selector_box,SIGNAL(activated(int)),
	  this,SLOT(panelActivatedData(int)));
  connect(panel_selector_box,SIGNAL(setupClicked()),
	  this,SLOT(panelSetupData()));

  if(panel_station_panels>0) {
    panel_number=0;
    panel_type=RDAirPlayConf::StationPanel;
    panel_buttons[0]->show();
  }
  else {
    if(panel_user_panels>0) {
      panel_number=0;
      panel_type=RDAirPlayConf::UserPanel;
      panel_buttons[0]->show();
    }
    else {
      setDisabled(true);
    }
  }
  
  //
  // Play Mode Box
  //
  panel_playmode_box=new QComboBox(this);
  panel_playmode_box->setFont(buttonFont());
  connect(panel_playmode_box,SIGNAL(activated(int)),
	  this,SLOT(playmodeActivatedData(int)));
  panel_playmode_box->insertItem(panel_playmode_box->count(),tr("Play All"));
  panel_playmode_box->insertItem(panel_playmode_box->count(),tr("Play Hook"));

  //
  // Reset Button
  //
  panel_reset_button=new RDPushButton(this);
  panel_reset_button->setFont(buttonFont());
  panel_reset_button->setText(tr("Reset"));
  panel_reset_button->setFlashColor(QColor(RDPANEL_RESET_FLASH_COLOR));
  panel_reset_button->setFocusPolicy(Qt::NoFocus);
  connect(panel_reset_button,SIGNAL(clicked()),this,SLOT(resetClickedData()));

  //
  // All Button
  //
  panel_all_button=new RDPushButton(this);
  panel_all_button->setFont(buttonFont());
  panel_all_button->setText(tr("All"));
  panel_all_button->setFlashColor(QColor(RDPANEL_RESET_FLASH_COLOR));
  panel_all_button->setFocusPolicy(Qt::NoFocus);
  panel_all_button->hide();
  connect(panel_all_button,SIGNAL(clicked()),this,SLOT(allClickedData()));

  //
  // Setup Button
  //
  panel_setup_button=new RDPushButton(this);
  panel_setup_button->setFont(buttonFont());
  panel_setup_button->setText(tr("Setup"));
  panel_setup_button->setFlashColor(QColor(RDPANEL_SETUP_FLASH_COLOR));
  panel_setup_button->setFocusPolicy(Qt::NoFocus);
  connect(panel_setup_button,SIGNAL(clicked()),this,SLOT(setupClickedData()));

  //
  // Button Dialog Box
  //
  panel_button_dialog=
    new RDButtonDialog(rda->station()->name(),panel_caption,
		       panel_label_template,panel_cart_dialog,panel_svcname,
		       this);

  //
  // CAE Setup
  //
  connect(rda->cae(),SIGNAL(timescalingSupported(int,bool)),
	  this,SLOT(timescalingSupportedData(int,bool)));

  //
  // RIPC Setup
  //
  connect(rda->ripc(),SIGNAL(onairFlagChanged(bool)),
	  this,SLOT(onairFlagChangedData(bool)));

  //
  // Load Panel Names
  //

  QString sql;
  sql=QString("select ")+
    "`PANEL_NO`,"+
    "`NAME` "+
    "from "+panel_name_tablename+" where "+
    QString::asprintf("(`TYPE`=%d)&&",RDAirPlayConf::StationPanel)+
    "(`OWNER`='"+RDEscapeString(rda->station()->name())+"') "+
    "order by `PANEL_NO`";
  RDSqlQuery *q=new RDSqlQuery(sql);
  q->first();
  for(int i=0;i<panel_station_panels;i++) {
    if(q->isValid()&&(q->value(0).toInt()==i)) {
      panel_selector_box->
	insertItem(QString::asprintf("[S:%d] ",i+1)+q->value(1).toString());
      q->next();
    }
    else {
      panel_selector_box->insertItem(QString::asprintf("[S:%d] Panel S:%d",
						       i+1,i+1));
    }
  }
  delete q;
  for(int i=0;i<panel_user_panels;i++) {
    panel_selector_box->insertItem(QString::asprintf("[U:%d] Panel U:%d",
						     i+1,i+1));
  }
  panel_selector_box->setFocus();

  panel_scan_timer=new QTimer(this);
  connect(panel_scan_timer,SIGNAL(timeout()),this,SLOT(scanPanelData()));
  panel_scan_timer->start(PANEL_SCAN_INTERVAL);
}


RDSoundPanel::~RDSoundPanel()
{
  for(unsigned i=0;i<panel_buttons.size();i++) {
    delete panel_buttons[i];
  }
}


QSize RDSoundPanel::sizeHint() const
{
  return QSize(panel_button_columns*(PANEL_BUTTON_SIZE_X+15),
	       panel_button_rows*(PANEL_BUTTON_SIZE_Y+15)+50);
}


QSizePolicy RDSoundPanel::sizePolicy() const
{
  return QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
}


int RDSoundPanel::card(int outnum) const
{
  return panel_card[outnum];
}


void RDSoundPanel::setCard(int outnum,int card)
{
  panel_card[outnum]=card;
  rda->cae()->requestTimescale(card);
}


int RDSoundPanel::port(int outnum) const
{
  return panel_port[outnum];
}


void RDSoundPanel::setPort(int outnum,int port)
{
  panel_port[outnum]=port;
}


QString RDSoundPanel::outputText(int outnum) const
{
  return panel_output_text[outnum];
}


void RDSoundPanel::setOutputText(int outnum,const QString &text)
{
  panel_output_text[outnum]=text;
}


void RDSoundPanel::setRmls(int outnum,const QString &start_rml,
			   const QString &stop_rml)
{
  panel_start_rml[outnum]=start_rml;
  panel_stop_rml[outnum]=stop_rml;
}


void RDSoundPanel::setSvcName(const QString &svcname)
{
  panel_svcname=svcname;
  panel_svcname.replace(" ","_");
}


void RDSoundPanel::play(RDAirPlayConf::PanelType type,int panel,
			int row, int col,RDLogLine::StartSource src,int mport,
                        bool pause_when_finished)
{
  PlayButton(type,panel,row,col,src,panel_playmode_box->currentIndex()==1,
	     mport,pause_when_finished);
}


bool RDSoundPanel::pause(RDAirPlayConf::PanelType type,int panel,
			 int row,int col,int mport)
{
  if(panel_pause_enabled) {
    PauseButton(type,panel,row,col,mport);
    return true;
  }
  return false;
}


void RDSoundPanel::stop(RDAirPlayConf::PanelType type,int panel,
			int row,int col,
                        int mport,bool pause_when_finished,int fade_out)
{
  StopButton(type,panel,row,col,mport,pause_when_finished,fade_out);
}


void RDSoundPanel::channelStop(int mport)
{
  RDPanelButton *button=NULL;
  RDPlayDeck *deck=NULL;
  for(unsigned i=0;i<RD_MAX_STREAMS;i++) {
    if((button=panel_active_buttons[i])!=NULL) {
      if(button->outputText().toInt()==(mport+1)) {
	if((deck=button->playDeck())!=NULL) {
	  if(deck->state()==RDPlayDeck::Playing) {
	    deck->stop();
	  }
	}
      }
    }
  }
}


void RDSoundPanel::setText(RDAirPlayConf::PanelType type,int panel,int row,
			   int col,const QString &str)
{
  RDPanelButton *button=
    panel_buttons[PanelOffset(type,panel)]->panelButton(row,col);
  button->setText(str);
  SaveButton(type,panel,row,col);
}


void RDSoundPanel::setColor(RDAirPlayConf::PanelType type,int panel,int row,
			    int col,const QColor &color)
{
  RDPanelButton *button=
    panel_buttons[PanelOffset(type,panel)]->panelButton(row,col);
  button->setDefaultColor(color);
  SaveButton(type,panel,row,col);
}


void RDSoundPanel::duckVolume(RDAirPlayConf::PanelType type,int panel,int row,
			      int col,int level,int fade,int mport)
{
  int edit_mport=mport;
  if (edit_mport==0) {
    edit_mport=-1;
    }
    for(int i=0;i<panel_button_columns;i++) {
	    for(int j=0;j<panel_button_rows;j++) {
      RDPlayDeck *deck=
        panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->playDeck();
      if((row==j || row==-1) && (col==i || col==-1)) {
	if(mport==-1) {
	  panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
	    setDuckVolume(level);
	}    
        if(deck!=NULL) {
          if(edit_mport==-1 || 
             edit_mport==panel_buttons[PanelOffset(type,panel)]->
	     panelButton(j,i)->outputText().toInt()) {
	    deck->duckVolume(level,fade);
          }
        }
      }
    }
  }
}


RDAirPlayConf::ActionMode RDSoundPanel::actionMode() const
{
  return panel_action_mode;
}


void RDSoundPanel::setActionMode(RDAirPlayConf::ActionMode mode)
{
  if(panel_setup_mode) {
    return;
  }
  switch(mode) {
  case RDAirPlayConf::CopyFrom:
    mode=RDAirPlayConf::CopyFrom;
    break;

  case RDAirPlayConf::CopyTo:
    mode=RDAirPlayConf::CopyTo;
    break;

  case RDAirPlayConf::AddTo:
    mode=RDAirPlayConf::AddTo;
    break;

  case RDAirPlayConf::DeleteFrom:
    mode=RDAirPlayConf::DeleteFrom;
    break;

  default:
    mode=RDAirPlayConf::Normal;
    break;
  }
  if(mode!=panel_action_mode) {
    panel_action_mode=mode;
    panel_setup_button->setEnabled(panel_action_mode==RDAirPlayConf::Normal);
    for(unsigned i=0;i<panel_buttons.size();i++) {
      if(i<(unsigned)panel_station_panels &&
          (!panel_config_panels) &&   
          (mode==RDAirPlayConf::AddTo || mode==RDAirPlayConf::CopyTo || mode==RDAirPlayConf::DeleteFrom)) {
        panel_buttons[i]->setActionMode(RDAirPlayConf::Normal);
        }
      else {
        panel_buttons[i]->setActionMode(panel_action_mode);
        }
      }
    }
}


bool RDSoundPanel::pauseEnabled() const
{
  return panel_pause_enabled;
}


void RDSoundPanel::setPauseEnabled(bool state)
{
  if(state) {
    panel_reset_button->show();
  }
  else {
    panel_reset_button->hide();
  }
  panel_pause_enabled=state;
}


int RDSoundPanel::currentNumber() const
{
   return panel_number;
}


RDAirPlayConf::PanelType RDSoundPanel::currentType() const
{
   return panel_type;
}


void RDSoundPanel::setButton(RDAirPlayConf::PanelType type,int panel,
			     int row,int col,unsigned cartnum,
			     const QString &title)
{
  QString str;

  RDPanelButton *button=
    panel_buttons[PanelOffset(type,panel)]->panelButton(row,col);
  if(button->playDeck()!=NULL) {
    return;
  }
  button->clear();
  if(cartnum>0) {
    button->setCart(cartnum);
    RDCart *cart=new RDCart(cartnum);
    if(cart->exists()) {
      if(title.isEmpty()) {
	button->
	  setText(RDLogLine::resolveWildcards(cartnum,panel_label_template));
      }
      else {
	button->setText(title);
      }
      button->setLength(false,cart->forcedLength());
      if(cart->averageHookLength()>0) {
	button->setLength(true,cart->averageHookLength());
      }
      else {
	button->setLength(true,cart->forcedLength());
      }
      button->setHookMode(panel_playmode_box->currentIndex()==1);
      switch(cart->type()) {
      case RDCart::Audio:
	if(button->length(button->hookMode())==0) {
	  button->setActiveLength(-1);
	}
	else {
	  button->setActiveLength(button->length(button->hookMode()));
	}
	break;

      case RDCart::Macro:
	button->setActiveLength(cart->forcedLength());
	break;

      case RDCart::All:
	break;
      }
    }
    else {
      if(title.isEmpty()) {
	str=QString(tr("Cart"));
	button->setText(str+QString::asprintf(" %06u",cartnum));
      }
      else {
	button->setText(title);
      }
    }
    delete cart;
  }
  SaveButton(type,panel,row,col);
}


void RDSoundPanel::acceptCartDrop(int row,int col,unsigned cartnum,
				  const QColor &color,const QString &title)
{
  setButton(panel_type,panel_number,row,col,cartnum,title);
  if(color.isValid()&&(color.name()!="#000000")) {
    setColor(panel_type,panel_number,row,col,color);
  }
}


void RDSoundPanel::changeUser()
{
  panel_config_panels=rda->user()->configPanels();
  LoadPanels();
  panel_buttons[PanelOffset(panel_type,panel_number)]->show();

  //
  // Remove Old Panel Names
  //
  int current_item=panel_selector_box->currentIndex();
  for(int i=0;i<panel_user_panels;i++) {
    panel_selector_box->removeItem(panel_station_panels);
  }

  //
  // Load New Panel Names
  //
  QString sql;
  sql=QString("select ")+
    "`PANEL_NO`,"+
    "`NAME` "+
    "from "+panel_name_tablename+" where "+
    QString::asprintf("(`TYPE`=%d)&&",RDAirPlayConf::UserPanel)+
    "(`OWNER`='"+RDEscapeString(rda->user()->name())+"') "+
    "order by `PANEL_NO`";
  RDSqlQuery *q=new RDSqlQuery(sql);
  q->first();
  for(int i=0;i<panel_user_panels;i++) {
    if(q->isValid()&&(q->value(0).toInt()==i)) {
      panel_selector_box->
	insertItem(QString::asprintf("[U:%d] ",i+1)+q->value(1).toString());
      q->next();
    }
    else {
      panel_selector_box->insertItem(QString::asprintf("[U:%d] Panel U:%d",
						       i+1,i+1));
    }
  }
  delete q;
  panel_selector_box->setCurrentIndex(current_item);

  UpdateButtonViewport();
}


void RDSoundPanel::tickClock()
{
  emit tick();
  if(panel_flash) {
    if(panel_flash_count++>1) {
      emit buttonFlash(panel_flash_state);
      panel_flash_state=!panel_flash_state;
      panel_flash_count=0;
    }
  }
}


void RDSoundPanel::panelUp()
{
  int index=panel_selector_box->currentIndex();

  if(index<(panel_selector_box->count()-1)) {
    panelActivatedData(index+1);
    panel_selector_box->setCurrentIndex(index+1);
  }
}


void RDSoundPanel::panelDown()
{
  int index=panel_selector_box->currentIndex();

  if(index>0) {
    panelActivatedData(index-1);
    panel_selector_box->setCurrentIndex(index-1);
  }
}


void RDSoundPanel::panelActivatedData(int n)
{
  panel_buttons[PanelOffset(panel_type,panel_number)]->hide();
  if(n<panel_station_panels) {
    panel_type=RDAirPlayConf::StationPanel;
    panel_number=n;
  }
  else {
    panel_type=RDAirPlayConf::UserPanel;
    panel_number=n-panel_station_panels;
  }
  panel_buttons[PanelOffset(panel_type,panel_number)]->show();

  UpdateButtonViewport();
}


void RDSoundPanel::resetClickedData()
{
  if(panel_reset_mode) {
    panel_reset_mode=false;
    panel_reset_button->setFlashingEnabled(false);
    panel_all_button->hide();
    panel_setup_button->show();
  }
  else {
    panel_reset_mode=true;
    panel_reset_button->setFlashingEnabled(true);
    panel_setup_button->hide();
    panel_all_button->show();
  }
}


void RDSoundPanel::allClickedData()
{
    StopButton(panel_type,panel_number,-1,-1);
}


void RDSoundPanel::playmodeActivatedData(int n)
{
  LoadPanel(panel_type,panel_number);
}


void RDSoundPanel::setupClickedData()
{
  if(panel_setup_mode) {
    panel_setup_mode=false;
    panel_setup_button->setFlashingEnabled(false);
    panel_reset_button->setEnabled(true);
    panel_playmode_box->setEnabled(true);
  }
  else {
    panel_setup_mode=true;
    panel_setup_button->setFlashingEnabled(true);
    panel_reset_button->setDisabled(true);
    panel_playmode_box->setDisabled(true);
  }
  if(rda->station()->enableDragdrop()&&(rda->station()->enforcePanelSetup())) {
    for(unsigned i=0;i<panel_buttons.size();i++) {
      if(panel_buttons[i]!=NULL) {
	panel_buttons[i]->setAcceptDrops(panel_setup_mode);
      }
    }
  }
  panel_selector_box->setSetupMode(panel_setup_mode);
}


void RDSoundPanel::buttonMapperData(int id)
{
  int row=id/panel_button_columns;
  int col=id-row*panel_button_columns;
  unsigned cartnum;

  switch(panel_action_mode) {
  case RDAirPlayConf::CopyFrom:
    if((cartnum=panel_buttons[PanelOffset(panel_type,panel_number)]->
	panelButton(row,col)->cart())>0) {
      emit selectClicked(cartnum,0,0);
    }
    break;
	
  case RDAirPlayConf::CopyTo:
    if(panel_buttons[PanelOffset(panel_type,panel_number)]->
       panelButton(row,col)->playDeck()==NULL
       && ((panel_type==RDAirPlayConf::UserPanel) || 
	   panel_config_panels)) { 
      emit selectClicked(0,row,col);
    }
    break;
	
  case RDAirPlayConf::AddTo:
    if(panel_buttons[PanelOffset(panel_type,panel_number)]->
       panelButton(row,col)->playDeck()==NULL
       && ((panel_type==RDAirPlayConf::UserPanel) || 
	   panel_config_panels)) { 
      emit selectClicked(0,row,col);
    }
    break;
	
  case RDAirPlayConf::DeleteFrom:
    if(panel_buttons[PanelOffset(panel_type,panel_number)]->
       panelButton(row,col)->playDeck()==NULL
       && ((panel_type==RDAirPlayConf::UserPanel) || 
	   panel_config_panels)) { 
      emit selectClicked(0,row,col);
    }
    break;
	
  default:
    if(panel_setup_mode) {
      if((panel_type==RDAirPlayConf::StationPanel)&&
	 (!panel_config_panels)) {
	ClearReset();
	return;
      }
      if(panel_button_dialog->
	 exec(panel_buttons[PanelOffset(panel_type,panel_number)]->
	      panelButton(row,col),panel_playmode_box->currentIndex()==1,
	      rda->user()->name(),rda->user()->password())) {
	SaveButton(panel_type,panel_number,row,col);
      }
    }
    else {
      RDPanelButton *button=
	panel_buttons[PanelOffset(panel_type,panel_number)]->
	panelButton(row,col);
      RDPlayDeck *deck=button->playDeck();
      if(panel_reset_mode) {
	StopButton(panel_type,panel_number,row,col);
      }
      else {
	if(deck==NULL) {
	  PlayButton(panel_type,panel_number,row,col,
		     RDLogLine::StartManual,
		     panel_playmode_box->currentIndex()==1);
	}
	else {
	  if(panel_pause_enabled) {
	    if(deck->state()!=RDPlayDeck::Paused) {
	      PauseButton(panel_type,panel_number,row,col);
	    }
	    else {
	      PlayButton(panel_type,panel_number,row,col,
			 RDLogLine::StartManual,button->hookMode());
	    }
	  }
	  else {
	    StopButton(panel_type,panel_number,row,col);
	  }
	}
      }
    }
  }
  ClearReset();
}


void RDSoundPanel::stateChangedData(int id,RDPlayDeck::State state)
{
  switch(state) {
  case RDPlayDeck::Playing:
    Playing(id);
    break;

  case RDPlayDeck::Stopped:
  case RDPlayDeck::Finished:
    Stopped(id);
    break;

  case RDPlayDeck::Paused:
    Paused(id);
    break;

  default:
    break;
  }
}


void RDSoundPanel::hookEndData(int id)
{
  RDPanelButton *button=panel_active_buttons[id];
  if(!button->hookMode()) {
    return;
  }
  RDPlayDeck *deck=button->playDeck();
  if(deck!=NULL) {
    switch(deck->state()) {
    case RDPlayDeck::Playing:
    case RDPlayDeck::Paused:
      StopButton(id);
      break;
	  
    default:
      break;
    }
  }
}


void RDSoundPanel::timescalingSupportedData(int card,bool state)
{
  for(unsigned i=0;i<RD_SOUNDPANEL_MAX_OUTPUTS;i++) {
    if(card==panel_card[i]) {
      panel_timescaling_supported[i]=state;
    }
  }
}


void RDSoundPanel::panelSetupData()
{
  if(rda->user()->configPanels()||(panel_type==RDAirPlayConf::UserPanel)) {
    QString sql;
    int cutpt=panel_selector_box->currentText().indexOf(" ");
    if(panel_selector_box->currentText().left(5)==tr("Panel")) {
      cutpt=-1;
    }
    QString tag=panel_selector_box->currentText().left(cutpt);
    
    QString panel_name=panel_selector_box->currentText().
      right(panel_selector_box->currentText().length()-cutpt-1);
    RDEditPanelName *edn=new RDEditPanelName(&panel_name);
    if(edn->exec()) {
      panel_selector_box->
	setItemText(panel_selector_box->currentIndex(),tag+" "+panel_name);
      panel_selector_box->
	setCurrentIndex(panel_selector_box->
			findText("["+PanelTag(panel_selector_box->
					      currentIndex())+"] "+panel_name));
      sql=QString("delete from ")+panel_name_tablename+" where "+
	QString::asprintf("(`TYPE`=%d)&&",panel_type)+
	"(`OWNER`='"+RDEscapeString(PanelOwner(panel_type))+"')&&"+
	QString::asprintf("(`PANEL_NO`=%d)",panel_number);
      RDSqlQuery::apply(sql);

      sql=QString("insert into ")+panel_name_tablename+" set "+
	QString::asprintf("`TYPE`=%d,",panel_type)+
	"`OWNER`='"+RDEscapeString(PanelOwner(panel_type))+"',"+
	QString::asprintf("`PANEL_NO`=%d,",panel_number)+
	"`NAME`='"+RDEscapeString(panel_name)+"'";
      RDSqlQuery::apply(sql);
    }
    delete edn;
  }
}


void RDSoundPanel::onairFlagChangedData(bool state)
{
  panel_onair_flag=state;
}


void RDSoundPanel::scanPanelData()
{
  if(panel_action_mode==RDAirPlayConf::Normal) {
    LoadPanel(panel_type,panel_number);
  }
}


void RDSoundPanel::resizeEvent(QResizeEvent *e)
{
  //  int w=size().width();
  int h=size().height();
  
  UpdateButtonViewport();
  
  panel_selector_box->setGeometry(0,h-50,2*PANEL_BUTTON_SIZE_X+10,50);
  panel_playmode_box->setGeometry(2*PANEL_BUTTON_SIZE_X+15,h-50,
				  PANEL_BUTTON_SIZE_X+10,50);
  panel_reset_button->setGeometry(2*PANEL_BUTTON_SIZE_X+140,h-50,
				  PANEL_BUTTON_SIZE_X,50);
  panel_all_button->setGeometry(2*PANEL_BUTTON_SIZE_X+235,h-50,
				  PANEL_BUTTON_SIZE_X,50);
  panel_setup_button->setGeometry(2*PANEL_BUTTON_SIZE_X+235,h-50,
				  PANEL_BUTTON_SIZE_X,50);
}


void RDSoundPanel::wheelEvent(QWheelEvent *e)
{
  if(e->orientation()==Qt::Vertical) {
    if(e->delta()>0) {
      panelDown();
    }
    if(e->delta()<0) {
      panelUp();
    }
  }
  e->accept();
}


void RDSoundPanel::UpdateButtonViewport()
{
  QRect viewport(0,0,size().width()-5,size().height()-60);
  RDButtonPanel *panel=panel_buttons[PanelOffset(panel_type,panel_number)];
  for(int i=0;i<panel_button_rows;i++) {
    for(int j=0;j<panel_button_columns;j++) {
      RDPanelButton *button=panel->panelButton(i,j);
      button->setVisible(viewport.contains(button->geometry()));
    }
  }
}


void RDSoundPanel::PlayButton(RDAirPlayConf::PanelType type,int panel,
		int row,int col,RDLogLine::StartSource src,bool hookmode,
		int mport,bool pause_when_finished)
{
  int edit_row=row;
  int edit_col=col;
  
  for(int i=0;i<panel_button_columns;i++) {
    for(int j=0;j<panel_button_rows;j++) {
      if(panel_buttons[PanelOffset(type,panel)]->
	 panelButton(j,i)->cart()>0 && 
         panel_buttons[PanelOffset(type,panel)]->
	 panelButton(j,i)->state()==false) {
        if(edit_col==-1 || col==i) {
	  edit_col=i;
	  if(edit_row==-1) {
	    edit_row=j;
	  } 
	}
      }
    }
  }
  if(edit_row==-1 || edit_col==-1) {
    return;
  }
  
  RDPanelButton *button=
    panel_buttons[PanelOffset(type,panel)]->panelButton(edit_row,edit_col);
  RDPlayDeck *deck=button->playDeck();
  if(deck!=NULL) {
    deck->play(deck->currentPosition());
    if(button->hookMode()) {
      button->setStartTime(QTime::currentTime().
			   addMSecs(rda->station()->timeOffset()).
			   addMSecs(-deck->currentPosition()+
				    deck->cut()->hookStartPoint()));
    }
    else {
      button->setStartTime(QTime::currentTime().
			   addMSecs(rda->station()->timeOffset()).
			   addMSecs(-deck->currentPosition()));
    }
    return;
  }

  int cartnum=0;

  if((cartnum=button->cart())==0) {
    LogLine(QString::asprintf("Tried to start empty button.  Row=%d, Col=%d",
			      edit_row,edit_col));
    return;
  }
  RDCart *cart=new RDCart(cartnum);
  if(!cart->exists()) {
    delete cart;
    LogLine(QString::asprintf("Tried to start non-existent cart: %u",cartnum));
    return;
  }
  button->setStartSource(src);
  if(panel_pause_enabled) {
    button->setPauseWhenFinished(pause_when_finished);
    }
  else {
    button->setPauseWhenFinished(false);
    }
  switch(cart->type()) {
  case RDCart::Audio:
    PlayAudio(button,cart,hookmode,mport);
    break;

  case RDCart::Macro:
    PlayMacro(button,cart);
    break;

  default:
    break;
  }
  delete cart;
}


bool RDSoundPanel::PlayAudio(RDPanelButton *button,RDCart *cart,bool hookmode,int mport)
{
  RDLogLine logline;

  bool timescale=false;
  int button_deck=GetFreeButtonDeck();
  if(button_deck<0) {
    LogLine(QString().
	    sprintf("No button deck available, playout aborted.  Cart=%u",
		    cart->number()));
    return false;
  }
  if(mport<=0 || mport>RD_SOUNDPANEL_MAX_OUTPUTS) {
    button->setOutput(GetFreeOutput());
    }
  else {
    button->setOutput(mport-1);
    }
  button->setOutputText(panel_output_text[button->output()]);
  button->setHookMode(hookmode);
  button->setPlayDeck(new RDPlayDeck(rda->cae(),button_deck,this));
  button->playDeck()->setCard(panel_card[button->output()]);
  button->playDeck()->setPort(panel_port[button->output()]);
  button->playDeck()->duckVolume(button->duckVolume(),0);
  if(panel_timescaling_supported[panel_card[button->output()]]&&
     cart->enforceLength()) {
    timescale=true;
  }
  logline.loadCart(cart->number(),RDLogLine::Play,0,timescale);
  if(!button->playDeck()->setCart(&logline,true)) {
    delete button->playDeck();
    button->setPlayDeck(NULL);
    LogLine(QString().
	    sprintf("No CAE stream available, playout aborted.  Cart=%u",
		    cart->number()));
    return false;
  }
  button->setCutName(logline.cutName());
  panel_active_buttons[button_deck]=button;

  //
  // Set Mappings
  //
  connect(button->playDeck(),SIGNAL(stateChanged(int,RDPlayDeck::State)),
	  this,SLOT(stateChangedData(int,RDPlayDeck::State)));
  connect(button->playDeck(),SIGNAL(hookEnd(int)),
	  this,SLOT(hookEndData(int)));
  connect(this,SIGNAL(tick()),button,SLOT(tickClock()));
  
  //
  // Calculate Start Parameters for Hook Playout
  //
  int start_pos=0;
  int segue_start=-1;
  int segue_end=-1;
  if(hookmode&&(logline.hookStartPoint()>=0)&&(logline.hookEndPoint()>=0)) {
    start_pos=logline.hookStartPoint()-logline.startPoint();
    segue_start=logline.hookEndPoint()-logline.startPoint();
    segue_end=logline.hookEndPoint()-logline.startPoint();
  }

  //
  // Start Playout
  //
  button->
    setStartTime(QTime::currentTime().addMSecs(rda->station()->timeOffset()));
  if(hookmode&&(button->playDeck()->cut()->hookStartPoint()>=0)) {
    button->setActiveLength(button->playDeck()->cut()->hookEndPoint()-
      button->playDeck()->cut()->hookStartPoint());
  }
  else {
    if(timescale) {
      button->setActiveLength(cart->forcedLength());
    }
    else {
      button->setActiveLength(button->playDeck()->cut()->length());
    }
  }
  button->playDeck()->play(start_pos,segue_start,segue_end);
  panel_event_player->
    exec(logline.resolveWildcards(panel_start_rml[button->output()]));
  emit channelStarted(button->output(),button->playDeck()->card(),
		      button->playDeck()->port());
  return true;
}


void RDSoundPanel::PlayMacro(RDPanelButton *button,RDCart *cart)
{
  RDMacro rml;
  rml.setRole(RDMacro::Cmd);
  rml.setAddress(rda->station()->address());
  rml.setEchoRequested(false);
  rml.setCommand(RDMacro::EX);
  rml.addArg(cart->number());
  rda->ripc()->sendRml(&rml);
  if(!panel_svcname.isEmpty()) {
    LogTrafficMacro(button);
  }
  if(button->pauseWhenFinished() && panel_pause_enabled) {
    button->setState(true);
    button->resetCounter();
    button->setColor(RDPANEL_PAUSED_BACKGROUND_COLOR);
  }
}


void RDSoundPanel::PauseButton(RDAirPlayConf::PanelType type,int panel,
			       int row,int col,int mport)
{
	for(int i=0;i<panel_button_columns;i++) {
		for(int j=0;j<panel_button_rows;j++) {
      RDPlayDeck *deck=
        panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->playDeck();
      if(deck!=NULL && (row==j || row==-1) && (col==i || col==-1)) {
        if(mport==-1 || 
           mport==panel_buttons[PanelOffset(type,panel)]->
	   panelButton(j,i)->outputText().toInt()) {
          deck->pause();

          panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
             setStartTime(QTime());
          }
       }
     }
   }
}


void RDSoundPanel::StopButton(RDAirPlayConf::PanelType type,int panel,
			    int row,int col,int mport,
                            bool pause_when_finished,int fade_out)
{
  int edit_mport=mport;
  if (edit_mport==0) {
    edit_mport=-1;
    }
    for(int i=0;i<panel_button_columns;i++) {
	    for(int j=0;j<panel_button_rows;j++) {
      RDPlayDeck *deck=
        panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->playDeck();
      if((row==j || row==-1) && (col==i || col==-1)) {
        if(deck!=NULL) {
          if(edit_mport==-1 || 
             edit_mport==panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
                     outputText().toInt()) {
            if(panel_pause_enabled) {
              panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
                    setPauseWhenFinished(pause_when_finished);
              }
            else {
              panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
                    setPauseWhenFinished(false);
              }
            switch(deck->state()) {
	    case RDPlayDeck::Playing:
	      deck->stop(fade_out,RD_FADE_DEPTH);
	      break;

	    case RDPlayDeck::Paused:
	      deck->clear();
	      break;

	    default:
	      deck->clear();
	      break;
            }
          }
        }
      else {
        if(!pause_when_finished && panel_pause_enabled) {
          panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->setState(false); 
          panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->
              setPauseWhenFinished(false); 
          panel_buttons[PanelOffset(type,panel)]->panelButton(j,i)->reset(); 
          }
        }
      }
    }
  }
  panel_reset_mode=false;
  panel_reset_button->setFlashingEnabled(false);
  panel_all_button->hide();
  panel_setup_button->show();
}


void RDSoundPanel::StopButton(int id)
{
  RDPlayDeck *deck=panel_active_buttons[id]->playDeck();
  StopButton(deck);
}


void RDSoundPanel::StopButton(RDPlayDeck *deck)
{
  if(deck!=NULL) {
    switch(deck->state()) {
    case RDPlayDeck::Playing:
      deck->stop();
      break;

    case RDPlayDeck::Paused:
      deck->clear();
      break;

    default:
      break;
    }
  }    
}


void RDSoundPanel::LoadPanels()
{
  for(unsigned i=0;i<panel_buttons.size();i++) {
    delete panel_buttons[i];
  }
  panel_buttons.clear();

  //
  // Load Buttons
  //
  for(int i=0;i<panel_station_panels;i++) {
    panel_buttons.push_back(new RDButtonPanel(panel_type,i,panel_button_columns,
					      panel_button_rows,
					      rda->station(),panel_flash,this));
    for(int j=0;j<panel_button_rows;j++) {
      for(int k=0;k<panel_button_columns;k++) {
	connect(panel_buttons.back()->panelButton(j,k),SIGNAL(clicked()),
		panel_mapper,SLOT(map()));
	panel_mapper->setMapping(panel_buttons.back()->panelButton(j,k),
			   j*panel_button_columns+k);
      }
    }
    LoadPanel(RDAirPlayConf::StationPanel,i);
    panel_buttons.back()->setAllowDrags(rda->station()->enableDragdrop());
  }
  for(int i=0;i<panel_user_panels;i++) {
    panel_buttons.push_back(new RDButtonPanel(panel_type,i,panel_button_columns,
					      panel_button_rows,
					      rda->station(),panel_flash,this));
    for(int j=0;j<panel_button_rows;j++) {
      for(int k=0;k<panel_button_columns;k++) {
	connect(panel_buttons.back()->panelButton(j,k),SIGNAL(clicked()),
		panel_mapper,SLOT(map()));
	panel_mapper->setMapping(panel_buttons.back()->panelButton(j,k),
			   j*panel_button_columns+k);
      }
    }
    panel_buttons.back()->setAllowDrags(rda->station()->enableDragdrop());
    LoadPanel(RDAirPlayConf::UserPanel,i);
  }
}


void RDSoundPanel::LoadPanel(RDAirPlayConf::PanelType type,int panel)
{
  QString owner;
  int offset=0;

  switch(type) {
  case RDAirPlayConf::UserPanel:
    owner=rda->user()->name();
    offset=panel_station_panels+panel;
    break;

  case RDAirPlayConf::StationPanel:
    owner=rda->station()->name();
    offset=panel;
    break;
  }

  QString sql=QString("select ")+
    panel_tablename+".`ROW_NO`,"+         // 00
    panel_tablename+".`COLUMN_NO`,"+      // 01
    panel_tablename+".`LABEL`,"+          // 02
    panel_tablename+".`CART`,"+           // 03
    panel_tablename+".`DEFAULT_COLOR`,"+  // 04
    "`CART`.`FORCED_LENGTH`,"+            // 05
    "`CART`.`AVERAGE_HOOK_LENGTH`,"+      // 06
    "`CART`.`TYPE` "+                     // 07
    "from "+panel_tablename+" "+          // 08
    "left join `CART` on "+panel_tablename+".`CART`=`CART`.`NUMBER` "+
    "where "+panel_tablename+QString::asprintf(".`TYPE`=%d && ",type)+
    panel_tablename+".`OWNER`='"+RDEscapeString(owner)+"' && "+
    panel_tablename+QString::asprintf(".`PANEL_NO`=%d ",panel)+
    "order by "+panel_tablename+".`COLUMN_NO`,"+panel_tablename+".`ROW_NO`";
  RDSqlQuery *q=new RDSqlQuery(sql);
  while(q->next()) {
    if(panel_buttons[offset]->panelButton(q->value(0).toInt(),
	      q->value(1).toInt())->playDeck()==NULL) {
      panel_buttons[offset]->
	panelButton(q->value(0).toInt(),q->value(1).toInt())->
	setText(q->value(2).toString());
      panel_buttons[offset]->
	panelButton(q->value(0).toInt(),q->value(1).toInt())->
	setCart(q->value(3).toInt());
      panel_buttons[offset]->
	panelButton(q->value(0).toInt(),q->value(1).toInt())->
	setLength(false,q->value(5).toInt());
      panel_buttons[offset]->
	panelButton(q->value(0).toInt(),q->value(1).toInt())->
	setLength(true,q->value(6).toInt());
      if((panel_playmode_box!=NULL)&&(panel_playmode_box->currentIndex()==1)&&
	 (q->value(6).toUInt()>0)) {
	panel_buttons[offset]->
	  panelButton(q->value(0).toInt(),q->value(1).toInt())->
	  setActiveLength(q->value(6).toInt());
      }
      else {
	if(q->value(7).toInt()==RDCart::Macro) {
	  panel_buttons[offset]->
	    panelButton(q->value(0).toInt(),q->value(1).toInt())->
	    setActiveLength(q->value(5).toInt());
	}
	else {
	  if(q->value(5).toInt()>0) {
	    panel_buttons[offset]->
	      panelButton(q->value(0).toInt(),q->value(1).toInt())->
	      setActiveLength(q->value(5).toInt());
	  }
	  else {
	    panel_buttons[offset]->
	      panelButton(q->value(0).toInt(),q->value(1).toInt())->
	      setActiveLength(-1);
	  }
	}
      }
      if(q->value(4).toString().isEmpty()) {
	panel_buttons[offset]->
	  panelButton(q->value(0).toInt(),q->value(1).toInt())->
	  setColor(palette().color(QPalette::Background));
	panel_buttons[offset]->
	  panelButton(q->value(0).toInt(),q->value(1).toInt())->
	  setDefaultColor(palette().color(QPalette::Background));
      }
      else {
	panel_buttons[offset]->
	  panelButton(q->value(0).toInt(),q->value(1).toInt())->
	  setColor(QColor(q->value(4).toString()));
	panel_buttons[offset]->
	  panelButton(q->value(0).toInt(),q->value(1).toInt())->
	  setDefaultColor(QColor(q->value(4).toString()));
      }
    }
  }
  delete q;

  UpdateButtonViewport();
}


void RDSoundPanel::SaveButton(RDAirPlayConf::PanelType type,
			    int panel,int row,int col)
{
  QString sql;
  QString sql1;
  RDSqlQuery *q;
  QString owner;
  int offset=0;

  switch(type) {
  case RDAirPlayConf::UserPanel:
    owner=rda->user()->name();
    offset=panel_station_panels+panel;
    break;

  case RDAirPlayConf::StationPanel:
    owner=rda->station()->name();
    offset=panel;
    break;
  }

  //
  // Determine if the button exists
  //
  sql=QString("select `LABEL` from ")+panel_tablename+" where "+
    QString::asprintf("`TYPE`=%d && ",type)+
    "`OWNER`='"+RDEscapeString(owner)+"' && "+
    QString::asprintf("`PANEL_NO`=%d && ",panel)+
    QString::asprintf("`ROW_NO`=%d && ",row)+
    QString::asprintf("`COLUMN_NO`=%d",col);
  q=new RDSqlQuery(sql);
  if(q->size()>0) {
    //
    // If so, update the record
    //
    delete q;
    sql1=QString("update ")+panel_tablename+" set "+
      "`LABEL`='"+RDEscapeString(panel_buttons[offset]->panelButton(row,col)->
				text())+"',"+
      QString::asprintf("`CART`=%d,",
			panel_buttons[PanelOffset(panel_type,panel_number)]->
			panelButton(row,col)->cart())+
      "`DEFAULT_COLOR`='"+panel_buttons[offset]->panelButton(row,col)->
      defaultColor().name()+"' where "+
      QString::asprintf("(`TYPE`=%d)&&",type)+
      "(`OWNER`='"+RDEscapeString(owner)+"')&&"+
      QString::asprintf("(`PANEL_NO`=%d)&&",panel)+
      QString::asprintf("(`ROW_NO`=%d)&&",row)+
      QString::asprintf("(`COLUMN_NO`=%d)",col);
    q=new RDSqlQuery(sql1);
    if(q->isActive()) {
      delete q;
      return;
    }
    delete q;
  }
  else {
    delete q;
    
    //
    // Otherwise, insert a new one
    //
    sql1=QString("insert into ")+panel_tablename+
      " (`TYPE`,"+
      "`OWNER`,"+
      "`PANEL_NO`,"+
      "`ROW_NO`,"+
      "`COLUMN_NO`,"+
      "`LABEL`,"+
      "`CART`,"+
      "`DEFAULT_COLOR`) "+
      QString::asprintf("values (%d,",type)+
      "'"+RDEscapeString(owner)+"',"+
      QString::asprintf("%d,%d,%d,",panel,row,col)+
      "'"+RDEscapeString(panel_buttons[offset]->
			  panelButton(row,col)->text())+"',"+
      QString::asprintf("%d,",
			panel_buttons[PanelOffset(panel_type,panel_number)]->
			panelButton(row,col)->cart())+
      "'"+RDEscapeString(panel_buttons[offset]->
			  panelButton(row,col)->defaultColor().name())+"')";
    q=new RDSqlQuery(sql1);
    delete q;
  }
}


int RDSoundPanel::PanelOffset(RDAirPlayConf::PanelType type,int panel)
{
  switch(type) {
  case RDAirPlayConf::StationPanel:
    return panel;
    break;

  case RDAirPlayConf::UserPanel:
    return panel_station_panels+panel;
    break;
  }
  return 0;
}


int RDSoundPanel::GetFreeButtonDeck()
{
  for(int i=0;i<RD_MAX_STREAMS;i++) {
    if(panel_active_buttons[i]==NULL) {
      return i;
    }
  }
  return -1;
}


int RDSoundPanel::GetFreeOutput()
{
  bool active=false;

  for(int i=0;i<RD_SOUNDPANEL_MAX_OUTPUTS;i++) {
    active=false;
    for(int j=0;j<RD_MAX_STREAMS;j++) {
      if((panel_active_buttons[j]!=NULL)&&
	 (panel_active_buttons[j]->output()==i)) {
	active=true;
      }
    }
    if(!active) {
      return i;
    }
  }
  return RD_SOUNDPANEL_MAX_OUTPUTS-1;
}


void RDSoundPanel::LogPlayEvent(unsigned cartnum,int cutnum)
{
  RDCut *cut=new RDCut(QString::asprintf("%06u_%03d",cartnum,cutnum));
  cut->logPlayout();
  delete cut;
}


void RDSoundPanel::LogTraffic(RDPanelButton *button)
{
  if(panel_svcname.isEmpty()) {
    return;
  }

  QString sql;
  RDSqlQuery *q;
  QDateTime now=QDateTime::currentDateTime();

  sql=QString("select ")+
    "`CART`.`TITLE`,"+         // 00
    "`CART`.`ARTIST`,"+        // 01
    "`CART`.`PUBLISHER`,"+     // 02
    "`CART`.`COMPOSER`,"+      // 03
    "`CART`.`USAGE_CODE`,"+    // 04
    "`CUTS`.`ISRC`,"+          // 05
    "`CART`.`ALBUM`,"+         // 06
    "`CART`.`LABEL`,"+         // 07
    "`CUTS`.`ISCI`,"+          // 08
    "`CART`.`CONDUCTOR`,"+     // 09
    "`CART`.`USER_DEFINED`,"+  // 10
    "`CART`.`SONG_ID`,"+       // 11
    "`CUTS`.`DESCRIPTION`,"+   // 12
    "`CUTS`.`OUTCUE` "+        // 13
    "from `CART` left join `CUTS` "+
    "on `CART`.`NUMBER`=`CUTS`.`CART_NUMBER` where "+
    "`CUTS`.`CUT_NAME`='"+RDEscapeString(button->cutName())+"'";
  q=new RDSqlQuery(sql);
  if(q->first()) {
    sql=QString("insert into `ELR_LINES` set ")+
      "`SERVICE_NAME`='"+RDEscapeString(panel_svcname)+"',"+
      QString::asprintf("`LENGTH`=%d,",button->startTime().msecsTo(now.time()))+
      QString::asprintf("`CART_NUMBER`=%u,",button->cart())+
      "`STATION_NAME`='"+RDEscapeString(rda->station()->name().toUtf8())+"',"+
      "`EVENT_DATETIME`="+
      RDCheckDateTime(QDateTime(now.date(),button->startTime()),
		      "yyyy-MM-dd hh:mm:ss")+","+
      QString::asprintf("`EVENT_TYPE`=%d,",RDAirPlayConf::TrafficStop)+
      QString::asprintf("`EVENT_SOURCE`=%d,",RDLogLine::SoundPanel)+
      QString::asprintf("`PLAY_SOURCE`=%d,",RDLogLine::SoundPanel)+
      QString::asprintf("`CUT_NUMBER`=%d,",button->cutName().right(3).toInt())+
      "`TITLE`='"+RDEscapeString(q->value(0).toString().toUtf8())+"',"+
      "`ARTIST`='"+RDEscapeString(q->value(1).toString().toUtf8())+"',"+
      "`PUBLISHER`='"+RDEscapeString(q->value(2).toString().toUtf8())+"',"+
      "`COMPOSER`='"+RDEscapeString(q->value(3).toString().toUtf8())+"',"+
      QString::asprintf("`USAGE_CODE`=%d,",q->value(4).toInt())+
      "`ISRC`='"+RDEscapeString(q->value(5).toString().toUtf8())+"',"+
      QString::asprintf("`START_SOURCE`=%d,",button->startSource())+
      "`ALBUM`='"+RDEscapeString(q->value(6).toString().toUtf8())+"',"+
      "`LABEL`='"+RDEscapeString(q->value(7).toString().toUtf8())+"',"+
      "`ISCI`='"+RDEscapeString(q->value(8).toString().toUtf8())+"',"+
      "`DESCRIPTION`='"+RDEscapeString(q->value(12).toString().toUtf8())+"',"+
      "`OUTCUE`='"+RDEscapeString(q->value(13).toString().toUtf8())+"',"+
      "`CONDUCTOR`='"+RDEscapeString(q->value(9).toString().toUtf8())+"',"+
      "`USER_DEFINED`='"+RDEscapeString(q->value(10).toString().toUtf8())+"',"+
      "`SONG_ID`='"+RDEscapeString(q->value(11).toString().toUtf8())+"',"+
      "`ONAIR_FLAG`='"+RDYesNo(panel_onair_flag)+"'";
    RDSqlQuery::apply(sql);
  }
  delete q;
}


void RDSoundPanel::LogTrafficMacro(RDPanelButton *button)
{
  QString sql;
  RDSqlQuery *q;
  QDateTime datetime(QDate::currentDate(),QTime::currentTime());

  sql=QString("select ")+
    "`TITLE`,"+          // 00
    "`ARTIST`,"+         // 01
    "`PUBLISHER`,"+      // 02
    "`COMPOSER`,"+       // 03
    "`USAGE_CODE`,"+     // 04
    "`FORCED_LENGTH`,"+  // 05
    "`ALBUM`,"+          // 06 
    "`LABEL` "+          // 07
    "from `CART` where "+
    QString::asprintf("`NUMBER`=%u",button->cart());
  q=new RDSqlQuery(sql);
  if(q->first()) {
    sql=QString("insert into `ELR_LINES` set ")+
      "`SERVICE_NAME`='"+RDEscapeString(panel_svcname)+"',"+
      QString::asprintf("`LENGTH`=%d,",q->value(5).toUInt())+
      QString::asprintf("`CART_NUMBER`=%u,",button->cart())+
      "`STATION_NAME`='"+RDEscapeString(rda->station()->name().toUtf8())+"',"+
      "`EVENT_DATETIME`='"+datetime.toString("yyyy-MM-dd hh:mm:ss")+"',"+
      QString::asprintf("`EVENT_TYPE`=%d,",RDAirPlayConf::TrafficMacro)+
      QString::asprintf("`EVENT_SOURCE`=%d,",RDLogLine::SoundPanel)+
      QString::asprintf("`PLAY_SOURCE`=%d,",RDLogLine::SoundPanel)+
      "`TITLE`='"+RDEscapeString(q->value(0).toString().toUtf8())+"',"+
      "`ARTIST`='"+RDEscapeString(q->value(1).toString().toUtf8())+"',"+
      "`PUBLISHER`='"+RDEscapeString(q->value(2).toString().toUtf8())+"',"+
      "`COMPOSER`='"+RDEscapeString(q->value(3).toString().toUtf8())+"',"+
      QString::asprintf("`USAGE_CODE`=%d,",q->value(4).toInt())+
      QString::asprintf("`START_SOURCE`=%d,",button->startSource())+
      "`ALBUM`='"+RDEscapeString(q->value(6).toString().toUtf8())+"',"+
      "`LABEL`='"+RDEscapeString(q->value(7).toString().toUtf8())+"',"+
      "`ONAIR_FLAG`='"+RDYesNo(panel_onair_flag)+"'";
    delete q;
    RDSqlQuery::apply(sql);
  }
}


void RDSoundPanel::LogLine(QString str)
{
  FILE *file;

  if(panel_logfile.isEmpty()) {
    return;
  }

  QDateTime current=QDateTime::currentDateTime();
  if((file=fopen(panel_logfile.toUtf8(),"a"))==NULL) {
    return;
  }
  fprintf(file,"%02d/%02d/%4d - %02d:%02d:%02d.%03d : RDSoundPanel: %s\n",
	  current.date().month(),
	  current.date().day(),
	  current.date().year(),
	  current.time().hour(),
	  current.time().minute(),
	  current.time().second(),
	  current.time().msec(),
	  str.toUtf8().constData());
  fclose(file);
}


void RDSoundPanel::Playing(int id)
{
  if(panel_active_buttons[id]==NULL) {
    LogLine(QString::asprintf("Invalid ID=%d in RDSoundPanel::Playing()",
			      id));
    return;
  }
  panel_active_buttons[id]->setState(true);
  panel_active_buttons[id]->setColor(RDPANEL_PLAY_BACKGROUND_COLOR);
  LogPlayEvent(panel_active_buttons[id]->playDeck()->cart()->number(),
	       panel_active_buttons[id]->playDeck()->cut()->cutNumber());
  LogLine(QString().
	  sprintf("Playout started: id=%d  cart=%u  cut=%d",
		  id,panel_active_buttons[id]->playDeck()->cart()->number(),
		  panel_active_buttons[id]->playDeck()->cut()->cutNumber()));
}


void RDSoundPanel::Paused(int id)
{
  if(panel_active_buttons[id]==NULL) {
    LogLine(QString::asprintf("Invalid ID=%d in RDSoundPanel::Paused()",
			      id));
    return;
  }
  panel_active_buttons[id]->setState(true);
  panel_active_buttons[id]->setColor(RDPANEL_PAUSED_BACKGROUND_COLOR);
  LogLine(QString().
	  sprintf("Playout paused: id=%d  cart=%u  cut=%d",
		  id,panel_active_buttons[id]->playDeck()->cart()->number(),
		  panel_active_buttons[id]->playDeck()->cut()->cutNumber()));
}


void RDSoundPanel::Stopped(int id)
{
  if(panel_active_buttons[id]==NULL) {
    LogLine(QString::asprintf("Invalid ID=%d in RDSoundPanel::Stopped()",
			      id));
    return;
  }
  LogTraffic(panel_active_buttons[id]);
  ClearChannel(id);
  if(panel_active_buttons[id]->pauseWhenFinished()) {
    panel_active_buttons[id]->setState(true);
    panel_active_buttons[id]->setColor(RDPANEL_PAUSED_BACKGROUND_COLOR);
    panel_active_buttons[id]->resetCounter();
    }
  else {
    panel_active_buttons[id]->setState(false);
    panel_active_buttons[id]->setHookMode(panel_playmode_box->currentIndex()==1);
  }
  disconnect(this,SIGNAL(tick()),panel_active_buttons[id],SLOT(tickClock()));
  panel_active_buttons[id]->playDeck()->disconnect();
  delete panel_active_buttons[id]->playDeck();
  panel_active_buttons[id]->setPlayDeck(NULL);
  if(!panel_active_buttons[id]->pauseWhenFinished()) {
    panel_active_buttons[id]->reset();
    }
  panel_active_buttons[id]->setDuckVolume(0);
  panel_active_buttons[id]=NULL;
  LogLine(QString::asprintf("Playout stopped: id=%d",id));
}


void RDSoundPanel::ClearChannel(int id)
{
  RDPlayDeck *playdeck=panel_active_buttons[id]->playDeck();
  if(rda->cae()->
     playPortActive(playdeck->card(),playdeck->port(),playdeck->stream())) {
    return;
  }
  panel_event_player->exec(panel_stop_rml[panel_active_buttons[id]->output()]);
  emit channelStopped(panel_active_buttons[id]->output(),
		      playdeck->card(),playdeck->port());
}


void RDSoundPanel::ClearReset()
{
  panel_reset_mode=false;
  panel_reset_button->setFlashingEnabled(false);
  panel_setup_button->setEnabled(true);
}


QString RDSoundPanel::PanelTag(int index)
{
  if(index<panel_station_panels) {
    return QString::asprintf("S:%d",index+1);
  }
  return QString::asprintf("U:%d",index-panel_station_panels+1);
}


QString RDSoundPanel::PanelOwner(RDAirPlayConf::PanelType type)
{
  switch(type) {
  case RDAirPlayConf::StationPanel:
    return rda->station()->name();

  case RDAirPlayConf::UserPanel:
    return rda->user()->name();
  }
  return QString();
}