/****************************************************************************
** ui.h extension file, included from the uic-generated form implementation.
**
** If you wish to add, delete or rename functions or slots use
** Qt Designer which will update this file, pres:erving your code. Ceate an
** init() function in place of a constructor, and a destroy() function in
** place of a destructor.
*****************************************************************************/
/*
    Copyright (C) 2005-2007  Michel de Boer <michel@twinklephone.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "twinkle_config.h"
#include "twinklesystray.h"

// Time (s) that the conversation timer of a line should stay visible after
// a call has ended
#define HIDE_LINE_TIMER_AFTER	5

void MphoneForm::init()
{
	// Forms
	dtmfForm = 0;
	inviteForm = 0;
	redirectForm = 0;
	transferForm = 0;
	termCapForm = 0;
	srvRedirectForm = 0;
	userProfileForm = 0;
	sysSettingsForm = 0;
	logViewForm = 0;
	historyForm = 0;
	selectUserForm = 0;
	selectProfileForm = 0;
	getAddressForm = 0;
	sysTray = 0;
	
	// Popup menu for a single buddy
	QIconSet inviteIcon(QPixmap::fromMimeSource("invite.png"));
	QIconSet messageIcon(QPixmap::fromMimeSource("message.png"));
	QIconSet editIcon(QPixmap::fromMimeSource("edit16.png"));
	QIconSet deleteIcon(QPixmap::fromMimeSource("editdelete.png"));
	buddyPopupMenu = new QPopupMenu(this);
	MEMMAN_NEW(buddyPopupMenu);
	buddyPopupMenu->insertItem(inviteIcon, tr("&Call..."), this, SLOT(doCallBuddy()));
	buddyPopupMenu->insertItem(messageIcon, tr("Instant &message..."), this, SLOT(doMessageBuddy()));
	buddyPopupMenu->insertItem(editIcon, tr("&Edit..."), this, SLOT(doEditBuddy()));
	buddyPopupMenu->insertItem(deleteIcon, tr("&Delete"), this, SLOT(doDeleteBuddy()));
	
	// Change availibility sub popup menu
	changeAvailabilityPopupMenu = new QPopupMenu(this);
	MEMMAN_NEW(changeAvailabilityPopupMenu);
	QIconSet availOnlineIcon(QPixmap::fromMimeSource("presence_online.png"));
	QIconSet availOfflineIcon(QPixmap::fromMimeSource("presence_offline.png"));
	changeAvailabilityPopupMenu->insertItem(availOfflineIcon, tr("O&ffline"), this, 
						SLOT(doAvailabilityOffline()));
	changeAvailabilityPopupMenu->insertItem(availOnlineIcon, tr("&Online"), this, 
						SLOT(doAvailabilityOnline()));
	
	// Popup menu for a buddy list (click on profile name)
	QIconSet changeAvailabilityIcon(QPixmap::fromMimeSource("presence_online.png"));
	QIconSet addIcon(QPixmap::fromMimeSource("buddy.png"));
	buddyListPopupMenu = new QPopupMenu(this);
	MEMMAN_NEW(buddyListPopupMenu);
	buddyListPopupMenu->insertItem(changeAvailabilityIcon, tr("&Change availability"), 
				       changeAvailabilityPopupMenu);
	buddyListPopupMenu->insertItem(addIcon, tr("&Add buddy..."), this, SLOT(doAddBuddy()));
	
	// Tool tip for buddy list
	buddyToolTip = new BuddyListViewTip(buddyListView);
	MEMMAN_NEW(buddyToolTip);
	
	// Line timers
	lineTimer1 = 0;
	lineTimer2 = 0;
	timer1TextLabel->hide();
	timer2TextLabel->hide();
	
	// Timer to hide the conversation timer after a conversation has ended.
	hideLineTimer1 = new QTimer(this);
	MEMMAN_NEW(hideLineTimer1);
	hideLineTimer2 = new QTimer(this);
	MEMMAN_NEW(hideLineTimer2);
	connect(hideLineTimer1, SIGNAL(timeout()), timer1TextLabel, SLOT(hide()));
	connect(hideLineTimer2, SIGNAL(timeout()), timer2TextLabel, SLOT(hide()));
	
	// Attach the MWI flash slot to the MWI flash timer
	connect(&tmrFlashMWI, SIGNAL(timeout()), this, SLOT(flashMWI()));
	
	// Set toolbar icons for disabled options.
	setDisabledIcon(callInvite, "invite-disabled.png");
	setDisabledIcon(callAnswer, "answer-disabled.png");
	setDisabledIcon(callBye, "bye-disabled.png");
	setDisabledIcon(callReject, "reject-disabled.png");
	setDisabledIcon(callRedirect, "redirect-disabled.png");
	setDisabledIcon(callTransfer, "transfer-disabled.png");
	setDisabledIcon(callHold, "hold-disabled.png");
	setDisabledIcon(callConference, "conf-disabled.png");
	setDisabledIcon(callMute, "mute-disabled.png");
	setDisabledIcon(callDTMF, "dtmf-disabled.png");
	setDisabledIcon(callRedial, "redial-disabled.png");
	
	// Set tool button icons for disabled options
	setDisabledIcon(addressToolButton, "kontact_contacts-disabled.png");
	
	// Some text labels on the main window are implemented as QLineEdit
	// objects as these do not automatically resize when a text set with setText
	// does not fit. The background of a QLineEdit is static however, it does not
	// automatically take a background color passed by the -bg parameter.
	// Set the background color of these QLineEdit objects here.
	from1Label->setPaletteBackgroundColor(paletteBackgroundColor());
	to1Label->setPaletteBackgroundColor(paletteBackgroundColor());
	subject1Label->setPaletteBackgroundColor(paletteBackgroundColor());
	from2Label->setPaletteBackgroundColor(paletteBackgroundColor());
	to2Label->setPaletteBackgroundColor(paletteBackgroundColor());
	subject2Label->setPaletteBackgroundColor(paletteBackgroundColor());
	
	// A QComboBox accepts a new line through copy/paste.
	QRegExp rxNoNewLine("[^\\n\\r]*");
	callComboBox->setValidator(new QRegExpValidator(rxNoNewLine, this));
	
	if (sys_config->get_gui_use_systray()) {
		// Create system tray icon
		sysTray = new t_twinkle_sys_tray(this, "twinkle_sys_tray");
		MEMMAN_NEW(sysTray);
		sysTray->setPixmap(
				QPixmap::fromMimeSource("sys_idle_dis.png"));
		sysTray->setCaption(PRODUCT_NAME);
		QToolTip::add(sysTray, PRODUCT_NAME);
		
		// Add items to the system tray menu
#ifdef HAVE_KDE
		KPopupMenu *menu;
#else
		QPopupMenu *menu;
#endif
		menu = sysTray->contextMenu();
		
		// Call menu
		callInvite->addTo(menu);
		callAnswer->addTo(menu);
		callBye->addTo(menu);
		callReject->addTo(menu);
		callRedirect->addTo(menu);
		callTransfer->addTo(menu);
		callHold->addTo(menu);
		callConference->addTo(menu);
		callMute->addTo(menu);
		callDTMF->addTo(menu);
		callRedial->addTo(menu);
		
		menu->insertSeparator();
		
		// Messaging
		actionSendMsg->addTo(menu);
		
		menu->insertSeparator();
		
		// Line activation
		actgrActivateLine->addTo(menu);
		
		menu->insertSeparator();
		
		// Service menu
		serviceDnd->addTo(menu);
		serviceRedirection->addTo(menu);
		serviceAutoAnswer->addTo(menu);
		servicesVoice_mailAction->addTo(menu);
		
		menu->insertSeparator();
		
		// View menu
		viewCall_HistoryAction->addTo(menu);
		
		// Exit application when user selects Quit from the tray menu
		connect(sysTray, SIGNAL(quitSelected()),
			this, SLOT(fileExit()));
		
		sysTray->dock();
		sysTray->show();
	}
}

void MphoneForm::destroy()
{
	if (dtmfForm) {
		MEMMAN_DELETE(dtmfForm);
		delete dtmfForm;
	}
	if (inviteForm) {
		MEMMAN_DELETE(inviteForm);
		delete inviteForm;
	}
	if (redirectForm) {
		MEMMAN_DELETE(redirectForm);
		delete redirectForm;
	}
	if (termCapForm) {
		MEMMAN_DELETE(termCapForm);
		delete termCapForm;
	}
	if (srvRedirectForm) {
		MEMMAN_DELETE(srvRedirectForm);
		delete srvRedirectForm;
	}
	if (userProfileForm) {
		MEMMAN_DELETE(userProfileForm);
		delete userProfileForm;
	}
	if (transferForm) {
		MEMMAN_DELETE(transferForm);
		delete transferForm;
	}
	if (sysSettingsForm) {
		MEMMAN_DELETE(sysSettingsForm);
		delete sysSettingsForm;
	}
	if (logViewForm) {
		if (logViewForm->isShown()) logViewForm->close();
		MEMMAN_DELETE(logViewForm);
		delete logViewForm;
	}
	if (historyForm) {
		if (historyForm->isShown()) historyForm->close();
		MEMMAN_DELETE(historyForm);
		delete historyForm;
	}
	if (selectUserForm) {
		MEMMAN_DELETE(selectUserForm);
		delete selectUserForm;
	}
	if (selectProfileForm) {
		MEMMAN_DELETE(selectProfileForm);
		delete selectProfileForm;
	}
	if (getAddressForm) {
		MEMMAN_DELETE(getAddressForm);
		delete getAddressForm;
	}
	if (sysTray) {
		MEMMAN_DELETE(sysTray);
		delete sysTray;
	}
	
	if (lineTimer1) {
		MEMMAN_DELETE(lineTimer1);
		delete lineTimer1;
	}
	if (lineTimer2) {
		MEMMAN_DELETE(lineTimer2);
		delete lineTimer2;
	}
	MEMMAN_DELETE(hideLineTimer1);
	delete hideLineTimer1;
	MEMMAN_DELETE(hideLineTimer2);
	delete hideLineTimer2;
	MEMMAN_DELETE(buddyPopupMenu);
	delete buddyPopupMenu;
	MEMMAN_DELETE(changeAvailabilityPopupMenu);
	delete changeAvailabilityPopupMenu;
	MEMMAN_DELETE(buddyListPopupMenu);
	delete buddyListPopupMenu;
	MEMMAN_DELETE(buddyToolTip);
	delete buddyToolTip;
}

QString MphoneForm::lineSubstate2str( int line) {
	QString reason;
	
	t_call_info call_info = phone->get_call_info(line);
	
	switch(phone->get_line_substate(line)) {
	case LSSUB_IDLE:	
		return tr("idle");
	case LSSUB_SEIZED:
		return tr("dialing");
	case LSSUB_OUTGOING_PROGRESS:
		reason = call_info.last_provisional_reason.c_str();
		if (reason == "") {
			return tr("attempting call, please wait");
		}
		return reason;
	case LSSUB_INCOMING_PROGRESS:
		return QString("<font color=red>") + tr("incoming call") + "</font>";
	case LSSUB_ANSWERING:
		return tr("establishing call, please wait");
	case LSSUB_ESTABLISHED:
		if (phone->has_line_media(line)) {
			return tr("established");
		} else {
			return tr("established (waiting for media)");
		}
		break;
	case LSSUB_RELEASING:
		return tr("releasing call, please wait");
	default:
		return tr("unknown state");
	}
}

void MphoneForm::closeEvent( QCloseEvent *e )
{
	if (sysTray && sys_config->get_gui_hide_on_close()) {
		hide();
	} else {
		fileExit();
	}
}

void MphoneForm::fileExit()
{
	hide();
	QApplication::exit(0);
}

// Append a string to the display window
void MphoneForm::display( const QString &s )
{
	displayContents.push_back(s);
	if (displayContents.size() > 100) {
		displayContents.pop_front();
	}
	
	displayTextEdit->setText(displayContents.join("\n"));
	
	// Set cursor position at the end of text
	displayTextEdit->setCursorPosition(displayTextEdit->paragraphs() - 1, 0);
}

// Print message header on display
void MphoneForm::displayHeader()
{
	display("");
	display(current_time2str("%a %H:%M:%S").c_str());
}

// Update the conversation timer
void MphoneForm::showLineTimer(int line)
{
	struct timeval t;
	gettimeofday(&t, NULL);
	
	QLabel *timerLabel;
	
	if (line == 0) {
		timerLabel = timer1TextLabel;
	} else {
		timerLabel = timer2TextLabel;
	}
	
	// Calculate duration of call
	t_call_record cr = phone->get_call_hist(line);
	unsigned long duration = t.tv_sec - cr.time_answer;

	timerLabel->setText(timer2str(duration).c_str());
}

void MphoneForm::showLineTimer1()
{
	showLineTimer(0);
}

void MphoneForm::showLineTimer2()
{
	showLineTimer(1);
}

// Update visibility of the conversation timer for a line
// Initialize the timer for a new established call.
void MphoneForm::updateLineTimer(int line)
{
	QLabel *timerLabel;
	QTimer **timer;
	QTimer *hideLineTimer;
	
	if (line == 0) {
		timerLabel = timer1TextLabel;
		timer = &lineTimer1;
		hideLineTimer = hideLineTimer1;
	} else {
		timerLabel = timer2TextLabel;
		timer = &lineTimer2;
		hideLineTimer = hideLineTimer2;
	}
	
	t_line_substate line_substate = phone->get_line_substate(line);
	
	// Stop hide timer if necessary
	switch(line_substate) {
	case LSSUB_IDLE:
	case LSSUB_RELEASING:
		// Timer can be shown as long as line is idle or releasing.
		break;
	default:
		// The timer showing the call duration should only stay
		// for a few seconds as long as the line is idle or being
		// released.
		// If a new call arrives on the line, the hide timer should
		// be stopped, otherwise the timer of the new call will
		// automatically disappear.
		if (hideLineTimer->isActive()) {
			hideLineTimer->stop();
			if (*timer == NULL) timerLabel->hide();
		}
		break;
	}
	
	switch(line_substate) {
	case LSSUB_ESTABLISHED:
		// Initialize and show call duration timer
		if (*timer == NULL) {
			timerLabel->setText(timer2str(0).c_str());
			timerLabel->show();
			*timer = new QTimer(this);
			MEMMAN_NEW(*timer);
			
			if (line == 0) {
				connect(*timer, SIGNAL(timeout()), this, 
					SLOT(showLineTimer1()));
			} else {
				connect(*timer, SIGNAL(timeout()), this, 
					SLOT(showLineTimer2()));
			}
			
			// Update timer every 1s
			(*timer)->start(1000, false);
		}
		break;
	default:
		// Hide call duration timer
		if (*timer != NULL) {
			// Hide the timer after a few seconds
			hideLineTimer->start(HIDE_LINE_TIMER_AFTER * 1000, true);
			(*timer)->stop();
			MEMMAN_DELETE(*timer);
			*timer = NULL;
		}
		break;
	}
}

void MphoneForm::updateLineEncryptionState(int line)
{
	QLabel *cryptLabel, *sasLabel;
	if (line == 0) {
		cryptLabel = crypt1Label;
		sasLabel = line1SasLabel;
	} else {
		cryptLabel = crypt2Label;
		sasLabel = line2SasLabel;
	}
	
	t_audio_session *as = phone->get_line(line)->get_audio_session();
	if (as && phone->is_line_encrypted(line)) {
		string zrtp_sas = as->get_zrtp_sas();
		bool zrtp_sas_confirmed = as->get_zrtp_sas_confirmed();
		string srtp_cipher_mode = as->get_srtp_cipher_mode();
		
		QToolTip::remove(cryptLabel);
		QString toolTip = tr("Voice is encrypted") + " (";
		toolTip.append(srtp_cipher_mode.c_str()).append(")");
		
		if (!zrtp_sas.empty()) {
			// Set tool tip on encryption icon
			toolTip.append("\nSAS = ");
			toolTip.append(zrtp_sas.c_str());
			
			// Show SAS
			sasLabel->setText(zrtp_sas.c_str());
			sasLabel->show();
		} else {
			sasLabel->hide();
		}
			
		if (!zrtp_sas_confirmed) {
			toolTip.append("\n").append(tr("Click to confirm SAS."));
			cryptLabel->setFrameStyle(QFrame::Panel | QFrame::Raised);
			cryptLabel->setPixmap(
				QPixmap::fromMimeSource("encrypted.png"));
		} else {
			toolTip.append("\n").append(tr("Click to clear SAS verification."));
			cryptLabel->setFrameStyle(QFrame::NoFrame);
			cryptLabel->setPixmap(
				QPixmap::fromMimeSource("encrypted_verified.png"));
		}

		QToolTip::add(cryptLabel, toolTip);
		cryptLabel->show();
	} else {
		cryptLabel->hide();
		sasLabel->hide();
	}
}

void MphoneForm::updateLineStatus(int line)
{
	QString state;
	bool on_hold; // indicates if a line is put on-hold
	bool in_conference; // indicates if a line is in a conference
	bool is_muted; // indicates is a line is muted
	t_refer_state refer_state; // indicates if a call transfer is in progress
	bool is_transfer_consult; // indicates if the call is a consultation
	bool to_be_transferred; // indicates if the line is to be transferred after consultation
	t_call_info call_info;
	unsigned short dummy;
	
	QLabel *statLabel, *holdLabel, *muteLabel, *confLabel, *referLabel, *statusTextLabel;
	
	if (line == 0) {
		statLabel = line1StatLabel;
		holdLabel = line1HoldLabel;
		muteLabel = line1MuteLabel;
		confLabel = line1ConfLabel;
		referLabel = line1ReferLabel;
		statusTextLabel = status1TextLabel;
	} else {
		statLabel = line2StatLabel;
		holdLabel = line2HoldLabel;
		muteLabel = line2MuteLabel;
		confLabel = line2ConfLabel;
		referLabel = line2ReferLabel;
		statusTextLabel = status2TextLabel;
	}
	
	state = lineSubstate2str(line);
	on_hold = phone->is_line_on_hold(line);
	if (on_hold) {
		holdLabel->show();
	} else {
		holdLabel->hide();
	}
	in_conference = phone->part_of_3way(line);
	if (in_conference) {
		confLabel->show();
	} else {
		confLabel->hide();
	}
	is_muted = phone->is_line_muted(line);
	if (is_muted) {
		muteLabel->show();
	} else {
		muteLabel->hide();
	}
	refer_state = phone->get_line_refer_state(line);
	is_transfer_consult = phone->is_line_transfer_consult(line, dummy);
	to_be_transferred = phone->line_to_be_transferred(line, dummy);
	if (refer_state != REFST_NULL || is_transfer_consult || to_be_transferred) {
		QString toolTip;
		QToolTip::remove(referLabel);
		referLabel->show();
		if (is_transfer_consult) {
			referLabel->setPixmap(
				QPixmap::fromMimeSource("consult-xfer.png"));
			toolTip = tr("Transfer consultation");
		} else {
			referLabel->setPixmap(
				QPixmap::fromMimeSource("cf.png"));
			toolTip = tr("Transferring call");
		}
		QToolTip::add(referLabel, toolTip);
	} else {
		referLabel->hide();
	}
	
	statusTextLabel->setText(state);
	
	t_line_substate line_substate;
	line_substate = phone->get_line_substate(line);
	switch (line_substate) {
	case LSSUB_IDLE:
		((t_gui *)ui)->clearLineFields(line);
		statLabel->hide();
		break;
	case LSSUB_SEIZED:
	case LSSUB_OUTGOING_PROGRESS:
		statLabel->setPixmap(QPixmap::fromMimeSource("stat_outgoing.png"));
		statLabel->show();
		break;
	case LSSUB_INCOMING_PROGRESS:
		statLabel->setPixmap(QPixmap::fromMimeSource("stat_ringing.png"));
		statLabel->show();
		break;
	case LSSUB_ANSWERING:
		statLabel->setPixmap(QPixmap::fromMimeSource("gear.png"));
		statLabel->show();
		break;
	case LSSUB_ESTABLISHED:
		if (phone->has_line_media(line)) {
			statLabel->setPixmap(QPixmap::fromMimeSource(
					"stat_established.png"));
		} else {
			statLabel->setPixmap(QPixmap::fromMimeSource(
					"stat_established_nomedia.png"));
		}
		statLabel->show();
		break;
	case LSSUB_RELEASING:
		statLabel->setPixmap(QPixmap::fromMimeSource("gear.png"));
		statLabel->show();
		break;
	default:
		statLabel->hide();
		break;
	}
	
	updateLineEncryptionState(line);
	updateLineTimer(line);
}

// Update line state and enable/disable buttons depending on state
void MphoneForm::updateState()
{
	QString state;
	int line, other_line;
	bool on_hold; // indicates if a line is put on-hold
	bool in_conference; // indicates if a line is in a conference
	bool is_muted; // indicates is a line is muted
	t_refer_state refer_state; // indicates if a call transfer is in progress
	bool is_transfer_consult; // indicates if the call is a consultation
	bool to_be_transferred; // indicates if the line is to be transferred after consultation
	bool has_media; // indicates if a media stream is present
	t_call_info call_info;
	unsigned short dummy;
	
	// Update status of line 1
	updateLineStatus(0);
	
	// Update status of line 2
	updateLineStatus(1);
	
	// Disable/enable controls depending on the active line state
	t_line_substate line_substate;
	line = phone->get_active_line();
	line_substate = phone->get_line_substate(line);
	on_hold = phone->is_line_on_hold(line);
	in_conference = phone->part_of_3way(line);
	is_muted = phone->is_line_muted(line);
	refer_state = phone->get_line_refer_state(line);
	is_transfer_consult = phone->is_line_transfer_consult(line, dummy);
	to_be_transferred = phone->line_to_be_transferred(line, dummy);
	has_media = phone->has_line_media(line);
	other_line = (line == 0 ? 1 : 0);
	call_info = phone->get_call_info(line);
	
	// The active line may change when one of the parties in a conference
	// releases the call. If this happens, then update the state of the
	// line radio buttons.
	if (line == 0 && line2RadioButton->isOn()) 
	{
		line1RadioButton->setChecked(true);
	} else if (line == 1 && line1RadioButton->isOn())
	{
		line2RadioButton->setChecked(true);
	}
	
	// Same logic for the activate line menu items
	if (line == 0 && actionLine2->isOn()) 
	{
		actionLine1->setOn(true);
	} else if (line == 1 && actionLine1->isOn())
	{
		actionLine2->setOn(true);
	}
	
	switch(line_substate) {
	case LSSUB_IDLE:
		enableCallOptions(true);
		callAnswer->setEnabled(false);
		callBye->setEnabled(false);
		callReject->setEnabled(false);
		callRedirect->setEnabled(false);
		callTransfer->setEnabled(false);
		callHold->setEnabled(false);
		callConference->setEnabled(false);
		callMute->setEnabled(false);
		callDTMF->setEnabled(false);
		callRedial->setEnabled(ui->can_redial());
		break;
	case LSSUB_OUTGOING_PROGRESS:
		enableCallOptions(false);
		callAnswer->setEnabled(false);
		callBye->setEnabled(true);
		callReject->setEnabled(false);
		callRedirect->setEnabled(false);
		callTransfer->setEnabled(false);
		callHold->setEnabled(false);
		callConference->setEnabled(false);
		callMute->setEnabled(false);
		callDTMF->setEnabled(call_info.dtmf_supported);
		callRedial->setEnabled(false);
		break;
	case LSSUB_INCOMING_PROGRESS:
		enableCallOptions(false);
		callAnswer->setEnabled(true);
		callBye->setEnabled(false);
		callReject->setEnabled(true);
		callRedirect->setEnabled(true);
		callTransfer->setEnabled(false);
		callHold->setEnabled(false);
		callConference->setEnabled(false);
		callMute->setEnabled(false);
		callDTMF->setEnabled(call_info.dtmf_supported);
		callRedial->setEnabled(false);
		break;
	case LSSUB_ESTABLISHED:
		enableCallOptions(false);
		callInvite->setEnabled(false);
		callAnswer->setEnabled(false);
		callBye->setEnabled(true);
		callReject->setEnabled(false);
		callRedirect->setEnabled(false);
		
		if (in_conference) {
			callTransfer->setEnabled(false);
			callHold->setEnabled(false);
			callConference->setEnabled(false);
			callDTMF->setEnabled(false);
		} else {
			callTransfer->setEnabled(has_media && 
						 call_info.refer_supported &&
						 refer_state == REFST_NULL &&
						 !to_be_transferred);
			callHold->setEnabled(has_media);
			callDTMF->setEnabled(call_info.dtmf_supported);
			
			if (phone->get_line_substate(other_line) == 
			    LSSUB_ESTABLISHED)
			{
				// If one of the lines is transferring a call, then a
				// conference cannot be setup.
				if (refer_state != REFST_NULL ||
				    phone->get_line_refer_state(other_line) != REFST_NULL)
				{
					callConference->setEnabled(false);
				} else {
					callConference->setEnabled(has_media);
				}
			} else {
				callConference->setEnabled(false);
			}
		}
		
		callMute->setEnabled(true);
		callRedial->setEnabled(false);
		break;
	case LSSUB_SEIZED:
	case LSSUB_ANSWERING:
	case LSSUB_RELEASING:
		// During dialing, answering and call release no other actions are 
		// possible
		enableCallOptions(false);
		callAnswer->setEnabled(false);
		callBye->setEnabled(false);
		callReject->setEnabled(false);
		callRedirect->setEnabled(false);
		callTransfer->setEnabled(false);
		callHold->setEnabled(false);
		callConference->setEnabled(false);
		callMute->setEnabled(false);
		callDTMF->setEnabled(false);
		callRedial->setEnabled(false);
		break;
	default:
		enableCallOptions(true);
		callAnswer->setEnabled(true);
		callBye->setEnabled(true);
		callReject->setEnabled(true);
		callRedirect->setEnabled(true);
		callTransfer->setEnabled(true);
		callHold->setEnabled(true);
		callConference->setEnabled(false);
		callMute->setEnabled(true);
		callDTMF->setEnabled(true);
		callRedial->setEnabled(ui->can_redial());
	}
	
	// Set hold action in correct state
	callHold->setOn(on_hold);
	
	// Set mute action in correct state
	callMute->setOn(is_muted);
	
	// Set transfer action in correct state
	callTransfer->setOn(is_transfer_consult);
	
	// Hide redirect form if it is still visible, but not applicable anymore
	if (!callRedirect->isEnabled() && redirectForm && 
	    redirectForm->isVisible()) 
	{
		redirectForm->hide();
	}
	
	// Hide transfer form if it is still visible, but not applicable anymore
	if (!callTransfer->isEnabled() && transferForm && 
	    transferForm->isVisible()) 
	{
		transferForm->hide();
	}
	
	// Hide DTMF form if it is still visible, but not applicable anymore
	if (!callDTMF->isEnabled() && dtmfForm && 
	    dtmfForm->isVisible()) 
	{
		dtmfForm->hide();
	}
	
	// Set last called address in the redial tool tip
	t_url last_url;
	string last_display;
	string last_subject;
	t_user *last_user;
	bool hide_user;
	if (callRedial->isEnabled() && 
	    ui->get_last_call_info(last_url, last_display, last_subject, &last_user, hide_user))
	{
		QString s = "<b>";
		s += tr("Repeat last call");
		s += "</b><br>";
		s += "<table>";
		
		s += "<tr><td>";
		s += tr("User:").append("</td><td>");
		s += last_user->get_profile_name().c_str();
		s.append("</td></tr><tr><td>").append(tr("Call:")).append("</td><td>");
		s += ui->format_sip_address(last_user,
					last_display, last_url).c_str();
		s += "</td></tr>";

		if (!last_subject.empty()) {
			s.append("<tr><td>").append(tr("Subject:")).append("</td><td>");
			s += last_subject.c_str();
			s += "</td></tr>";
		}
		
		if (hide_user) {
			s.append("<tr><td colspan=2>").append(tr("Hide identity"));
			s += "</td></tr>";
		}
		
		s += "</table>";
		callRedial->setToolTip(s);
	} else {
		callRedial->setToolTip(tr("Repeat last call"));
	}
	callRedial->setStatusTip(tr("Repeat last call"));
	
	updateSysTrayStatus();
}

// Update registration status
void MphoneForm::updateRegStatus()
{
	int num_registered = 0;
	int num_failed = 0;
	QString toolTip = "<b>";
	toolTip.append(tr("Registration status:"));
	toolTip.append("</b><br>");
	toolTip.append("<table>");
	
	// Count number of succesful and failed registrations.
	// Determine tool tip showing registration details for all users.
	list<t_user *>user_list = phone->ref_users();
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		toolTip.append("<tr><td>");
		toolTip.append((*i)->get_profile_name().c_str());
		toolTip.append("</td><td>");
		if (phone->get_is_registered(*i)) {
			num_registered++;
			toolTip.append(tr("Registered"));
		} else if (phone->get_last_reg_failed(*i)) {
			num_failed++;
			toolTip.append(tr("Failed"));
		} else {
			toolTip.append(tr("Not registered").replace(' ', "&nbsp;"));
		}
		toolTip.append("</td></tr>");
	}
	toolTip.append("</table><br>");
	toolTip.append("<i>");
	toolTip.append(tr("Click to show registrations.").replace(' ', "&nbsp;"));
	toolTip.append("</i>");
	
	// Set registration status
	if (num_registered == user_list.size()) {
		// All users are registered
		statRegLabel->setPixmap(QPixmap::fromMimeSource("twinkle16.png"));
	} else if (num_failed == user_list.size()) {
		// All users failed to register
		statRegLabel->setPixmap(QPixmap::fromMimeSource("reg_failed.png"));
	} else if (num_registered > 0) {
		// Some users are registered
		statRegLabel->setPixmap(QPixmap::fromMimeSource(
				"twinkle16.png"));
	} else if (num_failed > 0) {
		// Some users failed, none are registered
		statRegLabel->setPixmap(QPixmap::fromMimeSource("reg_failed.png"));
	} else {
		// No users are registered, no users failed
		statRegLabel->setPixmap(QPixmap::fromMimeSource("twinkle16-disabled.png"));
	}
	
	// Set tool tip with detailed info.
	QToolTip::remove(statRegLabel);
	
	if (num_registered > 0 || num_failed > 0) {
		QToolTip::add(statRegLabel, toolTip);
	} else {
		QToolTip::add(statRegLabel, tr("No users are registered."));
	}
	
	updateSysTrayStatus();
}

// Create a status message based on the number of waiting messages.
// On return, msg_waiting will indicate if the MWI indicator should show
// waiting messages.
QString MphoneForm::getMWIStatus(const t_mwi &mwi, bool &msg_waiting) const
{
	QString status;
	msg_waiting = false;
	t_msg_summary summary = mwi.get_voice_msg_summary();
		
	if (summary.newmsgs > 0 && summary.oldmsgs > 0) {
		if (summary.oldmsgs == 1) {
			status = tr("%1 new, 1 old message").
				 arg(summary.newmsgs);
		} else {
			status = tr("%1 new, %2 old messages").
				 arg(summary.newmsgs).
				 arg(summary.oldmsgs);
		}
		msg_waiting = true;
	} else if (summary.newmsgs > 0 && summary.oldmsgs == 0) {
		if (summary.newmsgs == 1) {
			status = tr("1 new message");
		} else {
			status = tr("%1 new messages").
				 arg(summary.newmsgs);
		}
		msg_waiting = true;
	} else if (summary.oldmsgs > 0) {
		if (summary.oldmsgs == 1) {
			status = tr("1 old message");
		} else {
			status = tr("%1 old messages").
				 arg(summary.oldmsgs);
		}
	} else {
		if (mwi.get_msg_waiting()) {
			status = tr("Messages waiting");
			msg_waiting = true;
		} else {
			status = tr("No messages");
		}
	}

	return status.replace(' ', "&nbsp;");
}

// Flash the MWI icon
void MphoneForm::flashMWI()
{
	if (mwiFlashStatus) {
		mwiFlashStatus = false;
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
				"mwi_none16.png"));
	} else {
		mwiFlashStatus = true;
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
				"mwi_new16.png"));
	}
}

// Update MWI
void MphoneForm::updateMwi()
{
	bool mwi_known = false;
	bool mwi_new_msgs = false;
	bool mwi_failure = false;

	// Determine tool tip
	QString toolTip = tr("<b>Voice mail status:</b>").append("\n");
	toolTip.append("<br><table>");
	list<t_user *>user_list = phone->ref_users();
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		toolTip.append("<tr><td>");
		toolTip.append((*i)->get_profile_name().c_str());
		t_mwi mwi = phone->get_mwi(*i);
		toolTip.append("</td><td>");
		if (phone->is_mwi_subscribed(*i)) {
			if (mwi.get_status() == t_mwi::MWI_KNOWN) {
				bool new_msgs;
				QString status = getMWIStatus(mwi, new_msgs);
				toolTip.append(status);
				mwi_known = true;
				mwi_new_msgs |= new_msgs;
			} else if (mwi.get_status() == t_mwi::MWI_FAILED) {
				toolTip.append(tr("Failure"));
				mwi_failure = true;
			} else {
				toolTip.append(tr("Unknown"));
			}
		} else {
			if ((*i)->get_mwi_sollicited()) {
				if (mwi.get_status() == t_mwi::MWI_FAILED) {
					toolTip.append(tr("Failure"));
					mwi_failure = true;
				} else {
					toolTip.append(tr("Unknown"));
				}
			} else {
				// Unsollicited MWI				
				if (mwi.get_status() == t_mwi::MWI_KNOWN) {
					bool new_msgs;
					QString status = getMWIStatus(mwi, new_msgs);
					toolTip.append(status);
					mwi_known = true;
					mwi_new_msgs |= new_msgs;
				} else {
					toolTip.append(tr("Unknown"));
				}
			}
		}
		toolTip.append("</td></tr>");
	}
	
	toolTip.append("</table><br>");
	toolTip.append("<i>");
	toolTip.append(tr("Click to access voice mail.").replace(' ', "&nbsp;"));
	toolTip.append("</i>");
	
	// Set MWI icon
	if (mwi_new_msgs) {
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
			"mwi_new16.png"));
		mwiFlashStatus = true;
		
		// Start the flash MWI timer to flash the indicator
		tmrFlashMWI.start(1000);
	} else if (mwi_failure) {
		tmrFlashMWI.stop();
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
			"mwi_failure16.png"));
	} else if (mwi_known) {
		tmrFlashMWI.stop();
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
			"mwi_none16.png"));
	} else {
		tmrFlashMWI.stop();
		statMWILabel->setPixmap(QPixmap::fromMimeSource(
			"mwi_none16_dis.png"));
	}
	
	// Set tool tip
	QToolTip::remove(statMWILabel);
	QToolTip::add(statMWILabel, toolTip);
	
	updateSysTrayStatus();
}

// Update active services status
void MphoneForm::updateServicesStatus()
{	
	int num_dnd = 0;
	int num_cf = 0;
	int num_auto_answer = 0;
	QString tipDnd = "<b>";
	tipDnd += tr("Do not disturb active for:").replace(' ', "&nbsp;");
	tipDnd += "</b><br>\n<table>";
	QString tipCf = "<b>";
	tipCf += tr("Redirection active for:").replace(' ', "&nbsp;");
	tipCf +=  "</b><br>\n<table>";
	QString tipAa = "<b>";
	tipAa += tr("Auto answer active for:").replace(' ', "&nbsp;");
	tipAa += "</b><br>\n<table>";
	
	// Calculate number of services active.
	// Determine tool tips with detailed service status for all users.
	list<t_user *>user_list = phone->ref_users();
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		if (phone->ref_service(*i)->is_dnd_active()) {
			num_dnd++;
			tipDnd.append("<tr><td>");
			tipDnd.append((*i)->get_profile_name().c_str());
			tipDnd.append("</td></tr>");
		}
		if (phone->ref_service(*i)->is_cf_active()) {
			num_cf++;
			tipCf.append("<tr><td>");
			tipCf.append((*i)->get_profile_name().c_str());
			tipCf.append("</td></tr>");
		}
		if (phone->ref_service(*i)->is_auto_answer_active()) {
			num_auto_answer++;
			tipAa.append("<tr><td>");
			tipAa.append((*i)->get_profile_name().c_str());
			tipAa.append("</td></tr>");
		}
	}
	
	QString footer = "<i>";
	footer += tr("Click to activate/deactivate").replace(' ', "&nbsp;");
	footer += "</i>";
	
	tipDnd.append("</table><br>");
	tipDnd.append(footer);
	tipCf.append("</table><br>");
	tipCf.append(footer);
	tipAa.append("</table><br>");
	tipAa.append(footer);
	
	// Set service status
	if (num_dnd == user_list.size()) {
		// All users enabled dnd
		statDndLabel->setPixmap(QPixmap::fromMimeSource("cancel.png"));
	} else if (num_dnd > 0) {
		// Some users enabled dnd
		statDndLabel->setPixmap(QPixmap::fromMimeSource("cancel.png"));
	} else {
		// No users enabeld dnd
		statDndLabel->setPixmap(QPixmap::fromMimeSource("cancel-disabled.png"));
	}
	
	if (num_cf == user_list.size()) {
		// All users enabled redirecton
		statCfLabel->setPixmap(QPixmap::fromMimeSource("cf.png"));
	} else if (num_cf > 0) {
		// Some users enabled redirection
		statCfLabel->setPixmap(QPixmap::fromMimeSource("cf.png"));
	} else {
		// No users enabled redirection
		statCfLabel->setPixmap(QPixmap::fromMimeSource("cf-disabled.png"));
	}
	
	if (num_auto_answer == user_list.size()) {
		// All users enabled auto answer
		statAaLabel->setPixmap(QPixmap::fromMimeSource("auto_answer.png"));
	} else if (num_auto_answer > 0) {
		// Some users enabled auto answer
		statAaLabel->setPixmap(QPixmap::fromMimeSource(
				"auto_answer.png"));
	} else {
		// No users enabeld auto answer
		statAaLabel->setPixmap(QPixmap::fromMimeSource(
				"auto_answer-disabled.png"));
	}
	
	// Set tool tip with detailed info for multiple users.
	QToolTip::remove(statDndLabel);
	QToolTip::remove(statCfLabel);
	QToolTip::remove(statAaLabel);

	QString clickToActivate("<i>");
	clickToActivate += tr("Click to activate").replace(' ', "&nbsp;");
	clickToActivate += "</i>";
	if (num_dnd > 0) {
		QToolTip::add(statDndLabel, tipDnd);
	} else {
		QString status("<p>");
		status += tr("Do not disturb is not active.").replace(' ', "&nbsp;");
		status += "</p>";
		status += clickToActivate;
		QToolTip::add(statDndLabel, status);
	}		
	
	if (num_cf > 0) {
		QToolTip::add(statCfLabel, tipCf);
	} else {
		QString status("<p>");
		status += tr("Redirection is not active.").replace(' ', "&nbsp;");
		status += "</p>";
		status += clickToActivate;
		QToolTip::add(statCfLabel, status);
	}
	
	if (num_auto_answer > 0) {
		QToolTip::add(statAaLabel, tipAa);
	} else {
		QString status("<p>");
		status += tr("Auto answer is not active.").replace(' ', "&nbsp;");
		status += "</p>";
		status += clickToActivate;
		QToolTip::add(statAaLabel, status);
	}
	
	updateSysTrayStatus();
}

void MphoneForm::updateMissedCallStatus(int num_missed_calls)
{
	QToolTip::remove(statMissedLabel);
	
	QString clickDetails("<i>");
	clickDetails += tr("Click to see call history for details.").replace(' ', "&nbsp;");
	clickDetails += "</i>";
	if (num_missed_calls == 0) {
		statMissedLabel->setPixmap(QPixmap::fromMimeSource("missed-disabled.png"));
		QString status("<p>");
		status += tr("You have no missed calls.").replace(' ', "&nbsp;");
		status += "</p>";
		status += clickDetails;
		QToolTip::add(statMissedLabel, status);
	} else {
		statMissedLabel->setPixmap(
			QPixmap::fromMimeSource("missed.png"));
		
		QString tip("<p>");
		if (num_missed_calls == 1) {
			tip += tr("You missed 1 call.").replace(' ', "&nbsp;");
		} else {
			tip += tr("You missed %1 calls.").arg(num_missed_calls).
			       replace(' ', "&nbsp;");
		}
		tip += "</p>";
		tip += clickDetails;
		QToolTip::add(statMissedLabel, tip);
	}
	
	updateSysTrayStatus();
}

// Update system tray status
void MphoneForm::updateSysTrayStatus()
{
	QString icon_name;
	bool cf_active = false;
	bool dnd_active = false;
	bool auto_answer_active = false;
	bool multi_services = false;
	int num_services;
	bool msg_waiting = false;
	
	if (!sysTray) return;
	
	// Get status of active line
	int line = phone->get_active_line();
	t_line_substate line_substate = phone->get_line_substate(line);
	
	list<t_user *> user_list = phone->ref_users();
	
	switch(line_substate) {
	case LSSUB_IDLE:
	case LSSUB_SEIZED:
		// Determine MWI and service status
		user_list = phone->ref_users();
		for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
			t_mwi mwi = phone->get_mwi(*i);
			if (mwi.get_status() == t_mwi::MWI_KNOWN &&
			    mwi.get_msg_waiting() &&
			    mwi.get_voice_msg_summary().newmsgs > 0)
			{
				msg_waiting = true;
			} else if (phone->ref_service(*i)->multiple_services_active()) {
				multi_services = true;
			} else {
				if (phone->ref_service(*i)->is_dnd_active())  {
					dnd_active = true;
				}
				if (phone->ref_service(*i)->is_cf_active()) {
					cf_active = true;
				}
				if (phone->ref_service(*i)->is_auto_answer_active()) {
					auto_answer_active = true;
				}
			}
		}
		
		// If there are messages waiting, then show MWI icon
		if (msg_waiting) {
			icon_name = "sys_mwi";
			break;
		}
		
		// If there are missed calls, then show the missed call icon
		if (call_history->get_num_missed_calls() > 0) {
			icon_name = "sys_missed";
			break;
		}
		
		// If a service is active, then show the service icon
		num_services = (dnd_active ? 1 : 0) + (cf_active ? 1 : 0) + 
			       (auto_answer_active ? 1 : 0);
		
		if (multi_services || num_services > 1) {
			icon_name = "sys_services";
		} else if (dnd_active) {
			icon_name = "sys_dnd";
		} else if (cf_active) {
			icon_name = "sys_redir";
		} else if (auto_answer_active) {
			icon_name = "sys_auto_ans";
		} else {
			// No service is active, show the idle icon
			if (icon_name.isEmpty()) icon_name = "sys_idle";
		}

		break;
	case LSSUB_ESTABLISHED:
		if (phone->is_line_on_hold(line)) {
			icon_name = "sys_hold";
		} else if (phone->is_line_muted(line)) {
			icon_name = "sys_mute";
		} else if (phone->is_line_encrypted(line)) {
			t_audio_session *as = phone->get_line(line)->get_audio_session();
			if (as && as->get_zrtp_sas_confirmed()) {
				icon_name = "sys_encrypted_verified";
			} else {
				icon_name = "sys_encrypted";
			}
		} else {
			icon_name = "sys_busy_estab";
		}
		break;
	default:
		// Line is in a busy transient state
		icon_name = "sys_busy_trans";
	}
	
	// Based on the registration status use the active or disabled version
	// of the icon.
	bool registered = false;
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		if (phone->get_is_registered(*i)) {
			registered = true;
			break;
		}
	}
	
	if (registered) {
		icon_name += ".png";
	} else {
		icon_name += "_dis.png";
	}
	
	sysTray->setPixmap(QPixmap::fromMimeSource(icon_name));
}

// Update menu status based on the number of active users
void MphoneForm::updateMenuStatus()
{
	// Some menu options should be toggle actions when there is only
	// 1 user active, but they should be normal actions when there are
	// multiple users.
	disconnect(serviceDnd, 0, 0, 0);
	disconnect(serviceAutoAnswer, 0, 0, 0);
	if (phone->ref_users().size() == 1) {
		t_service *srv = phone->ref_service(phone->ref_users().front());
		
		serviceDnd->setToggleAction(true);
		serviceDnd->setOn(srv->is_dnd_active());
		connect(serviceDnd, SIGNAL(toggled(bool)),
			this, SLOT(srvDnd(bool)));
		
		serviceAutoAnswer->setToggleAction(true);
		serviceAutoAnswer->setOn(srv->is_auto_answer_active());
		connect(serviceAutoAnswer, SIGNAL(toggled(bool)),
			this, SLOT(srvAutoAnswer(bool)));
	} else {
		serviceDnd->setOn(false);
		serviceDnd->setToggleAction(false);
		connect(serviceDnd, SIGNAL(activated()),
			this, SLOT(srvDnd()));
		
		serviceAutoAnswer->setOn(false);
		serviceAutoAnswer->setToggleAction(false);
		connect(serviceAutoAnswer, SIGNAL(activated()),
			this, SLOT(srvAutoAnswer()));
	}
}

void MphoneForm::phoneRegister()
{
	t_gui *gui = (t_gui *)ui;
	list<t_user *> user_list = phone->ref_users();
	
	if (user_list.size() > 1) {
		if (selectUserForm) {
			MEMMAN_DELETE(selectUserForm);
			delete (selectUserForm);
		}
		
		selectUserForm = new SelectUserForm(this, "register", true);
		MEMMAN_NEW(selectUserForm);
		
		connect(selectUserForm, SIGNAL(selection(list<t_user *>)), this, 
			SLOT(do_phoneRegister(list<t_user *>)));
		selectUserForm->show(SELECT_REGISTER);
	} else {
		gui->action_register(user_list);
	}
}

void MphoneForm::do_phoneRegister(list<t_user *> user_list)
{
	((t_gui *)ui)->action_register(user_list);
}

void MphoneForm::phoneDeregister()
{
	t_gui *gui = (t_gui *)ui;
	list<t_user *> user_list = phone->ref_users();
	
	if (user_list.size() > 1) {
		if (selectUserForm) {
			MEMMAN_DELETE(selectUserForm);
			delete (selectUserForm);
		}
		
		selectUserForm = new SelectUserForm(this, "deregister", true);
		MEMMAN_NEW(selectUserForm);
		
		connect(selectUserForm, SIGNAL(selection(list<t_user *>)), this, 
			SLOT(do_phoneDeregister(list<t_user *>)));
		selectUserForm->show(SELECT_DEREGISTER);
	} else {
		gui->action_deregister(user_list, false);
	}
}

void MphoneForm::do_phoneDeregister(list<t_user *> user_list)
{
	((t_gui *)ui)->action_deregister(user_list, false);
}

void MphoneForm::phoneDeregisterAll()
{
	t_gui *gui = (t_gui *)ui;
	list<t_user *> user_list = phone->ref_users();
	
	if (user_list.size() > 1) {
		if (selectUserForm) {
			MEMMAN_DELETE(selectUserForm);
			delete (selectUserForm);
		}
		
		selectUserForm = new SelectUserForm(this, "deregister all", true);
		MEMMAN_NEW(selectUserForm);
	
		connect(selectUserForm, SIGNAL(selection(list<t_user *>)), this, 
			SLOT(do_phoneDeregisterAll(list<t_user *>)));
		selectUserForm->show(SELECT_DEREGISTER_ALL);
	} else {
		gui->action_deregister(user_list, true);
	}
}

void MphoneForm::do_phoneDeregisterAll(list<t_user *> user_list)
{
	((t_gui *)ui)->action_deregister(user_list, true);
}

void MphoneForm::phoneShowRegistrations()
{
	list<t_user *> user_list = phone->ref_users();
	((t_gui *)ui)->action_show_registrations(user_list);
}


// Show the semi-modal invite window
void MphoneForm::phoneInvite(t_user * user_config, 
		const QString &dest, const QString &subject, bool anonymous)
{
	// Seize the line, so no incoming call can take the line
	if (!((t_gui *)ui)->action_seize()) return;
	
	if (inviteForm) {
		inviteForm->clear();
	} else {
		inviteForm = new InviteForm(this, "invite", true);
		MEMMAN_NEW(inviteForm);
		
		// Initialize the destination history list
		for (int i = callComboBox->count() - 1; i >= 0; i--) {
			inviteForm->addToInviteComboBox(callComboBox->text(i));
		}
		
		connect(inviteForm, 
			SIGNAL(destination(t_user *, const QString &, const t_url &, 
					   const QString &, bool)),
			this, 
			SLOT(do_phoneInvite(t_user *, const QString &, 
				    const t_url &, const QString &, bool)));
		
		connect(inviteForm, SIGNAL(raw_destination(const QString &)), 
			this, SLOT(addToCallComboBox(const QString &)));
	}
	
	inviteForm->show(user_config, dest, subject, anonymous);
	updateState();
}

void MphoneForm::phoneInvite(const QString &dest, const QString &subject, bool anonymous)
{
	t_user *user = phone->ref_user_profile(userComboBox->currentText().ascii());
	if (!user) {
		log_file->write_report("Cannot find user profile.",
			       "MphoneForm::phoneInvite", 
			       LOG_NORMAL, LOG_CRITICAL);
		return;
	}
	phoneInvite(user, dest, subject, anonymous);
}

void MphoneForm::phoneInvite()
{
	t_user *user = phone->ref_user_profile(userComboBox->currentText().ascii());
	if (!user) {
		log_file->write_report("Cannot find user profile.",
			       "MphoneForm::phoneInvite", 
			       LOG_NORMAL, LOG_CRITICAL);
		return;
	}
	phoneInvite(user, "", "", false);
}

// Execute the invite action. This slot is connected to the destination
// signal of the invite window.
void MphoneForm::do_phoneInvite(t_user *user_config, const QString &display, 
			const t_url &destination, const QString &subject,
			bool anonymous)
{
	((t_gui *)ui)->action_invite(user_config, destination, display.ascii(), subject.ascii(),
				     anonymous);
	updateState();
}

// Redial last call
void MphoneForm::phoneRedial(void)
{
	t_url url;
	string display, subject;
	t_user *user_config;
	bool hide_user;
	
	if (!ui->get_last_call_info(url, display, subject, &user_config, hide_user)) return;
	((t_gui *)ui)->action_invite(user_config, url, display, subject, hide_user);
	updateState();
}


void MphoneForm::phoneAnswer()
{
	((t_gui *)ui)->action_answer();
	updateState();
}

// A call can be answered from the systray popup. The user may have
// switched lines, the systray popup answer button should answer the
// correct line.
void MphoneForm::phoneAnswerFromSystrayPopup()
{
#ifdef HAVE_KDE
	unsigned short line = ((t_gui *)ui)->get_line_sys_tray_popup();
	unsigned short active_line = phone->get_active_line();
	
	if (line != active_line) {
		((t_gui *)ui)->action_activate_line(line);
	}
	
	((t_gui *)ui)->action_answer();
	updateState();
#endif
}

void MphoneForm::phoneBye()
{
	((t_gui *)ui)->action_bye();
	updateState();
}


void MphoneForm::phoneReject()
{
	((t_gui *)ui)->action_reject();
	updateState();
}

// A call can be rejected from the systray popup. The user may have
// switched lines, the systray popup reject button should answer the
// correct line.
void MphoneForm::phoneRejectFromSystrayPopup()
{
#ifdef HAVE_KDE
	unsigned short line = ((t_gui *)ui)->get_line_sys_tray_popup();
	((t_gui *)ui)->action_reject(line);
	updateState();
#endif
}


// Show the semi-modal redirect form
void MphoneForm::phoneRedirect(const list<string> &contacts)
{
	int active_line = phone->get_active_line();
	t_user *user_config = phone->get_line_user(active_line);
	
	if (redirectForm) {
		MEMMAN_DELETE(redirectForm);
		delete (redirectForm);
	}
	
	redirectForm = new RedirectForm(this, "redirect", true);
	MEMMAN_NEW(redirectForm);
	connect(redirectForm, SIGNAL(destinations(const list<t_display_url> &)),
		this, SLOT(do_phoneRedirect(const list<t_display_url> &)));
	
	redirectForm->show(user_config, contacts);
}

void MphoneForm::phoneRedirect()
{
	const list<string> l;
	phoneRedirect(l);
}

// Execute the redirect action.
void MphoneForm::do_phoneRedirect(const list<t_display_url> &destinations)
{
	((t_gui *)ui)->action_redirect(destinations);
	updateState();
}

// Show the semi-modal call transfer window
void MphoneForm::phoneTransfer(const string &dest, t_transfer_type transfer_type)
{
	int active_line = phone->get_active_line();
	t_user *user_config = phone->get_line_user(active_line);
	
	// Hold the call if setting in user profile indicates call hold
	if (user_config->get_referrer_hold()) {
		phoneHold(true);
	}
	
	if (transferForm) {
		MEMMAN_DELETE(transferForm);
		delete transferForm;
	}
	
	transferForm = new TransferForm(this, "transfer", true);
	MEMMAN_NEW(transferForm);
	connect(transferForm, SIGNAL(destination(const t_display_url &, t_transfer_type)),
		this, SLOT(do_phoneTransfer(const t_display_url &, t_transfer_type)));
	
	if (dest.empty() && transfer_type == TRANSFER_BASIC) {
		// Let form pick a default transfer type based on the current
		// call status.
		transferForm->show(user_config);
	} else {
		// Set passed destination and transfer type in form
		transferForm->show(user_config, dest, transfer_type);
	}
	updateState();
}

void MphoneForm::phoneTransfer()
{
	unsigned short active_line = phone->get_active_line();
	unsigned short dummy;
	
	if (phone->is_line_transfer_consult(active_line, dummy)) {
		do_phoneTransferLine();
	} else {
		phoneTransfer("", TRANSFER_BASIC);
	}
}

// Execute the transfer action. This slot is connected to the destination
// signal of the transfer window.
void MphoneForm::do_phoneTransfer(const t_display_url &destination, 
				  t_transfer_type transfer_type)
{
	unsigned short active_line;
	unsigned short other_line;
		
	switch (transfer_type) {
	case TRANSFER_BASIC:
		((t_gui *)ui)->action_refer(destination.url, destination.display);
		break;
	case TRANSFER_CONSULT:
		((t_gui *)ui)->action_setup_consultation_call(
				destination.url, destination.display);
		break;
	case TRANSFER_OTHER_LINE:
		active_line = phone->get_active_line();
		other_line = (active_line == 0 ? 1 : 0);
		
		if (phone->get_line_substate(other_line) == LSSUB_ESTABLISHED) {
			((t_gui *)ui)->action_refer(active_line, other_line);
		} else {
			// The other line was released while the user was entering
			// the refer-target.
			t_user *user_config = phone->get_line_user(active_line);
			if (user_config->get_referrer_hold()) {
				phoneHold(false);
			}
		}
		break;
	default:
		assert(false);
	}
	
	updateState();
}

// Transfer the remote party on the held line to the remote party on the
// active line.
void MphoneForm::do_phoneTransferLine()
{
	unsigned short active_line = phone->get_active_line();
	unsigned short line_to_be_transferred;
	
	if (!phone->is_line_transfer_consult(active_line, line_to_be_transferred)) {
		// Somehow the line is not a consultation call.
		updateState();
		return;
	}
	
	((t_gui *)ui)->action_refer(line_to_be_transferred, active_line);
	updateState();
}

void MphoneForm::phoneHold(bool on)
{
	if (on) {
		((t_gui *)ui)->action_hold();
	} else {
		((t_gui *)ui)->action_retrieve();
	}
	
	updateState();
}

void MphoneForm::phoneConference()
{
	((t_gui *)ui)->action_conference();
	updateState();
}

void MphoneForm::phoneMute(bool on)
{
	((t_gui *)ui)->action_mute(on);
	updateState();
}

void MphoneForm::phoneTermCap(const QString &dest)
{
	// In-dialog OPTIONS request
	int line = phone->get_active_line();
	if (phone->get_line_substate(line) == LSSUB_ESTABLISHED) {
		((t_gui *)ui)->action_options();
		return;
	}
	
	// Out-of-dialog OPTIONS request
	if (termCapForm) {
		MEMMAN_DELETE(termCapForm);
		delete (termCapForm);
	}
	
	termCapForm = new TermCapForm(this, "termcap", true);
	MEMMAN_NEW(termCapForm);
	connect(termCapForm, SIGNAL(destination(t_user *, const t_url &)),
		this, SLOT(do_phoneTermCap(t_user *, const t_url &)));
	
	t_user *user = phone->ref_user_profile(userComboBox->currentText().ascii());
	if (!user) {
		log_file->write_report("Cannot find user profile.",
			       "MphoneForm::phoneTermcap", 
			       LOG_NORMAL, LOG_CRITICAL);
		return;
	}
	termCapForm->show(user, dest);
}

void MphoneForm::phoneTermCap()
{
	phoneTermCap("");
}

void MphoneForm::do_phoneTermCap(t_user *user_config, const t_url &destination)
{
	((t_gui *)ui)->action_options(user_config, destination);
}

void MphoneForm::phoneDTMF()
{
	if (!dtmfForm) {
		dtmfForm = new DtmfForm(this);
		MEMMAN_NEW(dtmfForm);
		connect(dtmfForm, SIGNAL(digits(const QString &)),
			this, SLOT(sendDTMF(const QString &)));
	}
	
	dtmfForm->show();
}

void MphoneForm::sendDTMF(const QString &digits)
{
	((t_gui *)ui)->action_dtmf(digits.ascii());	
}

void MphoneForm::startMessageSession(void)
{
	t_user *user = phone->ref_user_profile(userComboBox->currentText().ascii());
	if (!user) {
		log_file->write_report("Cannot find user profile.",
			       "MphoneForm::startMessageSession", 
			       LOG_NORMAL, LOG_CRITICAL);
		return;
	}
	
	im::t_msg_session *session = new im::t_msg_session(user);
	MEMMAN_NEW(session);
	((t_gui  *)ui)->addMessageSession(session);
	MessageFormView *messageFormView = new MessageFormView(NULL, session);
	MEMMAN_NEW(messageFormView);
	messageFormView->show();	
}

void MphoneForm::startMessageSession(t_buddy *buddy)
{
	t_user *user_config = buddy->get_user_profile();
	t_url dest_url(ui->expand_destination(user_config, buddy->get_sip_address()));
	if (!dest_url.is_valid()) return;
	string display = buddy->get_name();
	
	// Find an existing session
	im::t_msg_session *session = ((t_gui *)ui)->getMessageSession(user_config, dest_url, display);
	if (!session) {
		// There is no session yet, create one.
		session = new im::t_msg_session(user_config, t_display_url(dest_url, display));
		MEMMAN_NEW(session);
		((t_gui *)ui)->addMessageSession(session);
		MessageFormView *view = new MessageFormView(NULL, session);
		MEMMAN_NEW(view);
		view->show();
	}
}

void MphoneForm::phoneConfirmZrtpSas(int line)
{
	((t_gui *)ui)->action_confirm_zrtp_sas(line);
	updateState();
}

void MphoneForm::phoneConfirmZrtpSas()
{
	((t_gui *)ui)->action_confirm_zrtp_sas();
	updateState();
}

void MphoneForm::phoneResetZrtpSasConfirmation(int line)
{
	((t_gui *)ui)->action_reset_zrtp_sas_confirmation(line);
	updateState();	
}

void MphoneForm::phoneResetZrtpSasConfirmation()
{
	((t_gui *)ui)->action_reset_zrtp_sas_confirmation();
	updateState();	
}

void MphoneForm::phoneEnableZrtp(bool on)
{
	if (on) {
		((t_gui *)ui)->action_enable_zrtp();
	} else {
		((t_gui *)ui)->action_zrtp_request_go_clear();
	}
	
	updateState();
}

void MphoneForm::phoneZrtpGoClearOk(unsigned short line)
{
	((t_gui *)ui)->action_zrtp_go_clear_ok(line);
	updateState();
}

// Radio button for line 1 changed state
void MphoneForm::line1rbChangedState( bool on )
{
	// If the radio button is switched off, then return, the toggle
	// on the other line will handle the action
	if (!on) return;
	
	((t_gui *)ui)->action_activate_line(0);
}

void MphoneForm::line2rbChangedState( bool on )
{
	// If the radio button is switched off, then return, the toggle
	// on the other line will handle the action
	if (!on) return;
	
	((t_gui *)ui)->action_activate_line(1);
}

void MphoneForm::actionLine1Toggled( bool on)
{
	if (!on) return;
	((t_gui *)ui)->action_activate_line(0);
}

void MphoneForm::actionLine2Toggled( bool on)
{
	if (!on) return;
	((t_gui *)ui)->action_activate_line(1);
}

// Enable/disable dnd when there is 1 user active
void MphoneForm::srvDnd( bool on ) 
{
	((t_gui *)ui)->srv_dnd(phone->ref_users(), on);
	updateServicesStatus();
}

// Enable/disable dnd when there are multiple users active
void MphoneForm::srvDnd()
{
	if (selectUserForm) {
		MEMMAN_DELETE(selectUserForm);
		delete (selectUserForm);
	}
		
	selectUserForm = new SelectUserForm(this, "dnd", true);
	MEMMAN_NEW(selectUserForm);
		
	connect(selectUserForm, SIGNAL(selection(list<t_user *>)), this, 
			SLOT(do_srvDnd_enable(list<t_user *>)));
	connect(selectUserForm, SIGNAL(not_selected(list<t_user *>)), this, 
			SLOT(do_srvDnd_disable(list<t_user *>)));
			
	selectUserForm->show(SELECT_DND);
}

void MphoneForm::do_srvDnd_enable(list<t_user *> user_list) {
	((t_gui *)ui)->srv_dnd(user_list, true);
	updateServicesStatus();
}

void MphoneForm::do_srvDnd_disable(list<t_user *> user_list) {
	((t_gui *)ui)->srv_dnd(user_list, false);
	updateServicesStatus();
}

// Enable/disable auto answer when there is 1 user active
void MphoneForm::srvAutoAnswer( bool on ) 
{
	((t_gui *)ui)->srv_auto_answer(phone->ref_users(), on);
	updateServicesStatus();
}

// Enable/disable auto answer when there are multiple users active
void MphoneForm::srvAutoAnswer()
{
	if (selectUserForm) {
		MEMMAN_DELETE(selectUserForm);
		delete (selectUserForm);
	}
		
	selectUserForm = new SelectUserForm(this, "auto answer", true);
	MEMMAN_NEW(selectUserForm);
		
	connect(selectUserForm, SIGNAL(selection(list<t_user *>)), this, 
			SLOT(do_srvAutoAnswer_enable(list<t_user *>)));
	connect(selectUserForm, SIGNAL(not_selected(list<t_user *>)), this, 
			SLOT(do_srvAutoAnswer_disable(list<t_user *>)));
			
	selectUserForm->show(SELECT_AUTO_ANSWER);
}

void MphoneForm::do_srvAutoAnswer_enable(list<t_user *> user_list) {
	((t_gui *)ui)->srv_auto_answer(user_list, true);
	updateServicesStatus();
}

void MphoneForm::do_srvAutoAnswer_disable(list<t_user *> user_list) {
	((t_gui *)ui)->srv_auto_answer(user_list, false);
	updateServicesStatus();
}

void MphoneForm::srvRedirect()
{
	if (!srvRedirectForm) {
		srvRedirectForm = new SrvRedirectForm(this, "call redirection", true);
		MEMMAN_NEW(srvRedirectForm);
		connect(srvRedirectForm, 
			SIGNAL(destinations(t_user *,
					    const list<t_display_url> &,
					    const list<t_display_url> &,
					    const list<t_display_url> &)),
			this, 
			SLOT(do_srvRedirect(t_user *,
					    const list<t_display_url> &,
					    const list<t_display_url> &,
					    const list<t_display_url> &)));
	}
	
	srvRedirectForm->show();
}

void MphoneForm::do_srvRedirect(t_user *user_config,
				const list<t_display_url> &always, 
				const list<t_display_url> &busy,
				const list<t_display_url> &noanswer)
{
	// Redirection always
	if (always.empty()) {
		((t_gui *)ui)->srv_disable_cf(user_config, CF_ALWAYS);
	} else {
		((t_gui *)ui)->srv_enable_cf(user_config, CF_ALWAYS, always);
	}
	
	// Redirection busy
	if (busy.empty()) {
		((t_gui *)ui)->srv_disable_cf(user_config, CF_BUSY);
	} else {
		((t_gui *)ui)->srv_enable_cf(user_config, CF_BUSY, busy);
	}
	
	// Redirection no answer
	if (noanswer.empty()) {
		((t_gui *)ui)->srv_disable_cf(user_config, CF_NOANSWER);
	} else {
		((t_gui *)ui)->srv_enable_cf(user_config, CF_NOANSWER, noanswer);
	}
	
	updateServicesStatus();
}


void MphoneForm::about()
{
	QString s = sys_config->about(true).c_str();
	
	QMessageBox mbAbout(PRODUCT_NAME, s.replace(' ', "&nbsp;"), 
		    QMessageBox::Information, 
		    QMessageBox::Ok | QMessageBox::Default,
		    QMessageBox::NoButton, QMessageBox::NoButton);
	mbAbout.setIconPixmap(QPixmap::fromMimeSource("twinkle48.png"));
	mbAbout.exec();
}

void MphoneForm::aboutQt()
{
	QMessageBox::aboutQt(this, PRODUCT_NAME);
}

void MphoneForm::editUserProfile()
{
	if (!userProfileForm) {
		userProfileForm = new UserProfileForm(this, "user profile", true);
		MEMMAN_NEW(userProfileForm);
	
		connect(userProfileForm,
			SIGNAL(authCredentialsChanged(t_user *, const string&)),
			this,
			SLOT(updateAuthCache(t_user *, const string&)));
		
		connect(userProfileForm,
			SIGNAL(stunServerChanged(t_user *)),
			this,
			SLOT(updateStunSettings(t_user *)));
		
		// MWI settings change triggers an unsubscribe
		connect(userProfileForm,
			SIGNAL(mwiChangeUnsubscribe(t_user *)),
			this,
			SLOT(unsubscribeMWI(t_user *)));
		
		// MWI settings change triggers a subscribe
		connect(userProfileForm,
			SIGNAL(mwiChangeSubscribe(t_user *)),
			this,
			SLOT(subscribeMWI(t_user *)));
	}
	
	userProfileForm->show(phone->ref_users(), 
			      userComboBox->currentText());
}

void MphoneForm::editSysSettings()
{
	if (!sysSettingsForm) {
		sysSettingsForm = new SysSettingsForm(this, "system settings", true);
		MEMMAN_NEW(sysSettingsForm);
		connect(sysSettingsForm, SIGNAL(sipUdpPortChanged()),
			this, SLOT(updateSipUdpPort()));
		connect(sysSettingsForm, SIGNAL(rtpPortChanged()),
			this, SLOT(updateRtpPorts()));
	}
	
	sysSettingsForm->show();
}

void MphoneForm::selectProfile()
{
	if (!selectProfileForm) {
		selectProfileForm = new SelectProfileForm(this, "select profile", true);
		MEMMAN_NEW(selectProfileForm);
		connect(selectProfileForm, SIGNAL(selection(const list<string> &)),
			this, SLOT(newUsers(const list<string> &)));
		connect(selectProfileForm, SIGNAL(profileRenamed()),
			this, SLOT(updateUserComboBox()));
		connect(selectProfileForm, SIGNAL(profileRenamed()),
			this, SLOT(populateBuddyList()));
	}
	
	selectProfileForm->showForm(this);
}

// A new set of users has been selected.
// Remove users from the current user set that are not in the selection.
// Add users from the selection that are not in the current set of users.
void MphoneForm::newUsers(const list<string> &profiles)
{
	string error_msg;
	
	// NOTE: First users must be removed. It could be that a
	// user profile of an active was renamed. In this case, the user 
	// with the old profile name is first removed and then added again.
	
	list<t_user *> user_list = phone->ref_users();
	
	// Remove current users that are not selected anymore.
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) {
		if (std::find(profiles.begin(), profiles.end(), 
			      (*i)->get_filename().c_str()) == profiles.end())
		{
			// User is not selected anymore.
			// Unsubscribe MWI
			if (phone->is_mwi_subscribed(*i)) {
				phone->pub_unsubscribe_mwi(*i);
			}
			
			// Unpublish presence of user
			phone->pub_unpublish_presence(*i);
			
			// Unsubscribe presence
			phone->pub_unsubscribe_presence(*i);
			
			// Deregister user
			if (phone->get_is_registered(*i)) {
				phone->pub_registration(*i, REG_DEREGISTER);
			}
			
			log_file->write_header("MphoneForm::newUsers");
			log_file->write_raw("Stop user profile: ");
			log_file->write_raw((*i)->get_profile_name());
			log_file->write_endl();
			log_file->write_footer();
			phone->remove_phone_user(*(*i));
		}
	}
	
	// Determine which users to add
	list<string> add_profile_list;
	for (list<string>::const_iterator i = profiles.begin(); i != profiles.end(); i++) {
		QString profile = (*i).c_str();
		// Strip off the .cfg extension
		profile.truncate(profile.length() - 4);
		
		if (!phone->ref_user_profile(profile.ascii())) {
			add_profile_list.push_back(*i);
		}
	}
	
	// Add new phone users
	QProgressDialog progress(tr("Starting user profiles..."), "Abort", add_profile_list.size(), this,
				 "starting user profiles", true);
	progress.setCaption(PRODUCT_NAME);
	progress.setMinimumDuration(200);
	int progressStep = 0;
	for (list<string>::iterator i = add_profile_list.begin(); i != add_profile_list.end(); i++) {
		progress.setProgress(progressStep);
		qApp->processEvents();
		
		if (progress.wasCancelled()) {
			log_file->write_report("User aborted startup of new users.", 
					       "MphoneForm::newUsers");
			break;
		}
		
		t_user user_config;
		
		// Read user configuration
		if (user_config.read_config(*i, error_msg)) {
			t_user *dup_user;
			
			log_file->write_header("MphoneForm::newUsers");
			log_file->write_raw("Run user profile: ");
			log_file->write_raw(user_config.get_profile_name());
			log_file->write_endl();
			log_file->write_footer();
			
			if (phone->add_phone_user(user_config, &dup_user))
			{
				// NAT discovery
				if (user_config.get_use_stun() &&
				    !phone->stun_discover_nat(&user_config, error_msg)) 
				{
					// Warn user that the STUN settings will not work.
					((t_gui *)ui)->cb_show_msg(this, error_msg, 
							MSG_WARNING);
				}
				
				// Register at startup
				if (user_config.get_register_at_startup()) {
					phone->pub_registration(&user_config,
						REG_REGISTER,
						DUR_REGISTRATION(&user_config));
				} else {
					// No registration needed, initialize extensions now.
					phone->init_extensions(&user_config);
				}
				
				// Extension initialization will be done after 
				// registration succeeded.
			} else {
				error_msg = tr("The following profiles are both for user %1").arg(user_config.get_name().c_str()).ascii();
				error_msg += '@';
				error_msg += user_config.get_domain();
				error_msg += ":\n\n";
				error_msg += user_config.get_profile_name();
				error_msg += "\n";
				error_msg += dup_user->get_profile_name();
				error_msg += "\n\n";
				error_msg += tr("You can only run multiple profiles for different users.");
				
				log_file->write_report(error_msg,
					"MphoneForm::newUsers", 
					LOG_NORMAL, LOG_WARNING);
				ui->cb_display_msg(error_msg, MSG_WARNING);
			}
		} else {
			log_file->write_report(error_msg,
					"MphoneForm::newUsers", 
					LOG_NORMAL, LOG_CRITICAL);
			ui->cb_display_msg(error_msg, MSG_CRITICAL);
		}
		
		progressStep++;
	}
	progress.setProgress(add_profile_list.size());
	
	populateBuddyList();
	updateUserComboBox();
	updateRegStatus();
	updateMwi();
	updateServicesStatus();
	updateSysTrayStatus();
	updateMenuStatus();
	updateState();
	
	call_history->clear_num_missed_calls();
}

void MphoneForm::updateUserComboBox()
{
	QString current_user;
	
	if (userComboBox->count() == 0) {
		// The last used profile
		current_user = sys_config->get_last_used_profile().c_str();
	} else {
		// Keep the current active profile
		current_user = userComboBox->currentText();
	}
	
	((t_gui *)ui)->fill_user_combo(userComboBox);
	
	// If previous selected user is still active, make it the current user
	for (int i = 0; i < userComboBox->count(); i++) {
		if (userComboBox->text(i) == current_user) {
			userComboBox->setCurrentItem(i);
		}
	}
}

void MphoneForm::updateSipUdpPort()
{
	((t_gui *)ui)->cb_show_msg(sysSettingsForm,
			tr("You have changed the SIP UDP port. This setting will only become "\
			"active when you restart Twinkle.").ascii(),
			MSG_INFO);
}

void MphoneForm::updateRtpPorts()
{
	phone->init_rtp_ports();
}

void MphoneForm::updateStunSettings(t_user *user_config)
{
	if (user_config->get_use_stun()) {
		string s;
		if (!phone->stun_discover_nat(user_config, s)) {
			// Warn user that the STUN settings will not work.
			((t_gui *)ui)->cb_show_msg(this, s, MSG_WARNING);
		}
	} else {
		// Disable STUN
		phone->disable_stun(user_config);
	}
}

void MphoneForm::updateAuthCache(t_user *user_config, const string &realm)
{
	phone->remove_cached_credentials(user_config, realm);
}

void MphoneForm::unsubscribeMWI(t_user *user_config)
{
	phone->pub_unsubscribe_mwi(user_config);
}

void MphoneForm::subscribeMWI(t_user *user_config)
{
	phone->pub_subscribe_mwi(user_config);
}

void MphoneForm::viewLog()
{
	if (!logViewForm) {
		logViewForm = new LogViewForm(NULL);
		MEMMAN_NEW(logViewForm);
	}
	
	logViewForm->show();
}

void MphoneForm::updateLog(bool log_zapped)
{
	if (logViewForm) logViewForm->update(log_zapped);
}

void MphoneForm::viewHistory()
{
	if (!historyForm) {
		historyForm = new HistoryForm(NULL);
		MEMMAN_NEW(historyForm);
	}
	
	connect(historyForm, 
		SIGNAL(call(t_user *, const QString &, const QString &, bool)), this,  
		SLOT(phoneInvite(t_user *, const QString &, const QString &, bool)));
	
	historyForm->show();
}

void MphoneForm::updateCallHistory()
{
	if (historyForm) historyForm->update();
}

t_twinkle_sys_tray *MphoneForm::getSysTray()
{
	return sysTray;
}

// Execute call directly from the main window (press call button)
void MphoneForm::quickCall()
{
	string display, dest_str;
	
	t_user *from_user = phone->ref_user_profile(
				userComboBox->currentText().ascii());
	if (!from_user) {
		log_file->write_report("Cannot find user profile.",
			       "MphoneForm::quickCall", 
			       LOG_NORMAL, LOG_CRITICAL);
		return;
	}
	
	ui->expand_destination(from_user, 
			       callComboBox->currentText().stripWhiteSpace().ascii(), 
			       display, dest_str);
	t_url dest(dest_str);
	
	if (dest.is_valid()) {
		QString destination = callComboBox->currentText();
		addToCallComboBox(destination);
		if (inviteForm) inviteForm->addToInviteComboBox(destination);
		callComboBox->setFocus();
		do_phoneInvite(from_user, display.c_str(), dest, "", false);
	}
}

// Add a destination to the list of callComboBox
void MphoneForm::addToCallComboBox(const QString &destination)
{
	// Remove duplicate entries
	for (int i = callComboBox->count() - 1; i >= 0; i--) {
		if (callComboBox->text(i) == destination) {
			callComboBox->removeItem(i);
		}
	}
	
	// Add entry
	callComboBox->insertItem(destination, 0);
	callComboBox->setCurrentItem(0);
	
	// Remove last entry is list exceeds maximum size
	if (callComboBox->count() > SIZE_REDIAL_LIST) {
		callComboBox->removeItem(callComboBox->count() - 1);
	}
	
	// Clearing the edit line must be done here as this function is
	// also called when a call is made through the inviteForm.
	// The insertItem puts the text also in the edit field. So it must
	// be cleared here.
	callComboBox->clearEdit();
}

void MphoneForm::showAddressBook()
{
	if (!getAddressForm) {
		getAddressForm = new GetAddressForm(
				this, "select address", true);
		MEMMAN_NEW(getAddressForm);
	}
	
	connect(getAddressForm, 
		SIGNAL(address(const QString &)),
		this, SLOT(selectedAddress(const QString &)));
	
	getAddressForm->show();
}

void MphoneForm::selectedAddress(const QString &address)
{
	callComboBox->setEditText(address);
}

// Enable/disable the various call widgets
void MphoneForm::enableCallOptions(bool enable)
{
	// Enable/disable widgets
	callInvite->setEnabled(enable);
	callPushButton->setEnabled(enable);
	callComboBox->setEnabled(enable);
	addressToolButton->setEnabled(enable);
	
	// Set focus on callComboBox
	if (enable) {
		callComboBox->setFocus();
	}
}

void MphoneForm::keyPressEvent(QKeyEvent *e)
{
	if (callPushButton->isEnabled()) {
		// Quick dial
		switch (e->key()) {
		case Qt::Key_Return:
		case Qt::Key_Enter:
			quickCall();
			break;
		default:
			e->ignore();
		}
	} else if (callDTMF->isEnabled()) {
		// DTMF keys
		switch (e->key()) {
		case Qt::Key_1:
			sendDTMF("1");
			break;
		case Qt::Key_2:
		case Qt::Key_A:
		case Qt::Key_B:
		case Qt::Key_C:
			sendDTMF("2");
			break;
		case Qt::Key_3:
		case Qt::Key_D:
		case Qt::Key_E:
		case Qt::Key_F:
			sendDTMF("3");
			break;
		case Qt::Key_4:
		case Qt::Key_G:
		case Qt::Key_H:
		case Qt::Key_I:
			sendDTMF("4");
			break;
		case Qt::Key_5:
		case Qt::Key_J:
		case Qt::Key_K:
		case Qt::Key_L:
			sendDTMF("5");
			break;
		case Qt::Key_6:
		case Qt::Key_M:
		case Qt::Key_N:
		case Qt::Key_O:
			sendDTMF("6");
			break;
		case Qt::Key_7:
		case Qt::Key_P:
		case Qt::Key_Q:
		case Qt::Key_R:
		case Qt::Key_S:
			sendDTMF("7");
			break;
		case Qt::Key_8:
		case Qt::Key_T:
		case Qt::Key_U:
		case Qt::Key_V:
			sendDTMF("8");
			break;
		case Qt::Key_9:
		case Qt::Key_W:
		case Qt::Key_X:
		case Qt::Key_Y:
		case Qt::Key_Z:
			sendDTMF("9");
			break;
		case Qt::Key_0:
		case Qt::Key_Space:
			sendDTMF("0");
			break;
		case Qt::Key_Asterisk:
			sendDTMF("*");
			break;
		case Qt::Key_NumberSign:
			sendDTMF("#");
			break;
		default:
			e->ignore();
		}
	} else {
		e->ignore();
	}
}

// QLabels do not have mouse click events. I want the status labels
// to be clickable however. Explicitly check here if a status label has
// been clicked.
void MphoneForm::mouseReleaseEvent(QMouseEvent *e)
{
	if (e->button() == Qt::LeftButton && e->type() == QEvent::MouseButtonRelease) {
		processLeftMouseButtonRelease(e);
	} else if (e->button() == Qt::RightButton && e->type() == QEvent::MouseButtonRelease) {
		processRightMouseButtonRelease(e);
	} else {
		e->ignore();
	}
}

void MphoneForm::processLeftMouseButtonRelease(QMouseEvent *e)
{
	if (statAaLabel->hasMouse()) {
		if (phone->ref_users().size() == 1) {
			bool enable = !serviceAutoAnswer->isOn();
			srvAutoAnswer(enable);
			serviceAutoAnswer->setOn(enable);
		} else {
			srvAutoAnswer();
		}
	} else if (statDndLabel->hasMouse()) {
		if (phone->ref_users().size() == 1) {
			bool enable = !serviceDnd->isOn();
			srvDnd(enable);
			serviceDnd->setOn(enable);
		} else {
			srvDnd();
		}
	} else if (statCfLabel->hasMouse()) {
		srvRedirect();
	} else if (statMWILabel->hasMouse()) {
		popupMenuVoiceMail(e->globalPos());
	} else if (statMissedLabel->hasMouse()) {
		// Open the history form, when the user clicks on the 
		// missed calls indication.
		viewHistory();
	} else if (statRegLabel->hasMouse()) {
		// Fetch registration status
		phoneShowRegistrations();
	} else if (crypt1Label->hasMouse()) {
		processCryptLabelClick(0);
	} else if (crypt2Label->hasMouse()) {
		processCryptLabelClick(1);
	} else {
		e->ignore();
	}
}

void MphoneForm::processRightMouseButtonRelease(QMouseEvent *e)
{
	e->ignore();
}

void MphoneForm::processCryptLabelClick(int line) 
{
	t_audio_session *as = phone->get_line(line)->get_audio_session();
	if (!as) return;
	
	if (as->get_zrtp_sas_confirmed()) {
		phoneResetZrtpSasConfirmation(line);
	} else {
		phoneConfirmZrtpSas(line);
	}
}

// Show popup menu to access voice mail
void MphoneForm::popupMenuVoiceMail(const QPoint &pos)
{
	QPopupMenu menu(this);
	QIconSet vmIcon(QPixmap::fromMimeSource("mwi_none16.png"));
	vmIcon.setPixmap(QPixmap::fromMimeSource("mwi_none16_dis.png"),
		       QIconSet::Automatic, QIconSet::Disabled);
	
	list<t_user *>user_list = phone->ref_users();
	map<int, t_user *> vm;
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); ++i) {
		QString address = (*i)->get_mwi_vm_address().c_str();
		QString entry		= (*i)->get_profile_name().c_str();
		entry += " - ";		
		if (address.isEmpty()) {
			entry += tr("not provisioned");
		} else {
			entry += address;
		}		
		
		int id = menu.insertItem(vmIcon, entry);
		if (address.isEmpty()) {
			menu.setItemEnabled(id, false);
		}
		
		vm.insert(make_pair(id, *i));
	}
	
	int selected;
	
	// If multiple profiles are active, then show the popup menu.
	// If one profile is active, then call voice mail immediately.
	if (user_list.size() > 1) {
		selected = menu.exec(pos);
		if (selected == -1) return;
	} else {
		if (vm.begin()->second->get_mwi_vm_address().empty()) {
			ui->cb_show_msg(
				tr("You must provision your voice mail address in your "
				   "user profile, before you can access it.").ascii(), 
				MSG_INFO);
			return;
		}
		selected = vm.begin()->first;
	}
	
	// Call can only be made if line is idle
	int line = phone->get_active_line();
	if (phone->get_line_state(line) == LS_BUSY) {
		ui->cb_show_msg(tr("The line is busy. Cannot access voice mail.").ascii(), 
				MSG_WARNING);
		return;
	}
	
	t_user *selectedUser = vm[selected];
	string display, dest_str;
	ui->expand_destination(selectedUser, 
		       selectedUser->get_mwi_vm_address(), 
		       display, dest_str);
	t_url dest(dest_str);
	
	if (dest.is_valid()) {
		QString destination = selectedUser->get_mwi_vm_address().c_str();
		addToCallComboBox(destination);
		if (inviteForm) inviteForm->addToInviteComboBox(destination);
		callComboBox->setFocus();
		do_phoneInvite(selectedUser, display.c_str(), dest, "", false);
	} else {
		QString msg(tr("The voice mail address %1 is an invalid address. "
			       "Please provision a valid address in your user profile."));
		ui->cb_show_msg(msg.arg(selectedUser->get_mwi_vm_address().c_str()).ascii(),
				MSG_CRITICAL);
	}
}

void MphoneForm::popupMenuVoiceMail(void)
{
	popupMenuVoiceMail(QCursor::pos());
}

void MphoneForm::showDisplay(bool on)
{
	if (on) {
		displayGroupBox->show();
	} else {
		int hDisplay = displayGroupBox->height();
		displayGroupBox->hide();
		
		if (hDisplay < minimumHeight()) {
			setMinimumHeight(minimumHeight() - hDisplay);
		}
		resize(width(), minimumHeight());
	}
	
	viewDisplay = on;
	viewDisplayAction->setOn(on);
}

void MphoneForm::showBuddyList(bool on)
{
	if (on) {
		buddyListView->show();
	} else {
		buddyListView->hide();
	}
	
	viewBuddyList = on;
	viewBuddyListAction->setOn(on);
}

void MphoneForm::showCompactLineStatus(bool on)
{
	if (on) {
		int hLabels = fromhead1Label->height() +
			      tohead1Label->height() +
			      subjecthead1Label->height() +
			      fromhead2Label->height() +
			      tohead2Label->height() +
			      subjecthead2Label->height();
		
		fromhead1Label->hide();
		tohead1Label->hide();
		subjecthead1Label->hide();
		from1Label->hide();
		to1Label->hide();
		subject1Label->hide();
		photo1Label->hide();
		fromhead2Label->hide();
		tohead2Label->hide();
		subjecthead2Label->hide();
		from2Label->hide();
		to2Label->hide();
		subject2Label->hide();
		photo2Label->hide();
		
		if (hLabels < minimumHeight()) {
			setMinimumHeight(minimumHeight() - hLabels);
		}
		resize(width(), minimumHeight());
	} else {
		fromhead1Label->show();
		tohead1Label->show();
		subjecthead1Label->show();
		from1Label->show();
		to1Label->show();
		subject1Label->show();
		fromhead2Label->show();
		tohead2Label->show();
		subjecthead2Label->show();
		from2Label->show();
		to2Label->show();
		subject2Label->show();
	}
	
	viewCompactLineStatus = on;
	//viewCompactLineStatusAction->setOn(on);
}

bool MphoneForm::getViewDisplay()
{
	return viewDisplay;
}

bool MphoneForm::getViewBuddyList()
{
	return viewBuddyList;
}

bool MphoneForm::getViewCompactLineStatus()
{
	return viewCompactLineStatus;
}

void MphoneForm::populateBuddyList()
{
	buddyListView->clear();

	list<t_user *> user_list = phone->ref_users();
	for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); ++i) {
		t_presence_epa *epa = phone->ref_presence_epa(*i);
		if (!epa) continue;
		
		BLViewUserItem *profileItem = new BLViewUserItem(buddyListView, epa);
		t_buddy_list *buddy_list = phone->ref_buddy_list(*i);
		
		list<t_buddy> *buddies = buddy_list->get_records();
		for (list<t_buddy>::iterator bit = buddies->begin(); bit != buddies->end(); ++bit) {
			QString name = bit->get_name().c_str();
			new BuddyListViewItem(profileItem, &(*bit));
		}
		
		profileItem->setOpen(true);
	}
}

void MphoneForm::showBuddyListPopupMenu(QListViewItem *item, const QPoint &pos)
{
	if (!item) return;
	
	BuddyListViewItem *buddyItem = dynamic_cast<BuddyListViewItem *>(item);
	if (buddyItem) {
		buddyPopupMenu->popup(pos);
	} else {
		buddyListPopupMenu->popup(pos);
	}
}

void MphoneForm::doCallBuddy()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BuddyListViewItem *item = dynamic_cast<BuddyListViewItem *>(qitem);
	if (!item) return;
	
	t_buddy *buddy = item->get_buddy();
	t_user *user_config = buddy->get_user_profile();
	
	phoneInvite(user_config, buddy->get_sip_address().c_str(), "", false);
}

void MphoneForm::doMessageBuddy(QListViewItem *qitem)
{	
	BuddyListViewItem *item = dynamic_cast<BuddyListViewItem *>(qitem);
	if (!item) return;
	
	t_buddy *buddy = item->get_buddy();
	
	startMessageSession(buddy);
}

void MphoneForm::doMessageBuddy()
{
	QListViewItem *item = buddyListView->currentItem();
	doMessageBuddy(item);
}

void MphoneForm::doEditBuddy()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BuddyListViewItem *item = dynamic_cast<BuddyListViewItem *>(qitem);
	if (!item) return;
	
	t_buddy *buddy = item->get_buddy();
	
	BuddyForm *form = new BuddyForm(this, "new_buddy", true, Qt::WDestructiveClose);
	// Do not call MEMMAN as this form will be deleted automatically.	
	form->showEdit(*buddy);
}

void MphoneForm::doDeleteBuddy()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BuddyListViewItem *item = dynamic_cast<BuddyListViewItem *>(qitem);
	if (!item) return;
	
	t_buddy *buddy = item->get_buddy();
	t_buddy_list *buddy_list = buddy->get_buddy_list();
	
	// Delete the list item before deleting the buddy as
	// deleting the item will detach the item from the buddy.
	delete item;
		
	if (buddy->is_presence_terminated()) {
		buddy_list->del_buddy(*buddy);
	} else {
		buddy->unsubscribe_presence(true);
	}
		
	string err_msg;
	if (!buddy_list->save(err_msg)) {
		QString msg = tr("Failed to save buddy list: %1").arg(err_msg.c_str());
		((t_gui *)ui)->cb_show_msg(this, msg.ascii(), MSG_CRITICAL);
	}
}

void MphoneForm::doAddBuddy()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BLViewUserItem *item = dynamic_cast<BLViewUserItem *>(qitem);
	if (!item) return;
	
	t_phone_user *pu = item->get_presence_epa()->get_phone_user();
	if (!pu) return;
	t_buddy_list *buddy_list = pu->get_buddy_list();
	if (!buddy_list) return;
	
	BuddyForm *form = new BuddyForm(this, "new_buddy", true, Qt::WDestructiveClose);
	// Do not call MEMMAN as this form will be deleted automatically.
	form->showNew(*buddy_list, item);
}

void MphoneForm::doAvailabilityOffline()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BLViewUserItem *item = dynamic_cast<BLViewUserItem *>(qitem);
	if (!item) return;
	
	t_phone_user *pu = item->get_presence_epa()->get_phone_user();
	if (!pu) return;
	
	pu->publish_presence(t_presence_state::ST_BASIC_CLOSED);
}

void MphoneForm::doAvailabilityOnline()
{
	QListViewItem *qitem = buddyListView->currentItem();
	BLViewUserItem *item = dynamic_cast<BLViewUserItem *>(qitem);
	if (!item) return;
	
	t_phone_user *pu = item->get_presence_epa()->get_phone_user();
	if (!pu) return;
	
	pu->publish_presence(t_presence_state::ST_BASIC_OPEN);
}


syntax highlighted by Code2HTML, v. 0.9.1