//
//  The contents of this file are subject to the Mozilla Public License
//  Version 1.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.mozilla.org/MPL/
// 
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
//  the License for the specific language governing rights and limitations
//  under the License.
// 
//  The Original Code is CPhone, a cross platform voip gui.
//
//  The Initial Developer of the Original Code is Derek Smithies.
//
//  Copyright (C) 2002 Indranet Technologies Ltd, 
//                                  http://www.indranet-technologies.com
//  All Rights Reserved.
//
//  Contributor(s): _______________


/*
 *
 *  $Log: mainwindowSub.cxx,v $
 *  Revision 1.21  2005/02/02 23:14:46  dereksmithies
 *  Fix compilation for when IXJ is absent.
 *
 *  Revision 1.20  2004/06/02 04:56:43  dereksmithies
 *  Bring up to date with CVS as of today, Uses codec plugins now.
 *  You may need to set the PWLIBPLUGINPATH environment variable.
 *
 *  Revision 1.19  2004/04/23 05:09:59  dereksmithies
 *  Fixes so it works with latest openh323 lib, and not lock X display.
 *
 *  Revision 1.18  2004/04/21 22:08:43  dereksmithies
 *  Fixes to bring up to date with current openh323 CVS. Thanks to Peter Nixon.
 *
 *  Revision 1.17  2004/04/14 07:25:19  dereksmithies
 *  Updates, so it compiles on windows XP. Big Thanks to Francisco Huerta
 *
 *  Revision 1.16  2004/02/19 21:25:43  dereksmithies
 *  Make nat traversal update address on incoming call. Thanks dag@bakke.com
 *
 *  Revision 1.15  2004/02/18 21:21:49  dereksmithies
 *  Implement suggestion from dag@bakke.com - nat traversal name resolved on call startup also.
 *
 *  Revision 1.14  2004/02/18 03:16:55  dereksmithies
 *  Fix Nat translation feature.  Thanks to dag@bakke.com for finding the problem.
 *
 *  Revision 1.13  2004/01/02 04:06:24  dereksmithies
 *  Bring up to date with openh323 libraries. Get Plugins to work corectly.
 *
 *  Revision 1.12  2003/09/02 03:37:32  dereksmithies
 *  Add statistics, new icons, toolbuttons (icon + name)
 *
 *  Revision 1.11  2003/08/26 04:48:02  dereksmithies
 *  Fixes to get statistics working right, add icons, add ok,cancel,apply buttons
 *
 *  Revision 1.10  2003/08/07 23:56:38  dereksmithies
 *  Main Program Icon always comes up at the end of a call.
 *
 *  Revision 1.9  2003/08/06 23:36:39  peternixon
 *  Change image to an embedded XPM
 *
 *  Revision 1.8  2003/08/06 21:41:19  peternixon
 *  stop segfault if graphic does not exist
 *
 *  Revision 1.7  2003/08/06 05:04:25  dereksmithies
 *  Add feature to display bmp
 *
 *  Revision 1.6  2003/05/20 03:32:28  dereksmithies
 *  Better handling of ixj handset states, and call progress states.
 *
 *  Revision 1.5  2003/05/09 04:34:46  dereksmithies
 *  Tidy up gatekeeper finding. Works reliably.
 *
 *  Revision 1.4  2003/05/09 03:08:24  dereksmithies
 *  Fix Microtelco handling, and message display
 *
 *  Revision 1.3  2003/05/06 22:40:36  dereksmithies
 *  Fixes to get it working correctly on microtelco.
 *
 *  Revision 1.2  2003/04/04 04:37:49  dereksmithies
 *  Major upgrade.
 *  Ixj & microtelco support added. Fix threading issues.
 *
 *  Revision 1.1.1.1  2002/05/12 22:55:05  dereksmithies
 *  Initial release.
 *
 *
 *
 *
 */

#include <ptlib.h>


#include <qapplication.h>
#include <qlabel.h>
#include <qtoolbutton.h>
#include <qapp.h>

#include "addressbookSub.h"
#include "audiopropertiesSub.h"
#include "callanswer.h"
#include "callanswerSub.h"
#include "cpendpoint.h"
#include "main.h"
#include "mainwindowSub.h"
#include "message.h"
#include "qtvid.h"
#include "settingsSub.h"
#include "videopropertiesSub.h"
#include "statisticsSub.h"

#include "h323pdu.h"
#include "h261codec.h"
#include "h263codec.h"
#include "ffh263codec.h"

#include <h323.h>

#include "images/cphone.xpm"
#include "images/connect_on.xpm"
#include "images/connect_off.xpm"


MainUi::MainUi()
  :FormMainWindow(0, "main ui", FALSE, 
                   WDestructiveClose | WType_TopLevel)
{
//  move(20,20);
  cPhoneIcon = NULL;
  epState = epNoTone;
  QCustomEvent *ce =new QCustomEvent(MAIN_UI_UP);
  QApplication::postEvent(this, ce);
}

MainUi::~MainUi()
{
  if (transmitVideoChannel != NULL)
    delete  transmitVideoChannel;

  if (receiveVideoChannel != NULL)
    delete receiveVideoChannel;
#ifdef HAS_IXJ
  lidThread.TerminateThread();
  lidThread.WaitForTermination();
#endif
}

void MainUi::InitialiseConfiguration()
{
  remoteComputer   = "";
  toolButtonConnect->setPixmap((const char **)connect_on);

  localVideoThread = NULL;
  transmitVideoChannel = NULL;
  receiveVideoChannel  = NULL;

  firstErrorOpenAudioChannel = TRUE;
  applicationIsExiting = FALSE;

#ifdef HAS_IXJ
  ixjCardNames = OpalIxJDevice::GetDeviceNames();
  lidThread.StartRunning();
#endif

  connectOpts.Initialise();
  videoOpts.Initialise();
  audioOpts.Initialise();

  soundCardNames = PSoundChannel::GetDeviceNames(PSoundChannel::Player);

  InitialiseVideoDisplay();

  CPhone::Current().SetWindowId(eMainUi, winId());


  QPixmap  p = QPixmap(cphone_xpm);
  cPhoneIcon = new QImage(p.width(), p.height(), 32);
  *cPhoneIcon = p;
  cPhoneIcon->convertDepth(32);


  CreateLocalVideoDisplayThread();

  audioOpts.AudioDeviceChange();

  //  return;

  if (InitialiseVoipSettings() == FALSE) {
    cerr << "Program cannot run due to an initialisation failure." << endl;
    exit(00);
  }
}

void MainUi::AudioClicked_slot()
{
  if (CPhone::Current().GetWindow(eAudioProperties) != NULL) {
    CPhone::Current().GetWindow(eAudioProperties)->raise();
    return;
  }

  AudioProperties *audioProperties = new AudioProperties(audioOpts);
  
  audioProperties->show();
}

void MainUi::ConnectClicked_slot()
{
  if (!currentCallToken.IsEmpty()) {
    ExitCurrentCall();
    return;
  }
  
  if (CPhone::Current().GetWindow(eAddressBook) != NULL) {
    CPhone::Current().GetWindow(eAddressBook)->raise();
    return;
  }

  AddressBook  *addressBook = new AddressBook();

  addressBook->show();
}

void MainUi::VideoClicked_slot()
{
  if (CPhone::Current().GetWindow(eVideoProperties) != NULL) {
    CPhone::Current().GetWindow(eVideoProperties)->raise();
    return;
  }

  VideoProperties *videoProperties = new VideoProperties(videoOpts);

  videoProperties->show();
}

void MainUi::SettingsClicked_slot()
{
  if (CPhone::Current().GetWindow(eSettings) != NULL) {
    CPhone::Current().GetWindow(eSettings)->raise();
    return;
  }

  Settings  *settingsCall = new Settings(connectOpts);

  settingsCall->show();
}


void MainUi::ExitClicked_slot()
{
  close(TRUE);
}


PVideoOutputDevice *MainUi::GetDisplayModule (BOOL isEncoding, BOOL isFlipped)
{
  VideoDisplay *displayWidget = 
       ((isEncoding) ? FormMainWindow::VideoDisplayLocal : 
                     FormMainWindow::VideoDisplayReceived);
  
  return  new QtVideoDevice(isEncoding, displayWidget, isFlipped);
}


BOOL MainUi::CreateLocalVideoDisplayThread()
{
  QImage *q = new QImage(*cPhoneIcon);
  q->detach();
  FormMainWindow::VideoDisplayReceived->DisplayQImage(q);
  
  if (!videoOpts.GetDisplayLocal())
    return TRUE;

  if (localVideoThread != NULL)
    return TRUE;

  transmitVideoChannel->AttachVideoPlayer(GetDisplayModule(TRUE, videoOpts.GetFlipLocal()), FALSE);

  localVideoThread = new LocalVideoDisplayThread(transmitVideoChannel);

  return TRUE;
}

BOOL MainUi::DeleteLocalVideoDisplayThread()
{
  if (localVideoThread != NULL) {
    localVideoThread->InitiateTermination();
    localVideoThread->WaitForTermination();
    delete localVideoThread;
    localVideoThread = NULL;
  }

  return TRUE;
}

void MainUi::SetDisplayString(QString qMessage)
{
  LabelDisplayUserInformation->prepareDisplayText(qMessage);

  QCustomEvent *ce =new QCustomEvent(QEvent::User);
  QApplication::postEvent(LabelDisplayUserInformation, ce);
  killTimers();

  startTimer(20 * 1000); //display text for 20 seconds;
}

void MainUi::DisplayInfoMessage(char *message)
{
  QString qMessage(message);
  DisplayInfoMessage(qMessage);
}

void MainUi::DisplayInfoMessage(PString message)
{
  QString qMessage(message.GetPointer());
  DisplayInfoMessage(qMessage);
}

void MainUi::DisplayInfoMessage(QString message)
{
  MessageCustomEvent * error = new MessageCustomEvent(CUSTOM_EVENT_MESSAGE, "Information", message);
  QApplication::postEvent(this, error);
}


void MainUi::DisplayCriticalError(QString message)
{
  MessageCustomEvent * error = new MessageCustomEvent(CUSTOM_EVENT_MESSAGE, "Critical", message);
  QApplication::postEvent(this, error);
}

void MainUi::DisplayCriticalError(PString message)
{
  QString qMessage(message.GetPointer());
  DisplayCriticalError(qMessage);
}

void MainUi::DisplayCriticalError(char *  message)
{
  QString qMessage(message);
  DisplayCriticalError(qMessage);
}

void  MainUi::QueryCallAnswer(PString remoteNode)
{
  if (CPhone::Current().GetWindow(eCallAnswer) != NULL)
    return;

  remoteComputer = remoteNode.GetPointer();
  QString incomingCall("Incoming call from " + remoteComputer);

  DisplayMessage(incomingCall);
#if defined(HAS_IXJ)
  lidThread.RingPhone();
#endif
  QApplication::postEvent(this, new QCustomEvent(CALL_ANSWER_INITIATE));
}


void MainUi::closeEvent(QCloseEvent *e)
{
  StartTerminationProcess();

  e->accept();
}

void MainUi::customEvent(QCustomEvent *e)
{
  //THESE three options are generated by the callanswer window.
  //The CallAnswer window guarantees one response per instantiation.
  if (e->type() == CALL_ANSWER_RESPONSE_YES) {
    AnswerCall(H323Connection::AnswerCallNow);  //yes from callanswer.
    DisplayMessage("In call with " + remoteComputer);
  }

  if (e->type() == CALL_ANSWER_RESPONSE_NO) 
    AnswerCall(H323Connection::AnswerCallDenied); //no from callanswer.

  if (e->type() == CALL_ANSWER_RESPONSE_TMO) 
    AnswerCall(H323Connection::AnswerCallDenied); //timeout from callanswer.

  //THIS option is generated when this class is created.
  //ensures that things are only built when the gui is ready for action.
  if (e->type() == MAIN_UI_UP) 
    InitialiseConfiguration();
 

  if (e->type() == CALL_ANSWER_INITIATE) {
    CallAnswer * dialogCallAnswer = new CallAnswer(remoteComputer);
    dialogCallAnswer->show();
  }
    

  if (e->type() == CUSTOM_EVENT_MESSAGE) {
    MessageCustomEvent *ce = (MessageCustomEvent *) e;
    ThreadSafeMessage *m = new ThreadSafeMessage(ce->GetTitle(), ce->GetMessage());
    m->show();
  }

  return;  
}

void MainUi::timerEvent(QTimerEvent *)
{
  LabelDisplayUserInformation->clear(); //setText("");
}

void MainUi::OnConnectionEstablished(PString message)
{
  //Before the current call starts to use video, delete the local display.
  DeleteLocalVideoDisplayThread();
  DisplayMessage(message);
  OnConnectionEstablished(TRUE);
}


void MainUi::OnConnectionCleared(PString message)
{
  DisplayMessage(message);

  videoOpts.AttachVideoCodec(NULL);
  CreateLocalVideoDisplayThread();

  OnConnectionEstablished(FALSE);
}

void MainUi::OnConnectionEstablished(BOOL isEstablished)
{
  Settings        *settingsCall = (Settings *)CPhone::Current().GetWindow(eSettings);
  if (settingsCall != NULL)
    settingsCall->OnConnectionEstablished(isEstablished);

  AudioProperties *audioProperties = (AudioProperties *)CPhone::Current().GetWindow(eAudioProperties);
  if (audioProperties != NULL)
    audioProperties->OnConnectionEstablished(isEstablished);

  AddressBook     *addressBook = (AddressBook *)CPhone::Current().GetWindow(eAddressBook);
  if (addressBook != NULL)
    addressBook->OnConnectionEstablished(isEstablished);

  UpdateToolButtonConnect(isEstablished);
}

void MainUi::UpdateToolButtonConnect(BOOL isEstablished)
{
  toolButtonConnect->setPixmap((const char **)(isEstablished ? connect_off : connect_on));
  toolButtonConnect->setTextLabel(isEstablished ? tr("Disconnect") : tr("Connect"));
}

void MainUi::DisplayMessage(PString message)
{
  DisplayMessage(QString((const char *)message));
}

void MainUi::DisplayMessage(char * message)
{
  DisplayMessage(QString(message));
}

void MainUi::DisplayMessage(QString message)
{
//  PTRACE(1, "GuiThread\t MainWindow " << (const char *)message);
  SetDisplayString(message);
}

BOOL MainUi::DisplayLocalIsNow(BOOL doDisplayLocal)
{
  if (doDisplayLocal)
    transmitVideoChannel->AttachVideoPlayer(GetDisplayModule(TRUE, videoOpts.GetFlipLocal()), FALSE);
  else 
    transmitVideoChannel->AttachVideoPlayer(PVideoOutputDevice::CreateDevice("NULL"), FALSE);

  if (IsInCall()) 
    return TRUE;

  if (doDisplayLocal) 
    CreateLocalVideoDisplayThread();
  else 
    DeleteLocalVideoDisplayThread();
  
  return TRUE;
}

void MainUi:: TranslateTCPAddress(PIPSocket::Address &localAddr, const PIPSocket::Address &remoteAddr)
{
  if (connectOpts.doNatTraversal) // &&  (remoteAddr.Byte1() != 192)) 
    localAddr = masqAddress;
  
  return;
}

BOOL MainUi::AttachPlayerReaderToVideoChannel(BOOL isEncoding)
{
  CpVideoChannel *vChannel= isEncoding ? transmitVideoChannel : receiveVideoChannel;
  BOOL          doFlip   = isEncoding ? videoOpts.GetFlipLocal() : videoOpts.GetFlipReceived();

  if (isEncoding) {    
    if (videoOpts.GetDisplayLocal())
      vChannel->AttachVideoPlayer(GetDisplayModule(isEncoding, doFlip), FALSE);

    vChannel->AttachVideoReader(NULL, FALSE);
    vChannel->AttachVideoReader(videoOpts.GetNewVideoGrabber(), FALSE);
  } else
    vChannel->AttachVideoPlayer(GetDisplayModule(isEncoding, doFlip), FALSE);

  return TRUE;
}

BOOL MainUi::InitialiseVideoDisplay()
{
  if (transmitVideoChannel == NULL)
    transmitVideoChannel = new CpVideoChannel();

  if (receiveVideoChannel == NULL)
    receiveVideoChannel = new CpVideoChannel();

  videoOpts.AttachVideoChannels(receiveVideoChannel, transmitVideoChannel);

  AttachPlayerReaderToVideoChannel(TRUE);  //Encoding 
  AttachPlayerReaderToVideoChannel(FALSE); //Decoding

  return TRUE;
}

void MainUi::DeleteVideoDisplay()
{
  if (receiveVideoChannel != NULL) 
   receiveVideoChannel->AttachVideoPlayer(NULL, FALSE);

 if (transmitVideoChannel != NULL) {
   transmitVideoChannel->AttachVideoPlayer(NULL, FALSE);
   transmitVideoChannel->AttachVideoReader(NULL, FALSE);
 }
}

void MainUi::UpdateUdpPorts()
{
    SetRtpIpPorts(connectOpts.lowUdpPort, connectOpts.highUdpPort); /* PortBase, PortMax*/
}

PStringToString MainUi::GetInterfaceAddressList()
{
  PIPSocket::InterfaceTable if_table;

  if (!PIPSocket::GetInterfaceTable(if_table)) {
    cerr << "GetInterfaceTable() failed. No interface table" << endl;
    exit(0);
  }

  PStringToString interfaceList;
  for (PINDEX i = 0; i < if_table.GetSize(); i++)
    interfaceList.SetAt(if_table[i].GetName(), if_table[i].GetAddress());

  return interfaceList;
}


BOOL MainUi::InitialiseVoipSettings()
{
  if (!connectOpts.userName.IsEmpty()) 
    SetLocalUserName(connectOpts.userName);

  initialBandwidth = 100000; // Standard 10base LAN in 100's of bits/sec

  soundChannelBuffers = audioOpts.GetBuffers();

  PINDEX i;
  PStringToString nameIpAddress = GetInterfaceAddressList();

  for (i = 0; i < nameIpAddress.GetSize(); i++) {
    PString interfaceStr = nameIpAddress.GetDataAt(i);

    PIPSocket::Address interfaceAddress(interfaceStr);
    
    H323ListenerTCP * listener = new H323ListenerTCP(*this, interfaceAddress, H323EndPoint::DefaultTcpPort);
    if (!StartListener(listener)) {
      cerr << "Warning:: could not open H.323 listener port on "
	   << interfaceAddress << PString(":") << PString(H323EndPoint::DefaultTcpPort) << endl;
      delete listener;   
    } 
  }

  if (GetListeners().GetSize() == 0) {
    cerr << "\n\nError: could not get any H323 listeners on ::" << endl
	 << nameIpAddress << endl
	 << "\n\nIs there another copy of CPhone already running?" << endl;

    return FALSE;
  }

  UpdateUdpPorts();
  AddAllCapabilities();
  connectOpts.LoadCapabilitiesFromDisk();
  RebuildCapabilityTable();

  if (connectOpts.doMicroTelco) {
    connectOpts.doMicroTelco = MicroTelcoActivate(TRUE);
    if (!connectOpts.doMicroTelco) 
      DisplayCriticalError("Failed to connect to MicroTelco \n" + GetGatekeeperRegistrationMessage());
  }

  if (!InitialiseGatekeeper())
      DisplayCriticalError("Gatekeeper discovery process \n" + GetGatekeeperRegistrationMessage());
  
  UpdateNatTraversalAddress();

  return TRUE;
}
  
void MainUi::SetCodecFrames(const PString & fmt, unsigned frames)
{
  H323Capability * cap = capabilities.FindCapability(fmt);
  if (cap == NULL) 
   DisplayMessage("Cannot find codec " + fmt);
  else 
    cap->SetTxFramesInPacket(frames);
}


void MainUi::AddAllCapabilities()
{ 
  int packetUtilisation = connectOpts.packetUtilization;
  
  int g711Opts[11] = {10, 12, 14, 18, 24, 30, 47, 80, 130, 190, 240};
  int g711Frames = g711Opts[packetUtilisation];

  int gsmFrames = (int) (((double)packetUtilisation * 6)/10) + 1;

  capabilities.RemoveAll();


#if defined(HAS_IXJ)
  lidThread.AddIxjCapabilities(capabilities);
#endif

  H323EndPoint::AddAllCapabilities(0, 0, "*");

  SetCodecFrames(OpalGSM0610, gsmFrames);
  SetCodecFrames(OpalG711uLaw64k, g711Frames);
  SetCodecFrames(OpalG711ALaw64k, g711Frames);
 
  //AddCapability puts the codec into the list of codecs we can send
  //SetCapability puts the codec into the list of codecs we can send and receive

  SetCapability(0, 1, new H323_H261Capability(2, 0, FALSE, FALSE, 6217));
  SetCapability(0, 1, new H323_H261Capability(0, 4, FALSE, FALSE, 6217));

#define ADD_H263_VIDEO_CAPAB(a)                         \
      SetCapability(0, 1, new  a(0, 0, 1, 0, 0, 0, 0)); \
      SetCapability(0, 1, new  a(0, 1, 0, 0, 0, 0, 0)); 

#if H323_AVCODEC
  ADD_H263_VIDEO_CAPAB (H323_FFH263Capability);
#endif
#if H323_VICH263
  ADD_H263_VIDEO_CAPAB (H323_H263Capability);
#endif

  H323_UserInputCapability::AddAllCapabilities(capabilities, 0, P_MAX_INDEX);

  PStringList availableCapabilities;
  for(PINDEX i = 0; i < capabilities.GetSize(); i++)
    availableCapabilities += capabilities[i].GetFormatName();
  connectOpts.SetAvailableCapabilities(availableCapabilities);
}


void MainUi::ReorderCapabilities()
{
  PStringArray toRemove = connectOpts.GetDisabledCapabilities();
  capabilities.Remove(toRemove);

  PStringArray toReorder = connectOpts.GetEnabledCapabilities();
  capabilities.Reorder(toReorder);

  for(PINDEX i = 0; i < toReorder.GetSize(); i++) 
    if (toReorder[i].Find("CIF") != P_MAX_INDEX) {
      autoStartTransmitVideo = TRUE;
      autoStartReceiveVideo  = TRUE;
      break;
    }
}

void MainUi::RebuildCapabilityTable()
{
  AddAllCapabilities();
  ReorderCapabilities();
}

BOOL MainUi::SetSoundDevice(const char * optionName,
                                    PSoundChannel::Directions dir)
{
  PString dev = audioOpts.GetAudioDevice();

  if (dir == PSoundChannel::Player) {
    if (SetSoundChannelPlayDevice(dev))
      return TRUE;
  }
  else {
    if (SetSoundChannelRecordDevice(dev))
      return TRUE;
  }

  cerr << "SET SOUND DEVICE to be "<< dev << " failed."<< endl;  
  return FALSE;
}


H323Connection * MainUi::CreateConnection(unsigned callReference)
{
  unsigned opt = 0;
  UpdateNatTraversalAddress();

  if (connectOpts.noFastStart)
    opt += H323Connection::FastStartOptionDisable;

  if (connectOpts.noH245Tunnelling) 
    opt += H323Connection::H245TunnelingOptionDisable;

  return new CpConnection(callReference, opt);
}


BOOL MainUi::OnIncomingCall(H323Connection & connection,
                                    const H323SignalPDU & setupPDU,
                                    H323SignalPDU &)
{
  if (applicationIsExiting)
    return FALSE;

  if (setupPDU.m_h323_uu_pdu.HasOptionalField(H225_H323_UU_PDU::e_nonStandardData)) {
    PString param = setupPDU.m_h323_uu_pdu.m_nonStandardData.m_data.AsString();
    if (!param)
      cout << "Incoming setup PDU contains non-standard parameter: \"" << param << "\"." << endl;
  }

  if (!currentCallToken.IsEmpty())
    cout << "WARNING: current call token not empty" << endl;

  if (currentCallToken.IsEmpty()  ) {
    currentCallToken = connection.GetCallToken();
    return TRUE;
  }

  PTime now;
  PString msg = "Call from \"" + connection.GetRemotePartyName() + "\" ignored";
  DisplayMessage(msg);
  connection.ClearCall(H323Connection::EndedByLocalBusy);

  return FALSE;
}

BOOL MainUi::WaitingForACall()
{
  return currentCallToken.IsEmpty();
}


void MainUi::OnNoAnswerTimeout()
{
  H323Connection * connection = FindConnectionWithLock(currentCallToken);
  if (connection == NULL)
    return;

  connection->ClearCall(H323Connection::EndedByNoAnswer);

  connection->Unlock();
}

void MainUi::OnConnectionEstablished(H323Connection & connection,
                                             const PString & /*token*/)
{
  OnConnectionEstablished( "Call with \"" +connection.GetRemotePartyName() + "\" started");
  epState = epCallInProgress;
}


void MainUi::OnConnectionCleared(H323Connection & connection, const PString & clearedCallToken)
{
  firstErrorOpenAudioChannel = TRUE;

  // stop any request for "Call Answer Y/N"
  StopCallAnswer();  

  audioOpts.AttachAudioChannel(TRUE, NULL); //IsEncoding channel is set to NULL
  audioOpts.AttachAudioChannel(FALSE, NULL);//receiving channel is set to NULL

  // ignore connections that are not the current connection
  if (clearedCallToken != currentCallToken) {
    return;
  }

  currentCallToken = PString(); // indicate that our connection is now cleared

  // indicate call has hungup
  epState = epCallHungup;

  PString remoteName = "\"" + connection.GetRemotePartyName() + "\"";
  PString message;

  switch (connection.GetCallEndReason()) {
  case H323Connection::EndedByCallForwarded :
    message = "";
    break;
  case H323Connection::EndedByRemoteUser :
    message = remoteName + " has cleared the call";
    break;
  case H323Connection::EndedByCallerAbort :
    message = remoteName + " has stopped calling";
    break;
  case H323Connection::EndedByRefusal :
    message = remoteName + " did not accept your call";
    break;
  case H323Connection::EndedByRemoteBusy :
    message = remoteName + " was busy";
    break;
  case H323Connection::EndedByRemoteCongestion :
    message = PString("Congested link to ") + remoteName;
    break;
  case H323Connection::EndedByNoAnswer :
    message = remoteName + " did not answer your call";
    break;
  case H323Connection::EndedByTransportFail :
    message = "Call with " + remoteName + " ended abnormally";
    break;
  case H323Connection::EndedByCapabilityExchange :
    message = "Could not find common codec with " + remoteName;
    break;
  case H323Connection::EndedByNoAccept :
    message = "Did not accept incoming call from " + remoteName;
    break;
  case H323Connection::EndedByAnswerDenied :
    message = "Refused incoming call from " + remoteName;
    break;
  case H323Connection::EndedByNoUser :
    message = "Gatekeeper could find user " + remoteName;
    break;
  case H323Connection::EndedByNoBandwidth :
    message = "Call to " + remoteName + " aborted, insufficient bandwidth";
    break;
  case H323Connection::EndedByUnreachable :
    message = remoteName + " could not be reached";
    break;
  case H323Connection::EndedByHostOffline :
    message = remoteName + " is not online";
    break;
  case H323Connection::EndedByNoEndPoint :
    message = "No phone running for " + remoteName;
    break;
  case H323Connection::EndedByConnectFail :
    message = "Transport error calling " + remoteName;
    break;
  default :
    PTimeInterval timeInt = PTime() - connection.GetConnectionStartTime() ;
    message = "Goodbye " + remoteName;
    if (IsInCall())
      message += ". Duration: " + timeInt.AsString(0);
  }

#if defined(HAS_IXJ)
  lidThread.RingPhone(FALSE);
#endif
  OnConnectionCleared(message);
}

//When a connection to a remote computer is created, this routine is called lots
//of times (4). We want to avoid displaying 4 Critical error messages.
//The variable firstErrorOpenAudioChannel is used to do this.
BOOL MainUi::OpenAudioChannel(H323Connection & connection,
                                         BOOL isEncoding,
                                         unsigned bufferSize,
                                         H323AudioCodec & codec)
{
 
#if defined(HAS_IXJ)
  if (lidThread.lid.IsOpen()) {
//    PTRACE(2, "ep\tAttaching " << lidThread.GetDeviceName() << " to codec");
    if (!codec.AttachChannel(new OpalLineChannel(lidThread.lid, OpalIxJDevice::POTSLine, codec)))
      return FALSE;
  }
  else
#endif
  if (!H323EndPoint::OpenAudioChannel(connection, isEncoding, bufferSize, codec)) {
    PStringStream str;
    if (isEncoding)
      str << GetSoundChannelRecordDevice();
    else
      str << GetSoundChannelPlayDevice();
    
    PString thisMessage = "Sound device \"" + str + "\" could not be opened.\n\n" + 
                     "Check:\n" +
                     "           1)Full duplex device, \n" +
                     "           2)permissions" ;

    if (firstErrorOpenAudioChannel) {
      firstErrorOpenAudioChannel = FALSE;
      DisplayCriticalError(thisMessage) ;
    }

    return FALSE;
  }

  codec.SetSilenceDetectionMode(audioOpts.GetDisableSilence() ?
				H323AudioCodec::NoSilenceDetection     :
				H323AudioCodec::AdaptiveSilenceDetection );

  PChannel *channel = codec.GetRawDataChannel();
  audioOpts.AttachAudioChannel(isEncoding, channel);

  if (isEncoding)
    audioOpts.VolumeMicrophoneChange();
  else
    audioOpts.VolumeSpeakerChange();

  return TRUE;
}


BOOL MainUi::OpenVideoChannel(H323Connection & connection,
                                     BOOL isEncoding,
                                     H323VideoCodec & codec)
{
  CpVideoChannel      * channel;
  channel = isEncoding ? transmitVideoChannel : receiveVideoChannel;

  if (isEncoding) {
    codec.SetTxQualityLevel(videoOpts.GetTransmitQuality());
    codec.SetBackgroundFill(4); //The videoFill parameter in ohphone);   
    codec.SetMaxBitRate(0);     // this disables bitrate control

    videoOpts.AttachVideoCodec(&codec);
  }
 
  //Select false, do NOT delete video channel on closing codec.
  return codec.AttachChannel(channel, FALSE);
}



//
//  if gateway is empty,     dest is assumed to be IP address[:port]
//  if gateway is non-empty, 
//             - gateway is assumed to be an IP address[:port]port,
//             - dest is an e164 address which is passed to the gateway.
//
void MainUi::MakeOutgoingCall(const PString & dest,
                                      const PString & gateway)
{
  UpdateNatTraversalAddress();

  DisplayMessage("Call " + dest);
  PString fullAddress;

  if (!gateway.IsEmpty()) {
    fullAddress = gateway;
    fullAddress = dest.Trim() + '@' + fullAddress;
  } else
    fullAddress = dest;

  UpdateToolButtonConnect(true);

  if (!MakeCall(fullAddress, currentCallToken)) {
    DisplayMessage("Cannot make call to \"" + fullAddress + "\"");
    return;
  }

  epState = epConnectingCall;
}

void MainUi::StartCall(const PString & ostr)
{
  MakeOutgoingCall(ostr, PString());

  return;
}

void MainUi::StartTerminationProcess()
{
  applicationIsExiting = TRUE;

  videoOpts.SaveSettings();
  audioOpts.SaveSettings();
  connectOpts.SaveSettings();

  ClearAllCalls();

#ifdef HAS_IXJ
  lidThread.TerminateThread();
  lidThread.WaitForTermination();
#endif

  epState = epDialTone;

  SendDieMessageToWindow(eAddressBook);
  SendDieMessageToWindow(eAudioProperties);
  SendDieMessageToWindow(eSettings);
  SendDieMessageToWindow(eStatistics);
  SendDieMessageToWindow(eVideoProperties);

  DeleteLocalVideoDisplayThread();
  DeleteVideoDisplay();

  connectOpts.CloseCurrentTraceFile();
}

void MainUi::ExitCurrentCall()
{
  StopCallAnswer();

  if ((epState ==  epDialTone) || (epState == epNoTone))
    return;

  if (!currentCallToken.IsEmpty())
    DisplayMessage("Closing call with " + currentCallToken);
  
  ClearAllCalls();
}

void MainUi::OpenStatistics_slot()
{
  if (CPhone::Current().GetWindow(eStatistics) != NULL)
    return;

  Statistics * dialogStatistics = new Statistics();
    dialogStatistics->show();
}
    
 
BOOL MainUi::SearchForAGatekeeper(PString gkName)
{
  BOOL foundGk = FALSE;

  if(gkName.IsEmpty()) {
    //Search for a gatekeeper of unknown name.
    DisplayMessage("Searching for gatekeeper");
    foundGk = DiscoverGatekeeper(new H323TransportUDP(*this));
  } else {
    //Search for gatekeeper of known name.
    DisplayMessage("Search for \"" + gkName + "\"");
    foundGk = LocateGatekeeper(gkName);
  }

  if (foundGk) {
    PStringStream str;
    str << *gatekeeper;
    DisplayMessage("Gatekeeper \"" + str + "\" found");
    connectOpts.gatekeeperName = str;
  } else {
    if(gkName.IsEmpty())
      DisplayMessage("Sorry: Gatekeeper discovery failed");
    else
      DisplayMessage("Gatekeeper \"" + gkName + "\" not found");
  }
  
  return foundGk;
}



void MainUi::AnswerCall(H323Connection::AnswerCallResponse response)
{
  if (epState != epAnsweringCall)
    return;
  
  //Make absolutely certain, "Call Answer Y/N"  has died.
  StopCallAnswer();

  H323Connection * connection = FindConnectionWithLock(currentCallToken);
  if (connection == NULL)
    return;

  connection->AnsweringCall(response);
  connection->Unlock();

  if (response == H323Connection::AnswerCallNow)     
    epState = epCallInProgress;
  else 
    epState = epCallHungup;
}

void MainUi::SendDieMessageToWindow(enum WindowName name)
{
  QWidget *dialog = CPhone::Current().GetWindow(name);

 if (dialog != NULL) 
    QApplication::sendEvent(dialog, new QCustomEvent(CUSTOM_EVENT_DIE));
}


void MainUi::StopCallAnswer()
{
  SendDieMessageToWindow(eCallAnswer);
}

BOOL MainUi::SetDisableSilenceDetection(BOOL disableSilence)
{
  SetSilenceDetectionMode(disableSilence ? 
			  H323AudioCodec::NoSilenceDetection     :
			  H323AudioCodec::AdaptiveSilenceDetection );
  return TRUE;
}

BOOL MainUi::SetEchoCancellation(PString newEchoCancellation)
{
#if defined(HAS_IXJ)
  PStringArray names = GetAvailableEchoCancNames();
  for (PINDEX i = 0; i < names.GetSize(); i++)
    if (strcmp(newEchoCancellation, names[i]) == 0) {
      return lidThread.SetEchoCancellation(i);
    }
#endif
  return FALSE;
};

PStringArray MainUi::GetAvailableDeviceNames()
{
  return soundCardNames + ixjCardNames;
}

PStringArray MainUi::GetAvailableEchoCancNames()
{
  const char *cNames[] = {"Off", "Low", "Medium", "High", "Auto AEC", "Auto AEC/AGC"};
  PStringArray names(6, cNames, FALSE);
  return names;
}

void MainUi::SetSoundChannelBufferDepth(unsigned int newDepth)
{
  H323EndPoint::SetSoundChannelBufferDepth(newDepth);
}

void MainUi::SetAudioJitterDelay(unsigned int low, unsigned int high)
{
  H323EndPoint::SetAudioJitterDelay(low, high);
}

BOOL MainUi::SetSoundChannelRecordDevice(const PString &newDevice)
{
  return H323EndPoint::SetSoundChannelRecordDevice(newDevice);
}

BOOL MainUi::SetSoundChannelPlayDevice(const PString &newDevice)
{
  return H323EndPoint::SetSoundChannelPlayDevice(newDevice);
}
  

void MainUi::UpdateNatTraversalAddress()
{
  PIPSocket::Address ip;
  if (connectOpts.doNatTraversal) {
    if (!PIPSocket::GetHostAddress(connectOpts.externalNatAddress, ip)) { 
      DisplayCriticalError("Sorry:: Failed to store address for " + connectOpts.externalNatAddress);
      masqAddress = 0;
    } else    
      masqAddress = ip;
  } else
    masqAddress = 0;
}

BOOL  MainUi::MicroTelcoActivate(BOOL doActivate)
{
  if (doActivate) {
    SetLocalUserName(connectOpts.userName); /*Which removes old alisases */
    AddAliasName(connectOpts.microTelcoAccount);
    SetGatekeeperPassword(connectOpts.microTelcoPassword);
    return SetGatekeeper("gk.microtelco.com", new H323TransportUDP(*this));
  }

  RemoveGatekeeper();
  return TRUE;
}

PString MainUi::GetGatekeeperName()
{
  H323Gatekeeper *gk =  CPhone::GetUi().GetGatekeeper();
  if (gk == NULL) 
    return PString();

  return gk->GetName();
}


BOOL MainUi::InitialiseGatekeeper()
{
 BOOL success = TRUE;
 if (connectOpts.searchForGatekeeper) 
      return DiscoverGatekeeper(new H323TransportUDP(*this));


 if (connectOpts.gatekeeperName.IsEmpty()) 
   RemoveGatekeeper();
 else
   success = SetGatekeeper(connectOpts.gatekeeperName);
   
 return success;
}

PString MainUi::GetGatekeeperRegistrationMessage()
{
  H323Gatekeeper *gk = GetGatekeeper();
  if (gk == NULL)
    return "No gatekeeper in system";

  switch (gk->GetRegistrationFailReason()) {
  case  H323Gatekeeper::RegistrationSuccessful:     
    return "Successful";
  case  H323Gatekeeper::UnregisteredLocally:        
    return "Unregistered locally";
  case  H323Gatekeeper::UnregisteredByGatekeeper:   
    return "Unregistered by gatekeeper";
  case  H323Gatekeeper::GatekeeperLostRegistration: 
    return "Lost registration";
  case  H323Gatekeeper::InvalidListener:            
    return "Invalid listener";
  case  H323Gatekeeper::DuplicateAlias:             
    return "Duplicate alias";
  case  H323Gatekeeper::SecurityDenied:             
    return "Security denied";
  case  H323Gatekeeper::TransportError:             
    return "Transport error";
  default:                          
    return "Unknown registration reason";
  }
  return "Unkonwn registration reason";
}

void MainUi::UpdateUserName()
{
  PString newUserName = (const char *)connectOpts.userName;

  SetLocalUserName(newUserName);

}

H323Connection *MainUi::GetCurrentConnectionWithLock()
{
  return FindConnectionWithLock(currentCallToken);
}


H323Connection::AnswerCallResponse MainUi::AnswerCallConditional(const PString & caller, PString newCallToken)
{
  currentCallToken = newCallToken;
  if (connectOpts.autoAnswer) {
    DisplayMessage("Commence call: " + PTime().AsString());
    return H323Connection::AnswerCallNow;
  }

  epState = epAnsweringCall;
  QueryCallAnswer(caller);  

  return H323Connection::AnswerCallPending;
}

void MainUi::HandleHandsetDown(BOOL isDown)
{
#ifdef HAS_IXJ
  DisplayMessage(lidThread.GetDeviceName() + " has gone " + (isDown ? "on" : "off") + " hook");
  if (isDown) {
    ExitCurrentCall();
    epState = epNoTone;
  } else {
    if (CPhone::Current().GetWindow(eCallAnswer) != NULL) 
      QApplication::postEvent(this, new QCustomEvent(CALL_ANSWER_RESPONSE_YES));
    else
      epState = epDialTone;
  }
#endif
}



syntax highlighted by Code2HTML, v. 0.9.1