// rdairplay.cpp
//
// The On Air Playout Utility for Rivendell.
//
//   (C) Copyright 2002-2010,2016-2018 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 <errno.h>
#include <math.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <syslog.h>

#include <qapplication.h>
#include <qwindowsstyle.h>
#include <qwidget.h>
#include <qpainter.h>
#include <q3sqlpropertymap.h>
#include <qmessagebox.h>
#include <qpushbutton.h>
#include <q3listview.h>
#include <qsignalmapper.h>
#include <qtimer.h>
#include <qtextcodec.h>
#include <qtranslator.h>
#include <qpainter.h>
//Added by qt3to4:
#include <QPixmap>
#include <QCloseEvent>
#include <QKeyEvent>
#include <Q3Frame>
#include <QPaintEvent>

#include <dbversion.h>
#include <rd.h>
#include <rdapplication.h>
#include <rdconf.h>
#include <rdaudio_port.h>
#include <rdcart_search_text.h>
#include <rdmixer.h>
#include <rdcheck_daemons.h>
#include <rdcart_dialog.h>
#include <rdmacro.h>
#include <rdcmd_switch.h>
#include <rdgetpasswd.h>
#include <rddatedecode.h>
#include <rddb.h>
#include <rdescape_string.h>
#include <rdhotkeys.h>
#include <rdhotkeylist.h>

#include "globals.h"
#include "rdairplay.h"
#include "wall_clock.h"

//
// Global Resources
//
RDAudioPort *rdaudioport_conf;
RDEventPlayer *rdevent_player;
RDCartDialog *rdcart_dialog;
MainWidget *prog_ptr;
RDHotKeyList *rdkeylist;
RDHotkeys *rdhotkeys;

//
// Icons
//
#include "../icons/rdairplay-128x128.xpm"
#include "../icons/rdairplay-22x22.xpm"

//
// Prototypes
//
void SigHandler(int signo);

void MainWidget::logLine(RDConfig::LogPriority prio,const QString &s)
{
  LogLine(prio,s);
}

MainWidget::MainWidget(QWidget *parent)
  :QWidget(parent)
{
  prog_ptr=this;
  QString str;
  int cards[3];
  int ports[3];
  QString start_rmls[3];
  QString stop_rmls[3];
  QPixmap *mainmap=NULL;
  QPixmap *pm=NULL;
  QPainter *pd=NULL;
  QString err_msg;

  air_refresh_pixmap=NULL;
  air_panel=NULL;

  //
  // Ensure Single Instance
  //
  air_lock=new RDInstanceLock(RDHomeDir()+"/.rdairplaylock");
  if(!air_lock->lock()) {
    QMessageBox::information(this,tr("RDAirPlay"),
			     tr("Multiple instances not allowed!"));
    exit(1);
  }

  //
  // Splash Screen
  //
  air_splash_screen=new QSplashScreen(QPixmap(rdairplay_128x128_xpm));
  air_splash_screen->hide();
  QTimer *timer=new QTimer(this);
  connect(timer,SIGNAL(timeout()),this,SLOT(clearSplashData()));
  timer->start(AIR_PLAY_SPLASH_TIME,true);

  //
  // Get the Startup Date/Time
  //
  air_startup_datetime=QDateTime(QDate::currentDate(),QTime::currentTime());

  //
  // Ensure that system daemons are running
  //
  RDInitializeDaemons();

  //
  // Open the Database
  //
  rda=new RDApplication("RDAirPlay","rdairplay",RDAIRPLAY_USAGE,this);
  if(!rda->open(&err_msg)) {
    QMessageBox::critical(this,"RDAirPlay - "+tr("Error"),err_msg);
    exit(1);
  }
  //  connect(RDDbStatus(),SIGNAL(logText(RDConfig::LogPriority,const QString &)),
  //	  this,SLOT(logLine(RDConfig::LogPriority,const QString &))); 


  //
  // Read Command Options
  //
  QString lineno;
  for(unsigned i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_start_line[i]=0;
    air_start_start[i]=false;
    for(unsigned j=0;j<rda->cmdSwitch()->keys();j++) {
      if(rda->cmdSwitch()->key(j)==QString().sprintf("--log%u",i+1)) {
	air_start_logname[i]=rda->cmdSwitch()->value(j);
	for(int k=0;k<rda->cmdSwitch()->value(j).length();k++) {
	  if(rda->cmdSwitch()->value(j).at(k)==QChar(':')) {
	    air_start_logname[i]=
	      RDDateTimeDecode(rda->cmdSwitch()->value(j).left(k),
			       air_startup_datetime,
			       rda->station(),rda->config());
	    lineno=rda->cmdSwitch()->value(j).right(rda->cmdSwitch()->value(j).
						    length()-(k+1));
	    if(lineno.right(1)=="+") {
	      air_start_start[i]=true;
	      lineno=lineno.left(lineno.length()-1);
	    }
	    air_start_line[i]=lineno.toInt();
	  }
	}
	rda->cmdSwitch()->setProcessed(j,true);
      }
    }
  }
  for(unsigned i=0;i<rda->cmdSwitch()->keys();i++) {
    if(!rda->cmdSwitch()->processed(i)) {
      QMessageBox::critical(this,"RDAirPlay - "+tr("Error"),
			    tr("Unknown command option")+": "+
			    rda->cmdSwitch()->key(i));
      exit(2);
    }
  }

  //
  // Fix the Window Size
  //
#ifndef RESIZABLE
  setMinimumWidth(sizeHint().width());
  setMaximumWidth(sizeHint().width());
  setMinimumHeight(sizeHint().height());
  setMaximumHeight(sizeHint().height());
#endif  // RESIZABLE

  //
  // Initialize the Random Number Generator
  //
  srandom(QTime::currentTime().msec());

  //
  // Generate Fonts
  //
  QFont default_font("Helvetica",12,QFont::Normal);
  default_font.setPixelSize(12);
  qApp->setFont(default_font);
  QFont button_font=QFont("Helvetica",16,QFont::Bold);
  button_font.setPixelSize(16);
  for(unsigned i=0;i<AIR_MESSAGE_FONT_QUANTITY;i++) {
    air_message_fonts[i]=QFont("helvetica",12+2*i,QFont::Normal);
    air_message_fonts[i].setPixelSize(12+2*i);
    air_message_metrics[i]=new QFontMetrics(air_message_fonts[i]);
  }

  //
  // Create And Set Icon
  //
  air_rivendell_map=new QPixmap(rdairplay_22x22_xpm);
  setIcon(*air_rivendell_map);

  air_start_next=false;
  air_next_button=0;
  air_action_mode=StartButton::Play;

  logfile=rda->config()->airplayLogname();

  str=QString("RDAirPlay")+" v"+VERSION+" - "+tr("Host:");
  setCaption(str+" "+rda->config()->stationName());

  //
  // Master Clock Timer
  //
  air_master_timer=new QTimer(this);
  connect(air_master_timer,SIGNAL(timeout()),this,SLOT(masterTimerData()));
  air_master_timer->start(MASTER_TIMER_INTERVAL);

  //
  // Allocate Global Resources
  //
  rdairplay_previous_exit_code=rda->airplayConf()->exitCode();
  rda->airplayConf()->setExitCode(RDAirPlayConf::ExitDirty);
  air_clear_filter=rda->airplayConf()->clearFilter();
  air_bar_action=rda->airplayConf()->barAction();
  air_op_mode_style=rda->airplayConf()->opModeStyle();
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_op_mode[i]=RDAirPlayConf::Previous;
  }
  air_editor_cmd=rda->station()->editorPath();
  mainmap=new QPixmap(rda->airplayConf()->skinPath());
  if(mainmap->isNull()||(mainmap->width()<1024)||(mainmap->height()<738)) {
    delete mainmap;
    mainmap=NULL;
  }
  else {
    setErasePixmap(*mainmap);
  }

  //
  // Load GPIO Channel Configuration
  //
  for(unsigned i=0;i<RDAirPlayConf::LastChannel;i++) {
    RDAirPlayConf::Channel chan=(RDAirPlayConf::Channel)i;
    air_start_gpi_matrices[i]=rda->airplayConf()->startGpiMatrix(chan);
    air_start_gpi_lines[i]=rda->airplayConf()->startGpiLine(chan)-1;
    air_start_gpo_matrices[i]=rda->airplayConf()->startGpoMatrix(chan);
    air_start_gpo_lines[i]=rda->airplayConf()->startGpoLine(chan)-1;
    air_stop_gpi_matrices[i]=rda->airplayConf()->stopGpiMatrix(chan);
    air_stop_gpi_lines[i]=rda->airplayConf()->stopGpiLine(chan)-1;
    air_stop_gpo_matrices[i]=rda->airplayConf()->stopGpoMatrix(chan);
    air_stop_gpo_lines[i]=rda->airplayConf()->stopGpoLine(chan)-1;
    air_channel_gpio_types[i]=rda->airplayConf()->gpioType(chan);
    air_audio_channels[i]=
      AudioChannel(rda->airplayConf()->card(chan),rda->airplayConf()->port(chan));

    if((rda->airplayConf()->card(chan)>=0)&&(rda->airplayConf()->port(chan)>=0)) {
      int achan=
	AudioChannel(rda->airplayConf()->card(chan),rda->airplayConf()->port(chan));
      if(air_channel_timers[0][achan]==NULL) {
	air_channel_timers[0][achan]=new QTimer(this);
	air_channel_timers[1][achan]=new QTimer(this);
      }
    }
  }

  //
  // Fixup Main Log GPIO Channel Assignments
  //
  if(((rda->airplayConf()->card(RDAirPlayConf::MainLog1Channel)==
      rda->airplayConf()->card(RDAirPlayConf::MainLog2Channel))&&
     (rda->airplayConf()->port(RDAirPlayConf::MainLog1Channel)==
      rda->airplayConf()->port(RDAirPlayConf::MainLog2Channel)))||
     rda->airplayConf()->card(RDAirPlayConf::MainLog2Channel)<0) {
    air_start_gpi_matrices[RDAirPlayConf::MainLog2Channel]=-1;
    air_start_gpo_matrices[RDAirPlayConf::MainLog2Channel]=-1;
    air_stop_gpi_matrices[RDAirPlayConf::MainLog2Channel]=
      air_stop_gpi_matrices[RDAirPlayConf::MainLog1Channel];
    air_stop_gpo_matrices[RDAirPlayConf::MainLog2Channel]=-1;
  }

  //
  // CAE Connection
  //
  connect(rda->cae(),SIGNAL(isConnected(bool)),
	  this,SLOT(caeConnectedData(bool)));
  rda->cae()->connectHost();

  //
  // Set Audio Assignments
  //
  air_segue_length=rda->airplayConf()->segueLength()+1;
  RDSetMixerPorts(rda->config()->stationName(),rda->cae());

  //
  // RIPC Connection
  //
  connect(rda->ripc(),SIGNAL(connected(bool)),this,SLOT(ripcConnectedData(bool)));
  connect(rda,SIGNAL(userChanged()),this,SLOT(userData()));
  connect(rda->ripc(),SIGNAL(rmlReceived(RDMacro *)),
	  this,SLOT(rmlReceivedData(RDMacro *)));
  connect(rda->ripc(),SIGNAL(gpiStateChanged(int,int,bool)),
	  this,SLOT(gpiStateChangedData(int,int,bool)));

  //
  // Macro Player
  //
  rdevent_player=new RDEventPlayer(rda->ripc(),this);

  //
  // User
  //
  //  rduser=NULL;

  //
  // UDP Transmission Socket
  //
  air_nownext_socket=new Q3SocketDevice(Q3SocketDevice::Datagram);

  //
  // Log Machines
  //
  QSignalMapper *reload_mapper=new QSignalMapper(this);
  connect(reload_mapper,SIGNAL(mapped(int)),this,SLOT(logReloadedData(int)));
  QSignalMapper *rename_mapper=new QSignalMapper(this);
  connect(rename_mapper,SIGNAL(mapped(int)),this,SLOT(logRenamedData(int)));
  QString default_svcname=rda->airplayConf()->defaultSvc();
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_log[i]=new RDLogPlay(i,rdevent_player,air_nownext_socket,"",
			     &air_plugin_hosts);
    air_log[i]->setDefaultServiceName(default_svcname);
    air_log[i]->setNowCart(rda->airplayConf()->logNowCart(i));
    air_log[i]->setNextCart(rda->airplayConf()->logNextCart(i));
    reload_mapper->setMapping(air_log[i],i);
    connect(air_log[i],SIGNAL(reloaded()),reload_mapper,SLOT(map()));
    rename_mapper->setMapping(air_log[i],i);
    connect(air_log[i],SIGNAL(renamed()),rename_mapper,SLOT(map()));
    connect(air_log[i],SIGNAL(refreshStatusChanged(bool)),
	    this,SLOT(refreshStatusChangedData(bool)));
    connect(air_log[i],SIGNAL(channelStarted(int,int,int,int)),
	    this,SLOT(logChannelStartedData(int,int,int,int)));
    connect(air_log[i],SIGNAL(channelStopped(int,int,int,int)),
	    this,SLOT(logChannelStoppedData(int,int,int,int)));
  }
  connect(air_log[0],SIGNAL(transportChanged()),
	  this,SLOT(transportChangedData()));

  //
  // Audio Channel Assignments
  //
  air_cue_card=rda->airplayConf()->card(RDAirPlayConf::CueChannel);
  air_cue_port=rda->airplayConf()->port(RDAirPlayConf::CueChannel);
  for(int i=0;i<3;i++) {
    air_meter_card[i]=rda->airplayConf()->card((RDAirPlayConf::Channel)i);
    air_meter_port[i]=rda->airplayConf()->port((RDAirPlayConf::Channel)i);
    cards[i]=rda->airplayConf()->card((RDAirPlayConf::Channel)i);
    ports[i]=rda->airplayConf()->port((RDAirPlayConf::Channel)i);
    start_rmls[i]=rda->airplayConf()->startRml((RDAirPlayConf::Channel)i);
    stop_rmls[i]=rda->airplayConf()->stopRml((RDAirPlayConf::Channel)i);
  }
  if((air_meter_card[1]<0)||(air_meter_port[1]<0)) {  // Fixup disabled main log port 2 playout
    air_meter_card[1]=air_meter_card[0];
    air_meter_port[1]=air_meter_port[0];
    cards[1]=cards[0];
    ports[1]=ports[0];
  }
  air_log[0]->setChannels(cards,ports,start_rmls,stop_rmls);

  for(int i=0;i<2;i++) {
    cards[i]=rda->airplayConf()->card(RDAirPlayConf::AuxLog1Channel);
    ports[i]=rda->airplayConf()->port(RDAirPlayConf::AuxLog1Channel);
    start_rmls[i]=rda->airplayConf()->startRml(RDAirPlayConf::AuxLog1Channel);
    stop_rmls[i]=rda->airplayConf()->stopRml(RDAirPlayConf::AuxLog1Channel);
  }
  air_log[1]->setChannels(cards,ports,start_rmls,stop_rmls);

  for(int i=0;i<2;i++) {
    cards[i]=rda->airplayConf()->card(RDAirPlayConf::AuxLog2Channel);
    ports[i]=rda->airplayConf()->port(RDAirPlayConf::AuxLog2Channel);
    start_rmls[i]=rda->airplayConf()->startRml(RDAirPlayConf::AuxLog2Channel);
    stop_rmls[i]=rda->airplayConf()->stopRml(RDAirPlayConf::AuxLog2Channel);
  }
  air_log[2]->setChannels(cards,ports,start_rmls,stop_rmls);

  //
  // Cart Picker
  //
  rdcart_dialog=
    new RDCartDialog(&air_add_filter,&air_add_group,&air_add_schedcode,this);

  //
  // Wall Clock
  //
  WallClock *clock=new WallClock(this);
  clock->
    setGeometry(10,5,clock->sizeHint().width(),clock->sizeHint().height());
  clock->setCheckSyncEnabled(rda->airplayConf()->checkTimesync());
  connect(air_master_timer,SIGNAL(timeout()),clock,SLOT(tickClock()));
 clock->setFocusPolicy(Qt::NoFocus);
  connect(clock,SIGNAL(timeModeChanged(RDAirPlayConf::TimeMode)),
	  this,SLOT(timeModeData(RDAirPlayConf::TimeMode)));

  //
  // Post Counter
  //
  air_post_counter=new PostCounter(this);
  air_post_counter->setGeometry(220,5,air_post_counter->sizeHint().width(),
				air_post_counter->sizeHint().height());
  air_post_counter->setPostPoint(QTime(),0,false,false);
  air_post_counter->setFocusPolicy(Qt::NoFocus);
  connect(air_master_timer,SIGNAL(timeout()),
	  air_post_counter,SLOT(tickCounter()));
  connect(air_log[0],SIGNAL(postPointChanged(QTime,int,bool,bool)),
	  air_post_counter,SLOT(setPostPoint(QTime,int,bool,bool)));

  //
  // Pie Counter
  //
  air_pie_counter=new PieCounter(rda->airplayConf()->pieCountLength(),this);
  air_pie_counter->setGeometry(426,5,air_pie_counter->sizeHint().width(),
				air_pie_counter->sizeHint().height());
  air_pie_counter->setCountLength(rda->airplayConf()->pieCountLength());
  air_pie_end=rda->airplayConf()->pieEndPoint();
  air_pie_counter->setOpMode(air_op_mode[0]);
  air_pie_counter->setFocusPolicy(Qt::NoFocus);
  if(mainmap!=NULL) {
    pm=new QPixmap(1024,738);
    pd=new QPainter(pm);
    pd->drawPixmap(-426,-5,*mainmap);
    pd->end();
    air_pie_counter->setErasePixmap(*pm);
    delete pd;
    delete pm;
  }
  connect(air_master_timer,SIGNAL(timeout()),
	  air_pie_counter,SLOT(tickCounter()));
  connect(rda->ripc(),SIGNAL(onairFlagChanged(bool)),
	  air_pie_counter,SLOT(setOnairFlag(bool)));

  //
  // Audio Meter
  //
  air_stereo_meter=new RDStereoMeter(this);
  air_stereo_meter->setGeometry(50,70,air_stereo_meter->sizeHint().width(),
				air_stereo_meter->sizeHint().height());
  air_stereo_meter->setMode(RDSegMeter::Peak);
 air_stereo_meter->setFocusPolicy(Qt::NoFocus);
  if(rda->config()->useStreamMeters()) {
    air_stereo_meter->hide();
  }

  //
  // Message Label
  //
  air_message_label=new RDLabel(this);
  air_message_label->setGeometry(sizeHint().width()-425,70,
		MESSAGE_WIDGET_WIDTH,air_stereo_meter->sizeHint().height());
  air_message_label->setWordWrapEnabled(true);
  air_message_label->setLineWidth(1);
  air_message_label->setMidLineWidth(1);
  air_message_label->setFrameStyle(Q3Frame::Box|Q3Frame::Raised);
  air_message_label->setAlignment(Qt::AlignCenter);
 air_message_label->setFocusPolicy(Qt::NoFocus);

  //
  // Stop Counter
  //
  air_stop_counter=new StopCounter(this);
  air_stop_counter->setGeometry(600,5,air_stop_counter->sizeHint().width(),
				air_stop_counter->sizeHint().height());
  air_stop_counter->setTime(QTime(0,0,0));
 air_stop_counter->setFocusPolicy(Qt::NoFocus);
  connect(air_master_timer,SIGNAL(timeout()),
	  air_stop_counter,SLOT(tickCounter()));
  connect(air_log[0],SIGNAL(nextStopChanged(QTime)),
	  air_stop_counter,SLOT(setTime(QTime)));

  //
  // Mode Display/Button
  //
  air_mode_display=new ModeDisplay(this);
  air_mode_display->
    setGeometry(sizeHint().width()-air_mode_display->sizeHint().width()-10,
		5,air_mode_display->sizeHint().width(),
		air_mode_display->sizeHint().height());
  air_mode_display->setFocusPolicy(Qt::NoFocus);
  air_mode_display->setOpModeStyle(air_op_mode_style);
  connect(air_mode_display,SIGNAL(clicked()),this,SLOT(modeButtonData()));

  //
  // Create Palettes
  //
  auto_color=
    QPalette(QColor(BUTTON_MODE_AUTO_COLOR),backgroundColor());
  manual_color=
    QPalette(QColor(BUTTON_MODE_MANUAL_COLOR),backgroundColor());
  active_color=palette();
  active_color.setColor(QPalette::Active,QColorGroup::ButtonText,
			BUTTON_LOG_ACTIVE_TEXT_COLOR);
  active_color.setColor(QPalette::Active,QColorGroup::Button,
			BUTTON_LOG_ACTIVE_BACKGROUND_COLOR);
  active_color.setColor(QPalette::Active,QColorGroup::Background,
			backgroundColor());
  active_color.setColor(QPalette::Inactive,QColorGroup::ButtonText,
			BUTTON_LOG_ACTIVE_TEXT_COLOR);
  active_color.setColor(QPalette::Inactive,QColorGroup::Button,
			BUTTON_LOG_ACTIVE_BACKGROUND_COLOR);
  active_color.setColor(QPalette::Inactive,QColorGroup::Background,
			backgroundColor());

  //
  // Add Button
  //
  air_add_button=new RDPushButton(this);
  air_add_button->setGeometry(10,sizeHint().height()-65,80,60);
  air_add_button->setFont(button_font);
  air_add_button->setText(tr("ADD"));
 air_add_button->setFocusPolicy(Qt::NoFocus);
  connect(air_add_button,SIGNAL(clicked()),this,SLOT(addButtonData()));

  //
  // Delete Button
  //
  air_delete_button=new RDPushButton(this);
  air_delete_button->setGeometry(100,sizeHint().height()-65,80,60);
  air_delete_button->setFont(button_font);
  air_delete_button->setText(tr("DEL"));
  air_delete_button->setFlashColor(AIR_FLASH_COLOR);
 air_delete_button->setFocusPolicy(Qt::NoFocus);
  connect(air_delete_button,SIGNAL(clicked()),this,SLOT(deleteButtonData()));

  //
  // Move Button
  //
  air_move_button=new RDPushButton(this);
  air_move_button->setGeometry(190,sizeHint().height()-65,80,60);
  air_move_button->setFont(button_font);
  air_move_button->setText(tr("MOVE"));
  air_move_button->setFlashColor(AIR_FLASH_COLOR);
 air_move_button->setFocusPolicy(Qt::NoFocus);
  connect(air_move_button,SIGNAL(clicked()),this,SLOT(moveButtonData()));

  //
  // Copy Button
  //
  air_copy_button=new RDPushButton(this);
  air_copy_button->setGeometry(280,sizeHint().height()-65,80,60);
  air_copy_button->setFont(button_font);
  air_copy_button->setText(tr("COPY"));
  air_copy_button->setFlashColor(AIR_FLASH_COLOR);
  air_copy_button->setFocusPolicy(Qt::NoFocus);
  connect(air_copy_button,SIGNAL(clicked()),this,SLOT(copyButtonData()));

  //
  // Refresh Indicator
  //
  air_refresh_label=new RDLabel(this);
  air_refresh_label->setGeometry(390,sizeHint().height()-65,120,60);
  air_refresh_label->setFont(button_font);
  QPalette p=palette();
  p.setColor(QColorGroup::Foreground,Qt::red);
  air_refresh_label->setPalette(p);
  air_refresh_label->setAlignment(Qt::AlignCenter);
  if(mainmap!=NULL) {
    air_refresh_pixmap=new QPixmap(1024,738);
    pd=new QPainter(air_refresh_pixmap);
    pd->drawPixmap(-390,-sizeHint().height()+65,*mainmap);
    pd->end();
    air_refresh_label->setErasePixmap(*air_refresh_pixmap);
    delete pd;
  }

  //
  // Meter Timer
  //
  timer=new QTimer(this);
  connect(timer,SIGNAL(timeout()),this,SLOT(meterData()));
  timer->start(RD_METER_UPDATE_INTERVAL);

  //
  // Sound Panel Array
  //
  if (rda->airplayConf()->panels(RDAirPlayConf::StationPanel) || 
      rda->airplayConf()->panels(RDAirPlayConf::UserPanel)){
    int card=-1;
    air_panel=
      new RDSoundPanel(AIR_PANEL_BUTTON_COLUMNS,AIR_PANEL_BUTTON_ROWS,
		       rda->airplayConf()->panels(RDAirPlayConf::StationPanel),
		       rda->airplayConf()->panels(RDAirPlayConf::UserPanel),
		       rda->airplayConf()->flashPanel(),
		       rda->airplayConf()->buttonLabelTemplate(),false,
		       rdevent_player,rdcart_dialog,this);
    air_panel->setLogfile(rda->config()->airplayLogname());
    air_panel->setGeometry(510,140,air_panel->sizeHint().width(),
			 air_panel->sizeHint().height());
    if(mainmap!=NULL) {
      pm=new QPixmap(1024,738);
      pd=new QPainter(pm);
      pd->drawPixmap(-510,-140,*mainmap);
      pd->end();
      air_panel->setErasePixmap(*pm);
      delete pd;
      delete pm;
    }
    air_panel->setPauseEnabled(rda->airplayConf()->panelPauseEnabled());
    air_panel->setCard(0,rda->airplayConf()->card(RDAirPlayConf::SoundPanel1Channel));
    air_panel->setPort(0,rda->airplayConf()->port(RDAirPlayConf::SoundPanel1Channel));
    air_panel->setFocusPolicy(Qt::NoFocus);
    if((card=rda->airplayConf()->card(RDAirPlayConf::SoundPanel2Channel))<0) {
      air_panel->setCard(1,air_panel->card(RDAirPlayConf::MainLog1Channel));
      air_panel->setPort(1,air_panel->port(RDAirPlayConf::MainLog1Channel));
    }
    else {
      air_panel->setCard(1,card);
      air_panel->setPort(1,rda->airplayConf()->port(RDAirPlayConf::SoundPanel2Channel));
    }
    if((card=rda->airplayConf()->card(RDAirPlayConf::SoundPanel3Channel))<0) {
      air_panel->setCard(2,air_panel->card(RDAirPlayConf::MainLog2Channel));
      air_panel->setPort(2,air_panel->port(RDAirPlayConf::MainLog2Channel));
    }
    else {
      air_panel->setCard(2,card);
      air_panel->setPort(2,rda->airplayConf()->port(RDAirPlayConf::SoundPanel3Channel));
    }
    if((card=rda->airplayConf()->card(RDAirPlayConf::SoundPanel4Channel))<0) {
      air_panel->setCard(3,air_panel->card(RDAirPlayConf::SoundPanel1Channel));
      air_panel->setPort(3,air_panel->port(RDAirPlayConf::SoundPanel1Channel));
    }
    else {
      air_panel->setCard(3,card);
      air_panel->setPort(3,rda->airplayConf()->port(RDAirPlayConf::SoundPanel4Channel));
    }
    if((card=rda->airplayConf()->card(RDAirPlayConf::SoundPanel5Channel))<0) {
      air_panel->setCard(4,air_panel->card(RDAirPlayConf::CueChannel));
      air_panel->setPort(4,air_panel->port(RDAirPlayConf::CueChannel));
    }
    else {
      air_panel->setCard(4,card);
      air_panel->setPort(4,rda->airplayConf()->
			 port(RDAirPlayConf::SoundPanel5Channel));
    }
    air_panel->setRmls(0,rda->airplayConf()->
		  startRml(RDAirPlayConf::SoundPanel1Channel),
		  rda->airplayConf()->stopRml(RDAirPlayConf::SoundPanel1Channel));
    air_panel->setRmls(1,rda->airplayConf()->
		  startRml(RDAirPlayConf::SoundPanel2Channel),
		  rda->airplayConf()->stopRml(RDAirPlayConf::SoundPanel2Channel));
    air_panel->setRmls(2,rda->airplayConf()->
		  startRml(RDAirPlayConf::SoundPanel3Channel),
		  rda->airplayConf()->stopRml(RDAirPlayConf::SoundPanel3Channel));
    air_panel->setRmls(3,rda->airplayConf()->
		  startRml(RDAirPlayConf::SoundPanel4Channel),
		  rda->airplayConf()->stopRml(RDAirPlayConf::SoundPanel4Channel));
    air_panel->setRmls(4,rda->airplayConf()->
		  startRml(RDAirPlayConf::SoundPanel5Channel),
		  rda->airplayConf()->stopRml(RDAirPlayConf::SoundPanel5Channel));
    int next_output=0;
    int channum[2];
    bool assigned=false;
    if((air_log[0]->card(0)==air_log[0]->card(RDAirPlayConf::MainLog2Channel))&&
       (air_log[0]->port(0)==air_log[0]->port(RDAirPlayConf::MainLog2Channel))) {
      next_output=2;
      channum[0]=1;
      channum[1]=1;
    }
    else {
      next_output=3;
      channum[0]=1;
      channum[1]=2;
    }
    for(int i=0;i<PANEL_MAX_OUTPUTS;i++) {
      air_panel->setOutputText(i,QString().sprintf("%d",next_output++));
      assigned=false;
      for(int j=0;j<2;j++) {
	if((air_panel->card((RDAirPlayConf::Channel)i)==air_log[0]->card(j))&&
	   (air_panel->port((RDAirPlayConf::Channel)i)==air_log[0]->port(j))) {
	  air_panel->setOutputText(i,QString().sprintf("%d",channum[j]));
	  next_output--;
	  assigned=true;
	  j=2;
	}
      }
      if(!assigned) {
	for(int j=0;j<i;j++) {
	  if((i!=j)&&(air_panel->card((RDAirPlayConf::Channel)i)==air_panel->card(j))&&
	     (air_panel->port((RDAirPlayConf::Channel)i)==air_panel->port(j))) {
	    air_panel->setOutputText(i,air_panel->outputText(j));
	    next_output--;
	    j=PANEL_MAX_OUTPUTS;
	  }
	}
      }
    }

    air_panel->setSvcName(rda->airplayConf()->defaultSvc());
    connect(rda->ripc(),SIGNAL(userChanged()),air_panel,SLOT(changeUser()));
    connect(air_master_timer,SIGNAL(timeout()),air_panel,SLOT(tickClock()));
    connect(air_panel,SIGNAL(selectClicked(unsigned,int,int)),
	    this,SLOT(selectClickedData(unsigned,int,int)));
    connect(air_panel,SIGNAL(channelStarted(int,int,int)),
	    this,SLOT(panelChannelStartedData(int,int,int)));
    connect(air_panel,SIGNAL(channelStopped(int,int,int)),
	    this,SLOT(panelChannelStoppedData(int,int,int)));
  }

  //
  // Full Log List
  //
  air_pause_enabled=rda->airplayConf()->pauseEnabled();
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_log_list[i]=new ListLog(air_log[i],rda->cae(),i,air_pause_enabled,this);
    air_log_list[i]->setGeometry(510,140,air_log_list[i]->sizeHint().width(),
			      air_log_list[i]->sizeHint().height());
    air_log_list[i]->hide();
    connect(air_log_list[i],SIGNAL(selectClicked(int,int,RDLogLine::Status)),
	    this,SLOT(selectClickedData(int,int,RDLogLine::Status)));
    connect(air_log_list[i],SIGNAL(cartDropped(int,int,RDLogLine *)),
	    this,SLOT(cartDroppedData(int,int,RDLogLine *)));
  }

  //
  // Full Log Buttons
  //
  QSignalMapper *mapper=new QSignalMapper(this);
  connect(mapper,SIGNAL(mapped(int)),this,SLOT(fullLogButtonData(int)));
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_log_button[i]=new QPushButton(this);
    air_log_button[i]->setGeometry(647+i*123,sizeHint().height()-65,118,60);
    air_log_button[i]->setFont(button_font);
    air_log_button[i]->setFocusPolicy(Qt::NoFocus);
    mapper->setMapping(air_log_button[i],i);
    connect(air_log_button[i],SIGNAL(clicked()),mapper,SLOT(map()));
  }
  air_log_button[0]->setText(tr("Main Log\n[--]"));
    air_log_button[1]->setText(tr("Aux 1 Log\n[--]"));
    if((!rda->airplayConf()->showAuxButton(0))||
       (!air_log[1]->channelsValid())) {
    air_log_button[1]->hide();
  }
    air_log_button[2]->setText(tr("Aux 2 Log\n[--]"));
    if((!rda->airplayConf()->showAuxButton(1))||
       (!air_log[2]->channelsValid())) {
    air_log_button[2]->hide();
  }

  //
  // Empty Cart
  //
  air_empty_cart=new RDEmptyCart(this);
  air_empty_cart->setGeometry(520,sizeHint().height()-51,32,32);
  if(!rda->station()->enableDragdrop()) {
    air_empty_cart->hide();
  }

  //
  // SoundPanel Button
  //
  air_panel_button=new QPushButton(this);
  air_panel_button->setGeometry(562,sizeHint().height()-65,80,60);
  air_panel_button->setFont(button_font);
  air_panel_button->setText(tr("Sound\nPanel"));
  air_panel_button->setPalette(active_color);
  air_panel_button->setFocusPolicy(Qt::NoFocus);
  connect(air_panel_button,SIGNAL(clicked()),this,SLOT(panelButtonData()));
  if (rda->airplayConf()->panels(RDAirPlayConf::StationPanel) || 
      rda->airplayConf()->panels(RDAirPlayConf::UserPanel)){
  } 
  else {
    air_panel_button->hide();
    air_log_button[0]->setPalette (active_color);
    air_log_list[0]->show();
  }	  


  //
  // Button Log
  //
  air_button_list=new ButtonLog(air_log[0],rda->cae(),0,rda->airplayConf(),
				air_pause_enabled,this);
  air_button_list->setGeometry(10,140,air_button_list->sizeHint().width(),
			       air_button_list->sizeHint().height());
  if(mainmap!=NULL) {
    pm=new QPixmap(1024,738);
    pd=new QPainter(pm);
    pd->drawPixmap(-10,-140,*mainmap);
    pd->end();
    air_button_list->setErasePixmap(*pm);
    delete pd;
    delete pm;
  }
  connect(air_button_list,SIGNAL(selectClicked(int,int,RDLogLine::Status)),
	  this,SLOT(selectClickedData(int,int,RDLogLine::Status)));
  connect(air_button_list,SIGNAL(cartDropped(int,int,RDLogLine *)),
	  this,SLOT(cartDroppedData(int,int,RDLogLine *)));

  //
  // Set Startup Mode
  //
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    switch(rda->airplayConf()->logStartMode(i)) {
      case RDAirPlayConf::Manual:
	SetManualMode(i);
	break;
	
      case RDAirPlayConf::LiveAssist:
	SetLiveAssistMode(i);
	break;
	
      case RDAirPlayConf::Auto:
	SetAutoMode(i);
	break;
	
      case RDAirPlayConf::Previous:
	if(air_op_mode_style==RDAirPlayConf::Unified) {
	  SetMode(i,rda->airplayConf()->opMode(0));
	}
	else {
	  SetMode(i,rda->airplayConf()->opMode(i));
	}
	break;
    }
  }

  //
  // Load Plugins
  //
  QString sql;
  RDSqlQuery *q;

  //
  // Load Plugins
  //
  sql=QString("select ")+
    "PLUGIN_PATH,"+  // 00
    "PLUGIN_ARG "+   // 01
    "from NOWNEXT_PLUGINS where "+
    "(STATION_NAME=\""+RDEscapeString(rda->config()->stationName())+"\")&&"+
     "(LOG_MACHINE=0)";
  q=new RDSqlQuery(sql);
  while(q->next()) {
    air_plugin_hosts.
      push_back(new RDRLMHost(q->value(0).toString(),q->value(1).toString(),
			      air_nownext_socket,this));
    LogLine(RDConfig::LogInfo,"Loading RLM \""+q->value(0).toString()+"\"");
    if(!air_plugin_hosts.back()->load()) {
      LogLine(RDConfig::LogWarning,"Failed to load RLM \""+q->value(0).toString()+"\"");
    }
  }
  delete q;

  // Create the HotKeyList object
  //
  rdkeylist=new RDHotKeyList();
  rdhotkeys=new RDHotkeys(rda->config()->stationName(),"rdairplay");
  AltKeyHit=false;
  CtrlKeyHit=false;

  //
  // Set Signal Handlers
  //
  signal(SIGCHLD,SigHandler);

  //
  // Start the RIPC Connection
  //
  rda->ripc()->connectHost("localhost",RIPCD_TCP_PORT,rda->config()->password());

  //
  // (Perhaps) Lock Memory
  //
  if(rda->config()->lockRdairplayMemory()) {
    if(mlockall(MCL_CURRENT|MCL_FUTURE)<0) {
      QMessageBox::warning(this,"RDAirPlay - "+tr("Memory Warning"),
			   tr("Unable to lock all memory")+
			   " ["+QString(strerror(errno))+"].");
    }
  }

  LogLine(RDConfig::LogInfo,"RDAirPlay started");
}


QSize MainWidget::sizeHint() const
{
  return QSize(1024,738);
}


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


void MainWidget::caeConnectedData(bool state)
{
  std::vector<int> cards;

  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::MainLog1Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::MainLog2Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::AuxLog1Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::AuxLog2Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::SoundPanel1Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::SoundPanel2Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::SoundPanel3Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::SoundPanel4Channel));
  cards.push_back(rda->airplayConf()->card(RDAirPlayConf::SoundPanel5Channel));
  rda->cae()->enableMetering(&cards);
}


void MainWidget::ripcConnectedData(bool state)
{
  QString logname;
  QHostAddress addr;
  QString sql;
  RDSqlQuery *q;
  RDMacro rml;
  rml.setRole(RDMacro::Cmd);
  addr.setAddress("127.0.0.1");
  rml.setAddress(addr);
  rml.setEchoRequested(false);

  //
  // Check Channel Assignments
  //
  if(!air_log[0]->channelsValid()) {
    QMessageBox::warning(this,"RDAirPlay - "+tr("Warning"),
			 tr("Main Log channel assignments are invalid!"));
  }

  //
  // Get Onair Flag State
  //
  rda->ripc()->sendOnairFlag();

  //
  // Load Initial Logs
  //
  for(unsigned i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    if(air_start_logname[i].isNull()) {
      switch(rda->airplayConf()->startMode(i)) {
	  case RDAirPlayConf::StartEmpty:
	    break;
	    
	  case RDAirPlayConf::StartPrevious:
	    air_start_logname[i]=
	      RDDateTimeDecode(rda->airplayConf()->currentLog(i),
			       air_startup_datetime,rda->station(),rda->config());
	    if(!air_start_logname[i].isEmpty()) {
	      if(rdairplay_previous_exit_code==RDAirPlayConf::ExitDirty) {
		if((air_start_line[i]=rda->airplayConf()->logCurrentLine(i))>=0) {
		  air_start_start[i]=rda->airplayConf()->autoRestart(i)&&
		    rda->airplayConf()->logRunning(i);
		}
	      }
	      else {
		air_start_line[i]=0;
		air_start_start[i]=false;
	      }
	    }
	    break;

	  case RDAirPlayConf::StartSpecified:
	    air_start_logname[i]=
	      RDDateTimeDecode(rda->airplayConf()->logName(i),
			       air_startup_datetime,rda->station(),rda->config());
	    if(!air_start_logname[i].isEmpty()) {
	      if(rdairplay_previous_exit_code==RDAirPlayConf::ExitDirty) {
		if(air_start_logname[i]==rda->airplayConf()->currentLog(i)) {
		  if((air_start_line[i]=rda->airplayConf()->logCurrentLine(i))>=
		     0) {
		    air_start_start[i]=rda->airplayConf()->autoRestart(i)&&
		      rda->airplayConf()->logRunning(i);
		  }
		  else {
		    air_start_line[i]=0;
		    air_start_start[i]=false;
		  }
		}
	      }
	    }
	    break;
      }
    }
    if(!air_start_logname[i].isEmpty()) {
      sql=QString("select NAME from LOGS where ")+
	"NAME=\""+RDEscapeString(air_start_logname[i])+"\"";
      q=new RDSqlQuery(sql);
      if(q->first()) {
	rml.setCommand(RDMacro::LL);  // Load Log
	rml.addArg(i+1);
	rml.addArg(air_start_logname[i]);
	rda->ripc()->sendRml(&rml);
      }
      else {
	fprintf(stderr,"rdairplay: log \"%s\" doesn't exist\n",
		(const char *)air_start_logname[i]);
      }
      delete q;
    }
  }
}


void MainWidget::rmlReceivedData(RDMacro *rml)
{
  RunLocalMacros(rml);
}


void MainWidget::gpiStateChangedData(int matrix,int line,bool state)
{
  //
  // Main Logs
  //
  for(unsigned i=0;i<2;i++) {
    if(state) {
      if((air_start_gpi_matrices[i]==matrix)&&
	 (air_start_gpi_lines[i]==line)) {
	if(AssertChannelLock(1,air_audio_channels[i])) {
	  air_log[0]->channelPlay(i);
	}
      }
    }
    else {
      if((air_stop_gpi_matrices[i]==matrix)&&
	 (air_stop_gpi_lines[i]==line)) {
	air_log[0]->channelStop(i);
      }
    }
  }

  //
  // Aux Logs
  //
  for(unsigned i=4;i<6;i++) {
    if(state) {
      if((air_start_gpi_matrices[i]==matrix)&&
	 (air_start_gpi_lines[i]==line)) {
	if(air_channel_timers[0][air_audio_channels[i]]->isActive()) {
	  air_channel_timers[0][air_audio_channels[i]]->stop();
	}
	else {
	  air_channel_timers[1][air_audio_channels[i]]->
	    start(AIR_CHANNEL_LOCKOUT_INTERVAL,true);
	  air_log[i-3]->channelPlay(0);
	}
      }
    }
    else {
      if((air_stop_gpi_matrices[i]==matrix)&&
	 (air_stop_gpi_lines[i]==line)) {
	air_log[i-3]->channelStop(0);
      }
    }
  }

  //
  // Sound Panel
  //
  if(!state) {
    if((air_stop_gpi_matrices[RDAirPlayConf::SoundPanel1Channel]==matrix)&&
       (air_stop_gpi_lines[RDAirPlayConf::SoundPanel1Channel]==line)) {
      air_panel->channelStop(0);
    }
    for(unsigned i=6;i<10;i++) {
      if((air_stop_gpi_matrices[i]==matrix)&&
	 (air_stop_gpi_lines[i]==line)) {
	air_panel->channelStop(i-5);
      }
    }
  }
}


void MainWidget::logChannelStartedData(int id,int mport,int card,int port)
{
  if(!AssertChannelLock(0,card,port)) {
    return;
  }
  switch(id) {
  case 0:  // Main Log
    switch(mport) {
    case 0:
      if(air_start_gpo_matrices[RDAirPlayConf::MainLog1Channel]>=0) {
	switch(air_channel_gpio_types[RDAirPlayConf::MainLog1Channel]) {
	case RDAirPlayConf::LevelGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 0!",
		      air_start_gpo_matrices[RDAirPlayConf::MainLog1Channel],
		      air_start_gpo_lines[RDAirPlayConf::MainLog1Channel]+1));
	  break;

	case RDAirPlayConf::EdgeGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 300!",
		      air_start_gpo_matrices[RDAirPlayConf::MainLog1Channel],
		      air_start_gpo_lines[RDAirPlayConf::MainLog1Channel]+1));
	  break;
	}
      }
      break;

    case 1:
      if(air_start_gpo_matrices[RDAirPlayConf::MainLog2Channel]>=0) {
	switch(air_channel_gpio_types[RDAirPlayConf::MainLog2Channel]) {
	case RDAirPlayConf::LevelGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 0!",
		      air_start_gpo_matrices[RDAirPlayConf::MainLog2Channel],
		      air_start_gpo_lines[RDAirPlayConf::MainLog2Channel]+1));
	  break;

	case RDAirPlayConf::EdgeGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 300!",
		      air_start_gpo_matrices[RDAirPlayConf::MainLog2Channel],
		      air_start_gpo_lines[RDAirPlayConf::MainLog2Channel]+1));
	  break;
	}
      }
      break;
    }
    break;

  case 1:  // Aux Log 1
    if(air_start_gpo_matrices[RDAirPlayConf::AuxLog1Channel]>=0) {
      switch(air_channel_gpio_types[RDAirPlayConf::AuxLog1Channel]) {
      case RDAirPlayConf::LevelGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 0!",
		      air_start_gpo_matrices[RDAirPlayConf::AuxLog1Channel],
		      air_start_gpo_lines[RDAirPlayConf::AuxLog1Channel]+1));
	break;

      case RDAirPlayConf::EdgeGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 300!",
		      air_start_gpo_matrices[RDAirPlayConf::AuxLog1Channel],
		      air_start_gpo_lines[RDAirPlayConf::AuxLog1Channel]+1));
	break;
      }
    }
    break;
    
  case 2:  // Aux Log 2
    if(air_start_gpo_matrices[RDAirPlayConf::AuxLog2Channel]>=0) {
      switch(air_channel_gpio_types[RDAirPlayConf::AuxLog2Channel]) {
      case RDAirPlayConf::LevelGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 0!",
		      air_start_gpo_matrices[RDAirPlayConf::AuxLog2Channel],
		      air_start_gpo_lines[RDAirPlayConf::AuxLog2Channel]+1));
	break;

      case RDAirPlayConf::EdgeGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 300!",
		      air_start_gpo_matrices[RDAirPlayConf::AuxLog2Channel],
		      air_start_gpo_lines[RDAirPlayConf::AuxLog2Channel]+1));
	break;
      }
    }
    break;
  }
}


void MainWidget::logChannelStoppedData(int id,int mport,int card,int port)
{
  switch(id) {
  case 0:  // Main Log
    switch(mport) {
    case 0:
      if(air_stop_gpo_matrices[RDAirPlayConf::MainLog1Channel]>=0) {
	switch(air_channel_gpio_types[RDAirPlayConf::MainLog1Channel]) {
	case RDAirPlayConf::LevelGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 0 0!",
		      air_stop_gpo_matrices[RDAirPlayConf::MainLog1Channel],
		      air_stop_gpo_lines[RDAirPlayConf::MainLog1Channel]+1));
	  break;

	case RDAirPlayConf::EdgeGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 300!",
		      air_stop_gpo_matrices[RDAirPlayConf::MainLog1Channel],
		      air_stop_gpo_lines[RDAirPlayConf::MainLog1Channel]+1));
	  break;
	}
      }
      break;

    case 1:
      if(air_stop_gpo_matrices[RDAirPlayConf::MainLog2Channel]>=0) {
	switch(air_channel_gpio_types[RDAirPlayConf::MainLog2Channel]) {
	case RDAirPlayConf::LevelGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 0 0!",
		      air_stop_gpo_matrices[RDAirPlayConf::MainLog2Channel],
		      air_stop_gpo_lines[RDAirPlayConf::MainLog2Channel]+1));
	  break;

	case RDAirPlayConf::EdgeGpio:
	  rdevent_player->
	    exec(QString().sprintf("GO %d %d 1 300!",
		      air_stop_gpo_matrices[RDAirPlayConf::MainLog2Channel],
		      air_stop_gpo_lines[RDAirPlayConf::MainLog2Channel]+1));
	  break;
	}
      }
      break;
    }
    break;

  case 1:  // Aux Log 1
    if(air_stop_gpo_matrices[RDAirPlayConf::AuxLog1Channel]>=0) {
      switch(air_channel_gpio_types[RDAirPlayConf::AuxLog1Channel]) {
      case RDAirPlayConf::LevelGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 0 0!",
		      air_stop_gpo_matrices[RDAirPlayConf::AuxLog1Channel],
		      air_stop_gpo_lines[RDAirPlayConf::AuxLog1Channel]+1));
	break;

      case RDAirPlayConf::EdgeGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 300!",
		      air_stop_gpo_matrices[RDAirPlayConf::AuxLog1Channel],
		      air_stop_gpo_lines[RDAirPlayConf::AuxLog1Channel]+1));
	break;
      }
    }
    break;

  case 2:  // Aux Log 2
    if(air_stop_gpo_matrices[RDAirPlayConf::AuxLog2Channel]>=0) {
      switch(air_channel_gpio_types[RDAirPlayConf::AuxLog2Channel]) {
      case RDAirPlayConf::LevelGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 0 0!",
		      air_stop_gpo_matrices[RDAirPlayConf::AuxLog2Channel],
		      air_stop_gpo_lines[RDAirPlayConf::AuxLog2Channel]+1));
	break;

      case RDAirPlayConf::EdgeGpio:
	rdevent_player->
	  exec(QString().sprintf("GO %d %d 1 300!",
		      air_stop_gpo_matrices[RDAirPlayConf::AuxLog2Channel],
		      air_stop_gpo_lines[RDAirPlayConf::AuxLog2Channel]+1));
	break;
      }
    }
    break;
  }
}


void MainWidget::panelChannelStartedData(int mport,int card,int port)
{
  if(!AssertChannelLock(0,card,port)) {
    return;
  }
  RDAirPlayConf::Channel chan=PanelChannel(mport);
  if(air_start_gpo_matrices[chan]>=0) {
    switch(air_channel_gpio_types[chan]) {
    case RDAirPlayConf::LevelGpio:
      rdevent_player->
	exec(QString().sprintf("GO %d %d 1 0!",
			       air_start_gpo_matrices[chan],
			       air_start_gpo_lines[chan]+1));
      break;

    case RDAirPlayConf::EdgeGpio:
      rdevent_player->
	exec(QString().sprintf("GO %d %d 1 300!",
			       air_start_gpo_matrices[chan],
			       air_start_gpo_lines[chan]+1));
      break;
    }
  }
}


void MainWidget::panelChannelStoppedData(int mport,int card,int port)
{
  RDAirPlayConf::Channel chan=PanelChannel(mport);

  if(air_stop_gpo_matrices[chan]>=0) {
    switch(air_channel_gpio_types[chan]) {
    case RDAirPlayConf::LevelGpio:
      rdevent_player->
	exec(QString().sprintf("GO %d %d 0 0!",
			       air_stop_gpo_matrices[chan],
			       air_stop_gpo_lines[chan]+1));
      break;

    case RDAirPlayConf::EdgeGpio:
      rdevent_player->
	exec(QString().sprintf("GO %d %d 1 300!",
			       air_stop_gpo_matrices[chan],
			       air_stop_gpo_lines[chan]+1));
      break;
    }
  }
}


void MainWidget::logRenamedData(int log)
{
  QString str;
  QString logname=air_log[log]->logName();
  QString labelname=logname;
  if(logname.isEmpty()) {
    labelname="--";
  }
  switch(log) {
  case 0:
    air_log_button[0]->setText(tr("Main Log")+"\n["+labelname+"]");
    SetCaption();
    if(air_panel) {
      air_panel->setLogName(logname);
    }
    break;

  case 1:
    air_log_button[1]->setText(tr("Aux 1 Log")+"\n["+labelname+"]");
    break;
	
  case 2:
    air_log_button[2]->setText(tr("Aux 2 Log")+"\n["+labelname+"]");
    break;
  }
}


void MainWidget::logReloadedData(int log)
{
  QString str;
  QHostAddress addr;
  QString labelname=air_log[log]->logName();
  if(labelname.isEmpty()) {
    labelname="--";
  }

  switch(log) {
  case 0:
    air_log_button[0]->setText(tr("Main Log")+"\n["+labelname+"]");
    LogLine(RDConfig::LogInfo,"loaded log '"+air_log[0]->logName()+"' in Main Log");
    if(air_log[0]->logName().isEmpty()) {
      if(air_panel!=NULL) {
	air_panel->setSvcName(rda->airplayConf()->defaultSvc());
      }
    }
    else {
      if(air_panel!=NULL) {
	air_panel->setSvcName(air_log[0]->serviceName());
      }
    }
    break;

  case 1:
    air_log_button[1]->setText(tr("Aux 1 Log")+"\n["+labelname+"]");
    LogLine(RDConfig::LogInfo,"loaded log '"+air_log[1]->logName()+"' in Aux 1 Log");
    break;
	
  case 2:
    air_log_button[2]->setText(tr("Aux 2 Log")+"\n["+labelname+"]");
    LogLine(RDConfig::LogInfo,"loaded log '"+air_log[2]->logName()+"' in Aux 2 Log");
    break;
  }
  SetCaption();

  //
  // Load Initial Log
  //
  if(air_start_logname[log].isEmpty()) {
    return;
  }
  RDMacro rml;
  rml.setRole(RDMacro::Cmd);
  addr.setAddress("127.0.0.1");
  rml.setAddress(addr);
  rml.setEchoRequested(false);

  if(air_start_line[log]<air_log[log]->size()) {
    rml.setCommand(RDMacro::MN);  // Make Next
    rml.addArg(log+1);
    rml.addArg(air_start_line[log]);
    rda->ripc()->sendRml(&rml);
    
    if(air_start_start[log]) {
      rml.clear();
      rml.setRole(RDMacro::Cmd);
      addr.setAddress("127.0.0.1");
      rml.setAddress(addr);
      rml.setEchoRequested(false);
      rml.setCommand(RDMacro::PN);  // Start Next
      rml.addArg(log+1);
      rda->ripc()->sendRml(&rml);
    }
  }
  else {
    fprintf(stderr,"rdairplay: line %d doesn't exist in log \"%s\"\n",
	    air_start_line[log],(const char *)air_start_logname[log]);
  }
  air_start_logname[log]="";
}


void MainWidget::userData()
{
  LogLine(RDConfig::LogInfo,"user changed to '"+rda->ripc()->user()+"'");
  SetCaption();

  //
  // Set Control Perms
  //
  bool add_allowed=rda->user()->addtoLog();
  bool delete_allowed=rda->user()->removefromLog();
  bool arrange_allowed=rda->user()->arrangeLog();
  bool playout_allowed=rda->user()->playoutLog();

  air_add_button->setEnabled(add_allowed&&arrange_allowed&&playout_allowed);
  air_move_button->setEnabled(arrange_allowed&&playout_allowed);
  air_delete_button->
    setEnabled(delete_allowed&&arrange_allowed&&playout_allowed);
  air_copy_button->setEnabled(add_allowed&&arrange_allowed&&playout_allowed);
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_log_list[i]->userChanged(add_allowed,delete_allowed,
				 arrange_allowed,playout_allowed);
  }
}


void MainWidget::addButtonData()
{
  if((air_action_mode==StartButton::AddFrom)||
     (air_action_mode==StartButton::AddTo)) {
    SetActionMode(StartButton::Stop);
  }
  else {
    SetActionMode(StartButton::AddFrom);
  }
}


void MainWidget::deleteButtonData()
{
  if(air_action_mode==StartButton::DeleteFrom) {
    SetActionMode(StartButton::Stop);
  }
  else {
    SetActionMode(StartButton::DeleteFrom);
  }
}


void MainWidget::moveButtonData()
{
  if((air_action_mode==StartButton::MoveFrom)||
    (air_action_mode==StartButton::MoveTo)) {
    SetActionMode(StartButton::Stop);
  }
  else {
    SetActionMode(StartButton::MoveFrom);
  }
}


void MainWidget::copyButtonData()
{
  if((air_action_mode==StartButton::CopyFrom)||
    (air_action_mode==StartButton::CopyTo)) {
    SetActionMode(StartButton::Stop);
  }
  else {
    SetActionMode(StartButton::CopyFrom);
  }
}


void MainWidget::fullLogButtonData(int id)
{
#ifdef SHOW_SLOTS
  printf("fullLogButtonData()\n");
#endif
  if(air_log_list[id]->isVisible()) {
    return;
  }
  else {
    for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
      if(air_log_list[i]->isVisible()) {
	air_log_list[i]->hide();
	air_log_button[i]->setPalette(palette());
      }
    }
    air_log_list[id]->show();
    air_log_button[id]->setPalette(active_color);
    if (air_panel_button) air_panel_button->setPalette(palette());
  }
}


void MainWidget::panelButtonData()
{
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    if(air_log_list[i]->isVisible()) {
      air_log_list[i]->hide();
      air_log_button[i]->setPalette(palette());
    }
  }
  air_panel_button->setPalette(active_color);
}


void MainWidget::modeButtonData()
{
  int mach=-1;
  switch(air_op_mode_style) {
  case RDAirPlayConf::Unified:
    mach=-1;
    break;

  case RDAirPlayConf::Independent:
    mach=0;
    break;
  }
  switch(air_op_mode[0]) {
      case RDAirPlayConf::Manual:
	SetMode(mach,RDAirPlayConf::LiveAssist);
	break;

      case RDAirPlayConf::LiveAssist:
	SetMode(mach,RDAirPlayConf::Auto);
	break;

      case RDAirPlayConf::Auto:
	SetMode(mach,RDAirPlayConf::Manual);
	break;

      default:
	break;
  }
}


void MainWidget::selectClickedData(int id,int line,RDLogLine::Status status)
{
  RDLogLine *logline;

  switch(air_action_mode) {
      case StartButton::AddTo:
	if(line<0) {
	  air_log[id]->
	    insert(air_log[id]->size(),air_add_cart,RDLogLine::Play);
	  air_log[id]->logLine(air_log[id]->size()-1)->
	    setTransType(rda->airplayConf()->defaultTransType());
	  air_log_list[id]->refresh(air_log[id]->size()-1);
	}
	else {
	  air_log[id]->
	    insert(line,air_add_cart,air_log[id]->nextTransType(line));
	  air_log[id]->logLine(line)->
	    setTransType(rda->airplayConf()->defaultTransType());
	  air_log_list[id]->refresh(line);
	}
	SetActionMode(StartButton::Stop);
	break;

      case StartButton::DeleteFrom:
	if(status==RDLogLine::Finished) {
	  return;
	}
	air_log[id]->remove(line,1);
	SetActionMode(StartButton::Stop);
	break;

      case StartButton::MoveFrom:
	if((logline=air_log[id]->logLine(line))!=NULL) {
	  air_copy_line=line;
	  air_add_cart=logline->cartNumber();
	  air_source_id=id;
	  SetActionMode(StartButton::MoveTo);
	}
	else {
	  SetActionMode(StartButton::Stop);
	}
	break;

      case StartButton::MoveTo:
	if(air_source_id==id) {
	  if(line<0) {
	    air_log[id]->move(air_copy_line,air_log[id]->size());
	    air_log_list[id]->refresh(air_log[id]->size()-1);
	  }
	  else {
	    if(line>air_copy_line) {
	      line--;
	    }
	    air_log[id]->move(air_copy_line,line);
	    air_log_list[id]->refresh(line);
	  }
	}
	else {
	  air_log[air_source_id]->remove(air_copy_line,1);
	  if(line<0) {
	    air_log[id]->
	      insert(air_log[id]->size(),air_add_cart,RDLogLine::Play);
	    air_log_list[id]->refresh(air_log[id]->size()-1);
	    
	  }
	  else {
	    air_log[id]->
	      insert(line,air_add_cart,air_log[id]->nextTransType(line));
	    air_log_list[id]->refresh(line);
	  }
	}
	SetActionMode(StartButton::Stop);
	break;

      case StartButton::CopyFrom:
	if((logline=air_log[id]->logLine(line))!=NULL) {
	  air_copy_line=line;
	  air_add_cart=logline->cartNumber();
	  air_source_id=id;
	  SetActionMode(StartButton::CopyTo);
	}
	else {
	  SetActionMode(StartButton::Stop);
	}
	break;

      case StartButton::CopyTo:
	if(air_source_id==id) {
	  if(line<0) {
	    air_log[id]->copy(air_copy_line,air_log[id]->size());
	  }
	  else {
	    air_log[id]->copy(air_copy_line,line);
	  }
	}
	else {
	  if(line<0) {
	    air_log[id]->insert(air_log[id]->size(),air_add_cart,
				rda->airplayConf()->defaultTransType(),
				rda->airplayConf()->defaultTransType());
	    air_log[id]->logLine(air_log[id]->size()-1)->
	      setTransType(rda->airplayConf()->defaultTransType());
	    air_log_list[id]->refresh(air_log[id]->size()-1);
	  }
	  else {
	    air_log[id]->
	      insert(line,air_add_cart,air_log[id]->nextTransType(line),
		     rda->airplayConf()->defaultTransType());
	    air_log[id]->logLine(line)->
	      setTransType(rda->airplayConf()->defaultTransType());
	    air_log_list[id]->refresh(line);
	  }
	}
	SetActionMode(StartButton::Stop);
	break;

      default:
	break;
  }
}


void MainWidget::selectClickedData(unsigned cartnum,int row,int col)
{
  switch(air_action_mode) {
      case StartButton::CopyFrom:
	air_copy_line=-1;
	air_add_cart=cartnum;
	air_source_id=-1;
	SetActionMode(StartButton::CopyTo);
	break;

      case StartButton::CopyTo:
	if(air_panel!=NULL) {
	  air_panel->setButton(air_panel->currentType(),
			       air_panel->currentNumber(),row,col,air_add_cart);
	}
	SetActionMode(StartButton::Stop);
	break;

      case StartButton::AddTo:
	if(air_panel!=NULL) {
	  air_panel->setButton(air_panel->currentType(),
			       air_panel->currentNumber(),row,col,air_add_cart);
	}
	SetActionMode(StartButton::Stop);
	break;

      case StartButton::DeleteFrom:
	if(air_panel!=NULL) {
	  air_panel->setButton(air_panel->currentType(),
			       air_panel->currentNumber(),row,col,0);
	}
	SetActionMode(StartButton::Stop);
	break;

      default:
	break;
  }
}


void MainWidget::cartDroppedData(int id,int line,RDLogLine *ll)
{
  if(ll->cartNumber()==0) {
    air_log[id]->remove(line,1);
  }
  else {
    if(line<0) {
      air_log[id]->
	insert(air_log[id]->size(),ll->cartNumber(),RDLogLine::Play);
      air_log[id]->logLine(air_log[id]->size()-1)->
	setTransType(rda->airplayConf()->defaultTransType());
      air_log_list[id]->refresh(air_log[id]->size()-1);
    }
    else {
      air_log[id]->
	insert(line,ll->cartNumber(),air_log[id]->nextTransType(line));
      air_log[id]->logLine(line)->
	setTransType(rda->airplayConf()->defaultTransType());
      air_log_list[id]->refresh(line);
    }
  }
}


void MainWidget::meterData()
{
#ifdef SHOW_METER_SLOTS
  printf("meterData()\n");
#endif
  double ratio[2]={0.0,0.0};
  short level[2];

  for(int i=0;i<AIR_TOTAL_PORTS;i++) {
    if(FirstPort(i)) {
      rda->cae()->outputMeterUpdate(air_meter_card[i],air_meter_port[i],level);
      for(int j=0;j<2;j++) {
	ratio[j]+=pow(10.0,((double)level[j])/1000.0);
      }
    }
  }
  air_stereo_meter->setLeftPeakBar((int)(log10(ratio[0])*1000.0));
  air_stereo_meter->setRightPeakBar((int)(log10(ratio[1])*1000.0));
}


void MainWidget::masterTimerData()
{
  static unsigned counter=0;
  static QTime last_time=QTime::currentTime();

  if(counter++>=5) {
    QTime current_time=QTime::currentTime();
    if(current_time<last_time) {
      for(unsigned i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	air_log[i]->resync();
      }
    }
    last_time=current_time;
    counter=0;
  }
}


void MainWidget::transportChangedData()
{
  int lines[TRANSPORT_QUANTITY];
  int line=0;
  int count;
  QTime end_time;
  RDLogLine *logline;
  RDLogLine *next_logline;
  RDAirPlayConf::PieEndPoint pie_end=RDAirPlayConf::CartEnd;

  if((count=air_log[0]->runningEvents(lines,false))>0) {
    for(int i=0;i<count;i++) {
      if((logline=air_log[0]->logLine(lines[i]))!=NULL) {
	switch(logline->type()) {
	    case RDLogLine::Cart:
	      if(logline->startTime(RDLogLine::Actual).
		 addMSecs(logline->effectiveLength()-
			  ((RDPlayDeck *)logline->playDeck())->
			  lastStartPosition())>end_time) {
		end_time=logline->startTime(RDLogLine::Actual).
		  addMSecs(logline->effectiveLength()-
			   ((RDPlayDeck *)logline->playDeck())->
			   lastStartPosition());
		line=lines[i];
	      }
	      break;

	    case RDLogLine::Macro:
	      line=lines[i];
	      break;

	    default:
	      break;
	}
      }
    }

    logline=air_log[0]->logLine(line);
    switch(air_op_mode[0]) {
	case RDAirPlayConf::Manual:
	case RDAirPlayConf::LiveAssist:
	  pie_end=RDAirPlayConf::CartEnd;
	  break;

	case RDAirPlayConf::Auto:
	  pie_end=air_pie_end;
	  break;

	default:
	  break;
    }
    if(logline->effectiveLength()>0) {
      if((air_pie_counter->line()!=logline->id())) {
	switch(pie_end) {
	    case RDAirPlayConf::CartEnd:
	      air_pie_counter->setTime(logline->effectiveLength());
	      break;
	      
	    case RDAirPlayConf::CartTransition:
	      if((next_logline=air_log[0]->
		  logLine(air_log[0]->nextLine(line)))!=NULL) {
		if((unsigned)logline->startTime(RDLogLine::Actual).
		   msecsTo(QTime::currentTime())<
		   logline->segueLength(next_logline->transType())-
		   logline->playPosition()) {
		  air_pie_counter->
		    setTime(logline->segueLength(next_logline->transType()));
		}
		else {
		  air_pie_counter->setTime(logline->effectiveLength());
		}
	      }
	      else {
		air_pie_counter->setTime(logline->effectiveLength());
	      }
	      break;
	}
	if(logline->talkStartPoint()==0) {
          air_pie_counter->setTalkStart(0);
  	  air_pie_counter->setTalkEnd(logline->talkEndPoint());
        }
        else {
	air_pie_counter->
	  setTalkStart(logline->talkStartPoint()-logline->
		         startPoint());
	air_pie_counter->
	  setTalkEnd(logline->talkEndPoint()-logline->
		         startPoint());
        }
	air_pie_counter->setTransType(air_log[0]->nextTrans(line));
	if(logline->playDeck()==NULL) {
	  air_pie_counter->setLogline(NULL);
	  air_pie_counter->start(rda->station()->timeOffset());
	}
	else {
	  air_pie_counter->setLogline(logline);
	  air_pie_counter->start(((RDPlayDeck *)logline->playDeck())->
				 currentPosition()+
				 rda->station()->timeOffset());
	}
      }
    }
    else {
      air_pie_counter->stop();
      air_pie_counter->resetTime();
      air_pie_counter->setLine(-1);
    }
  }
  else {
    air_pie_counter->stop();
    air_pie_counter->resetTime();
    air_pie_counter->setLine(-1);
  }
}


void MainWidget::timeModeData(RDAirPlayConf::TimeMode mode)
{
  air_button_list->setTimeMode(mode);
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    air_log_list[i]->setTimeMode(mode);
  }
  air_stop_counter->setTimeMode(mode);
  air_post_counter->setTimeMode(mode);
}


void MainWidget::refreshStatusChangedData(bool active)
{
  if(active) {
    air_refresh_label->setErasePixmap(QPixmap());
    air_refresh_label->setText(tr("LOG\nREFRESHING"));
  }
  else {
    if(air_refresh_pixmap!=NULL) {
      air_refresh_label->setErasePixmap(*air_refresh_pixmap);
    }
    air_refresh_label->setText("");
  }
  //
  // FIXME: Disabled in 2.10.3caefix05 due to segfault problems.
  //        Do we really need this?
  //
  //  qApp->processEvents();
}


void MainWidget::clearSplashData()
{
  air_splash_screen->hide();
  delete air_splash_screen;
  air_splash_screen=NULL;
}


void MainWidget::keyPressEvent(QKeyEvent *e)
{
 switch(e->key()) {
      case Qt::Key_Space:
	break;

      case Qt::Key_X:
	if(((e->state()&Qt::AltModifier)!=0)&&
	   ((e->state()&Qt::ShiftModifier)==0)&&((e->state()&Qt::ControlModifier)==0)) {
	  QCloseEvent *ce=new QCloseEvent();
	  closeEvent(ce);
	  delete ce;
	}
	break;

      case Qt::Key_Alt:
        keystrokecount++;
        AltKeyHit = true;
        break;

      case Qt::Key_Control:
        keystrokecount++;
        CtrlKeyHit = true;
        break;

      default:
        QWidget::keyPressEvent(e);
	break;
  }
}

void MainWidget::keyReleaseEvent(QKeyEvent *e)
{
  int keyhit = e->key();
  QString mystring=(*rdkeylist).GetKeyCode(keyhit);
  QString hotkeystrokes;
  QString hot_label;
  QString temp_string;

  switch(e->key()) {
      case Qt::Key_Space:
	switch(air_bar_action) {
	    case RDAirPlayConf::StartNext:
	      if(!e->isAutoRepeat()){
		air_log[0]->
		  play(air_log[0]->nextLine(),RDLogLine::StartManual);
	      }
	      break;

	    default:
	      break;
	}
	break;
  }
//  Try to figure out if this is a hot key combination
  if ( (e->key() == Qt::Key_Shift) || 
       (e->key() == Qt::Key_Up) || 
       (e->key() == Qt::Key_Left) ||
       (e->key() == Qt::Key_Right) ||
       (e->key() == Qt::Key_Down) )    {
      QWidget::keyReleaseEvent(e);
      keystrokecount = 0;
      hotkeystrokes = QString ("");
      return;
  }

  if ((e->key() == Qt::Key_Alt) ||
      (e->key() == Qt::Key_Control)) {
      if (keystrokecount != 0 ) hotkeystrokes = QString ("");
      if (AltKeyHit) {
          AltKeyHit = false;
          if (keystrokecount > 0) keystrokecount--;
      }
      if (CtrlKeyHit) {
          CtrlKeyHit = false;
          if (keystrokecount > 0) keystrokecount--;
      }
      return;
  }

  if (!e->isAutoRepeat()) {
      if (keystrokecount == 0)
          hotkeystrokes = QString ("");
      if (AltKeyHit) {
          hotkeystrokes =  (*rdkeylist).GetKeyCode(Qt::Key_Alt);
          hotkeystrokes +=  QString(" + ");
      }
      if (CtrlKeyHit) {
          if (AltKeyHit) {
              hotkeystrokes +=  (*rdkeylist).GetKeyCode(Qt::Key_Control);
              hotkeystrokes += QString (" + ");
          }
          else {
              hotkeystrokes =  (*rdkeylist).GetKeyCode(Qt::Key_Control);
              hotkeystrokes += QString (" + ");
          }
      }

      hotkeystrokes += mystring; 
      keystrokecount = 0 ;
  }
  
      // Have any Hot Key Combinations now...

  if (hotkeystrokes.length() > 0)  {

    hot_label=(*rdhotkeys).GetRowLabel(RDEscapeString(rda->config()->stationName()),
                        (const char *)"airplay",(const char *)hotkeystrokes);

    if (hot_label.length()>0) {

        // "we found a keystroke label
 
        if (strcmp(hot_label,"Add") == 0)
        {
            addButtonData();
            return;
        }

        if (strcmp(hot_label,"Delete") == 0)
        {
            deleteButtonData();
            return;
        }

        if (strcmp(hot_label,"Copy") == 0)
        {
            copyButtonData();
            return;
        }

        if (strcmp(hot_label,"Move") == 0)
        {
            moveButtonData();
            return;
        }
    
        if (strcmp(hot_label,"Sound Panel") == 0)
        {
            panelButtonData();
            return;
        }
    
        if (strcmp(hot_label,"Main Log") == 0)
        {
            fullLogButtonData(0);
            return;
        }
    
        if ((strcmp(hot_label,"Aux Log 1") == 0) &&
             (rda->airplayConf()->showAuxButton(0) ) )
        {
            fullLogButtonData(1);
            return;
        }
    
        if ( (strcmp(hot_label,"Aux Log 2") == 0) &&
             (rda->airplayConf()->showAuxButton(1) ) )
        {
            fullLogButtonData(2);
            return;
        }
    
        for (int i = 1; i < 8 ; i++)
        {
            temp_string = QString().sprintf("Start Line %d",i);
            if (strcmp(hot_label,temp_string) == 0)
                air_button_list->startButton(i-1);
    
            temp_string = QString().sprintf("Stop Line %d",i);
            if (strcmp(hot_label,temp_string) == 0)
                air_button_list->stopButtonHotkey(i-1);
    
            temp_string = QString().sprintf("Pause Line %d",i);
            if (strcmp(hot_label,temp_string) == 0)
                air_button_list->pauseButtonHotkey(i-1);
        }
      }
  }
  QWidget::keyReleaseEvent(e);
}

void MainWidget::closeEvent(QCloseEvent *e)
{
  if(!rda->airplayConf()->exitPasswordValid("")) {
    QString passwd;
    RDGetPasswd *gw=new RDGetPasswd(&passwd,this);
    gw->exec();
    if(!rda->airplayConf()->exitPasswordValid(passwd)) {
      return;
    }
    rda->airplayConf()->setExitCode(RDAirPlayConf::ExitClean);
    LogLine(RDConfig::LogInfo,"RDAirPlay exiting");
    air_lock->unlock();
    exit(0);
  }
  if(QMessageBox::question(this,"",tr("Exit RDAirPlay?"),
			   QMessageBox::Yes,QMessageBox::No)!=
     QMessageBox::Yes) {
    return;
  }
  for(unsigned i=0;i<air_plugin_hosts.size();i++) {
    air_plugin_hosts[i]->unload();
  }
  for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
    delete air_log[i];
  }
  rda->airplayConf()->setExitCode(RDAirPlayConf::ExitClean);
  LogLine(RDConfig::LogInfo,"RDAirPlay exiting");
  air_lock->unlock();
  exit(0);
}


void MainWidget::paintEvent(QPaintEvent *e)
{
  QPainter *p=new QPainter(this);
  p->setPen(Qt::black);
  p->fillRect(10,70,410,air_stereo_meter->sizeHint().height(),Qt::black);
  p->end();
  delete p;
}


void SigHandler(int signo)
{
  pid_t pLocalPid;

  switch(signo) {
  case SIGCHLD:
    pLocalPid=waitpid(-1,NULL,WNOHANG);
    while(pLocalPid>0) {
      pLocalPid=waitpid(-1,NULL,WNOHANG);
    }
    signal(SIGCHLD,SigHandler);
    return;
  }
}


void MainWidget::SetCaption()
{
  QString log=air_log[0]->logName();
  if(log.isEmpty()) {
    log="--    ";
  }
  setCaption(QString("RDAirPlay")+" v"+VERSION+" - "+tr("Host")+": "+
	     rda->config()->stationName()+" "+
	     tr("User:")+" "+rda->ripc()->user()+" "+
	     tr("Log:")+" "+log.left(log.length()-4)+" "+
	     tr("Service:")+" "+air_log[0]->serviceName());
}


void MainWidget::SetMode(int mach,RDAirPlayConf::OpMode mode)
{
  if(mach<0) {
    for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
      SetMode(i,mode);
    }
    return;
  }
  if(air_op_mode[mach]==mode) {
    return;
  }
  switch(mode) {
      case RDAirPlayConf::Manual:
	SetManualMode(mach);
	break;

      case RDAirPlayConf::LiveAssist:
	SetLiveAssistMode(mach);
	break;

      case RDAirPlayConf::Auto:
	SetAutoMode(mach);
	break;

      default:
	break;
  }
}


void MainWidget::SetManualMode(int mach)
{
  if(mach<0) {
    for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
      SetManualMode(i);
    }
    return;
  }
  if(mach==0) {
    air_pie_counter->setOpMode(RDAirPlayConf::Manual);
  }
  air_mode_display->setOpMode(mach,RDAirPlayConf::Manual);
  air_op_mode[mach]=RDAirPlayConf::Manual;
  rda->airplayConf()->setOpMode(mach,RDAirPlayConf::Manual);
  air_log[mach]->setOpMode(RDAirPlayConf::Manual);
  air_log_list[mach]->setOpMode(RDAirPlayConf::Manual);
  if(mach==0) {
    air_button_list->setOpMode(RDAirPlayConf::Manual);
    air_post_counter->setDisabled(true);
  }
  LogLine(RDConfig::LogInfo,
	  QString().sprintf("log machine %d mode set to MANUAL",mach+1));
}


void MainWidget::SetAutoMode(int mach)
{
  if(mach<0) {
    for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
      SetAutoMode(i);
    }
    return;
  }
  if(mach==0) {
    air_pie_counter->setOpMode(RDAirPlayConf::Auto);
  }
  air_mode_display->setOpMode(mach,RDAirPlayConf::Auto);
  air_op_mode[mach]=RDAirPlayConf::Auto;
  rda->airplayConf()->setOpMode(mach,RDAirPlayConf::Auto);
  air_log[mach]->setOpMode(RDAirPlayConf::Auto);
  air_log_list[mach]->setOpMode(RDAirPlayConf::Auto);
  if(mach==0) {
    air_button_list->setOpMode(RDAirPlayConf::Auto);
    air_post_counter->setEnabled(true);
  }
  LogLine(RDConfig::LogInfo,
	  QString().sprintf("log machine %d mode set to AUTO",mach+1));
}


void MainWidget::SetLiveAssistMode(int mach)
{
  if(mach<0) {
    for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
      SetLiveAssistMode(i);
    }
    return;
  }
  if(mach==0) {
    air_pie_counter->setOpMode(RDAirPlayConf::LiveAssist);
  }
  air_mode_display->setOpMode(mach,RDAirPlayConf::LiveAssist);
  air_op_mode[mach]=RDAirPlayConf::LiveAssist;
  rda->airplayConf()->setOpMode(mach,RDAirPlayConf::LiveAssist);
  air_log[mach]->setOpMode(RDAirPlayConf::LiveAssist);
  air_log_list[mach]->setOpMode(RDAirPlayConf::LiveAssist);
  if(mach==0) {
    air_button_list->setOpMode(RDAirPlayConf::LiveAssist); 
    air_post_counter->setDisabled(true);
  }
  LogLine(RDConfig::LogInfo,
	  QString().sprintf("log machine %d mode set to LIVE ASSIST",mach+1));
}


void MainWidget::SetActionMode(StartButton::Mode mode)
{
  QString svc_name[RD_MAX_DEFAULT_SERVICES];
  int svc_quan=0;
  QString sql;
  RDSqlQuery *q;
  QStringList services_list;

  if(air_action_mode==mode) {
    return;
  }
  air_action_mode=mode;
  switch(mode) {
      case StartButton::Stop:
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::Normal);
	}
	air_button_list->setActionMode(RDAirPlayConf::Normal);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::Normal);
	}

	break;

      case StartButton::AddFrom:
	if(air_clear_filter) {
	  air_add_filter="";
	}
	air_add_cart=0;
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  svc_name[i]=air_log[i]->serviceName();
	  if(!svc_name[i].isEmpty()) {
	    svc_quan=RDAIRPLAY_LOG_QUANTITY;
	  }
	}
	if(svc_quan==0) {
	  sql=QString("select SERVICE_NAME from SERVICE_PERMS where ")+
	    "STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\"";
	  q=new RDSqlQuery(sql);
	  while(q->next()) {
	    services_list.append( q->value(0).toString() );
	  }
	  delete q;
          for ( QStringList::Iterator it = services_list.begin(); 
                it != services_list.end()&&svc_quan<(RD_MAX_DEFAULT_SERVICES-1);
                ++it ) {
            svc_name[svc_quan++]=*it;
          }

	}
	air_add_button->setFlashColor(BUTTON_FROM_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(true);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::Normal);
	}
	air_button_list->setActionMode(RDAirPlayConf::Normal);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::Normal);
	}
	if(rdcart_dialog->exec(&air_add_cart,RDCart::All,0,0,
			       rda->user()->name(),rda->user()->password())==0) {
	  SetActionMode(StartButton::AddTo);
	}
	else {
	  SetActionMode(StartButton::Stop);
	}
	break;
	
      case StartButton::AddTo:
	air_add_button->setFlashColor(BUTTON_TO_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(true);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::AddTo,&air_add_cart);
	}
	air_button_list->setActionMode(RDAirPlayConf::AddTo);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::AddTo);
	}
	break;

      case StartButton::DeleteFrom:
	air_delete_button->setFlashColor(BUTTON_FROM_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(true);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::DeleteFrom);
	}
	air_button_list->setActionMode(RDAirPlayConf::DeleteFrom);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::DeleteFrom);
	}
	break;

      case StartButton::MoveFrom:
	air_move_button->setFlashColor(BUTTON_FROM_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(true);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::MoveFrom);
	}
	air_button_list->setActionMode(RDAirPlayConf::MoveFrom);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::MoveFrom);
	}
	break;

      case StartButton::MoveTo:
	air_move_button->setFlashColor(BUTTON_TO_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(true);
	air_copy_button->setFlashingEnabled(false);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::MoveTo);
	}
	air_button_list->setActionMode(RDAirPlayConf::MoveTo);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::MoveTo);
	}
	break;

      case StartButton::CopyFrom:
	air_copy_button->setFlashColor(BUTTON_FROM_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(true);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::CopyFrom);
	}
	air_button_list->setActionMode(RDAirPlayConf::CopyFrom);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::CopyFrom);
	}
	break;

      case StartButton::CopyTo:
	air_move_button->setFlashColor(BUTTON_TO_BACKGROUND_COLOR);
	air_add_button->setFlashingEnabled(false);
	air_delete_button->setFlashingEnabled(false);
	air_move_button->setFlashingEnabled(false);
	air_copy_button->setFlashingEnabled(true);
	for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
	  air_log_list[i]->setActionMode(RDAirPlayConf::CopyTo);
	}
	air_button_list->setActionMode(RDAirPlayConf::CopyTo);
	if(air_panel!=NULL) {
	  air_panel->setActionMode(RDAirPlayConf::CopyTo);
	}
	break;

      default:
	break;
  }
}


int main(int argc,char *argv[])
{
  QApplication a(argc,argv);
  
  //
  // Load Translations
  //
  QTranslator qt(0);
  qt.load(QString("/usr/share/qt4/translations/qt_")+QTextCodec::locale(),
	  ".");
  a.installTranslator(&qt);

  QTranslator rd(0);
  rd.load(QString(PREFIX)+QString("/share/rivendell/librd_")+
	     QTextCodec::locale(),".");
  a.installTranslator(&rd);

  QTranslator rdhpi(0);
  rdhpi.load(QString(PREFIX)+QString("/share/rivendell/librdhpi_")+
	     QTextCodec::locale(),".");
  a.installTranslator(&rdhpi);

  QTranslator tr(0);
  tr.load(QString(PREFIX)+QString("/share/rivendell/rdairplay_")+
	     QTextCodec::locale(),".");
  a.installTranslator(&tr);

  //
  // Start Event Loop
  //
  MainWidget *w=new MainWidget();
  a.setMainWidget(w);
  w->setGeometry(QRect(QPoint(0,0),w->sizeHint()));
  w->show();
  return a.exec();
}


QString logfile;


void LogLine(RDConfig::LogPriority prio,const QString &line)
{
  FILE *file;

  rda->config()->log("rdairplay",prio,line);

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

  QDateTime current=QDateTime::currentDateTime();
  if((file=fopen(logfile,"a"))==NULL) {
    return;
  }
  chmod(logfile,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
  fprintf(file,"%02d/%02d/%4d - %02d:%02d:%02d.%03d : %s\n",
	  current.date().month(),
	  current.date().day(),
	  current.date().year(),
	  current.time().hour(),
	  current.time().minute(),
	  current.time().second(),
	  current.time().msec(),
	  (const char *)line);
  fclose(file);
}


bool MainWidget::FirstPort(int index)
{
  for(int i=0;i<index;i++) {
    if((air_meter_card[index]==air_meter_card[i])&&
       (air_meter_port[index]==air_meter_port[i])) {
      return false;
    }
  }
  return true;
}


bool MainWidget::AssertChannelLock(int dir,int card,int port)
{
  return AssertChannelLock(dir,AudioChannel(card,port));
}


bool MainWidget::AssertChannelLock(int dir,int achan)
{
  if(achan>=0) {
    int odir=!dir;
    if(air_channel_timers[odir][achan]->isActive()) {
      air_channel_timers[odir][achan]->stop();
      return false;
    }
    air_channel_timers[dir][achan]->start(AIR_CHANNEL_LOCKOUT_INTERVAL,true);
    return true;
  }
  return false;
}


int MainWidget::AudioChannel(int card,int port) const
{
  return RD_MAX_PORTS*card+port;
}


RDAirPlayConf::Channel MainWidget::PanelChannel(int mport) const
{
  RDAirPlayConf::Channel chan=RDAirPlayConf::SoundPanel1Channel;
  switch(mport) {
  case 0:
    chan=RDAirPlayConf::SoundPanel1Channel;
    break;

  case 1:
    chan=RDAirPlayConf::SoundPanel2Channel;
    break;

  case 2:
    chan=RDAirPlayConf::SoundPanel3Channel;
    break;

  case 3:
    chan=RDAirPlayConf::SoundPanel4Channel;
    break;

  case 4:
    chan=RDAirPlayConf::SoundPanel5Channel;
    break;
  }
  return chan;
}
