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

//define ENABLE_PRETEND_TESTING_COMMAND 1

#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 "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 "dcchat.h"
#include "dchubsearch.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/core/cfile.h>
#include <dclib/dclib.h>
#include <dclib/clistenmanager.h>

/** the link list*/
const char * p_LinkList[] = {
	"HTTP://",
	"HTTPS://",
	"NEWS://",
	"FILE://",
	"WWW.",
	"FTP://",
	"FTP.",
	"HKP://",
	"LDAP://",
	"DCFILE://",
	"DCHUB://",
	"DCHUBS://",	
	"DCCMD://",
	"MAGNET:",
	NULL
};

#define MAX_HISTORY_COUNT 25

/** */
DCChat::DCChat( QWidget * parent, const char *name, Qt::WindowFlags wflags, DCClient * client, bool bprivate ) : QWidget(parent, name, wflags)
{
	// ASSERT(client);
	
	setupUi(this);
	
	// set default icon
	setIcon( g_pIconLoader->GetPixmap(eiGLOBE) );

	m_pParent      = parent;
	m_bPrivateChat = bprivate;
	m_pClient      = client;
	m_bSendAway    = TRUE;
	m_eSecureState = esecsNONE;
	m_pCurrentHistory = 0;

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

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

/** */
DCChat::~DCChat()
{
	/* cancel all running shell commands */
	for ( int i = 0; i < m_pShellRunners->count(); i++ )
	{
		m_pShellRunners->at(i)->cancel();
		usleep(100000);
		delete m_pShellRunners->at(i);
	}
	
	/* when all finished */
	m_pShellRunners->clear();
	delete m_pShellRunners;
}

/** */
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, 1, 1, 1, 1, "");
		gl->addWidget( TextEdit_CHATINPUT, 0, 0 );
	}

	m_pTextEdit_CHATOUTPUT = new DCQTextEdit( Frame_OUTPUT );
	m_pTextEdit_CHATOUTPUT->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)5, 0, 0, m_pTextEdit_CHATOUTPUT->sizePolicy().hasHeightForWidth() ) );
	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()) );

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

	installEventFilter(this);

	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() == TRUE )
	{
		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->state() == Qt::AltModifier )
		{
			if ( (e->key() == Qt::Key_S) && (g_pConfig->GetSendChat() == tr("Alt + S").toAscii().data()) )
			{
				send = TRUE;
			}
			else if( ((e->key() == Qt::Key_Enter) || (e->key() == Qt::Key_Return )) &&
				   (g_pConfig->GetSendChat() == tr("Alt + Enter").toAscii().data()))
			{
				send = TRUE;
			}
		}
		else if( (g_pConfig->GetSendChat() == tr("Ctrl + Enter").toAscii().data()) &&
			 (e->state() == Qt::ControlModifier) && ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) )
		{
			send = TRUE;
		}
		else if( (g_pConfig->GetSendChat() == tr("Enter").toAscii().data() && (e->state() != Qt::ControlModifier) &&
			 ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) )
		{
			send = TRUE;
		}

		if( send == TRUE )
		{
			SendMessage();
			e->accept();
			return true;
		}
		else if ( e->state() == Qt::ControlModifier )
		{
			CString * ps = 0;

			// check for history
			if ( (e->key() == Qt::Key_Down) && (m_pCurrentHistory != 0) )
			{
				ps = m_History.Next(m_pCurrentHistory);

			}
			else if ( e->key() == Qt::Key_Up )
			{
				ps = m_History.Forw(m_pCurrentHistory);

				if ( m_pCurrentHistory == 0 )
				{
					// save current text
					m_sHistoryTempString = TextEdit_CHATINPUT->text().toAscii().data();
					// save current cursor position
					// QT3 TextEdit_CHATINPUT->getCursorPosition( &m_nHistoryTempPara, &m_nHistoryTempIndex );
					m_nHistoryTempIndex = TextEdit_CHATINPUT->textCursor().position();
				}
			}

			if ( ps != 0 )
			{
				m_pCurrentHistory = ps;
				TextEdit_CHATINPUT->setPlainText(m_pCurrentHistory->Data());
			}
			else if (e->key() == Qt::Key_Down)
			{
				m_pCurrentHistory = ps;
				TextEdit_CHATINPUT->setPlainText(m_sHistoryTempString.Data());
				// 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() == EVENT_TRANSLATION )
	{
		DC_TranslationEvent *e = (DC_TranslationEvent*)event;

		if ( e->m_bTranslate == TRUE )
		{
			AddStatus( (tr("Translation: ") + "'" + e->m_sOriginal + "' -> '" + e->m_sTranslation + "'").toAscii().data() );
		}
		else
		{
			AddStatus( (tr("Translation failed: ") + "'" + e->m_sOriginal + "'" + " ( " +tr("Error was: ") + "'" + e->m_sTranslation + "' )").toAscii().data() );
		}
	}
	/* else if ( event->type() == EVENT_SHELL_COMMAND_FINISHED )
	{
		DC_ShellCommandFinishedEvent * e = (DC_ShellCommandFinishedEvent*)event;
		{
			int pos = m_pShellRunners->indexOf(e->m_pShellCommandRunner);
			if ( pos == -1 )
			{
				AddStatus( tr("Shell command event not found in list").toAscii().data() );
				AddStatus( e->output.toAscii().data() );
			}
			else
			{
				if ( e->succeeded )
				{
					SendMessage( e->output.toAscii().data() );
				}
				else
				{
					AddStatus( e->output.toAscii().data() );
				}
				
				m_pShellRunners->removeAt(pos);
				delete e->m_pShellCommandRunner;
				return TRUE;
			}
		}
	} */
	else if ( (event->type() == QEvent::Resize) && (object == m_pTextEdit_CHATOUTPUT) )
	{
		bool bscroll, b;

		if ( m_pTextEdit_CHATOUTPUT->verticalScrollBar()->maxValue() == 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
}

/** */
void DCChat::AddHistory( CString message )
{
	CString *ps;

	if ( m_History.Count() == MAX_HISTORY_COUNT )
	{
		ps = m_History.Next(0);
		m_History.Del(ps);
	}

	m_pCurrentHistory = 0;
	m_History.Add(new CString(message));
}

/** */
void DCChat::SetCrypt( eSecureState e )
{
	if ( e == esecsENCRYPTED )
	{
		QToolTip::remove(m_pStatusCrypt);
		QToolTip::add(m_pStatusCrypt, tr("Line is encrypted."));
		m_pStatusCrypt->setPixmap( g_pIconLoader->GetPixmap(eiSSL_YES) );
	}
	else
	{
		QToolTip::remove(m_pStatusCrypt);
		QToolTip::add(m_pStatusCrypt, 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->text() != "" )
		{
			PushButton_SEND->setEnabled(TRUE);
		}
		else
		{
			PushButton_SEND->setEnabled(FALSE);
		}
	}
}

/** */
void DCChat::slotClickedChatOutput()
{
	int i;
	CString pressedLink;
	QPoint p;
	//int x,y;
	DCFileTool filetool;

	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().data();

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

	for(i=0;p_LinkList[i]!=0;i++)
	{
		if ( pressedLink.Left(CString(p_LinkList[i]).Length()).ToUpper() == p_LinkList[i] )
		{
			if ( p_LinkList[i] == CString("DCFILE://") )
			{
				// DC-File-Link
				QMenu * m;
				DCMenuHandler mhandler;
				QAction * chosen = 0;
				CString hubhost,hubname,nick,file;
				ulonglong size;

				if ( CUtils::ConvertDCLink( pressedLink, hubhost, hubname, nick, size, file ) == FALSE )
				{
					return;
				}

				m = new QMenu(this);

				QAction * dl = mhandler.addAction( m, emiDOWNLOAD );
				QAction * dl_to = mhandler.addAction( m, emiDOWNLOAD_TO );
				QAction * dl_as = mhandler.addAction( m, emiDOWNLOAD_AS );
				QAction * dl_in = mhandler.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 == "" )
							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 == "" )
							return;

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

						if ( (localrootpath=="") || (localname=="") )
							return;
					}

					// add transfer to the waitlist
					filetool.CheckFile( nick, hubname, hubhost,
							file, localname.toAscii().data(), "", localrootpath.toAscii().data(), eltFILE,
							size, "" );
				}
				else if ( chosen == dl_in )
				{
					QString localrootpath;
					QString localname;
					QString localpath;

					if ( filetool.SelectFileSource( size, localname, localrootpath, localpath ) == FALSE )
					{
						return;
					}

					// add transfer to the waitlist
					filetool.CheckFile( nick, hubname, hubhost,
							  file, localname.toAscii().data(), localpath.toAscii().data(), localrootpath.toAscii().data(), eltFILE,
					  		size, "", TRUE);
				}
			}
			else if ( p_LinkList[i] == CString("DCHUB://") )
			{
				pressedLink = pressedLink.Right( pressedLink.Length()-8 );
				// remove all '/' chars
				pressedLink = pressedLink.Replace('/',"");

				g_pConnectionManager->Connect( pressedLink, pressedLink );
			}
			else if ( p_LinkList[i] == CString("DCHUBS://") )
			{
				pressedLink = pressedLink.Right( pressedLink.Length()-8 );
				// remove all '/' chars
				pressedLink = pressedLink.Replace('/',"");

				g_pConnectionManager->Connect( pressedLink, pressedLink, TRUE );
			}
			else if ( p_LinkList[i] == CString("DCCMD://") )
			{
				pressedLink = pressedLink.Right( pressedLink.Length()-8 );
			}
			else if ( p_LinkList[i] == CString("MAGNET:") )
			{
				//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.Find("xt=urn:tree:tiger:");
				if (i == -1)
				{
					i = pressedLink.Find("xt.1=urn:tree:tiger:");
					tth = pressedLink.Mid(i + 20, 39).Data();
				}
				else
				{
					tth = pressedLink.Mid(i + 18, 39).Data();
				}
				
				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)
				{
					g_pHubSearch->Combobox_SEARCH->setEditText(tth);
					g_pHubSearch->LineEdit_SEARCHSIZE->setText("0");
					g_pHubSearch->ComboBox_SEARCHTYPE->setCurrentItem(9);
					g_pHubSearch->ComboBox_SEARCHUNIT->setCurrentItem(0);
					g_pHubSearch->ComboBox_SEARCHLIMIT->setCurrentItem(0);
		
					if ( g_pHubSearch->isMinimized() )
					{
						g_pHubSearch->showNormal();
					}
					else if ( g_pHubSearch->isVisible()==FALSE )
					{
						g_pHubSearch->setEnabled(TRUE);
						g_pHubSearch->show();
					}

					if ( g_pHubSearch->TabWidget_HUBSEARCH->isVisible()==FALSE )
					{
						g_pHubSearch->TabWidget_HUBSEARCH->show();
					}
					
					// put search dialog above chat
					g_pHubSearch->raise();
					g_pHubSearch->setActiveWindow();
				}
				else if (chosen == copy)
				{
					QApplication::clipboard()->setText(tth);
				}
				else if (chosen == lookup)
				{
					// http://bitzi.com/lookup/tree:tiger:PKQRUF7BKROIPCX2XWTJ7XUKRXRPUMW5V6REAMY
					if ( g_pConfig->GetBrowser() != "" )
					{
						QProcess::startDetached( QString(g_pConfig->GetBrowser().Data()) + " http://bitzi.com/lookup/tree:tiger:" + tth );
					}
				}
				
			}
			else if ( g_pConfig->GetBrowser() != "" )
			{
				QProcess::startDetached( QString(g_pConfig->GetBrowser().Data()) + " " + QString(pressedLink.Data()) );
			}
		}
	}
}

/** */
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;
	DCMenuHandler mhandler;
	QString s;	
	CList<DC_UserMenuCommand> usercommands;
	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().data());
	
	// cut out the nick
	nickStart = 1 + pressedPara.find("<");
	nickLen = pressedPara.find(">") - nickStart;
	
	// sanity check
	if ( (nickStart == 0) || (nickLen < 0) )
	{
		if (m_bPrivateChat)
		{
			pressedNick = QString(m_sNick.Data());
		}
		else
		{
			pressedNick = "Unknown";
		}
	}
	else
	{
		pressedNick = pressedPara.mid(nickStart, nickLen);
	}
	
	// check for joins / parts message
	if ( pressedNick == "VALKNUT" )
	{
		int lenJoin = QString(tr("Joins: ")).length();
		int lenPart = QString(tr("Parts: ")).length();
		int joinPos = pressedPara.find(tr("Joins: "));
		int partPos = pressedPara.find(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 = QString(m_sNick.Data());
	}
	
	//printf("Debug pressedNick: %s\n",pressedNick.toAscii().data());
	
	cPressedNick = CString(pressedNick.toAscii().data());
	isNickOnline = m_pClient->UserList()->IsUserOnline(cPressedNick);
	
	// select the user in the userlist
	if (isNickOnline)
	{
		m_pClient->jumpToNick( pressedNick );
	}
	
	m = new QMenu(this);
	
	muser = mhandler.addMenu( m , emisCHAT_RIGHTCLICK_USER, TRUE, pressedNick );
	QAction * privchat = mhandler.addAction( muser, emiPRIVATE_CHAT, isNickOnline );
	QAction * addfriend = mhandler.addAction( muser, emiADD_FRIEND,  isNickOnline );
	QAction * browseuser = mhandler.addAction( muser, emiBROWSE_USER_FILES, isNickOnline );
	mhandler.addAction( muser, emiSEPARATOR );
	
	mslot = mhandler.addMenu( muser, emiUPLOAD_SLOT, TRUE);
	QAction * addslot = mhandler.addAction( mslot, emiADD, TRUE);
	QAction * addpermslot = mhandler.addAction( mslot, emiADD_PERMANENT, TRUE);
	QAction * removeslot = mhandler.addAction( mslot, emiREMOVE, TRUE);
	
	mhandler.addAction( muser, emiSEPARATOR );
	QAction * kick = mhandler.addAction( muser, emiKICK, (isNickOnline && m_pClient->UserList()->IsAdmin(m_pClient->GetNick())) );
	QAction * forcemove = mhandler.addAction( muser, emiFORCE_MOVE, (isNickOnline && m_pClient->UserList()->IsAdmin(m_pClient->GetNick())) );
	
	mhandler.addAction( muser, emiSEPARATOR );
	
	g_pConfig->GetUserMenuCommands(&usercommands);
	
	umc = 0;
	while ( (umc=usercommands.Next(umc)) != 0 )
	{
		if ( (umc->m_nContext) == 2)
		{
			umc->m_pAction = muser->addAction(umc->m_sName.Data());
			umc->m_pAction->setEnabled( isNickOnline );
		}
	}
	
	mhandler.addAction( m, emiSEPARATOR );
	
	QAction * smiley = mhandler.addAction( m, emiINSERTSMILEY, g_pConfig->GetEnableEmoticons() );
	mhandler.addAction( m, emiSEPARATOR );
	QAction * copy = mhandler.addAction( m, emiCOPY, m_pTextEdit_CHATOUTPUT->hasSelectedText() );
	QAction * clear = mhandler.addAction( m, emiCLEAR, TRUE );
	QAction * selectall = mhandler.addAction( m, emiSELECT_ALL, TRUE );
	mhandler.addAction( m, emiSEPARATOR );
	QAction * translate = mhandler.addAction( m, emiTRANSLATE, m_pTextEdit_CHATOUTPUT->hasSelectedText() );
	QAction * translator = mhandler.addAction( m, emiTRANSLATOR );

	mhandler.addAction( m, emiSEPARATOR );
	QAction * refresh = mhandler.addAction( m, emiREFRESH );
	mhandler.addAction( m, emiSEPARATOR );

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

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

	QAction * save = mhandler.addAction( m, emiSAVE );

	QAction * dock = 0;

	if ( parentWidget() == 0 )
		dock = mhandler.addAction( m, emiDOCK );
	else
		dock = mhandler.addAction( m, emiUNDOCK );

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

	if ( m_bPrivateChat )
	{
		hidepriv = mhandler.addAction( m, emiHIDE, m_bPrivateChat && !g_pConfig->GetShowChatInTab() );
		closepriv = mhandler.addAction( m, emiCLOSE, m_bPrivateChat );
	}
	
	if ( !m_bPrivateChat )
	{
		mhandler.addAction( m, emiSEPARATOR );
		
		umc = 0;
		while ( (umc=usercommands.Next(umc)) != 0 )
		{
			if ( (umc->m_nContext & 1) == 1)
			{
				umc->m_pAction = m->addAction(umc->m_sName.Data());
			}
		}
	}
	
	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(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.Data();
						smiley.replace( "&lt;", "<" );
						smiley.replace( "&gt;", ">" );
						smiley.replace( "&amp;", "&" );
						smiley.replace( "&apos;", "\'" );
						smiley.replace( "&quot;", "\"" );
						
						TextEdit_CHATINPUT->insert(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.replace("<!--StartFragment-->","");
		g_pTranslator->Translate( this, m_sLanguage.Data(), 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.").toAscii().data() );
	}
	else if ( chosen == selectall )
	{
		m_pTextEdit_CHATOUTPUT->selectAll();
	}
	else if ( chosen == reqsecchat )
	{
		m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick, "<request secchannel>" );
	}
	else if ( chosen == closesecchat )
	{
		m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick, "<close secchannel>" );
	}
	else if ( chosen == dock )
	{
		if ( parentWidget() == 0 )
		{
			DCChat::reparent(m_pParent,QPoint(),TRUE);

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

				p->insertTab(this,m_sLabel);
				p->showPage(this);
			}

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

				m_sLabel = p->tabLabel(this);
				p->removePage(this);
			}

			DCChat::reparent(0,QPoint(),TRUE);

			move(0,0);

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

		if ( s != "" )
		{
			QFile f(s);

			if ( f.open(QIODevice::WriteOnly) == TRUE )
			{
				f.write(m_pTextEdit_CHATOUTPUT->text().toUtf8());
				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.toAscii().data());
	}
	else if ( chosen == closepriv )
	{
		close();
	}
	else if ( chosen == hidepriv )
	{
		setEnabled(FALSE);
		hide();
	}
	else if ( chosen == privchat )
	{
		m_pClient->DC_PrivateChat( pressedNick, "", "", TRUE );
	}
	else if ( chosen == addfriend )
	{
		g_pUsersList->AddFriend( cPressedNick,
				m_pClient->GetHubName(), m_pClient->GetHost(), "" );
	}
	else if ( chosen == browseuser )
	{
		g_pTransferView->DLM_QueueAdd( cPressedNick, m_pClient->GetHubName(),
				m_pClient->GetIP()+":"+QString().setNum(m_pClient->GetPort()).toAscii().data(),
				DC_USER_FILELIST_HE3, DC_USER_FILELIST_HE3, "", "", eltBUFFER,
				0, 0, 0, ""  );
	}
	else if ( chosen == kick )
	{
		QString kickMessage;
		
		if ( !m_pClient->GetOPKickMessage(kickMessage) )
		{
			return;
		}
		m_pClient->OPKick( pressedNick, kickMessage );
		
	}
	else if ( chosen == forcemove )
	{
		QString host, message;
		if ( !m_pClient->GetOPForceMoveMessage(message,host) )
		{
			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);
		g_pTransferView->DLM_AddUserSlot(cPressedNick, m_pClient->GetHubName(), 0, TRUE);
	}
	else if ( chosen == removeslot )
	{
		g_pTransferView->DLM_AddUserSlot(cPressedNick, m_pClient->GetHubName(), 0);
	}
	else
	{
		umc = 0;
		while ( (umc=usercommands.Next(umc)) != 0 )
		{
			if ( (umc->m_pAction != 0) && (chosen == umc->m_pAction) )
			{
				userCommand = m_pClient->replaceCommandTags( umc->m_sCommand.Data(), pressedNick );
				
				AddStatus(userCommand.toAscii().data());
				m_pClient->SendString(userCommand.toAscii().data());
			}
		}
	}
}

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

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

	if ( !m_bFirstInit )
		return;
		
	m_bFirstInit = FALSE;

/*	QValueList<int> list = Splitter3->sizes();
	QValueList<int>::Iterator it = list.begin();
	printf("SP: %d\n",*it);
	++it;
	printf("SP: %d\n",*it);*/
	
	// resize splitter
	QList<int> list = Splitter3->sizes();
	QList<int>::Iterator it = list.begin();
	i = *it;
	++it;
	i += *it;
	i -= TextEdit_CHATINPUT->minimumHeight()*2;
	it = list.begin();
	*it = i;
	++it;
	*it = TextEdit_CHATINPUT->minimumHeight()*2;
	Splitter3->setSizes(list);
	
	// update view
	update();
}

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

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

	if ( nick != "" )
	{
		setCaption(tr("Private Chat:")+QString(" ")+QString::fromAscii(nick.Data())+QString(" [")+QString::fromAscii(hubname.Data())+QString("]"));
	}
	else
	{
		setCaption(tr("Chat:")+QString(" [")+QString::fromAscii(hubname.Data())+QString("]"));
	}
}

/** */
CString DCChat::GetTimeStamp()
{
	CString s = "";

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

	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( CString message )
{
	bool bscroll;
	CString s;

	if ( m_pTextEdit_CHATOUTPUT->verticalScrollBar()->maxValue() == m_pTextEdit_CHATOUTPUT->verticalScrollBar()->value() )
	{
		bscroll = TRUE;
	}
	else
	{
		bscroll = FALSE;
	}

	// convert newline
	message = message.Replace( "\xa\\r", "\xa" );
	message = message.Replace( "\xa", "<br />" );
	
	// convert tabs to spaces
	message = message.Replace( "	", "&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( QString::fromAscii(message.Data()) );

	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 );
	}

#if QT_VERSION >= 304
	message += "<br />";
#endif

	// 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 += "_" + m_pClient->GetHubName();

				if ( g_pConfig->GetLogChatOption(elcoAPPENDHUBHOST) )
					s += "_" + m_pClient->GetIP();

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

				// damn hacking ;-)
				s = s.Replace('/',"");
				s = s.Replace('\\',"");
				s = s.Replace(':',"");

				s = g_pConfig->GetChatLogPath() + s + ".html";

				QFile f(s.Data());

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

					f.writeBlock(message.Data(),message.Length());
					f.close();
				}
			}
		}
	}
}

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

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

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

		// convert newline
		message = message.Replace( "\\r\xa", "\xa" );

#if QT_VERSION < 304
		if ( message.Find('\xa',0) == -1 )
			message += "<br />";
#endif
		message = message.Replace( "\xa", "<br />" );

		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 == "" )
		AddMessage( msg->m_sSrcNick.Data(), msg->m_sMessage.Data(), bremote, forward );
	else
		AddMessage( msg->m_sMultiSrcNick.Data(), msg->m_sMessage.Data(), bremote, forward );
}

/** */
void DCChat::AddMessage( CString nick, CString message, bool bremote, bool forward )
{
	unsigned int len;
	bool b;
	CString 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 );

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

	if ( message == "" )
	{
		return;
	}

	if ( CheckForData(message) == TRUE )
	{
		return;
	}

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

	// convert newline
	message = message.Replace( "\\r\xa", "\xa" );
	message = message.Replace( "\\r", "\xa" );

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

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

	s1 = m_pClient->GetNick() + ": ";

	if ( ( message.Left(4).ToUpper() == "/ME " ) || ( message.Left(4).ToUpper() == "+ME " ) )
	{
		bMe    = TRUE;
		message = message.Right( message.Length() - 4 );
	}

	if ( message.Left(s1.Length()) == s1 )
	{
		bSay    = TRUE;
		// message = message.Right( message.Length() - s1.Length() );
	}

	msg = message;
	s1 = "";

	b1 = b2 = TRUE;

	while ( msg != "" )
	{
		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;

	// convert spaces
	s = "";
	b = TRUE;

	for(len=0;len<message.Length();len++)
	{
		if ( message.Data()[len] == '\xa' )
			b = TRUE;
		else if ( message.Data()[len] != ' ' )
			b = FALSE;

		if ( message.Data()[len] == ' ' )
		{
			if (b)
				s += "&nbsp;";
			else
				s += " ";
			b = !b;
		}
		else
		{
			s += message.Data()[len];
		}
	}

	message = s;

	// convert newline
#if QT_VERSION < 304
	if ( message.Find('\xa',0) == -1 )
		message += "<br />";
#endif

	message = message.Replace( "\xa", "<br />" );

	// set color
	if ( m_bPrivateChat )
		if ( bremote && (nick != m_pClient->GetNick().Data()) )
			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 != m_pClient->GetNick().Data()) )
			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 != m_pClient->GetNick().Data() )
		{
			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 != m_pClient->GetNick().Data() )
			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 )
		{
			s = g_pConfig->GetAwayMessage().Data();

			if ( (s != "") && (m_bSendAway) )
			{
				m_pClient->SendPrivateMessage( m_pClient->GetNick(), m_sNick, s );
				m_bSendAway = FALSE;
				AddStatus("Sent away message: " + s);
			}
		}
		else
		{
			m_bSendAway = TRUE;
		}
	}
}

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

	if ( !m_pClient )
	{
		return;
	}

	if ( message == "" )
	{
		message = TextEdit_CHATINPUT->text().toAscii().data();
	}
	else
	{
		local = TRUE;
	}

	if ( message != "" )
	{
		if ( local == FALSE )
		{
			// add history
			AddHistory(message);
			m_sHistoryTempString = "";
			cmd = CheckForCommand();
		}
		
		if ( cmd == FALSE )
		{
			bool isAllowed = FALSE;
			
			if ( g_pConfig->GetSendUnknownCommandsAsChat() == FALSE )
			{
				QStringList allowed = QStringList::split( ",", g_pConfig->GetAllowedUnknownCommands() );
				
				for ( int i = 0; i < allowed.count(); i++ )
				{
					// e.g. allow "/me" and "/me waves" but not "/mediqjdoh"
					int len = allowed[i].length();
					CString tail = message.Mid(len,1);
					if ( (message.Left(len) == allowed[i].toAscii().data()) &&
					     ((tail == " ") || (tail == "")) )
					{
						isAllowed = TRUE;
						break;
					}
				}
			}
			
			// if unknown command that isn't allowed
			if ( (g_pConfig->GetSendUnknownCommandsAsChat() == FALSE) && (message.Left(1) == "/") && (isAllowed == FALSE) )
			{
				AddStatus( message );
				AddStatus( tr("Unknown command, try /dchelp").toAscii().data() );
			}
			else
			{
				// convert the newlines
				message = message.Replace( "\\r\xa", "\xa" );
				message = message.Replace( "\xa", "\xd\xa" );

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

				if ( err == 0 )
				{
					if ( m_bPrivateChat )
						AddMessage( m_pClient->GetNick(), message, FALSE );
					g_pConfig->PlaySound(eusSEND);
				}
				else
				{
					AddStatus( tr("Message not sent!").toAscii().data() );
				}
			}
		}

		if ( local == FALSE )
		{
			TextEdit_CHATINPUT->setPlainText("");
		}
	}
}

/** */
int DCChat::FindFirstLink( CString msg )
{
	int i,i1,i2;

	for(i=0,i1=-1,i2=-1;p_LinkList[i]!=0;i++)
	{
		if ( (i1 = msg.Find(p_LinkList[i],0,FALSE)) != -1 )
		{
			if ( (i2 == -1) || (i2 > i1) )
			{
				i2 = i1;
			}
		}
	}

	return i2;
}

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

	// convert links
	for(i=0;p_LinkList[i]!=0;i++)
	{
		if ( msg.Left(strlen(p_LinkList[i])).ToUpper() == p_LinkList[i] )
		{
			// link end at space or newline
			i1 = msg.Find(' ',0,FALSE);
			i2 = msg.Find('\xa',0,FALSE);
			i3 = msg.Find('\xd',0,FALSE);

			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( CString msg )
{
	int i1,i2;
	CList<DC_EmoticonObject> * elist;

	elist = g_pConfig->EmoticonList();

	i1 = i2 = -1;

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

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

	return i2;
}

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

	elist = g_pConfig->EmoticonList();

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

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

	return ret;
}

/** */
bool DCChat::CheckForCommand()
{
	bool res = FALSE;
	bool addedSpace = FALSE;
	QString timestamp;
	CString cmd,s;
	CList<DC_UserChatCommand> usercommands;
	DC_UserChatCommand * ucc = 0;
	
	cmd = TextEdit_CHATINPUT->text().toAscii().data();
	
	// replace custom commands with their actions
	if ( cmd.Right(1) != " " )
	{
		cmd = cmd + " ";
		addedSpace = TRUE;
	}
	
	g_pConfig->GetUserChatCommands(&usercommands);
	
	while( (ucc=usercommands.Next(ucc)) != 0 )
	{
		if ( cmd.Left(ucc->m_sName.Length() + 1) == (ucc->m_sName + " ") )
		{
			cmd = cmd.Replace( ucc->m_sName, ucc->m_sCommand );
		}
	}
		
	if (addedSpace)
	{
		cmd = cmd.Left(cmd.Length() - 1);
	}
	
	if ( (cmd != "") && (cmd.Left(1) == '/') )
	{
		// help
		if ( cmd == "/dchelp" )
		{
			s  = tr("Help:").toAscii().data();
			s += "\n";
			s += tr("/clear - clears the chat window").toAscii().data();
			s += "\n";
			s += tr("/mode - shows the current mode").toAscii().data();
			s += "\n";
			s += tr("/refresh - refresh share").toAscii().data();
			s += "\n";
			s += tr("/rebuild - remove no longer present files from hash database").toAscii().data();
			s += "\n";
			s += tr("/slots &lt;N&gt; - set number of upload slots to N").toAscii().data();
			s += "\n";
			s += tr("/ts - switch time display in chat on/off").toAscii().data();
			s += "\n";
			s += tr("/adv - send an advertisment to the hub").toAscii().data();
			s += "\n";
			s += tr("/join &lt;address&gt; - disconnect from currently connected hub and connect to another hub").toAscii().data();
			s += "\n";
			s += tr("/msg &lt;nick&gt; &lt;message&gt; - send private message").toAscii().data();
			s += "\n";
			s += tr("/away &lt;message&gt; - set automatic response for private messages; if the message is empty, disable it").toAscii().data();
			s += "\n";
			s += tr("/bye &lt;message&gt; - disconnect from channel with an optional message").toAscii().data();
			s += "\n";
			s += tr("/uptime [show] - show valknut uptime [to other users]").toAscii().data();
			s += "\n";
			s += tr("/ls &lt;nick&gt; - get share list from user").toAscii().data();
			s += "\n";
			s += tr("/grant &lt;nick&gt; - grant user a slot").toAscii().data();
			s += "\n";
			s += tr("/grantp &lt;nick&gt; - grant user a permanent slot").toAscii().data();
			s += "\n";
			s += tr("/friend &lt;nick&gt; - add user to friend list").toAscii().data();
			s += "\n";
			s += tr("/fav - add hub to bookmark list").toAscii().data();
			s += "\n";
			s += tr("/info &lt;nick&gt; - show user info").toAscii().data();
			s += "\n";
			s += tr("/now - send current time to the chat").toAscii().data();
			s += "\n";
			s += tr("/sh &lt;command&gt; &lt;args...&gt; - send output of shell command to chat").toAscii().data();
			s += "\n";
			s += tr("/raw - send raw message").toAscii().data();
			s += "\n";
			s += tr("/disablesorting - disable sorting of the user list").toAscii().data();
			s += "\n";
			s += tr("/enablesorting - enable sorting of the user list").toAscii().data();
			s += "\n";
			s += tr("/ignore &lt;nick&gt; - do not show main chat lines from the user").toAscii().data();
			s += "\n";
			s += tr("/unignore &lt;nick&gt; - show main chat lines from the user again").toAscii().data();
			s += "\n";
			s += tr("/ratio [show] - show share ratio [to other users]").toAscii().data();
			s += "\n";
			s += tr("/newlog - start a new logfile").toAscii().data();
			s += "\n";
			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /clear - clear the chat window
		else if ( cmd == "/clear" )
		{
			m_pTextEdit_CHATOUTPUT->clear();

			AddStatus( tr("Chat Cleared.").toAscii().data(), TRUE );
			res = TRUE;
		}
		// /info - show user info
		else if ( cmd.Left(5) == "/info" )
		{
			CString nick;
			CMessageMyInfo myinfo;
			
			if ( m_bPrivateChat && (cmd.Length() == 5) )
			{
				nick = m_sNick;
			}
			else
			{
				nick = cmd.Mid(6,cmd.Length()-6);	
			}
						
			if (m_pClient->UserList()->GetUserMyInfo( nick, &myinfo ) == FALSE)
			{
				s = tr("Get info failed.");
			}
			else
			{
				s  = CString(tr("Info:").toAscii().data()) + "\n";
				s += CString(tr("Nick:").toAscii().data()) + " " + myinfo.m_sNick + "\n";
				s += CString(tr("Operator:").toAscii().data()) + " ";

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

				s += CString("\n");

				s += CString(tr("Comment:").toAscii().data()) + " " + myinfo.m_sComment + "\n";
				s += CString(tr("Speed:").toAscii().data()) + " " + myinfo.m_sUserSpeed + "\n";
				s += CString(tr("EMail:").toAscii().data()) + " " + myinfo.m_sEMail + "\n";
				s += CString(tr("Shared:").toAscii().data()) + " " + CString().setNum(myinfo.m_nShared) + " Bytes\n";

				s += CString(tr("Away:").toAscii().data()) + " ";

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

				s += "\n";

				s += CString(tr("Version:").toAscii().data()) + " ";

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

				s += "\n";

				s += CString(tr("Tag:").toAscii().data()) + " " + myinfo.m_sVerComment + "\n";
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /uptime - show valknut uptime
		else if ( cmd.Left(7) == "/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  = CString(tr("Valknut uptime").toAscii().data()) + ": ";

			if ( d > 0 )
			{
				s += CString().setNum(d);
				s += " ";
				if ( d == 1 )
					s += CString(tr("day").toAscii().data());
				else
					s += CString(tr("days").toAscii().data());
				s += ",";
			}
			if ( h > 0 )
			{
				s += CString().setNum(h) + " ";
				if ( h == 1 )
					s += CString(tr("hour").toAscii().data());
				else
					s += CString(tr("hours").toAscii().data());
				s += ",";
			}
			s += CString().setNum(m) + " ";
			if ( m == 1 )
				s += CString(tr("minute").toAscii().data());
			else
				s += CString(tr("minutes").toAscii().data());
			
			if ( cmd.Mid(8, 4).ToLower() == "show" )
				SendMessage(s);
			else
				AddStatus( s, TRUE );

			res = TRUE;
		}
		// /mode - show current mode
		else if ( cmd == "/mode" )
		{
			CString lms;
			
			s  = CString(tr("Current mode: ").toAscii().data());

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

			// get listen manager status
			if ( CListenManager::Instance() )
			{
				lms = CListenManager::Instance()->GetSocketErrorMsg();
			
				if ( lms != "" )
				{
					s += " (" + lms + ")";
				}	
			}
			
			s += ".";
			
			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /refresh - refresh share 
		else if ( cmd == "/refresh" )
		{
			if ( CFileManager::Instance()->CreateShareList() == TRUE )
			{
				s = tr("Refresh share in progress.").toAscii().data();
			}
			else
			{
				s = tr("Refresh share already in progress.").toAscii().data();
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /rebuild - clean up share databases
		else if ( cmd == "/rebuild" )
		{
			if ( CFileManager::Instance()->RebuildLists() == TRUE )
			{
				s = tr("Rebuild share in progress.").toAscii().data();
			}
			else
			{
				s = tr("Refresh/rebuild already in progress.").toAscii().data();
			}
			
			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /slots - set number of slots
		else if ( cmd.Left(7) == "/slots " )
		{
			int i = (cmd.Mid(7, cmd.Length() - 7)).asINT();
			
			if ( (i >= 0) && (i <= 99) )
			{
				g_pConfig->SetMaxUpload(i);
				AddStatus( tr("Setting upload slots to: " + QString::number(i)).toAscii().data(), TRUE );
			}
			else
			{
				AddStatus( tr("Invalid number of upload slots specified.").toAscii().data(), TRUE );
			}
			
			res = TRUE;
		}
		// /ts - switch time display in chat on/off 
		else if ( cmd == "/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 ").toAscii().data();

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

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /adv - send an advertisment to the hub
		else if ( cmd == "/adv" )
		{
			AddStatus( tr("Send advertisment.").toAscii().data(), 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 ( ((cmd.Left(4) == "/now") && cmd.Length() == 4) ||
		          (cmd.Left(5) == "/now ") )
		{
			if ( cmd.Length() > 4 )
			{
				s = " " + cmd.Mid(5,cmd.Length()-5);
			}
			
			s = QTime::currentTime().toString("hh:mm:ss").toAscii().data() + s;

			SendMessage(s);

			res = TRUE;
		}
		// /raw - send raw message
		else if ( cmd.Left(5) == "/raw " )
		{
			CString msg;

			if ( !m_bPrivateChat )
			{
				msg = cmd.Mid(5,cmd.Length()-5);
			
				if ( msg != "" )
				{
					AddStatus( msg, TRUE );
					m_pClient->SendString(msg);
				}
			}

			res = TRUE;
		}
#ifdef ENABLE_PRETEND_TESTING_COMMAND
		else if ( cmd.Left(9) == "/pretend " )
		{
			CString recv = cmd.Mid(9,cmd.Length()-9);
			
			AddStatus( "pretend: " + recv );
			m_pClient->HandleMessage(recv);
			
			res = TRUE;
		}
#endif
		// /join <address> - disconnect from currently connected hub and connect to another hub 
		else if ( cmd.Left(6) == "/join " )
		{
			CString address;

			address = cmd.Mid(6,cmd.Length()-6);

			if ( address == "" )
				s = tr("Wrong parameter for '/join'").toAscii().data();
			else
			{
				s = tr("Join to: ").toAscii().data() + address;

				m_pClient->SetHubName(address);
				m_pClient->setCaption(address.Data());
				m_pClient->Connect(address);
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /ls - get share list from user
		else if ( cmd.Left(3) == "/ls" )
		{
			CString nick;

			if ( m_bPrivateChat && (cmd.Length() == 3) )
			{
				nick = m_sNick;
			}
			else
			{
				nick = cmd.Mid(4,cmd.Length()-4);
			}

			if ( nick != "" )
			{
				/** add transfer to the waitlist */
				g_pTransferView->DLM_QueueAdd( nick, m_pClient->GetHubName(), m_pClient->GetIP()+":"+QString().setNum(m_pClient->GetPort()).toAscii().data(),
							DC_USER_FILELIST_HE3, DC_USER_FILELIST_HE3, "", "", eltBUFFER,
							0, 0, 0, "" );

				s = tr("Download sharelist from") + " '" + nick + "'";
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /grantp - grant perm. slot to user
		else if ( cmd.Left(7) == "/grantp" )
		{
			CString nick;

			if ( m_bPrivateChat && (cmd.Length() == 7) )
			{
				nick = m_sNick;
			}
			else
			{
				nick = cmd.Mid(8,cmd.Length()-8);
			}

			if ( nick != "" )
			{
				g_pTransferView->DLM_AddUserSlot( nick, m_pClient->GetHubName(), 0 );
				g_pTransferView->DLM_AddUserSlot( nick, m_pClient->GetHubName(), 0, TRUE );

				s = tr("Permanent slot added for") + " '" + nick + "'";
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /grant - grant slot to user
		else if ( cmd.Left(6) == "/grant" )
		{
			CString nick;

			if ( m_bPrivateChat && (cmd.Length() == 6) )
			{
				nick = m_sNick;
			}
			else
			{
				nick = cmd.Mid(7,cmd.Length()-7);
			}

			if ( nick != "" )
			{
				g_pTransferView->DLM_AddUserSlot( nick, m_pClient->GetHubName(), 1 );

				s = tr("Slot added for") + " '" + nick + "'";
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /friend - add user to friend list
		else if ( cmd.Left(7) == "/friend" )
		{
			CString nick;

			if ( m_bPrivateChat && (cmd.Length() == 7) )
			{
				nick = m_sNick;
			}
			else
			{
				nick = cmd.Mid(8,cmd.Length()-8);
			}

			if ( nick != "" )
			{
				g_pUsersList->AddFriend( nick, m_pClient->GetHubName(), m_pClient->GetHost(), "" );

				s = tr("Added to friend list ") + " '" + nick + "'";
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /fav - add hub to bookmarks
		else if ( cmd == "/fav" )
		{
			g_pHubListManager->AddBookmark( m_pClient->GetHubName().Data(), m_pClient->GetHost().Data(), "" );

			s = tr("Add bookmark hub") + " '" + m_pClient->GetHubName() + "'";

			AddStatus( s, TRUE );
			res = TRUE;
		}
		// /bye <msg> - disconnect from channel
		else if ( cmd.Left(4) == "/bye" )
		{
			CString message;

			message = cmd.Mid(5,cmd.Length()-5);

			if ( message != "" )
			{
				SendMessage(message);
			}

			m_pClient->Disconnect(TRUE);

			res = TRUE;
		}
		// /msg <nick> <message> - send private message 
		else if ( cmd.Left(5) == "/msg " )
		{
			int i;
			CString nick,message="";
			
			AddStatus( cmd, TRUE );

			cmd = cmd.Mid(5,cmd.Length()-5);

			if ( (i = cmd.Find(' ')) != -1 )
			{
				nick    = cmd.Left(i);
				message = cmd.Mid(i+1,cmd.Length()-i-1);
			}

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

			AddStatus( s, TRUE );
			res = TRUE;

		}
		// /away <message> - set automatic response for private messages; if the message is empty, disable it 
		else if ( cmd.Left(5) == "/away" )
		{
			int i;

			s = "";

			i = cmd.Find(' ',5);

			if ( i > 0 )
				s = cmd.Mid(i+1,cmd.Length()-i-1);

			g_pConfig->SetAwayMessage(s);

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

			AddStatus( s, TRUE );
			res = TRUE;
		}
		else if ( cmd.Left(4) == "/sh " )
		{
			// display the command the user entered
			AddStatus( cmd, TRUE );
			
			// actually this is better - more portable, doesn't depend on /bin/sh
			QStringList shellargs = QStringList::split( QString(" "), QString((cmd.Mid(4, cmd.Length() - 4)).Data()) );
			
			// QStringList shellargs;
			// shellargs.append( QString("/bin/sh") );
			// shellargs.append( QString("-c") );
			// shellargs.append( QString((cmd.Mid(4, cmd.Length() - 4)).Data()) );
			
			// debug code - show the args we are going to run
			//AddStatus( (shellargs.join( QString(",") )).toAscii().data(), TRUE );
			
			if ( shellargs.isEmpty() )
			{
				AddStatus( tr("No command entered.").toAscii().data(), TRUE );
				return TRUE;
			}
			
			if ( g_pConfig->GetThreadsForShellCommands() )
			{
				DCShellCommandRunner * runner = new DCShellCommandRunner( shellargs );
				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() ).stripWhiteSpace();
							if ( s == "" )
							{
								AddStatus( tr("Command produced no visible output.").toAscii().data(), TRUE );
							}
							else
							{
								SendMessage(s);
							}
						}
						else
						{
							AddStatus( tr("Process exited with status " + QString::number(exitcode)).toAscii().data(), TRUE);
						}
					}
					else
					{
						AddStatus( tr("Process was killed or crashed.").toAscii().data(), TRUE);
					}
				}
				else
				{
					AddStatus( QString(tr("Process still running after") + " " + QString().setNum(g_pConfig->GetShellCommandTimeout()) + " " + tr("seconds, killing process...")).toAscii().data(), TRUE );
					shellproc.terminate();
					sleep(1);
					if ( shellproc.state() != QProcess::NotRunning )
					{
						shellproc.kill();
					}
				}
			}
			
			res = TRUE;
		}
		else if ( cmd == "/sendphoto" )
		{
			if ( !m_bPrivateChat )
			{
				s = tr("Only allowed in private chat.").toAscii().data();
			}
			else if ( m_pClient->UserList()->GetUserClientVersion(m_sNick) != eucvDCGUI )
			{
				s = tr("The user is using an old version or not using Valknut").toAscii().data();
			}
			else
			{
				CString fn = g_pConfig->GetUserPhotoFileName();
				CDir dir;
				CFile f;
				CByteArray bas,bad;
				ulonglong size;
				CBase64 base64;

				size = dir.getFileSize( fn, FALSE );

				if ( (size == 0) || (f.Open(fn,IO_READONLY|IO_RAW) == FALSE) )
				{
					s = tr("Photo not found.").toAscii().data();
				}
				else
				{
					bas.SetSize(size);

					if ( f.Read((char*)bas.Data(),size) != size )
					{
						s = tr("Can't read your photo.").toAscii().data();
					}
					else
					{
						base64.Encode( &bad, &bas );

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

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

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

				f.Close();
			}

			AddStatus( s, TRUE );
			res = TRUE;
		}
		else if ( cmd == "/disablesorting" )
		{
			m_pClient->disableUserListSorting();
			s = tr("Disabled user list sorting.").toAscii().data();
			AddStatus( s, TRUE );
			res = TRUE;
		}
		else if ( cmd == "/enablesorting" )
		{
			m_pClient->enableUserListSorting();
			s = tr("Enabled user list sorting.").toAscii().data();
			AddStatus( s, TRUE );
			res = TRUE;
		}
		else if (cmd.Left(7) == "/ignore")
		{
			CString nick = cmd.Mid(8, cmd.Length()-8);
			if (nick != "")
			{
				if (g_pUsersList->isNickInList(nick) == FALSE)
				{
					g_pUsersList->AddFriend( nick, m_pClient->GetHubName(), m_pClient->GetHost(), "" );
				}
				
				g_pUsersList->setIgnore( nick, TRUE );
				s = tr(QString("Ignoring main chat lines from ") + QString(nick.Data()));
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}
			
			AddStatus( s, TRUE );
			res = TRUE;
			
		}
		else if (cmd.Left(9) == "/unignore")
		{
			CString nick = cmd.Mid(10, cmd.Length()-10);
			if (nick != "")
			{
				if (g_pUsersList->ignoreNick( nick ) == TRUE)
				{
					g_pUsersList->setIgnore( nick, FALSE);
					s = tr(QString("Showing main chat for ") + QString(nick.Data()));
				}
				else
				{
					s = tr(QString(nick.Data()) + QString(" is not in ignore list"));
				}
				
			}
			else
			{
				s = tr("No nick, try /dchelp");
			}
			
			AddStatus( s, TRUE );
			res = TRUE;
		}
		else if ( cmd.Left(6) == "/ratio" )
		{
			ulonglong up = CSocket::m_Traffic.GetTraffic(ettTX);
			ulonglong down = CSocket::m_Traffic.GetTraffic(ettRX);
			
			float ratio = 0;
			
			if ( down > 0 )
			{
				ratio = (double) up / (double) down;
			}
			
			s  = tr("ratio: overall: ").toAscii().data();
			s += QString().setNum( ratio, 'f', 2 ).toAscii().data();
			s += " (";
			s += tr("uploads").toAscii().data();
			s += ": ";
			s += CUtils::GetSizeString( up, g_pConfig->GetUnit() );
			s += " | ";
			s += tr("downloads").toAscii().data();
			s += ": ";
			s += CUtils::GetSizeString( down, g_pConfig->GetUnit() );
			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: ").toAscii().data();
			s += QString().setNum( ratio, 'f', 2 ).toAscii().data();
			s += " (";
			s += tr("uploads").toAscii().data();
			s += ": ";
			s += CUtils::GetSizeString( up, g_pConfig->GetUnit() );
			s += " | ";
			s += tr("downloads").toAscii().data();
			s += ": ";
			s += CUtils::GetSizeString( down, g_pConfig->GetUnit() );
			s += ")";
			
			if ( cmd.Mid( 7, 4 ).ToLower() == "show" )
			{
				SendMessage( s );
			}
			else
			{
				AddStatus( s, TRUE );
			}
			res = TRUE;
		}
		else if ( cmd == "/newlog" )
		{
			m_sTimeStamp  = QDate::currentDate().toString("yyyyMMdd").toAscii().data();
			m_sTimeStamp += "-";
			m_sTimeStamp += QTime::currentTime().toString("hhmmss").toAscii().data();
			
			s  = tr("New log timestamp: ").toAscii().data();
			s += m_sTimeStamp;
			
			if ( g_pConfig->GetLogChatOption(elcoAPPENDDATE) == FALSE )
			{
				s += "\n";
				s += tr("Warning: log timestamp not enabled.").toAscii().data();
			}
			
			AddStatus( s, TRUE );
			res = TRUE;
		}
	}
	return res;
}

/** */
bool DCChat::CheckForData( CString message )
{
	bool res = FALSE;

	// now we check for special private messages
	if ( m_bPrivateChat )
	{
		// remote send a photo
		if ( (message.Find("<photo ") == 0) && (message.Find(">",7) > 0) )
		{
			int i1,i2;
			CString s;
			CByteArray ba;
			CBase64 base64;

			if ( (i1 = message.Find("data=\"")) != -1 )
			{
				if ( (i2 = message.Find("\"",i1+6)) != -1 )
				{
					//printf("'%s'\n",message.Data());
					//printf("'%s'\n",message.Mid(i1+6,i2-i1-6).Data());

					s = message.Mid(i1+6,i2-i1-6);

					if ( base64.Decode( &ba, &s ) > 0 )
					{
						g_pUsersList->AddFriendPhoto(m_sNick,&ba);
						AddStatus(tr("Photo received.").toAscii().data(), 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->text();
		m_sTabSaved = s;
		if ( i == 0 )
			end = 0;
		else
			end = i - 1;
		start = s.findRev(" ", end, FALSE);
		m_nTabStart = start;
		m_nTabEnd = end;
	}

	if ( (s.isEmpty() == TRUE) && (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() == TRUE )
			{
				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() == TRUE )
			{
				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->selectedText();
	
	//printf("selected=%s\n", selected.toAscii().data());
	
	// 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") == TRUE) && (g_pConfig->GetEnableEmoticons() == TRUE) )
	{
		// get the html
		QString html = m_pTextEdit_CHATOUTPUT->textCursor().selection().toHtml();
		
		QStringList smileys;
		int start = 0;
		const int len = QString("src=\"emoticon").length();
		CList<DC_EmoticonObject> * elist = g_pConfig->EmoticonList();
		
		while ( start != -1 )
		{
			// crap, the html doesn't include the title=":-)", only src="emoticon5"
			//printf("html=%s\n", html.toAscii().data());
			start = html.indexOf( "src=\"emoticon" );
			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.Data();
					
					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 == "" )
	{
		return;
	}
	
	// remove all html tags - not needed in QT4
	/* QRegExp re;
	re.setMinimal(TRUE);
	re.setPattern("<.*>");
	selected = selected.replace( 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.simplifyWhiteSpace();
	
	// add space on end
	selected += " ";
	
	if ( TextEdit_CHATINPUT->text() == "" )
	{
		TextEdit_CHATINPUT->setPlainText(selected);
	}
	else
	{
		if ( TextEdit_CHATINPUT->text().right(1) != " " )
		{
			selected = " " + selected;
		}
		
		// not using TextEdit_CHATINPUT->append(), that would add a new line
		TextEdit_CHATINPUT->setPlainText( TextEdit_CHATINPUT->text() + 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").toAscii().data() );
		AddStatus( output.toAscii().data() );
	}
	else
	{
		if ( ok )
		{
			SendMessage( output.toAscii().data() );
		}
		else
		{
			AddStatus( output.toAscii().data() );
		}
		
		m_pShellRunners->removeAt(pos);
		disconnect( runner, SIGNAL(finished(bool,QString)), this, SLOT(slotShellCommandFinished(bool,QString)) );
		delete runner;
	}
}

/** */
DCShellCommandRunner::DCShellCommandRunner( QStringList a ) : QThread( )
{
	args = a;
	stop = FALSE;
}

/** */
DCShellCommandRunner::~DCShellCommandRunner()
{
	cancel();
	if ( !wait(100) )
	{
		printf("~DCShellCommandRunner: warning not finished\n");
	}
}

/** */
void DCShellCommandRunner::run()
{
	QString output;
	bool succeeded = FALSE;
	QString program = args.takeFirst();
	QProcess process;
	process.start( program, args );
	process.closeWriteChannel();
	process.waitForFinished( 100 );
	
	int i = 0;
	while ( ((process.state() == QProcess::Starting) || (process.state() == QProcess::Running)) && (i < 2400) && (stop == FALSE) )
	{
		process.waitForFinished( 50 );
		i++;
	}
	
	if ( stop == TRUE )
	{
		/* just kill the damn process */
		process.kill();
		return;
	}
	
	if ( (process.state() == QProcess::NotRunning) )
	{
		if ( process.exitStatus() == QProcess::NormalExit )
		{
			int exitcode = process.exitCode();
			if ( exitcode == 0 )
			{
				output = QString( process.readAllStandardOutput() ).stripWhiteSpace();
				if ( output == "" )
				{
					output = tr("Command produced no visible output.");
				}
				else
				{
					succeeded = TRUE;
				}
			}
			else
			{
				output = tr("Process exited with status") + " " + QString::number(exitcode);
			}
		}
		else
		{
			output = tr("Process was killed or crashed.");
		}
	}
	else
	{
		output = tr("Process still running after 2 minutes, killing process...");
		process.terminate();
		QThread::msleep(1000);
		if ( process.state() != QProcess::NotRunning )
		{
			process.kill();
		}
	}

	if ( stop == FALSE ) // stop is only set to TRUE when things are being deleted
	{
		emit finished( succeeded, output );
	}
}

/** */
void DCShellCommandRunner::cancel()
{
	stop = TRUE;
}
