/***************************************************************************
                          dcchat.cpp  -  description
                             -------------------
    begin                : Don Mär 28 2002
    copyright            : (C) 2002 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "dcchat.h"

#include <qdatetime.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qclipboard.h>
#include <qapplication.h>
#include <qmessagebox.h>
#include <qcursor.h>
#include <qcombobox.h>
#include <qlayout.h>
#include <QProcess>
#include <qglobal.h>
#include <qstatusbar.h>
#include <qtooltip.h>
#include <qfile.h>
#include <QFileDialog>
#include <qsplitter.h>
#include <qinputdialog.h>
#include <qtabwidget.h>
//Added by qt3to4:
#include <QLabel>
#include <QPixmap>
#include <QGridLayout>
#include <QList>
#include <QKeyEvent>
#include <QEvent>
#include <QTextDocumentFragment>
#include <QMdiArea>

#include "dcevent.h"
#include "dcclient.h"
#include "dcmenuhandler.h"
#include "dcconfig.h"
#include "dctranslator.h"
#include "cdialogpicturemap.h"
#include "dcfiletool.h"
#include "dchublistmanager.h"
#include "dcconnectionmanager.h"
#include "dcuserslist.h"
#include "dciconloader.h"
#include "dctransferview.h"
#include "dchubsearch.h"
#include "dcshellcommandrunner.h"
#include "dcguiutils.h"
#include "dcqtextedit.h"

#include <dclib/cfilemanager.h>
#include <dclib/dcos.h>
#include <dclib/cutils.h>
#include <dclib/core/cbase64.h>
#include <dclib/core/cdir.h>
#include <dclib/dclib.h>
#include <dclib/clistenmanager.h>
#include <dclib/core/cbytearray.h>

/** the link list */
static const QStringList g_LinkList = QString("HTTP://,HTTPS://,NEWS://,FILE://,WWW.,FTP://,FTP.,HKP://,LDAP://,DCFILE://,DCHUB://,DCHUBS://,DCCMD://,MAGNET:").split(',');

#define DCFILE_LINK_INDEX  9
#define DCHUB_LINK_INDEX  10
#define DCHUBS_LINK_INDEX 11
#define DCCMD_LINK_INDEX  12
#define MAGNET_LINK_INDEX 13

#define MAX_HISTORY_COUNT 25

/** */
DCChat::DCChat( QWidget * parent, DCClient * client, bool bprivate ) : QWidget( parent )
{
	// ASSERT(client);
	
	setupUi(this);
	
	// set default icon
	setWindowIcon( g_pIconLoader->GetPixmap(eiGLOBE) );

	m_pParent      = parent;
	m_bPrivateChat = bprivate;
	m_pClient      = client;
	m_bSendAway    = true;
	m_eSecureState = esecsNONE;
	m_nCurrentHistoryIndex = -1;

	m_sTimeStamp  = QDate::currentDate().toString("yyyyMMdd");
	m_sTimeStamp += "-";
	m_sTimeStamp += QTime::currentTime().toString("hhmmss");

	m_nTabStart   = -1;
	m_nTabEnd     = -1;
	m_nTabPressed = 0;
	m_sTabSaved   = "";
	m_sTabNick    = "";
	
	m_pShellRunners = new QList<DCShellCommandRunner*>();
	
	InitDocument();
}

/** */
DCChat::~DCChat()
{
	/*
	 * Call delete on all running shell commands.
	 * The DCShellCommand destructor will wait
	 * for the thread to finish.
	 */
	for ( int i = 0; i < m_pShellRunners->count(); i++ )
	{
		delete m_pShellRunners->at(i);
	}
	
	m_pShellRunners->clear();
	delete m_pShellRunners;
}

/** */
void DCChat::QT3TranslationSync()
{
	QString qt31 = tr("Shell command event not found in list");
	QString qt32 = tr("Failed to start shell command.");
}

/** */
void DCChat::InitDocument()
{
	//int i;

	m_pStatusBar = new QStatusBar(this);
	m_pStatusBar->setSizeGripEnabled(false);
	this->layout()->addWidget( m_pStatusBar );
	m_pStatusCrypt = new QLabel(m_pStatusBar);
	m_pStatusBar->addWidget(m_pStatusCrypt);

	SetCrypt(esecsNONE);

	if ( !m_bPrivateChat )
	{
		m_pStatusBar->hide();
	}
	else
	{
		m_pStatusBar->show();
	}

/*	if ( g_pConfig->ShowChatStatusBar() == false )
	{
		m_pStatusBar->hide();
	}
*/
	if ( g_pConfig->GetShowChatSendButton() )
	{
		connect( PushButton_SEND, SIGNAL(clicked()), this, SLOT(slotSendMessage()) );
		slotTextChangedChatInput();
	}
	else
	{
		// remove send button and recreate layout
		delete PushButton_SEND;
		PushButton_SEND = NULL;
		delete Frame_SEND->layout();
		QGridLayout * gl = new QGridLayout();
		Frame_SEND->setLayout(gl);
		gl->addWidget( TextEdit_CHATINPUT, 0, 0 );
	}

	m_pTextEdit_CHATOUTPUT = new DCQTextEdit( Frame_OUTPUT );
	
	// QSizePolicy::Preferred is the default anyway...
	QSizePolicy policy = m_pTextEdit_CHATOUTPUT->sizePolicy();
	policy.setHorizontalPolicy( QSizePolicy::Preferred );
	policy.setVerticalPolicy( QSizePolicy::Preferred );
	policy.setHorizontalStretch( 0 );
	policy.setVerticalStretch( 0 );
	m_pTextEdit_CHATOUTPUT->setSizePolicy( policy );
	
	m_pTextEdit_CHATOUTPUT->setMinimumSize( QSize( 0, 60 ) );
	//m_pTextEdit_CHATOUTPUT->setTextFormat( Qt::RichText );
	m_pTextEdit_CHATOUTPUT->setLineWrapMode( QTextEdit::WidgetWidth );
	m_pTextEdit_CHATOUTPUT->setWordWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere );
    	m_pTextEdit_CHATOUTPUT->setReadOnly( true );
	m_pTextEdit_CHATOUTPUT->setContextMenuPolicy( Qt::CustomContextMenu );
	
	if ( g_pConfig->GetChatMaxParagraph() != 0 )
	{
		m_pTextEdit_CHATOUTPUT->document()->setMaximumBlockCount( g_pConfig->GetChatMaxParagraph() );
	}
	
	slotLoadEmoticons();
	
	Frame_OUTPUT->layout()->addWidget(m_pTextEdit_CHATOUTPUT);

	connect( m_pTextEdit_CHATOUTPUT, SIGNAL(customContextMenuRequested( const QPoint& )), this, SLOT(slotRightButtonClickedChatOutput( const QPoint& )) );
	
	connect( m_pTextEdit_CHATOUTPUT, SIGNAL(clicked()), this, SLOT(slotClickedChatOutput()) );
	connect( m_pTextEdit_CHATOUTPUT, SIGNAL(doubleClicked()), this, SLOT(slotDoubleClickedChatOutput()) );

	connect( g_pConfig, SIGNAL(emoticonThemeChanged()), this, SLOT(slotLoadEmoticons()) );
	connect( g_pConfig, SIGNAL(chatBackgroundColorChanged()), this, SLOT(slotBGColorChanged()) );
	
	if ( g_pConfig->GetChatBackgroundColorEnabled() )
	{
		/* using QTextCharFormat or style sheets did not work */
		QPalette palette = m_pTextEdit_CHATOUTPUT->palette();
		palette.setColor( QPalette::Base, g_pConfig->GetChatBackgroundColor() );
		m_pTextEdit_CHATOUTPUT->setAutoFillBackground(true);
		m_pTextEdit_CHATOUTPUT->setPalette(palette);
		
		palette = TextEdit_CHATINPUT->palette();
		palette.setColor( QPalette::Base, g_pConfig->GetChatBackgroundColor() );
		TextEdit_CHATINPUT->setAutoFillBackground(true);
		TextEdit_CHATINPUT->setPalette(palette);
	}

	if ( PushButton_SEND )
	{
		connect( TextEdit_CHATINPUT, SIGNAL(textChanged()), this, SLOT(slotTextChangedChatInput()) );
	}

	m_pTextEdit_CHATOUTPUT->installEventFilter(this);
	TextEdit_CHATINPUT->installEventFilter(this);
	TextEdit_CHATINPUT->setFocus();
}

/** FIXME I could not find any way to unload the old emoticons in the QT docs */
void DCChat::slotLoadEmoticons()
{
	if ( g_pConfig->GetEnableEmoticons() )
	{
		g_pConfig->AddEmoticons( m_pTextEdit_CHATOUTPUT->document() );
	}
}

/** event filter */
bool DCChat::eventFilter( QObject * object, QEvent * event )
{
	if ((event->type() == QEvent::KeyPress)&&((QTextEdit*)object==TextEdit_CHATINPUT))
	{
		QKeyEvent * e = (QKeyEvent*)event;

		bool send = false;
		if ( e->modifiers() == Qt::AltModifier )
		{
			if ( (e->key() == Qt::Key_S) && (g_pConfig->GetSendChat() == 3) )
			{
				send = true;
			}
			else if( ((e->key() == Qt::Key_Enter) || (e->key() == Qt::Key_Return )) &&
				   (g_pConfig->GetSendChat() == 2))
			{
				send = true;
			}
		}
		else if( (g_pConfig->GetSendChat() == 0) &&
			 (e->modifiers() == Qt::ControlModifier) && ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) )
		{
			send = true;
		}
		else if( ((g_pConfig->GetSendChat() == 1) && (e->modifiers() != Qt::ControlModifier) &&
			 ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) )
		{
			send = true;
		}

		if ( send )
		{
			SendMessage();
			e->accept();
			return true;
		}
		else if ( e->modifiers() == Qt::ControlModifier )
		{
			int histindex = -1;

			// check for history
			if ( (e->key() == Qt::Key_Down) && (m_nCurrentHistoryIndex != -1) )
			{
				histindex = m_nCurrentHistoryIndex - 1;

			}
			else if ( e->key() == Qt::Key_Up )
			{
				if ( m_nCurrentHistoryIndex == -1 )
				{
					histindex = 0;
					
					// save current text
					m_sHistoryTempString = TextEdit_CHATINPUT->toPlainText();
					// save current cursor position
					// QT3 TextEdit_CHATINPUT->getCursorPosition( &m_nHistoryTempPara, &m_nHistoryTempIndex );
					m_nHistoryTempIndex = TextEdit_CHATINPUT->textCursor().position();
				}
				else
				{
					histindex = m_nCurrentHistoryIndex + 1;
					if ( histindex >= m_lHistory.size() )
					{
						histindex = -1;
					}
				}
			}

			if ( histindex != -1 )
			{
				m_nCurrentHistoryIndex = histindex;
				TextEdit_CHATINPUT->setPlainText(m_lHistory[histindex]);
			}
			else if (e->key() == Qt::Key_Down)
			{
				m_nCurrentHistoryIndex = -1;
				TextEdit_CHATINPUT->setPlainText(m_sHistoryTempString);
				// QT3 TextEdit_CHATINPUT->setCursorPosition( m_nHistoryTempPara, m_nHistoryTempIndex );
				QTextCursor cursor = TextEdit_CHATINPUT->textCursor();
				cursor.setPosition( m_nHistoryTempIndex );
				TextEdit_CHATINPUT->setTextCursor( cursor );
			}
		}

		if ( e->key() == Qt::Key_Tab )
		{
			NickCompletion();
			e->accept();
			return true;
		}
		else if ( m_nTabPressed != 0 )
		{
			// reset tab settings
			m_nTabStart   = -1;
			m_nTabEnd     = -1;
			m_nTabPressed = 0;
			m_sTabSaved   = "";
			//m_sTabNick    = "";
		}
	}
	else if ( (event->type() == QEvent::Resize) && (object == m_pTextEdit_CHATOUTPUT) )
	{
		bool bscroll, b;

		if ( m_pTextEdit_CHATOUTPUT->verticalScrollBar()->maximum() == m_pTextEdit_CHATOUTPUT->verticalScrollBar()->value() )
		{
			bscroll = true;
		}
		else
		{
			bscroll = false;
		}

		b = QWidget::eventFilter( object, event );

		if ( bscroll )
		{
			// QT3 m_pTextEdit_CHATOUTPUT->scrollToBottom();
			// QT3 m_pTextEdit_CHATOUTPUT->moveCursor( Q3TextEdit::MoveEnd, false );
			QTextCursor cursor = m_pTextEdit_CHATOUTPUT->textCursor();
			cursor.movePosition( QTextCursor::End, QTextCursor::MoveAnchor, 1 );
			m_pTextEdit_CHATOUTPUT->setTextCursor( cursor );
		}
		
		return b;
	}
	
	return QWidget::eventFilter( object, event );    // standard event processing
}

/** handle translation event */
void DCChat::customEvent( QEvent * event )
{
	if ( event->type() == EVENT_TRANSLATION )
	{
		DC_TranslationEvent *e = (DC_TranslationEvent*)event;

		if ( e->m_bTranslate )
		{
			AddStatus( tr("Translation: ") + "'" + e->m_sOriginal + "' -> '" + e->m_sTranslation + "'" );
		}
		else
		{
			AddStatus( tr("Translation failed: ") + "'" + e->m_sOriginal + "'" + " ( " +tr("Error was: ") + "'" + e->m_sTranslation + "' )" );
		}
		
		event->accept();
	}
	else
	{
		event->ignore();
	}
}

/** */
void DCChat::AddHistory( QString message )
{
	if ( m_lHistory.size() == MAX_HISTORY_COUNT )
	{
		m_lHistory.pop_back();
	}

	m_nCurrentHistoryIndex = -1;
	m_lHistory.prepend(message);
}

/** */
void DCChat::SetCrypt( eSecureState e )
{
	if ( e == esecsENCRYPTED )
	{
		m_pStatusCrypt->setToolTip(tr("Line is encrypted."));
		m_pStatusCrypt->setPixmap( g_pIconLoader->GetPixmap(eiSSL_YES) );
	}
	else
	{
		m_pStatusCrypt->setToolTip(tr("Line is not encrypted."));
		m_pStatusCrypt->setPixmap( g_pIconLoader->GetPixmap(eiSSL_NO) );
	}

	m_eSecureState = e;
}

/** */
void DCChat::slotTextChangedChatInput()
{
	if ( PushButton_SEND )
	{
		if ( TextEdit_CHATINPUT->toPlainText().isEmpty() )
		{
			PushButton_SEND->setEnabled(false);
		}
		else
		{
			PushButton_SEND->setEnabled(true);
		}
	}
}

/** */
void DCChat::slotClickedChatOutput()
{
	QString pressedLink;
	QPoint p;
	//int x,y;

	p = m_pTextEdit_CHATOUTPUT->mapFromGlobal(QCursor::pos());
	//m_pTextEdit_CHATOUTPUT->viewportToContents(p.x(),p.y(),x,y);
	//p.setX(x);
	//p.setY(y);
	pressedLink = m_pTextEdit_CHATOUTPUT->anchorAt(p).toAscii().constData();

	//printf("pressedLink=\"%s\"\n",pressedLink.Data());

	for( int i = 0; i < g_LinkList.size(); ++i )
	{
		if ( pressedLink.startsWith(g_LinkList[i], Qt::CaseInsensitive) )
		{
			if ( i == DCFILE_LINK_INDEX )
			{
				// DC-File-Link
				QMenu * m;
				QAction * chosen = 0;
				CString hubhost,hubname,nick,file,tth;
				ulonglong size;

				if ( CUtils::ConvertDCLink( pressedLink.toAscii().constData(), hubhost, hubname, nick, size, file, tth ) == false )
				{
					return;
				}

				m = new QMenu(this);

				QAction * dl = DCMenuHandler::addAction( m, emiDOWNLOAD );
				QAction * dl_to = DCMenuHandler::addAction( m, emiDOWNLOAD_TO );
				QAction * dl_as = DCMenuHandler::addAction( m, emiDOWNLOAD_AS );
				QAction * dl_in = DCMenuHandler::addAction( m, emiDOWNLOAD_IN );

				chosen = m->exec(QCursor::pos());

				delete m;

				if ( (chosen == dl) || (chosen == dl_as) || (chosen == dl_to) )
				{
					QString localrootpath;
					QString localname;

					// select downloadfolder for all selected files
					if ( chosen == dl_to )
					{
						localrootpath = QFileDialog::getExistingDirectory( this, tr("Select download folder"), g_pConfig->GetDownloadFolder().Data(), QFileDialog::ShowDirsOnly );

						if ( localrootpath.isEmpty() )
							return;
					}

					CDir d;
					d.SetPath(file);
					QFileInfo fi(d.DirName().Data());
					localname = fi.fileName();

					if ( chosen == dl_as )
					{
						localrootpath = QFileDialog::getSaveFileName( this, tr("Select file for") + " " + localname, localname );

						if ( localrootpath.isEmpty() )
							return;

						QFileInfo fi(localrootpath);
						localrootpath = fi.path();
						localname     = fi.fileName();

						if ( (localrootpath.isEmpty()) || (localname.isEmpty()) )
							return;
					}

					// add transfer to the waitlist
					DCFileTool::CheckFile( this, nick, hubname, hubhost,
							file, localname.toAscii().constData(), CString(), localrootpath.toAscii().constData(), eltFILE,
							size, tth );
				}
				else if ( chosen == dl_in )
				{
					QString localrootpath;
					QString localname;
					QString localpath;

					if ( DCFileTool::SelectFileSource( this, size, QString(tth.Data()), localname, localrootpath, localpath ) == false )
					{
						return;
					}

					// add transfer to the waitlist
					DCFileTool::CheckFile( this, nick, hubname, hubhost,
							  file, localname.toAscii().constData(), localpath.toAscii().constData(), localrootpath.toAscii().constData(), eltFILE,
					  		size, tth, true);
				}
			}
			else if ( i == DCHUB_LINK_INDEX )
			{
				pressedLink = pressedLink.right( pressedLink.length()-8 );
				// remove all '/' chars
				pressedLink.remove('/');

				g_pConnectionManager->Connect( pressedLink.toAscii().constData(), pressedLink.toAscii().constData() );
			}
			else if ( i == DCHUBS_LINK_INDEX )
			{
				pressedLink = pressedLink.right( pressedLink.length()-9 );
				// remove all '/' chars
				pressedLink.remove('/');

				g_pConnectionManager->Connect( pressedLink.toAscii().constData(), pressedLink.toAscii().constData(), true );
			}
			/* else if ( i == DCCMD_LINK_INDEX )
			{
				pressedLink = pressedLink.right( pressedLink.length()-8 );
			} */
			else if ( i == MAGNET_LINK_INDEX )
			{
				//magnet:?xt=urn:tree:tiger:EOSA5AGTL5SD3VWCF3R2OH2WMGXV3S3R7SYN4YA&xl=708780032&dn=FC-6-i386-disc1.iso
				// TTHs are always 39 characters long
				QMenu * m = new QMenu(this);
				QAction * chosen = 0;
				int i;
				QString tth;
				
				i = pressedLink.indexOf("xt=urn:tree:tiger:");
				if (i == -1)
				{
					i = pressedLink.indexOf("xt.1=urn:tree:tiger:");
					tth = pressedLink.mid(i + 20, 39);
				}
				else
				{
					tth = pressedLink.mid(i + 18, 39);
				}
				
				QAction * search = m->addAction( QIcon(g_pIconLoader->GetPixmap(eiFILEFIND)), QObject::tr("Search by TTH") );
				QAction * copy = m->addAction( QIcon(g_pIconLoader->GetPixmap(eiEDITCOPY)), QObject::tr("Copy TTH to clipboard") );
				QAction * lookup = m->addAction( QIcon(g_pIconLoader->GetPixmap(eiFILEFIND)), QObject::tr("Look up TTH at Bitzi.com") );
				
				chosen = m->exec(QCursor::pos());
				
				delete m;
				
				if (chosen == search)
				{
					DCHubSearch * hubsearch = new DCHubSearch( g_pConnectionManager->GetMdiArea() );
					hubsearch->SetSearchForFile( tth, eftHASH );
					hubsearch->show();
					hubsearch->StartSearchWithPrompt();
				}
				else if (chosen == copy)
				{
					QApplication::clipboard()->setText(tth);
				}
				else if (chosen == lookup)
				{
					// http://bitzi.com/lookup/tree:tiger:PKQRUF7BKROIPCX2XWTJ7XUKRXRPUMW5V6REAMY
					g_pConfig->OpenURL( "http://bitzi.com/lookup/tree:tiger:" + tth );
				}
				
			}
			else
			{
				g_pConfig->OpenURL(pressedLink);
			}
		}
	}
}

/** */
void DCChat::slotRightButtonClickedChatOutput( const QPoint& )
{
	//int para;
	QString pressedPara, pressedNick, userCommand;
	CString cPressedNick;
	QPoint p;
	//int x,y
	int nickStart, nickLen;
	QAction * chosen;
	bool isNickOnline;
	QMenu *m,*muser,*mslot;
	QString s;	
	QMap<QAction*, DC_UserMenuCommand*> addedcommands;
	DC_UserMenuCommand * umc = 0;
	
	// get the paragraph of text where the right click was
	// Qt3 m_pTextEdit_CHATOUTPUT->viewportToContents(p.x(),p.y(),x,y);
	// QT3 p.setX(x);
	// QT3 p.setY(y);
	// QT3 para = m_pTextEdit_CHATOUTPUT->paragraphAt(p);
	// QT3 pressedPara = m_pTextEdit_CHATOUTPUT->text(para);
	p = m_pTextEdit_CHATOUTPUT->mapFromGlobal(QCursor::pos());
	QTextCursor cursor = m_pTextEdit_CHATOUTPUT->cursorForPosition(p);
	pressedPara = cursor.block().text();
	
	//printf("pressedPara=\"%s\"\n", pressedPara.toAscii().constData());
	
	// cut out the nick
	nickStart = 1 + pressedPara.indexOf("<");
	nickLen = pressedPara.indexOf(">") - nickStart;
	
	// sanity check
	if ( (nickStart == 0) || (nickLen < 0) )
	{
		if (m_bPrivateChat)
		{
			pressedNick = m_sNick;
		}
		else
		{
			// assume a /me line
			if ( g_pConfig->GetTimeStamp(etsHUBCHAT) )
			{
				nickStart = QString("[hh.mm.ss] ").length();
			}
			else
			{
				nickStart = 0;
			}
			
			nickLen = pressedPara.indexOf(" ", nickStart) - nickStart;
			
			pressedNick = pressedPara.mid(nickStart, nickLen);
		}
	}
	else
	{
		pressedNick = pressedPara.mid(nickStart, nickLen);
	}
	
	// check for joins / parts message
	if ( pressedNick == "VALKNUT" )
	{
		int lenJoin = tr("Joins: ").length();
		int lenPart = tr("Parts: ").length();
		int joinPos = pressedPara.indexOf(tr("Joins: "));
		int partPos = pressedPara.indexOf(tr("Parts: "));
		
		if ( joinPos == -1 )
		{
			nickStart = lenPart + partPos;
		}
		else
		{
			nickStart = lenJoin + joinPos;	
		}
		
		if ( (joinPos != -1) || (partPos != -1) )
		{
			nickLen = pressedPara.length() - nickStart;
			pressedNick = pressedPara.mid(nickStart, nickLen);
		}
	}
	
	// check for private chat
	if ( (pressedNick == "VALKNUT") && m_bPrivateChat )
	{
		pressedNick = m_sNick;
	}
	
	//printf("Debug pressedNick: %s\n",pressedNick.toAscii().constData());
	
	cPressedNick = pressedNick.toAscii().constData();
	isNickOnline = m_pClient->UserList()->IsUserOnline(cPressedNick);
	
	// select the user in the userlist
	if (isNickOnline)
	{
		m_pClient->jumpToNick( pressedNick );
	}
	
	m = new QMenu(this);
	
	muser = DCMenuHandler::addMenu( m , emisCHAT_RIGHTCLICK_USER, true, pressedNick );
	QAction * privchat = DCMenuHandler::addAction( muser, emiPRIVATE_CHAT, isNickOnline );
	QAction * addfriend = DCMenuHandler::addAction( muser, emiADD_FRIEND,  isNickOnline );
	QAction * browseuser = DCMenuHandler::addAction( muser, emiBROWSE_USER_FILES, isNickOnline );
	DCMenuHandler::addAction( muser, emiSEPARATOR );
	
	mslot = DCMenuHandler::addMenu( muser, emiUPLOAD_SLOT, true);
	QAction * addslot = DCMenuHandler::addAction( mslot, emiADD, true);
	QAction * addpermslot = DCMenuHandler::addAction( mslot, emiADD_PERMANENT, true);
	QAction * removeslot = DCMenuHandler::addAction( mslot, emiREMOVE, true);
	
	DCMenuHandler::addAction( muser, emiSEPARATOR );
	QAction * kick = DCMenuHandler::addAction( muser, emiKICK, (isNickOnline && m_pClient->UserList()->IsAdmin(m_pClient->GetNick())) );
	QAction * forcemove = DCMenuHandler::addAction( muser, emiFORCE_MOVE, (isNickOnline && m_pClient->UserList()->IsAdmin(m_pClient->GetNick())) );
	
	addedcommands = m_pClient->AddMenuCommands( muser, euccChat );
	
	DCMenuHandler::addAction( m, emiSEPARATOR );
	
	QAction * smiley = DCMenuHandler::addAction( m, emiINSERTSMILEY, g_pConfig->GetEnableEmoticons() );
	DCMenuHandler::addAction( m, emiSEPARATOR );
	QAction * copy = DCMenuHandler::addAction( m, emiCOPY, m_pTextEdit_CHATOUTPUT->textCursor().hasSelection() );
	QAction * clear = DCMenuHandler::addAction( m, emiCLEAR, true );
	QAction * selectall = DCMenuHandler::addAction( m, emiSELECT_ALL, true );
	DCMenuHandler::addAction( m, emiSEPARATOR );
	QAction * translate = DCMenuHandler::addAction( m, emiTRANSLATE, m_pTextEdit_CHATOUTPUT->textCursor().hasSelection() );
	QAction * translator = DCMenuHandler::addAction( m, emiTRANSLATOR );

	DCMenuHandler::addAction( m, emiSEPARATOR );
	QAction * refresh = DCMenuHandler::addAction( m, emiREFRESH );
	DCMenuHandler::addAction( m, emiSEPARATOR );

	QAction * reqsecchat = 0;
	QAction * closesecchat = 0;

	if ( m_bPrivateChat )
	{
		// insert request/close secure chat
		if ( m_eSecureState == esecsNONE )
			reqsecchat = DCMenuHandler::addAction( m, emiREQUEST_SECURE_CHAT, dclibSupportsSSL() );
		else
			closesecchat = DCMenuHandler::addAction( m, emiCLOSE_SECURE_CHAT );
	}

	QAction * save = DCMenuHandler::addAction( m, emiSAVE );

	QAction * dock = 0;

	if ( parentWidget() == 0 )
		dock = DCMenuHandler::addAction( m, emiDOCK );
	else
		dock = DCMenuHandler::addAction( m, emiUNDOCK );

	QAction * hidepriv = 0;
	QAction * closepriv = 0;

	if ( m_bPrivateChat )
	{
		hidepriv = DCMenuHandler::addAction( m, emiHIDE, m_bPrivateChat && !g_pConfig->GetShowChatInTab() );
		closepriv = DCMenuHandler::addAction( m, emiCLOSE, m_bPrivateChat );
	}
	
	if ( !m_bPrivateChat )
	{
		addedcommands.unite( m_pClient->AddMenuCommands( m, euccHub ) );
	}
	
	chosen = m->exec(QCursor::pos());

	delete m;

	if ( chosen == 0 )
	{
		return;
	}
	else if ( chosen == smiley )
	{
		int x,y;
		CList<DC_EmoticonObject> * elist;

		elist = g_pConfig->EmoticonList();

		if ( elist )
		{
			CDialogPictureMap * dialog = new CDialogPictureMap(this);
			QPixmap p = QPixmap::fromImage(g_pConfig->GetEmoticonImage());
			dialog->SetPixmap(p);

			if ( dialog->exec() == QDialog::Accepted )
			{
				dialog->GetXY(x,y);

				DC_EmoticonObject * EmoticonObject = 0;

				while ( (EmoticonObject=elist->Next(EmoticonObject)) != 0 )
				{
					if ( ((EmoticonObject->left<x) && (EmoticonObject->right>x)) &&
					     ((EmoticonObject->top<y)  && (EmoticonObject->bottom>y)) )
					{
						// damn hacks
						QString smiley = EmoticonObject->m_Text;
						smiley.replace( "&lt;", "<" );
						smiley.replace( "&gt;", ">" );
						smiley.replace( "&amp;", "&" );
						smiley.replace( "&apos;", "\'" );
						smiley.replace( "&quot;", "\"" );
						
						smiley += " ";
						
						TextEdit_CHATINPUT->insertPlainText(smiley);

						break;
					}
				}
			}

			delete dialog;
		}
	}
	else if ( chosen == translate )
	{
		QString sc;
		QClipboard * cb = QApplication::clipboard();
		
		// save old text
		sc = cb->text();
		// copy to cb
		m_pTextEdit_CHATOUTPUT->copy();
		// get new text
		s = cb->text();
		// save old text
		cb->setText(sc);
		
		s.remove("<!--StartFragment-->");
		g_pTranslator->Translate( this, m_sLanguage, s );
	}
	else if ( chosen == translator )
	{
		m_sLanguage = g_pTranslator->SelectLanguage( m_sLanguage, this );
	}
	else if ( chosen == copy )
	{
		m_pTextEdit_CHATOUTPUT->copy();
	}
	else if ( chosen == clear )
	{
		m_pTextEdit_CHATOUTPUT->clear();
		AddStatus( tr("Chat Cleared.") );
	}
	else if ( chosen == selectall )
	{
		m_pTextEdit_CHATOUTPUT->selectAll();
	}
	else if ( chosen == reqsecchat )
	{
		m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick.toAscii().constData(), "<request secchannel>" );
	}
	else if ( chosen == closesecchat )
	{
		m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick.toAscii().constData(), "<close secchannel>" );
	}
	else if ( chosen == dock )
	{
		if ( parentWidget() == 0 )
		{
			setParent( m_pParent );

			if ( !m_bPrivateChat || g_pConfig->GetShowChatInTab() )
			{
				QTabWidget * p = (QTabWidget*)m_pParent;

				int index = p->addTab(this,m_sLabel);
				p->setCurrentIndex(index);
			}
			else
			{
				g_pConnectionManager->GetMdiArea()->addSubWindow(this);
			}

			show();
		}
		else
		{
			if ( !m_bPrivateChat || g_pConfig->GetShowChatInTab() )
			{
				QTabWidget * p = (QTabWidget*)m_pParent;

				int index = p->indexOf(this);
				m_sLabel = p->tabText(index);
				p->removeTab(index);
			}
			else
			{
				g_pConnectionManager->GetMdiArea()->removeSubWindow(this);
				/*
				 * unfortunately this leaves an empty QMdiSubWindow but using
				 * QMdiArea::closeActiveSubWindow() closes the hub
				 */
			}

			setParent(0);
			show();

			// set default icon
			setWindowIcon( g_pIconLoader->GetPixmap(eiGLOBE) );
		}
	}
	else if ( chosen == save )
	{
		QString s = QFileDialog::getSaveFileName(
			this,
			tr("Choose a filename to save under"),
			QString(),
			"HTML (*.html *.htm)" );

		if ( !s.isEmpty() )
		{
			QFile f(s);

			if ( f.open(QIODevice::WriteOnly) )
			{
				f.write(m_pTextEdit_CHATOUTPUT->toHtml().toAscii());
				f.close();
			}
		}
	}
	else if ( chosen == refresh )
	{
		if ( CFileManager::Instance()->CreateShareList() )
			s = tr("Refresh share in progress.");
		else
			s = tr("Refresh share already in progress.");
		AddStatus(s);
	}
	else if ( chosen == closepriv )
	{
		close();
	}
	else if ( chosen == hidepriv )
	{
		setEnabled(false);
		hide();
	}
	else if ( chosen == privchat )
	{
		m_pClient->DC_PrivateChat( pressedNick, QString(), QString(), true );
	}
	else if ( chosen == addfriend )
	{
		g_pUsersList->AddFriend(
			pressedNick,
			QString::fromAscii(m_pClient->GetHubName().Data()),
			QString::fromAscii(m_pClient->GetHost().Data()),
			QString()
		);
	}
	else if ( chosen == browseuser )
	{
		CString empty;
		g_pTransferView->DLM_QueueAdd( cPressedNick, m_pClient->GetHubName(),
				m_pClient->GetHost(),
				DC_USER_FILELIST, DC_USER_FILELIST, empty, empty, eltBUFFER,
				0, 0, 0, empty );
	}
	else if ( chosen == kick )
	{
		QString kickMessage;
		
		if ( !m_pClient->GetOPKickMessage(kickMessage,this) )
		{
			return;
		}
		m_pClient->OPKick( pressedNick, kickMessage );
		
	}
	else if ( chosen == forcemove )
	{
		QString host, message;
		if ( !m_pClient->GetOPForceMoveMessage(message,host,this) )
		{
			return;
		}
		m_pClient->OPForceMove(pressedNick, message, host);
	}
	else if ( chosen == addslot )
	{
		g_pTransferView->DLM_AddUserSlot(cPressedNick, m_pClient->GetHubName(), 1);
	}
	else if ( chosen == addpermslot )
	{
		g_pTransferView->DLM_AddUserSlot(cPressedNick, m_pClient->GetHubName(), 0, true);
	}
	else if ( chosen == removeslot )
	{
		g_pTransferView->DLM_AddUserSlot(cPressedNick, m_pClient->GetHubName(), 0);
	}
	else if ( addedcommands.contains( chosen ) )
	{
		umc = addedcommands[chosen];
		
		QString origcommand = umc->m_sCommand;
		userCommand = m_pClient->replaceCommandTags( origcommand, pressedNick );
		
		if ( !userCommand.isEmpty() )
		{
			AddStatus(userCommand);
			m_pClient->SendString(userCommand.toAscii().constData());
		}
	}
}

/** */
bool DCChat::close()
{
	// re-set the wflags ...
	//QWidget::setWindowFlags(WDestructiveClose);
	QWidget::setAttribute(Qt::WA_DeleteOnClose);
	return QWidget::close();
}

/** */
void DCChat::slotSendMessage()
{
	SendMessage();
}

/** */
void DCChat::SetNick( QString nick, QString hubname )
{
	m_sNick = nick;

	if ( nick.isEmpty() )
	{
		setWindowTitle(tr("Chat:")+QString(" [")+hubname+QString("]"));
	}
	else
	{
		setWindowTitle(tr("Private Chat:")+QString(" ")+nick+QString(" [")+hubname+QString("]"));
	}
}

/** */
QString DCChat::GetTimeStamp()
{
	QString s;

	if ( (g_pConfig->GetTimeStamp(etsPRIVATECHAT) && (m_bPrivateChat)) ||
	     (g_pConfig->GetTimeStamp(etsHUBCHAT) && (!m_bPrivateChat)) )
	{
		s = QTime::currentTime().toString("[hh:mm:ss]");
	}

	if ( m_bPrivateChat )
		s = "<font color=\"" + g_pConfig->GetChatColor(eccCHATTIMESTAMP) + "\">" + s + "</font> ";
	else
		s = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATTIMESTAMP) + "\">" + s + "</font> ";

	return s;
}

/** */
void DCChat::AddOutput( QString message )
{
	bool bscroll;
	QString s;

	if ( m_pTextEdit_CHATOUTPUT->verticalScrollBar()->maximum() == m_pTextEdit_CHATOUTPUT->verticalScrollBar()->value() )
	{
		bscroll = true;
	}
	else
	{
		bscroll = false;
	}

	// convert newline
	message.replace( "\n", "<br />" );
	
	// convert tabs to spaces
	message.replace( "\t", "&nbsp;&nbsp;&nbsp;&nbsp;" );

	// QT3 - replaced with QTextDocument::setMaximumBlockCount in QT4
	/* if ( (g_pConfig->GetChatMaxParagraph()!= 0) && (m_pTextEdit_CHATOUTPUT->paragraphs() > (g_pConfig->GetChatMaxParagraph()+5)) )
	{
		m_pTextEdit_CHATOUTPUT->removeParagraph(0);
		m_pTextEdit_CHATOUTPUT->removeParagraph(0);
		m_pTextEdit_CHATOUTPUT->removeParagraph(0);
		m_pTextEdit_CHATOUTPUT->removeParagraph(0);
		m_pTextEdit_CHATOUTPUT->removeParagraph(0);
	} */

	m_pTextEdit_CHATOUTPUT->append( message );

	if ( bscroll )
	{
		// QT3 m_pTextEdit_CHATOUTPUT->scrollToBottom();
		// QT3 m_pTextEdit_CHATOUTPUT->moveCursor( Q3TextEdit::MoveEnd, false );
		QTextCursor cursor = m_pTextEdit_CHATOUTPUT->textCursor();
		cursor.movePosition( QTextCursor::End, QTextCursor::MoveAnchor, 1 );
		m_pTextEdit_CHATOUTPUT->setTextCursor( cursor );
	}

	message += "<br />";

	// save to logfile
	if ( g_pConfig->GetLogChatOption(elcoENABLELOGGING) )
	{
		if ( !(g_pConfig->GetLogChatOption(elcoDISABLEPUBLICCHAT) && !m_bPrivateChat) )
		{
			if ( g_pConfig->CheckLogChatNickNameFilter(m_sNick) )
			{
				s = m_sNick;

				if ( g_pConfig->GetLogChatOption(elcoAPPENDHUBNAME) )
				{
					s += "_";
					s += QString::fromAscii(m_pClient->GetHubName().Data());
				}

				if ( g_pConfig->GetLogChatOption(elcoAPPENDHUBHOST) )
				{
					s += "_";
					s += QString::fromAscii(m_pClient->GetIP().Data());
				}

				if ( g_pConfig->GetLogChatOption(elcoAPPENDDATE) )
				{
 					s += "_";
					s += m_sTimeStamp;
				}

				// damn hacking ;-)
				s.remove('/');
				s.remove('\\');
				s.remove(':');

				s = QString::fromAscii(g_pConfig->GetChatLogPath().Data()) + s + ".html";

				QFile f(s);

				if ( f.open(QIODevice::WriteOnly | QIODevice::Append) )
				{
					// add newline
					message += "\n";

					f.write(message.toAscii());
					f.close();
				}
			}
		}
	}
}

/** */
void DCChat::AddStatus( QString message, bool show )
{
	QString timestamp;

	// convert special chars ... damn hacking ;-)
	message.replace( "<", "&lt;" );
	message.replace( ">", "&gt;" );

	if ( g_pConfig->GetShowStatusMessage() || show )
	{
		timestamp = GetTimeStamp();

		if ( m_bPrivateChat )
			message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATSTATUSTEXT) + "\">" + message + "</font>";
		else
			message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATSTATUSTEXT) + "\">" + message + "</font>";

		if ( m_bPrivateChat )
			message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATSTATUSNICK) + "\">" + "<b>&lt;VALKNUT&gt;</b> " + "</font>" + message;
		else
			message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATSTATUSNICK) + "\">" + "<b>&lt;VALKNUT&gt;</b> " + "</font>" + message;

		AddOutput( timestamp + message );
	}
}

/** */
void DCChat::AddMessage( CMessagePrivateChat * msg, bool bremote, bool forward )
{
	if ( msg->m_eSecureState != m_eSecureState )
	{
		SetCrypt(msg->m_eSecureState);
	}

	if ( msg->m_sSrcNick.IsEmpty() )
		AddMessage( QString::fromAscii(msg->m_sSrcNick.Data()), QString::fromAscii(msg->m_sMessage.Data()), bremote, forward );
	else
		AddMessage( QString::fromAscii(msg->m_sMultiSrcNick.Data()), QString::fromAscii(msg->m_sMessage.Data()), bremote, forward );
}

/** */
void DCChat::AddMessage( QString nick, QString message, bool bremote, bool forward )
{
	QString msg,s,s1,timestamp;
	int index;
	int i1,i2;
	bool b1,b2;
	bool bSay = false;
	bool bMe = false;
	bool isOP = m_pClient->UserList()->IsAdmin( nick.toAscii().constData() );
	QString clientnick = QString::fromAscii( m_pClient->GetNick().Data() );

	//printf("ADD MESSAGE '%s'\n",message.Data());

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

	if ( CheckForData(message) )
	{
		return;
	}

	// check for nick - but avoid string searching if not enabled
	if ( !m_bPrivateChat && g_pConfig->GetSoundEnabled(eusNICKMENTIONED) )
	{
		if ( message.indexOf(clientnick) != -1 )
		{
			g_pConfig->PlaySound(eusNICKMENTIONED);
		}
	}

	// convert special chars ... damn hacking ;-)
	message.replace( "<", "&lt;" );
	message.replace( ">", "&gt;" );

	// convert special chars in nick ... damn hacking ;-)
	nick.replace( "<", "&lt;" );
	nick.replace( ">", "&gt;" );

	s1  = clientnick;
	s1 += ": ";

	if ( ( message.left(4).toUpper() == "/ME " ) || ( message.left(4).toUpper() == "+ME " ) )
	{
		bMe    = true;
		message = message.right( message.length() - 4 );
	}

	if ( message.startsWith(s1) )
	{
		bSay    = true;
		// message = message.right( message.length() - s1.length() );
	}

	msg = message;
	s1 = "";

	b1 = b2 = true;

	while ( !msg.isEmpty() )
	{
		if (b1)
		{
			if ( (i1 = FindFirstLink(msg)) == -1 )
			{
				b1 = false;
			}
		}
		else
		{
			i1 = -1;
		}

		if (b2)
		{
			if ( (i2 = FindFirstEmoticon(msg)) == -1 )
			{
				b2 = false;
			}
		}
		else
		{
			i2 = -1;
		}

		if ( ((i1 < i2) || (i2 == -1)) && (i1 != -1) )
		{
			// convert link
			s1 += msg.left(i1);
			msg = msg.right(msg.length()-i1);

			index = ConvertLinks( msg, s );
		}
		else if ( ((i2 < i1) || (i1 == -1)) && (i2 != -1) )
		{
			// convert emot
			s1 += msg.left(i2);
			msg = msg.right(msg.length()-i2);

			index = ConvertEmoticons( msg, s );
		}
		else
		{
			// no link & no emot found
			s1 += msg;
			msg = "";
			break;
		}

		if ( index > 1 )
		{
			s1 += s;
			msg = msg.right(msg.length()-index);
			continue;
		}

		if ( index == 1 )
		{
			s1 += msg.left(1);
		}

		msg = msg.right(msg.length()-index);
	}

	message = s1;

	// set color
	if ( m_bPrivateChat )
		if ( bremote && (nick != clientnick) )
			if ( bSay )
				message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATSAY) + "\">" + message + "</font>";
			else
				message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATREMOTETEXT) + "\">" + message + "</font>";
		else
			message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATLOCALTEXT) + "\">" + message + "</font>";
	else
		if ( forward )
			message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICPRIVATECHATTEXT) + "\">" + message + "</font>";
		else if ( bremote && (nick != clientnick) )
			if ( bSay )
				message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATSAY) + "\">" + message + "</font>";
			else
				message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATREMOTETEXT) + "\">" + message + "</font>";
		else
			message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATLOCALTEXT) + "\">" + message + "</font>";

	// add nick
	if ( m_bPrivateChat )
	{
		if ( nick != clientnick )
		{
			if ( isOP )
				message = "<font color=\"" + g_pConfig->GetChatColor(eccOPNICK) + "\">" + "<b>" + nick + "</b>: " + message + "</font>";
			else
				message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATREMOTENICK) + "\">" + "<b>" + nick + "</b>: " + message + "</font>";
		}
		else
			message = "<font color=\"" + g_pConfig->GetChatColor(eccCHATLOCALNICK) + "\">" + "<b>" + nick + "</b>: " + message + "</font>";
	}
	else
	{
		if ( forward )
			message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICPRIVATECHATNICK) + "\">" + "<b>&lt;" + nick + "&gt;</b> " + "</font>" + message;
		else if ( nick != clientnick )
			if ( bMe )
				message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATMENICK) + "\">" + "<b>" + nick + "</b> " + "</font>" + message;
			else
			{
				if ( isOP )
					message = "<font color=\"" + g_pConfig->GetChatColor(eccOPNICK) + "\">" + "<b>&lt;" + nick + "&gt;</b> " + "</font>" + message;
				else
					message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATREMOTENICK) + "\">" + "<b>&lt;" + nick + "&gt;</b> " + "</font>" + message;
			}
		else
			if ( bMe )
				message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATMENICK) + "\">" + "<b>" + nick + "</b> " + "</font>" + message;			
			else
				message = "<font color=\"" + g_pConfig->GetChatColor(eccPUBLICCHATLOCALNICK) + "\">" + "<b>&lt;" + nick + "&gt;</b> " + "</font>" + message;
	}

	timestamp = GetTimeStamp();

	AddOutput( timestamp + message );

	if ( bremote && m_bPrivateChat )
	{
		g_pConfig->PlaySound(eusRECEIVE);

		// send away message
		if ( g_pConfig->GetAwayMode() == euamAWAY )
		{
			CString awaymsg = g_pConfig->GetAwayMessage();

			if ( !(awaymsg.IsEmpty()) && (m_bSendAway) )
			{
				m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick.toAscii().constData(), awaymsg );
				m_bSendAway = false;
				AddStatus(tr("Sent away message: ") + QString::fromAscii(awaymsg.Data()));
			}
		}
		else
		{
			m_bSendAway = true;
		}
	}
}

/** */
void DCChat::SendMessage( QString message )
{
	int err;
	QString s;
	bool local = false;
	bool cmd = false;

	if ( !m_pClient )
	{
		return;
	}

	if ( message.isEmpty() )
	{
		message = TextEdit_CHATINPUT->toPlainText();
	}
	else
	{
		local = true;
	}

	if ( !message.isEmpty() )
	{
		if ( local == false )
		{
			// add history
			AddHistory(message);
			m_sHistoryTempString = QString();
			if ( message.at(0) == '/' )
			{
				cmd = CheckForCommand();
			}
		}
		
		if ( cmd == false )
		{
			// convert the newlines
			// any windows to unix then all unix to windows
			// so windows do not become double newlines
			message.replace( "\r\n", "\n" );
			message.replace( "\n", "\r\n" );

			if ( m_bPrivateChat )
				err = m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick.toAscii().constData(), message.toAscii().constData() );
			else
				err = m_pClient->SendChat( m_pClient->GetNick(), message.toAscii().constData() );

			if ( err == 0 )
			{
				if ( m_bPrivateChat )
					AddMessage( QString::fromAscii(m_pClient->GetNick().Data()), message, false );
				g_pConfig->PlaySound(eusSEND);
			}
			else
			{
				AddStatus( tr("Message not sent!") );
			}
		}

		if ( local == false )
		{
			TextEdit_CHATINPUT->clear();
		}
	}
}

/** */
int DCChat::FindFirstLink( QString msg )
{
	int current = -1;
	int first = -1;

	for ( int i = 0; i < g_LinkList.size(); ++i )
	{
		current = msg.indexOf(g_LinkList[i],0,Qt::CaseInsensitive);
		if ( current != -1 )
		{
			if ( (first == -1) || (first > current) )
			{
				first = current;
			}
		}
	}

	return first;
}

/** */
int DCChat::ConvertLinks( QString msg, QString & s )
{
	int ret = 1;
	int i1,i2,i3;

	// convert links
	for ( int i = 0; i < g_LinkList.size(); ++i )
	{
		if ( msg.startsWith(g_LinkList[i], Qt::CaseInsensitive) )
		{
			// link end at space or newline
			i1 = msg.indexOf(' ',0,Qt::CaseInsensitive);
			i2 = msg.indexOf('\xa',0,Qt::CaseInsensitive);
			i3 = msg.indexOf('\xd',0,Qt::CaseInsensitive);

			if ( i1 == -1 )
				i1 = msg.length();
			if ( (i1 > i2) && (i2 != -1) )
				i1 = i2;
			if ( (i1 > i3) && (i3 != -1) )
				i1 = i3;

			s = msg.left(i1);
			s = "<a title=\""+s+"\" name=\""+s+"\" href=\""+s+"\">"+s+"</a>";

			ret = i1;

			break;
		}
	}

	return ret;
}

/** */
int DCChat::FindFirstEmoticon( QString msg )
{
	int i1,i2;
	CList<DC_EmoticonObject> * elist;

	elist = g_pConfig->EmoticonList();

	i1 = i2 = -1;

	if ( g_pConfig->GetEnableEmoticons() && (elist != 0) )
	{
		DC_EmoticonObject * EmoticonObject = 0;

		while ( (EmoticonObject=elist->Next(EmoticonObject)) != 0 )
		{
			if ( (i1 = msg.indexOf( EmoticonObject->m_Text, 0 )) != -1 )
			{
				if ( (i2 == -1) || (i2 > i1) )
				{
					i2 = i1;
				}
			}
		}
	}

	return i2;
}

/** */
int DCChat::ConvertEmoticons( QString msg, QString & s )
{
	int ret = 1;
	CList<DC_EmoticonObject> * elist;

	elist = g_pConfig->EmoticonList();

	// convert emoticons
	if ( g_pConfig->GetEnableEmoticons() && (elist != 0) )
	{
		DC_EmoticonObject * EmoticonObject = 0;

		while ( (EmoticonObject=elist->Next(EmoticonObject)) != 0 )
		{
			if ( msg.startsWith(EmoticonObject->m_Text) )
			{
				s   = "<img alt=\"";
				s  += EmoticonObject->m_Text;
				s  += "\" title=\"";
				s  += EmoticonObject->m_Text;
				s  += "\" align=\"center\" source=\"";
				s  += g_pConfig->GetEmoticonTheme();
				s  += "/emoticon";
				s  += QString().setNum(EmoticonObject->m_nID);
				s  += "\" />";
				ret = EmoticonObject->m_Text.length();
				break;
			}
		}
	}

	return ret;
}

/** */
bool DCChat::CheckForCommand()
{
	bool res = false;
	QString s;
	
	QString cmd = g_pConfig->ReplaceUserChatCommands( TextEdit_CHATINPUT->toPlainText() );
	
	if ( cmd.isEmpty() )
	{
		return res;
	}
	
	if ( cmd.at(0) == '/' )
	{
		/*
		 * Extract command and remaining arguments.
		 * More complicated commands need to
		 * extract their arguments themselves but
		 * most commands just take a nick.
		 *
		 * Commands whose first argument is
		 * not a nick should not use theRest
		 * because it gets set to the nick
		 * if private chat.
		 */
		
		int firstspace = cmd.indexOf( ' ' );
		
		QString cmdOnly;
		QString theRest;
		
		if ( firstspace == -1 )
		{
			cmdOnly = cmd;
			if ( m_bPrivateChat )
			{
				theRest = m_sNick;
			}
		}
		else
		{
			cmdOnly = cmd.left( firstspace );
			theRest = cmd.mid( firstspace + 1 );
		}
		
		// AddStatus( "cmdOnly ='" + cmdOnly + "'", true );
		// AddStatus( "theRest = '" + theRest + "'", true );
		
		// help
		if ( cmdOnly == "/dchelp" )
		{
			s  = tr("Help:");
			s += "\n";
			s += tr("/clear - clears the chat window");
			s += "\n";
			s += tr("/mode - shows the current mode");
			s += "\n";
			s += tr("/refresh - refresh share");
			s += "\n";
			s += tr("/rebuild - remove no longer present files from hash database");
			s += "\n";
			s += tr("/validate - check hash database for errors");
			s += "\n";
			s += tr("/slots &lt;N&gt; - set number of upload slots to N");
			s += "\n";
			s += tr("/ts - switch time display in chat on/off");
			s += "\n";
			s += tr("/adv - send an advertisment to the hub");
			s += "\n";
			s += tr("/join &lt;address&gt; - disconnect from currently connected hub and connect to another hub");
			s += "\n";
			s += tr("/msg &lt;nick&gt; &lt;message&gt; - send private message");
			s += "\n";
			s += tr("/away &lt;message&gt; - set automatic response for private messages; if the message is empty, disable it");
			s += "\n";
			s += tr("/bye &lt;message&gt; - disconnect from channel with an optional message");
			s += "\n";
			s += tr("/uptime [show] - show valknut uptime [to other users]");
			s += "\n";
			s += tr("/ls &lt;nick&gt; - get share list from user");
			s += "\n";
			s += tr("/grant &lt;nick&gt; - grant user a slot");
			s += "\n";
			s += tr("/grantp &lt;nick&gt; - grant user a permanent slot");
			s += "\n";
			s += tr("/friend &lt;nick&gt; - add user to friend list");
			s += "\n";
			s += tr("/fav - add hub to bookmark list");
			s += "\n";
			s += tr("/info &lt;nick&gt; - show user info");
			s += "\n";
			s += tr("/now - send current time to the chat");
			s += "\n";
			s += tr("/sh &lt;command&gt; &lt;args...&gt; - send output of shell command to chat");
			s += "\n";
			s += tr("/raw - send raw message");
			s += "\n";
			s += tr("/disablesorting - disable sorting of the user list");
			s += "\n";
			s += tr("/enablesorting - enable sorting of the user list");
			s += "\n";
			s += tr("/ignore &lt;nick&gt; - do not show chat messages from the user");
			s += "\n";
			s += tr("/unignore &lt;nick&gt; - show chat messages from the user again");
			s += "\n";
			s += tr("/ratio [show] - show share ratio [to other users]");
			s += "\n";
			s += tr("/newlog - start a new logfile");
			s += "\n";
			AddStatus( s, true );
			res = true;
		}
		// /clear - clear the chat window
		else if ( cmdOnly == "/clear" )
		{
			m_pTextEdit_CHATOUTPUT->clear();

			AddStatus( tr("Chat Cleared."), true );
			res = true;
		}
		// /info - show user info
		else if ( cmdOnly == "/info" )
		{
			CMessageMyInfo myinfo;
			
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else if (m_pClient->UserList()->GetUserMyInfo( theRest.toAscii().constData(), &myinfo ) == false)
			{
				s = tr("Get info failed.");
			}
			else
			{
				s  = tr("Info:");
				s += "\n";
				s += tr("Nick:");
				s += ' ';
				s += QString::fromAscii(myinfo.m_sNick.Data());
				s += "\n";
				s += tr("Operator:");
				s += ' ';

				if ( myinfo.m_bOperator )
				{
					s += tr("yes");
				}
				else
				{
					s += tr("no");
				}

				s += "\n";

				s += tr("Comment:");
				s += ' ';
				s += QString::fromAscii(myinfo.m_sComment.Data());
				s += "\n";
				s += tr("Speed:");
				s += ' ';
				s += QString::fromAscii(myinfo.m_sUserSpeed.Data());
				s += "\n";
				s += tr("EMail:");
				s += ' ';
				s += QString::fromAscii(myinfo.m_sEMail.Data());
				s += "\n";
				s += tr("Shared:");
				s += ' ';
				s += QString().setNum(myinfo.m_nShared);
				s += ' ';
				s += tr("Bytes");
				s += "\n";

				s += tr("Away:");
				s += ' ';

				switch(myinfo.m_eAwayMode)
				{
					case euamAWAY:
						s += tr("on");
						break;
					default:
						s += tr("off");
						break;
				}

				s += "\n";

				s += tr("Version:");
				s += ' ';

				switch(myinfo.m_eClientVersion)
				{
					case eucvDCPP:
						s += "DC++";
						break;
					case eucvDCGUI:
						s += "Valknut";
						break;
					case eucvMICRODC:
						s += "microdc";
						break;
					case eucvSHAKESPEER:
						s += "ShakesPeer";
						break;
					default:
 						s += tr("Unknown");
						break;
				}

				s += "\n";

				s += tr("Tag:");
				s += ' ';
				s += QString::fromAscii(myinfo.m_sVerComment.Data());
				s += "\n";
				s += tr("Supports encryption:");
				s += ' ';
				if ( myinfo.m_bTLSFlag )
				{
					s += tr("yes");
				}
				else
				{
					s += tr("no");
				}
				s += "\n";
			}

			AddStatus( s, true );
			res = true;
		}
		// /uptime - show valknut uptime
		else if ( cmdOnly == "/uptime" )
		{
			int d,h,m;
			int i = time(0)-dclibUptime();

			d = i/(60*60*24);
			i = i%(60*60*24);
			h = i/(60*60);
			i = i%(60*60);
			m = i/60;

			s  = tr("Valknut uptime");
			s += ": ";

			if ( d > 0 )
			{
				s += QString().setNum(d);
				s += ' ';
				if ( d == 1 )
					s += tr("day");
				else
					s += tr("days");
				s += ',';
			}
			if ( h > 0 )
			{
				s += QString().setNum(h);
				s += ' ';
				if ( h == 1 )
					s += tr("hour");
				else
					s += tr("hours");
				s += ',';
			}
			s += QString().setNum(m);
			s += ' ';
			if ( m == 1 )
				s += tr("minute");
			else
				s += tr("minutes");
			
			if ( cmd.mid( 8 ).toLower() == "show" )
				SendMessage(s);
			else
				AddStatus( s, true );

			res = true;
		}
		// /mode - show current mode
		else if ( cmdOnly == "/mode" )
		{
			s  = tr("Current mode: ");

			if ( g_pConfig->GetMode() == ecmACTIVE )
				s += tr("active");
			else
				s += tr("passive");

			// get listen manager status
			if ( CListenManager::Instance() )
			{
				CString lms = CListenManager::Instance()->GetSocketErrorMsg();
			
				if ( lms.NotEmpty() )
				{
					s += " (";
					s += QString::fromAscii(lms.Data());
					s += ')';
				}	
			}
			
			s += '.';
			
			AddStatus( s, true );
			res = true;
		}
		// /refresh - refresh share 
		else if ( cmdOnly == "/refresh" )
		{
			if ( CFileManager::Instance()->CreateShareList() )
			{
				s = tr("Refresh share in progress.");
			}
			else
			{
				s = tr("A share operation is already in progress.");
			}

			AddStatus( s, true );
			res = true;
		}
		// /rebuild - clean up share databases
		else if ( cmdOnly == "/rebuild" )
		{
			if ( CFileManager::Instance()->RebuildLists() )
			{
				s = tr("Rebuild share in progress.");
			}
			else
			{
				s = tr("A share operation is already in progress.");
			}
			
			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/validate" )
		{
			if ( CFileManager::Instance()->ValidateLeaves() )
			{
				s = tr("Validate hash database in progress.");
			}
			else
			{
				s = tr("A share operation is already in progress.");
			}
			
			AddStatus( s, true );
			res = true;
		}
		// /slots - set number of slots
		else if ( cmdOnly == "/slots" )
		{
			s = cmd.mid(7);
			if ( s.isEmpty() )
			{
				AddStatus( tr("Upload slots: ") + QString().setNum(g_pConfig->GetMaxUpload()), true );
			}
			else
			{
				bool valid = false;
				int i = s.toInt(&valid);
				
				if ( valid && (i >= 0) && (i <= 99) )
				{
					g_pConfig->SetMaxUpload(i);
					AddStatus( tr("Setting upload slots to") + " " + QString::number(i), true );
				}
				else
				{
					AddStatus( tr("Invalid number of upload slots specified."), true );
				}
			}
			
			res = true;
		}
		// /ts - switch time display in chat on/off 
		else if ( cmdOnly == "/ts" )
		{
			bool b;

			if ( m_bPrivateChat )
			{
				b = !g_pConfig->GetTimeStamp(etsPRIVATECHAT);
				g_pConfig->SetTimeStamp(etsPRIVATECHAT,b);
			}
			else
			{
				b = !g_pConfig->GetTimeStamp(etsHUBCHAT);
				g_pConfig->SetTimeStamp(etsHUBCHAT,b);
			}

			s = tr("Switch timestamp ");

			if ( b )
				s += tr("on.");
			else
				s += tr("off.");

			AddStatus( s, true );
			res = true;
		}
		// /adv - send an advertisment to the hub
		else if ( cmdOnly == "/adv" )
		{
			AddStatus( tr("Send advertisment."), true );

			s = tr("\nDownload Valknut from http://wxdcgui.sourceforge.net\nIt does everything !\nWorks on almost any platform you can think of and makes you coffee.\n");

			SendMessage(s);

			res = true;
		}
		// /now - send current time to the chat
		else if ( cmdOnly == "/now" )
		{
			s = QTime::currentTime().toString("hh:mm:ss");
			
			if ( cmd.length() > 5 )
			{
				s += " ";
				s += cmd.mid(5,cmd.length()-5);
			}
			
			SendMessage(s);

			res = true;
		}
		// /raw - send raw message
		else if ( cmdOnly == "/raw" )
		{
			s = cmd.mid( 5 );
			
			if ( !(s.isEmpty()) )
			{
				AddStatus( cmd, true );
				m_pClient->SendString(s.toAscii().constData());
			}
			else
			{
				AddStatus( tr("Wrong parameter for '/raw'"), true );
			}

			res = true;
		}
#ifdef ENABLE_PRETEND_TESTING_COMMAND
		else if ( cmdOnly == "/pretend" )
		{
			AddStatus( cmd, true );
			
			/* no attempt is made to make encoding correct */
			QByteArray ba = cmd.mid(9).toAscii();
			m_pClient->DataAvailable(ba.constData(),ba.size());
			
			res = true;
		}
#endif
		// /join <address> - disconnect from currently connected hub and connect to another hub 
		else if ( cmdOnly == "/join" )
		{
			QString address = cmd.mid(6,cmd.length()-6);

			if ( address.isEmpty() )
			{
				s = tr("Wrong parameter for '/join'");
			}
			else
			{
				s = tr("Join to: ") + address;
				
				CString caddress = address.toAscii().constData();

				m_pClient->SetHubName(caddress);
				m_pClient->setWindowTitle(address);
				m_pClient->Connect(caddress);
			}

			AddStatus( s, true );
			res = true;
		}
		// /ls - get share list from user
		else if ( cmdOnly == "/ls" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				/** add transfer to the waitlist */
				CString empty;
				g_pTransferView->DLM_QueueAdd( theRest.toAscii().constData(), m_pClient->GetHubName(), m_pClient->GetHost(),
							DC_USER_FILELIST, DC_USER_FILELIST, empty, empty, eltBUFFER,
							0, 0, 0, empty );

				s  = tr("Download sharelist from");
				s += " '";
				s += theRest;
				s += '\'';
			}

			AddStatus( s, true );
			res = true;
		}
		// /grantp - grant perm. slot to user
		else if ( cmdOnly == "/grantp" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				g_pTransferView->DLM_AddUserSlot( theRest.toAscii().constData(), m_pClient->GetHubName(), 0, true );

				s  = tr("Permanent slot added for");
				s += " '";
				s += theRest;
				s += '\'';
			}

			AddStatus( s, true );
			res = true;
		}
		// /grant - grant slot to user
		else if ( cmdOnly == "/grant" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				g_pTransferView->DLM_AddUserSlot( theRest.toAscii().constData(), m_pClient->GetHubName(), 1 );

				s  = tr("Slot added for");
				s += " '";
				s += theRest;
				s += '\'';
			}

			AddStatus( s, true );
			res = true;
		}
		// /friend - add user to friend list
		else if ( cmdOnly == "/friend" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				g_pUsersList->AddFriend( theRest, QString::fromAscii(m_pClient->GetHubName().Data()), QString::fromAscii(m_pClient->GetHost().Data()), QString() );

				s  = tr("Added to friend list ");
				s += " '";
				s += theRest;
				s + '\'';
			}

			AddStatus( s, true );
			res = true;
		}
		// /fav - add hub to bookmarks
		else if ( cmdOnly == "/fav" )
		{
			QString qhubname = QString::fromAscii( m_pClient->GetHubName().Data() );
			g_pHubListManager->AddBookmark( qhubname, QString::fromAscii(m_pClient->GetHost().Data()), QString() );

			s  = tr("Add bookmark hub");
			s += " '";
			s += qhubname;
			s += '\'';

			AddStatus( s, true );
			res = true;
		}
		// /bye <msg> - disconnect from channel
		else if ( cmdOnly == "/bye" )
		{
			s = cmd.mid( 5 );
			if ( !(s.isEmpty()) )
			{
				SendMessage(s);
			}

			m_pClient->Disconnect(true);

			res = true;
		}
		// /msg <nick> <message> - send private message 
		else if ( cmdOnly == "/msg" )
		{
			QString nick,message;
			
			AddStatus( cmd, true );

			int i = theRest.indexOf(' ');
			if ( i != -1 )
			{
				nick    = theRest.left(i);
				message = theRest.mid(i+1);
			}

			if ( nick.isEmpty() || message.isEmpty() )
			{
				s = tr("Wrong parameter for '/msg'");
			}
			else
			{
				if ( m_pClient->SendPrivateMessage( m_pClient->GetNick(), nick.toAscii().constData(), message.toAscii().constData() ) == 0 )
				{
					s = tr("Private message sent.");
					g_pConfig->PlaySound(eusSEND);
				}
				else
				{
					s = tr("Private message not sent!");
				}
			}

			AddStatus( s, true );
			res = true;

		}
		// /away <message> - set automatic response for private messages; if the message is empty, disable it 
		else if ( cmdOnly == "/away" )
		{
			s = cmd.mid(6);

			g_pConfig->SetAwayMessage(s.toAscii().constData());

			if ( s.isEmpty() )
				s = tr("Away message disabled.");
			else
				s = tr("Away message set.");

			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/sh" )
		{
			// display the command the user entered
			AddStatus( cmd, true );
			
			// actually this is better - more portable, doesn't depend on /bin/sh
			QString args = cmd.mid(4, cmd.length() - 4);
			
			if ( args.isEmpty() )
			{
				AddStatus( tr("No command entered."), true );
				return true;
			}
			
			QStringList shellargs = args.split(" ");
			
			// QStringList shellargs;
			// shellargs.append( QString("/bin/sh") );
			// shellargs.append( QString("-c") );
			// shellargs.append( cmd.mid(4, cmd.length() - 4) );
			
			// debug code - show the args we are going to run
			//AddStatus( shellargs.join( QString(",") ), true );
			
			if ( g_pConfig->GetThreadsForShellCommands() )
			{
				DCShellCommandRunner * runner = new DCShellCommandRunner( shellargs, this );
				m_pShellRunners->append(runner);
				connect( runner, SIGNAL(finished(bool,QString)), this, SLOT(slotShellCommandFinished(bool,QString)) );
				runner->start();
			}
			else
			{
				QProcess shellproc( this );
				QString program = shellargs.takeFirst();
				shellproc.start( program, shellargs );
				shellproc.closeWriteChannel();
				shellproc.waitForFinished( g_pConfig->GetShellCommandTimeout() * 1000 );
				
				if ( (shellproc.state() == QProcess::NotRunning) )
				{
					if ( shellproc.exitStatus() == QProcess::NormalExit )
					{
						int exitcode = shellproc.exitCode();
						if ( exitcode == 0 )
						{
							s = QString( shellproc.readAllStandardOutput() ).trimmed();
							if ( s.isEmpty() )
							{
								AddStatus( tr("Command produced no visible output."), true );
							}
							else
							{
								SendMessage(s);
							}
						}
						else
						{
							AddStatus( tr("Process exited with status") + " " + QString::number(exitcode), true );
						}
					}
					else
					{
						AddStatus( tr("Process was killed or crashed."), true);
					}
				}
				else
				{
					AddStatus( tr("Process still running after") + " " + QString().setNum(g_pConfig->GetShellCommandTimeout()) + " " + tr("seconds, killing process..."), true );
					shellproc.terminate();
					sleep(1);
					if ( shellproc.state() != QProcess::NotRunning )
					{
						shellproc.kill();
					}
				}
			}
			
			res = true;
		}
		else if ( cmdOnly == "/sendphoto" )
		{
			if ( !m_bPrivateChat )
			{
				s = tr("Only allowed in private chat.");
			}
			else if ( m_pClient->UserList()->GetUserClientVersion(m_sNick.toAscii().constData()) != eucvDCGUI )
			{
				s = tr("The user is using an old version or not using Valknut");
			}
			else
			{
				CString fn = g_pConfig->GetUserPhotoFileName().toAscii().constData();
				CByteArray bas,bad;

				if ( CDir().IsFile( fn, false ) )
				{
					if ( (bas.LoadFromFile(fn) == false) || (bas.Size() == 0) )
					{
						s = tr("Can't read your photo.");
					}
					else
					{
						CBase64::Encode( &bad, &bas );

						if ( bad.Size() == 0 )
						{
							s = tr("Photo encode error.");
						}
						else
						{
							CString se;

							se.Set((char*)bad.Data(),bad.Size());
							se  = "<photo data=\"" + se + "\">";

							if ( m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick.toAscii().constData(), se ) != 0 )
							{
								s = tr("Photo not sent.");
							}
							else
							{
								s = tr("Photo sent.");
							}
						}
					}
				}
				else
				{
					s = tr("Photo not found.");
				}
			}

			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/disablesorting" )
		{
			m_pClient->disableUserListSorting();
			s = tr("Disabled user list sorting.");
			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/enablesorting" )
		{
			m_pClient->enableUserListSorting();
			s = tr("Enabled user list sorting.");
			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/ignore" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				if ( g_pUsersList->isNickInList(theRest) == false )
				{
					g_pUsersList->AddFriend( theRest, QString::fromAscii(m_pClient->GetHubName().Data()), QString::fromAscii(m_pClient->GetHost().Data()), QString() );
				}
				
				g_pUsersList->setIgnore( theRest, true );
				s  = tr("Ignoring chat from ");
				s += theRest;
			}
			
			AddStatus( s, true );
			res = true;
			
		}
		else if ( cmdOnly == "/unignore" )
		{
			if ( theRest.isEmpty() )
			{
				s = tr("No nick, try /dchelp");
			}
			else
			{
				if ( g_pUsersList->ignoreNick( theRest ) )
				{
					g_pUsersList->setIgnore( theRest, false );
					s  = tr("Showing chat from ");
					s += theRest;
				}
				else
				{
					s  = theRest;
					s += tr(" is not in ignore list");
				}
				
			}
			
			AddStatus( s, true );
			res = true;
		}
		else if ( cmdOnly == "/ratio" )
		{
			ulonglong up = CSocket::m_Traffic.GetTraffic(ettTX);
			ulonglong down = CSocket::m_Traffic.GetTraffic(ettRX);
			
			double ratio = 0;
			
			if ( down > 0 )
			{
				ratio = (double) up / (double) down;
			}
			
			s  = tr("ratio: overall: ");
			s += QString().setNum( ratio, 'f', 2 );
			s += " (";
			s += tr("uploads");
			s += ": ";
			s += DCGuiUtils::GetSizeString( up );
			s += " | ";
			s += tr("downloads");
			s += ": ";
			s += DCGuiUtils::GetSizeString( down );
			s += ")\n";
			
			up -= g_pConfig->GetStartUploaded();
			down -= g_pConfig->GetStartDownloaded();
			
			if ( down > 0 )
			{
				ratio = (double) up / (double) down;
			}
			else
			{
				ratio = 0;
			}
			
			s += tr("this session: ");
			s += QString().setNum( ratio, 'f', 2 );
			s += " (";
			s += tr("uploads");
			s += ": ";
			s += DCGuiUtils::GetSizeString( up );
			s += " | ";
			s += tr("downloads");
			s += ": ";
			s += DCGuiUtils::GetSizeString( down );
			s += ")";
			
			if ( cmd.mid(7).toLower() == "show" )
			{
				SendMessage( s );
			}
			else
			{
				AddStatus( s, true );
			}
			res = true;
		}
		else if ( cmdOnly == "/newlog" )
		{
			m_sTimeStamp  = QDate::currentDate().toString("yyyyMMdd");
			m_sTimeStamp += "-";
			m_sTimeStamp += QTime::currentTime().toString("hhmmss");
			
			s  = tr("New log timestamp: ");
			s += m_sTimeStamp;
			
			if ( g_pConfig->GetLogChatOption(elcoAPPENDDATE) == false )
			{
				s += "\n";
				s += tr("Warning: log timestamp not enabled.");
			}
			
			AddStatus( s, true );
			res = true;
		}
		else if ( g_pConfig->GetSendUnknownCommandsAsChat() == false )
		{
			bool isAllowed = false;
			
			QString allowedstring = g_pConfig->GetAllowedUnknownCommands();
			
			if ( !allowedstring.isEmpty() )
			{
				QStringList allowed = allowedstring.split( ",", QString::SkipEmptyParts );
				
				for ( int i = 0; i < allowed.count(); i++ )
				{
					// e.g. allow "/me" and "/me waves" but not "/mediqjdoh"
					QString tail = cmd.mid(allowed[i].length(),1);
					if ( cmd.startsWith(allowed[i]) &&
					    ((tail == " ") || (tail.isEmpty())) )
					{
						isAllowed = true;
						break;
					}
				}
			}
			
			if ( !isAllowed )
			{
				AddStatus( cmd );
				AddStatus( tr("Unknown command, try /dchelp") );
				res = true;
			}
		}
	}
	else
	{
		/* A custom chat command was replaced with a message, send it */
		AddStatus( TextEdit_CHATINPUT->toPlainText(), true );
		SendMessage( cmd );
		res = true;
	}
	
	return res;
}

/** */
bool DCChat::CheckForData( QString message )
{
	bool res = false;

	// now we check for special private messages
	if ( m_bPrivateChat )
	{
		// remote send a photo
		if ( message.startsWith("<photo data=\"") && message.endsWith("\">") )
		{
			CString s;
			CByteArray ba;

			//printf("CheckForData message='%s'\n",message.toAscii().constData());

			s = message.mid(13,message.length()-15).toAscii().constData();
			
			//printf("CheckForData s='%s'\n",s.Data());
			
			if ( CBase64::Decode( &ba, &s ) > 0 )
			{
				g_pUsersList->AddFriendPhoto(m_sNick,&ba);
				AddStatus(tr("Photo received."), true );
				
				res = true;
			}
		}
	}

	return res;
}

/** */
void DCChat::NickCompletion()
{
	// shameles stolen from ksirc
	int start, end;
	int i;
	QString s;

	if ( m_nTabPressed > 0 )
	{
		s     = m_sTabSaved;
		start = m_nTabStart;
		end   = m_nTabEnd;
	}
	else
	{
		// QT3 TextEdit_CHATINPUT->getCursorPosition(&p,&i);
		i = TextEdit_CHATINPUT->textCursor().position();
		s = TextEdit_CHATINPUT->toPlainText();
		m_sTabSaved = s;
		if ( i == 0 )
			end = 0;
		else
			end = i - 1;
		start = s.lastIndexOf(QRegExp("\\s"), end);
		m_nTabStart = start;
		m_nTabEnd = end;
	}

	if ( s.isEmpty() && (m_sTabNick.isEmpty() == false) )
	{
		// use the saved nick
		QString line = m_sTabNick + ": ";
		TextEdit_CHATINPUT->setPlainText(line);
		// QT3 TextEdit_CHATINPUT->setCursorPosition( 0,line.length() );
		QTextCursor cursor = TextEdit_CHATINPUT->textCursor();
		cursor.setPosition( line.length() );
		TextEdit_CHATINPUT->setTextCursor( cursor );
	}
	else
	{
		int cpos = 1;

		if ( start == -1 )
		{
			m_sTabNick = m_pClient->findNick( s.mid(0, end+1), m_nTabPressed );

			if ( m_sTabNick.isNull() )
			{
				m_nTabPressed = 0;
				m_sTabNick = m_pClient->findNick( s.mid(0, end+1), m_nTabPressed );
			}

			s.replace(0, end + 1, m_sTabNick + ": " );
			cpos += 2;
		}
		else
		{
			m_sTabNick = m_pClient->findNick( s.mid(start + 1, end - start), m_nTabPressed );

			if ( m_sTabNick.isNull() )
			{
				m_nTabPressed = 0;
				m_sTabNick = m_pClient->findNick( s.mid(start + 1, end - start), m_nTabPressed );
			}

			s.replace(start + 1, end - start, m_sTabNick);
		}

		if ( m_sTabNick.isEmpty() == false )
		{
			TextEdit_CHATINPUT->setPlainText(s);

			// QT3 TextEdit_CHATINPUT->setCursorPosition(0,start + m_sTabNick.length() + cpos);
			QTextCursor cursor = TextEdit_CHATINPUT->textCursor();
			cursor.setPosition( start + m_sTabNick.length() + cpos );
			TextEdit_CHATINPUT->setTextCursor( cursor );

			m_nTabPressed++;
		}
	}
}

/** insert double clicked word into chat input box */
void DCChat::slotDoubleClickedChatOutput()
{
	// double clicking already selects the text
	QString selected = m_pTextEdit_CHATOUTPUT->textCursor().selectedText();
	
	//printf("selected=%s\n", selected.toAscii().constData());
	
	// replace all emoticon special characters with the proper text
	// WARNING: the whole thing could break if QT changes its internal HTML
	if ( selected.contains("\ufffc") && g_pConfig->GetEnableEmoticons() )
	{
		// get the html
		QString html = m_pTextEdit_CHATOUTPUT->textCursor().selection().toHtml();
		
		QStringList smileys;
		int start = 0;
		QString src = "src=\"" + g_pConfig->GetEmoticonTheme() + "/emoticon";
		const int len = src.length();
		CList<DC_EmoticonObject> * elist = g_pConfig->EmoticonList();
		
		while ( start != -1 )
		{
			// crap, the html doesn't include the title=":-)", only src="theme/emoticon5"
			//printf("html=%s\n", html.toAscii().constData());
			start = html.indexOf( src );
			if ( start == -1 )
			{
				break;
			}
			
			start = start + len;
			int end = html.indexOf( "\"", start );
			if ( end == -1 )
			{
				break;
			}
		
			int id = html.mid( start, end - start ).toInt();
			html = html.right( html.length() - (end+1) );
			//printf( "start=%d end=%d id=%d\n", start, end, id );
			
			DC_EmoticonObject * EmoticonObject = 0;
			QString smiley;
			
			while ( (EmoticonObject=elist->Next(EmoticonObject)) != 0 )
			{
				if ( EmoticonObject->m_nID == id )
				{
					smiley = EmoticonObject->m_Text;
					
					smiley.replace( "&lt;", "<" );
					smiley.replace( "&gt;", ">" );
					smiley.replace( "&amp;", "&" );
					smiley.replace( "&apos;", "\'" );
					smiley.replace( "&quot;", "\"" );
					
					break;
				}
			}
		
			smileys << smiley;
		}
		
		// finally replace those stupid characters
		for( int i = 0; i < smileys.size(); i++ )
		{
			int pos = selected.indexOf("\ufffc");
			if ( pos == -1 )
			{
				break;
			}
			selected.replace( pos, 1, smileys[i] );
		}
		selected.replace( "\ufffc", "???" );
	}

	// do noting on no selection
	if ( selected.isEmpty() )
	{
		return;
	}
	
	// remove all html tags - not needed in QT4
	/* QRegExp re;
	re.setMinimal(true);
	re.setPattern("<.*>");
	selected = selected.remove( re );
	
	// "NICK> " gets selected, trim "> " off nick
	if ( selected.right(5) == "&gt; " )
	{
		selected = selected.left( selected.length() - 5 );
	} */
	
	// trim any white space off ends
	selected = selected.trimmed();
	
	// add space on end
	selected += " ";
	
	if ( TextEdit_CHATINPUT->toPlainText().isEmpty() )
	{
		TextEdit_CHATINPUT->insertPlainText( selected );
	}
	else
	{
		if ( TextEdit_CHATINPUT->toPlainText().right(1) != " " )
		{
			selected.prepend(" ");
		}
		
		// not using TextEdit_CHATINPUT->append(), that would add a new line
		TextEdit_CHATINPUT->insertPlainText( selected );
	}
}

/** */
void DCChat::slotShellCommandFinished( bool ok, QString output )
{
	DCShellCommandRunner * runner = qobject_cast<DCShellCommandRunner*>(sender());
	int pos = m_pShellRunners->indexOf(runner);
	if ( pos == -1 )
	{
		AddStatus( tr("Shell command event not found in list") );
		AddStatus( output );
	}
	else
	{
		if ( ok )
		{
			SendMessage( output );
		}
		else
		{
			AddStatus( output );
		}
		
		m_pShellRunners->removeAt(pos);
		disconnect( runner, SIGNAL(finished(bool,QString)), this, SLOT(slotShellCommandFinished(bool,QString)) );
		delete runner;
	}
}

/** */
void DCChat::slotBGColorChanged()
{
	if ( g_pConfig->GetChatBackgroundColorEnabled() )
	{
		/*
		 * strangely trying to change the text background colour using QTextFormat
		 * or style sheets does not appear to work, this method could be applied to
		 * any widget
		 */
		QPalette palette = m_pTextEdit_CHATOUTPUT->palette();
		palette.setColor( QPalette::Base, g_pConfig->GetChatBackgroundColor() );
		m_pTextEdit_CHATOUTPUT->setAutoFillBackground(true);
		m_pTextEdit_CHATOUTPUT->setPalette(palette);
		
		palette = TextEdit_CHATINPUT->palette();
		palette.setColor( QPalette::Base, g_pConfig->GetChatBackgroundColor() );
		TextEdit_CHATINPUT->setAutoFillBackground(true);
		TextEdit_CHATINPUT->setPalette(palette);
	}
	else
	{
		QTextEdit te;
		m_pTextEdit_CHATOUTPUT->setAutoFillBackground(te.autoFillBackground());
		m_pTextEdit_CHATOUTPUT->setPalette(te.palette());
		TextEdit_CHATINPUT->setAutoFillBackground(te.autoFillBackground());
		TextEdit_CHATINPUT->setPalette(te.palette());
	}
}
