/*
 * groupchatdlg.cpp - dialogs for handling groupchat
 * Copyright (C) 2001, 2002  Justin Karneges
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include"groupchatdlg.h"

#include<qlabel.h>
#include<qlayout.h>
#include<qpushbutton.h>
#include<qgroupbox.h>
#include<qmessagebox.h>
#include<qtextedit.h>
#include<qsplitter.h>
#include<qtimer.h>
#include<qheader.h>
#include<qtoolbutton.h>
#include<qinputdialog.h>
#include<qguardedptr.h>
#include<qaction.h>
#include<qobjectlist.h>
#include<qpopupmenu.h>
#include<qcursor.h>
#include"psicon.h"
#include"psiaccount.h"
#include"userlist.h"
#include"statusdlg.h"
#include"busywidget.h"
#include"common.h"
#include"msgmle.h"
#include"iconwidget.h"
#include"iconselect.h"
#include"psitoolbar.h"
#include"iconaction.h"

#ifdef Q_WS_WIN
#include<windows.h>
#endif


//----------------------------------------------------------------------------
// GCJoinDlg
//----------------------------------------------------------------------------
class GCJoinDlg::Private
{
public:
	Private() {}

	PsiCon *psi;
	PsiAccount *pa;
	AccountsComboBox *cb_ident;
	BusyWidget *busy;
	QStringList rl;
	Jid jid;
};

GCJoinDlg::GCJoinDlg(PsiCon *psi, PsiAccount *pa)
:GCJoinUI(0, 0, false, WDestructiveClose)
{
	d = new Private;
	d->psi = psi;
	d->pa = 0;
	d->psi->dialogRegister(this);
	d->busy = busy;

	updateIdentity(pa);

	d->cb_ident = d->psi->accountsComboBox(this,true);
	connect(d->cb_ident, SIGNAL(activated(PsiAccount *)), SLOT(updateIdentity(PsiAccount *)));
	d->cb_ident->setAccount(pa);
	replaceWidget(lb_ident, d->cb_ident);

	d->rl = d->psi->recentGCList();
	for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) {
		Jid j(*it);
		QString s = tr("%1 on %2").arg(j.resource()).arg(j.userHost());
		cb_recent->insertItem(s);
	}

	setCaption(CAP(caption()));
	pb_join->setDefault(true);
	connect(pb_close, SIGNAL(clicked()), SLOT(close()));
	connect(pb_join, SIGNAL(clicked()), SLOT(doJoin()));
	connect(cb_recent, SIGNAL(activated(int)), SLOT(recent_activated(int)));
	if(d->rl.isEmpty()) {
		cb_recent->setEnabled(false);
		le_host->setFocus();
	}
	else
		recent_activated(0);
}

GCJoinDlg::~GCJoinDlg()
{
	if(d->psi)
		d->psi->dialogUnregister(this);
	if(d->pa)
		d->pa->dialogUnregister(this);
	delete d;
}

/*void GCJoinDlg::closeEvent(QCloseEvent *e)
{
	e->ignore();
	reject();
}*/

void GCJoinDlg::done(int r)
{
	if(d->busy->isActive()) {
		int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to cancel joining groupchat?"), tr("&Yes"), tr("&No"));
		if(n != 0)
			return;
		d->pa->groupChatLeave(d->jid.host(), d->jid.user());
	}
	QDialog::done(r);
}

void GCJoinDlg::updateIdentity(PsiAccount *pa)
{
	if(d->pa)
		disconnect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected()));

	d->pa = pa;
	pb_join->setEnabled(d->pa);

	if(!d->pa) {
		d->busy->stop();
		return;
	}

	connect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected()));
}

void GCJoinDlg::pa_disconnected()
{
	if(d->busy->isActive()) {
		d->busy->stop();
	}
}

void GCJoinDlg::recent_activated(int x)
{
	int n = 0;
	bool found = false;
	QString str;
	for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) {
		if(n == x) {
			found = true;
			str = *it;
			break;
		}
		++n;
	}
	if(!found)
		return;

	Jid j(str);
	le_host->setText(j.host());
	le_room->setText(j.user());
	le_nick->setText(j.resource());
}

void GCJoinDlg::doJoin()
{
	if(!d->pa->checkConnected(this))
		return;

	QString host = le_host->text();
	QString room = le_room->text();
	QString nick = le_nick->text();

	if(host.isEmpty() || room.isEmpty() || nick.isEmpty()) {
		QMessageBox::information(this, tr("Error"), tr("You must fill out the fields in order to join."));
		return;
	}

	Jid j = room + '@' + host + '/' + nick;
	if(!j.isValid()) {
		QMessageBox::information(this, tr("Error"), tr("You entered an invalid room name."));
		return;
	}

	if(!d->pa->groupChatJoin(host, room, nick)) {
		QMessageBox::information(this, tr("Error"), tr("You are in or joining this room already!"));
		return;
	}

	d->psi->dialogUnregister(this);
	d->jid = room + '@' + host + '/' + nick;
	d->pa->dialogRegister(this, d->jid);

	disableWidgets();
	d->busy->start();
}

void GCJoinDlg::disableWidgets()
{
	d->cb_ident->setEnabled(false);
	cb_recent->setEnabled(false);
	gb_info->setEnabled(false);
	pb_join->setEnabled(false);
}

void GCJoinDlg::enableWidgets()
{
	d->cb_ident->setEnabled(true);
	if(!d->rl.isEmpty())
		cb_recent->setEnabled(true);
	gb_info->setEnabled(true);
	pb_join->setEnabled(true);
}

void GCJoinDlg::joined()
{
	d->psi->recentGCAdd(d->jid.full());
	d->busy->stop();

	closeDialogs(this);
	deleteLater();
}

void GCJoinDlg::error(int, const QString &str)
{
	d->busy->stop();
	enableWidgets();

	pb_join->setFocus();

	d->pa->dialogUnregister(this);
	d->psi->dialogRegister(this);

	QMessageBox::information(this, tr("Error"), tr("Unable to join groupchat.\nReason: %1").arg(str));
}


//----------------------------------------------------------------------------
// GCUserView
//----------------------------------------------------------------------------
GCUserViewItem::GCUserViewItem(QListView *par)
:QListViewItem(par)
{
}

GCUserView::GCUserView(QWidget *parent, const char *name)
:QListView(parent, name), QToolTip(viewport())
{
	setResizeMode(QListView::AllColumns);
	setSorting(0);
	header()->hide();
	addColumn("");

	connect(this, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *)));
	connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), SLOT(qlv_contextMenuRequested(QListViewItem *, const QPoint &, int)));
}

GCUserView::~GCUserView()
{
}

void GCUserView::updateAll()
{
	for(GCUserViewItem *i = (GCUserViewItem *)firstChild(); i; i = (GCUserViewItem *)i->nextSibling())
		i->setPixmap(0, is->status(i->s));
}

QStringList GCUserView::nickList() const
{
	QStringList list;

	for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling())
		list << lvi->text(0);

	qstringlistisort(list); // caseless sorting
	return list;
}

QListViewItem *GCUserView::findEntry(const QString &nick)
{
	for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling()) {
		if(lvi->text(0) == nick)
			return lvi;
	}
	return 0;
}

void GCUserView::updateEntry(const QString &nick, const Status &s)
{
	GCUserViewItem *lvi = (GCUserViewItem *)findEntry(nick);
	if(!lvi) {
		lvi = new GCUserViewItem(this);
		lvi->setText(0, nick);
	}

	lvi->s = s;
	lvi->setPixmap(0, is->status(lvi->s));
}

void GCUserView::removeEntry(const QString &nick)
{
	QListViewItem *lvi = findEntry(nick);
	if(lvi)
		delete lvi;
}

void GCUserView::maybeTip(const QPoint &pos)
{
	GCUserViewItem *lvi = (GCUserViewItem *)itemAt(pos);
	if(!lvi)
		return;

	QRect r(itemRect(lvi));

	const QString &nick = lvi->text(0);
	const Status &s = lvi->s;
	UserListItem u;
	// SICK SICK SICK SICK
	u.setJid(((GCMainDlg *)topLevelWidget())->jid().withResource(nick));
	u.setName(nick);

	// make a resource so the contact appears online
	UserResource ur;
	ur.setName(nick);
	ur.setStatus(s);
	u.userResourceList().append(ur);

	tip(r, u.makeTip());
}

void GCUserView::qlv_doubleClicked(QListViewItem *i)
{
	GCUserViewItem *lvi = (GCUserViewItem *)i;
	if(!lvi)
		return;

	if(option.defaultAction == 0)
		action(lvi->text(0), lvi->s, 0);
	else
		action(lvi->text(0), lvi->s, 1);
}

void GCUserView::qlv_contextMenuRequested(QListViewItem *i, const QPoint &pos, int)
{
	GCUserViewItem *lvi = (GCUserViewItem *)i;
	if(!lvi)
		return;

	QPopupMenu *pm = new QPopupMenu;
	pm->insertItem(IconsetFactory::icon("psi/sendMessage"), tr("Send &message"), 0);
	pm->insertItem(IconsetFactory::icon("psi/start-chat"), tr("Open &chat window"), 1);
	pm->insertSeparator();
	//pm->insertItem(tr("Send &file"), 4);
	//pm->insertSeparator();
	pm->insertItem(tr("Check &Status"), 2);
	pm->insertItem(IconsetFactory::icon("psi/vCard"), tr("User &Info"), 3);
	int x = pm->exec(pos);
	delete pm;

	if(x == -1)
		return;
	action(lvi->text(0), lvi->s, x);
}


//----------------------------------------------------------------------------
// GCMainDlg
//----------------------------------------------------------------------------
class GCMainDlg::Private : public QObject
{
	Q_OBJECT
public:
	enum { Connecting, Connected, Idle };
	Private(GCMainDlg *d) {
		dlg = d;
		nickSeparator = ":";
		typingStatus = Typing_Normal;
	}

	GCMainDlg *dlg;
	int state;
	PsiAccount *pa;
	Jid jid;
	QString self;
	ChatView *te_log;
	ChatEdit *mle;
	QLineEdit *le_topic;
	GCUserView *lv_users;
	QPushButton *pb_topic;
	PsiToolBar *toolbar;
	IconAction *act_find, *act_clear, *act_icon;
	QPopupMenu *pm_settings;
	bool smallChat;
	int pending;

	bool trackBar;
	int  trackBarParagraph;

	QTimer *flashTimer;
	int flashCount;

	QStringList hist;
	int histAt;

	QGuardedPtr<GCFindDlg> findDlg;
	QString lastSearch;

public slots:
	void addEmoticon(const Icon *icon) {
		if ( !dlg->isActiveWindow() )
		     return;

		QString text;

		QDict<QString> itext = icon->text();
		QDictIterator<QString> it ( itext );
		for ( ; it.current(); ++it) {
			if ( it.current() && !it.current()->isEmpty() ) {
				text = *(it.current()) + " ";
				break;
			}
		}

		if ( !text.isEmpty() )
			mle->insert( text );
	}

	void addEmoticon(QString text) {
		if ( !dlg->isActiveWindow() )
		     return;

		mle->insert( text + " " );
	}

	void deferredScroll() {
		//QTimer::singleShot(250, this, SLOT(slotScroll()));
		te_log->scrollToBottom();
	}

protected slots:
	void slotScroll() {
		te_log->scrollToBottom();
	}

public:
	// Nick auto-completion code follows...
	enum TypingStatus {
		Typing_Normal = 0,
		Typing_TabPressed,
		Typing_TabbingNicks,
		Typing_MultipleSuggestions
	};
	TypingStatus typingStatus;
	QString lastReferrer;  // contains nick of last person, who have said "yourNick: ..."
	QString nickSeparator; // in case of "nick: ...", it equals ":"
	QStringList suggestedNicks;
	int  suggestedIndex;
	bool suggestedFromStart;

	QString beforeNickText(QString text) {
		int i;
		for (i = text.length() - 1; i >= 0; --i)
			if ( text[i].isSpace() )
				break;

		QString beforeNick = text.left(i+1);
		return beforeNick;
	}

	QStringList suggestNicks(QString text, bool fromStart) {
		QString nickText = text;
		QString beforeNick;
		if ( !fromStart ) {
			beforeNick = beforeNickText(text);
			nickText   = text.mid(beforeNick.length());
		}

		QStringList nicks = lv_users->nickList();
		QStringList::Iterator it = nicks.begin();
		QStringList suggestedNicks;
		for ( ; it != nicks.end(); ++it) {
			if ( (*it).left(nickText.length()).lower() == nickText.lower() ) {
				if ( fromStart )
					suggestedNicks << *it;
				else
					suggestedNicks << beforeNick + *it;
			}
		}

		return suggestedNicks;
	}

	QString longestSuggestedString(QStringList suggestedNicks) {
		QString testString = suggestedNicks.first();
		while ( testString.length() > 0 ) {
			bool found = true;
			QStringList::Iterator it = suggestedNicks.begin();
			for ( ; it != suggestedNicks.end(); ++it) {
				if ( (*it).left(testString.length()).lower() != testString.lower() ) {
					found = false;
					break;
				}
			}

			if ( found )
				break;

			testString = testString.left( testString.length() - 1 );
		}

		return testString;
	}

	QString insertNick(bool fromStart, QString beforeNick = "") {
		typingStatus = Typing_MultipleSuggestions;
		suggestedFromStart = fromStart;
		suggestedNicks = lv_users->nickList();
		QStringList::Iterator it = suggestedNicks.begin();
		for ( ; it != suggestedNicks.end(); ++it)
			*it = beforeNick + *it;

		QString newText;
		if ( !lastReferrer.isEmpty() ) {
			newText = beforeNick + lastReferrer;
			suggestedIndex = -1;
		}
		else {
			newText = suggestedNicks.first();
			suggestedIndex = 0;
		}

		if ( fromStart ) {
			newText += nickSeparator;
			newText += " ";
		}

		return newText;
	}

	QString suggestNick(bool fromStart, QString origText, bool *replaced) {
		suggestedFromStart = fromStart;
		suggestedNicks = suggestNicks(origText, fromStart);
		suggestedIndex = -1;

		QString newText;
		if ( suggestedNicks.count() ) {
			if ( suggestedNicks.count() == 1 ) {
				newText = suggestedNicks.first();
				if ( fromStart ) {
					newText += nickSeparator;
					newText += " ";
				}
			}
			else {
				newText = longestSuggestedString(suggestedNicks);
				if ( !newText.length() )
					return origText;

				typingStatus = Typing_MultipleSuggestions;
				// TODO: display a tooltip that will contain all suggestedNicks
			}

			*replaced = true;
		}

		return newText;
	}

	void doAutoNickInsertion() {
		int para, index;
		mle->getCursorPosition(&para, &index);
		QString paraText = mle->text(para);
		QString origText = paraText.left(index);
		QString newText;

		bool replaced = false;

		if ( typingStatus == Typing_MultipleSuggestions ) {
			suggestedIndex++;
			if ( suggestedIndex >= (int)suggestedNicks.count() )
				suggestedIndex = 0;

			newText = suggestedNicks[suggestedIndex];
			if ( suggestedFromStart ) {
				newText += nickSeparator;
				newText += " ";
			}

			replaced = true;
		}

		if ( !para && !replaced ) {
			if ( !index && typingStatus == Typing_TabbingNicks ) {
				newText = insertNick(true, "");
				replaced = true;
			}
			else {
				newText = suggestNick(true, origText, &replaced);
			}
		}

		if ( !replaced ) {
			if ( (!index || origText[index-1].isSpace()) && typingStatus == Typing_TabbingNicks ) {
				newText = insertNick(false, beforeNickText(origText));
				replaced = true;
			}
			else {
				newText = suggestNick(false, origText, &replaced);
			}
		}

		if ( replaced ) {
			mle->setUpdatesEnabled( false );
			QString newParaText = newText + paraText.mid(index, paraText.length() - index - 1);
			mle->insertParagraph(newParaText, para);
			mle->removeParagraph(para+1);
			mle->setCursorPosition(para, newText.length());
			mle->setUpdatesEnabled( true );
			mle->viewport()->update();
		}
	}

	bool eventFilter( QObject *obj, QEvent *ev ) {
		if ( obj == mle && ev->type() == QEvent::KeyPress ) {
			QKeyEvent *e = (QKeyEvent *)ev;

			if ( e->key() == Key_Tab ) {
				switch ( typingStatus ) {
				case Typing_Normal:
					typingStatus = Typing_TabPressed;
					break;
				case Typing_TabPressed:
					typingStatus = Typing_TabbingNicks;
					break;
				default:
					break;
				}

				doAutoNickInsertion();
				return TRUE;
			}

			typingStatus = Typing_Normal;

			return FALSE;
		}

		return QObject::eventFilter( obj, ev );
	}
};

GCMainDlg::GCMainDlg(PsiAccount *pa, const Jid &j)
:AdvancedWidget<QWidget>(0, 0, WDestructiveClose)
{
	nicknumber=0;
	d = new Private(this);
	d->pa = pa;
	d->jid = j.userHost();
	d->self = j.resource();
	d->pa->dialogRegister(this, d->jid);
	connect(d->pa, SIGNAL(updatedActivity()), SLOT(pa_updatedActivity()));

	d->pending = 0;
	d->flashTimer = 0;

	d->histAt = 0;
	d->findDlg = 0;

	d->trackBar = false;
	d->trackBarParagraph = 0;

	d->state = Private::Connected;

#ifndef Q_WS_MAC
	setIcon(IconsetFactory::icon("psi/groupChat"));
#endif

	QVBoxLayout *dlg_layout = new QVBoxLayout(this, 4);

	QWidget *vsplit;
	if ( !option.chatLineEdit ) {
		vsplit = new QSplitter(this);
		((QSplitter *)vsplit)->setOrientation( QSplitter::Vertical );
		dlg_layout->addWidget(vsplit);
	}
	else
		vsplit = this;

	// --- top part ---
	QWidget *sp_top = new QWidget(vsplit);
	sp_top->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
	if ( option.chatLineEdit )
		dlg_layout->addWidget( sp_top );
	QVBoxLayout *vb_top = new QVBoxLayout(sp_top, 0, 4);

	// top row
	QWidget *sp_top_top = new QWidget( sp_top );
	vb_top->addWidget( sp_top_top );
	QHBoxLayout *hb_top = new QHBoxLayout( sp_top_top, 0, 4 );

	d->pb_topic = new QPushButton(tr("Topic:"), sp_top_top);
	connect(d->pb_topic, SIGNAL(clicked()), SLOT(doTopic()));
	hb_top->addWidget(d->pb_topic);

	d->le_topic = new QLineEdit(sp_top_top);
	d->le_topic->setReadOnly(true);
	hb_top->addWidget(d->le_topic);

	d->act_find = new IconAction(tr("Find"), "psi/search", tr("&Find"), CTRL+Key_F, this);
	connect(d->act_find, SIGNAL(activated()), SLOT(openFind()));
	d->act_find->addTo( sp_top_top );

	QLabel *lb_ident = d->pa->accountLabel(sp_top_top, true);
	lb_ident->setSizePolicy(QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ));
	hb_top->addWidget(lb_ident);

	// bottom row
	QSplitter *hsp = new QSplitter(sp_top);
	hsp->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
	vb_top->addWidget(hsp);
	hsp->setOrientation(QSplitter::Horizontal);

	d->te_log = new ChatView(hsp);
	d->te_log->setTextFormat( RichText );
#ifdef Q_WS_MAC
	connect(d->te_log,SIGNAL(selectionChanged()),SLOT(logSelectionChanged()));
	d->te_log->setFocusPolicy(QWidget::NoFocus);
#endif

	d->lv_users = new GCUserView(hsp);
	d->lv_users->setMinimumWidth(20);
	connect(d->lv_users, SIGNAL(action(const QString &, const Status &, int)), SLOT(lv_action(const QString &, const Status &, int)));

	// --- bottom part ---
	QWidget *sp_bottom = new QWidget(vsplit);
	sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
	if ( option.chatLineEdit )
		dlg_layout->addWidget( sp_bottom );
	QVBoxLayout *vb_bottom = new QVBoxLayout(sp_bottom);

	// toolbar
	d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this);
	connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) );

	connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString)));
	d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this );
	d->act_icon->setPopup( pa->psi()->iconSelectPopup() );

	d->toolbar = new PsiToolBar( tr("Groupchat toolbar"), 0, sp_bottom );
	d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults
	d->toolbar->setFrameShape( QFrame::NoFrame );
	vb_bottom->addWidget( d->toolbar );

	d->act_clear->addTo( d->toolbar );
	d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar));
	d->act_icon->addTo( d->toolbar );

	// chat edit
	if ( !option.chatLineEdit ) {
		d->mle = new ChatEdit(sp_bottom);
		vb_bottom->addWidget(d->mle);
	}
	else {
		QHBoxLayout *hb5 = new QHBoxLayout( dlg_layout );
		d->mle = new LineEdit( vsplit );
#ifdef Q_WS_MAC
		hb5->addSpacing( 16 );
#endif
		hb5->addWidget( d->mle );
#ifdef Q_WS_MAC
		hb5->addSpacing( 16 );
#endif
	}

	d->mle->installEventFilter( d );

	d->pm_settings = new QPopupMenu(this);
	connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu()));

	// resize the horizontal splitter
	QValueList<int> list;
	list << 500;
	list << 80;
	hsp->setSizes(list);

	list.clear();
	list << 324;
	list << 10;
	if ( !option.chatLineEdit )
		(( QSplitter *)vsplit)->setSizes(list);

	resize(580,420);

	d->smallChat = option.smallChats;
	X11WM_CLASS("groupchat");

	d->mle->setFocus();

	setLooks();
	updateCaption();
}

GCMainDlg::~GCMainDlg()
{
	if(d->state != Private::Idle)
		d->pa->groupChatLeave(d->jid.host(), d->jid.user());

	//QMimeSourceFactory *m = d->te_log->mimeSourceFactory();
	//d->te_log->setMimeSourceFactory(0);
	//delete m;

	d->pa->dialogUnregister(this);
	delete d;
}

void GCMainDlg::keyPressEvent(QKeyEvent *e)
{
	if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton)))
		mle_returnPressed();
	else if(e->key() == Key_PageUp && (e->state() & ShiftButton))
		d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() - d->te_log->visibleHeight()/2);
	else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
		d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() + d->te_log->visibleHeight()/2);
	else
		e->ignore();
}

void GCMainDlg::closeEvent(QCloseEvent *e)
{
	e->accept();
}

void GCMainDlg::windowActivationChange(bool oldstate)
{
	QWidget::windowActivationChange(oldstate);

	// if we're bringing it to the front, get rid of the '*' if necessary
	if(isActiveWindow()) {
		if(d->pending > 0) {
			d->pending = 0;
			updateCaption();
		}
		doFlash(false);

		d->mle->setFocus();
		d->trackBar = false;
	} else {
		d->trackBar = true;
	}
}


void GCMainDlg::logSelectionChanged()
{
#ifdef Q_WS_MAC
	// A hack to only give the message log focus when text is selected
	if (d->te_log->hasSelectedText()) 
		d->te_log->setFocus();
	else 
		d->mle->setFocus();
#endif
}

void GCMainDlg::mle_returnPressed()
{
	if(d->mle->text().isEmpty())
		return;

	QString str = d->mle->text();
	if(str == "/clear") {
		doClear();

		d->histAt = 0;
		d->hist.prepend(str);
		d->mle->setText("");
		return;
	}

	if(str.lower().startsWith("/nick ")) {
		QString nick = str.mid(6).stripWhiteSpace();
		if ( !nick.isEmpty() ) {
			d->self = nick;
			d->pa->groupChatChangeNick(d->jid.host(), d->jid.user(), d->self, d->pa->status());
		}
		d->mle->setText("");
		return;
	}

	if(d->state != Private::Connected)
		return;

	Message m(d->jid);
	m.setType("groupchat");
	m.setBody(str);
	m.setTimeStamp(QDateTime::currentDateTime());

	aSend(m);

	d->histAt = 0;
	d->hist.prepend(str);
	d->mle->setText("");
}

/*void GCMainDlg::le_upPressed()
{
	if(d->histAt < (int)d->hist.count()) {
		++d->histAt;
		d->le_input->setText(d->hist[d->histAt-1]);
	}
}

void GCMainDlg::le_downPressed()
{
	if(d->histAt > 0) {
		--d->histAt;
		if(d->histAt == 0)
			d->le_input->setText("");
		else
			d->le_input->setText(d->hist[d->histAt-1]);
	}
}*/

void GCMainDlg::doTopic()
{
	bool ok = false;
	QString str = QInputDialog::getText(
		tr("Set Groupchat Topic"),
		tr("Enter a topic:"),
		QLineEdit::Normal, d->le_topic->text(), &ok, this);

	if(ok) {
		Message m(d->jid);
		m.setType("groupchat");
		m.setSubject(str);
		m.setBody(QString("/me ") + tr("has set the topic to: %1").arg(str));
		m.setTimeStamp(QDateTime::currentDateTime());
		aSend(m);
	}
}

void GCMainDlg::doClear()
{
	d->te_log->setText("");
}

void GCMainDlg::doClearButton()
{
	int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"), tr("&Yes"), tr("&No"));
	if(n == 0)
		doClear();
}

void GCMainDlg::openFind()
{
	if(d->findDlg)
		bringToFront(d->findDlg);
	else {
		d->findDlg = new GCFindDlg(0, 0, d->lastSearch, this);
		connect(d->findDlg, SIGNAL(find(int, int, const QString &)), SLOT(doFind(int, int, const QString &)));
		d->findDlg->show();
	}
}

void GCMainDlg::doFind(int para, int index, const QString &str)
{
	d->lastSearch = str;

	if(!d->te_log->find(str, false, false, true, &para, &index))
		d->findDlg->error(str);
	else {
		// pass one character over
		d->findDlg->found(para, index+1);
	}
}

void GCMainDlg::goDisc()
{
	if(d->state != Private::Idle) {
		d->state = Private::Idle;
		d->pb_topic->setEnabled(false);
		appendSysMsg(tr("Disconnected."), true);
	}
}

void GCMainDlg::goConn()
{
	if(d->state == Private::Idle) {
		d->state = Private::Connecting;
		appendSysMsg(tr("Reconnecting..."), true);

		QString host = d->jid.host();
		QString room = d->jid.user();
		QString nick = d->self;

		if(!d->pa->groupChatJoin(host, room, nick)) {
			appendSysMsg(tr("Error: You are in or joining this room already!"), true);
			d->state = Private::Idle;
		}
	}
}

void GCMainDlg::pa_updatedActivity()
{
	if(!d->pa->loggedIn())
		goDisc();
	else {
		if(d->state == Private::Idle)
			goConn();
		else if(d->state == Private::Connected)
			d->pa->groupChatSetStatus(d->jid.host(), d->jid.user(), d->pa->status());
	}
}

Jid GCMainDlg::jid() const
{
	return d->jid;
}

void GCMainDlg::error(int, const QString &str)
{
	d->pb_topic->setEnabled(false);

	if(d->state == Private::Connecting)
		appendSysMsg(tr("Unable to join groupchat.  Reason: %1").arg(str), true);
	else
		appendSysMsg(tr("Unexpected groupchat error: %1").arg(str), true);

	d->state = Private::Idle;
}

void GCMainDlg::presence(const QString &nick, const Status &s)
{
	if(s.isAvailable()) {
		/*if ((option.showJoins)&&(d->lv_users->findEntry(nick)==0)) {
			//contact joining
			QString m=nick+tr(" has joined the channel");
			appendSysMsg(m, false, QDateTime::currentDateTime());
		}*/
		d->lv_users->updateEntry(nick, s);
	} else {
		/*if (option.showJoins) {
			//contact leaving
			QString m=nick+tr(" has left the channel");
			appendSysMsg(m, false, QDateTime::currentDateTime());
		}*/
		d->lv_users->removeEntry(nick);
	}
}

void GCMainDlg::message(const Message &m)
{
	QString from = m.from().resource();
	bool alert = false;

	if(!m.subject().isEmpty()) {
		d->le_topic->setText(m.subject());
		d->le_topic->setCursorPosition(0);
		QToolTip::add(d->le_topic, QString("<qt><p>%1</p></qt>").arg(m.subject()));
	}

	// code to determine if the speaker was addressing this client in chat
	if(m.body().contains(d->self) > 0)
		alert = true;

	if (m.body().left(d->self.length()) == d->self)
		d->lastReferrer = m.from().resource();

	if(option.gcHighlighting) {
		for(QStringList::Iterator it=option.gcHighlights.begin();it!=option.gcHighlights.end();it++) {
			if(m.body().contains((*it),false) > 0) {
				alert = true;
			}
		}
	}

	// play sound?
	if(from == d->self) {
		if(!m.spooled())
			d->pa->playSound(option.onevent[eSend]);
	}
	else {
		if(alert || (!option.noGCSound && !m.spooled() && !from.isEmpty()) )
			d->pa->playSound(option.onevent[eChat2]);
	}

	if(from.isEmpty())
		appendSysMsg(m.body(), alert, m.timeStamp());
	else
		appendMessage(m, alert);
}

void GCMainDlg::joined()
{
	if(d->state == Private::Connecting) {
		d->lv_users->QListView::clear();
		d->state = Private::Connected;
		d->pb_topic->setEnabled(true);
		appendSysMsg(tr("Connected."), true);
	}
}

void GCMainDlg::appendSysMsg(const QString &str, bool alert, const QDateTime &ts)
{
	bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();

	QString hr ="";
	if (d->trackBar) {
		hr = QString("<hr>");
		d->trackBar = false;

		if ( d->trackBarParagraph ) {
			d->te_log->setUpdatesEnabled( false );
			d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
			d->te_log->removeSelectedText(1);
			d->te_log->setUpdatesEnabled( true );
		}
		d->trackBarParagraph = d->te_log->paragraphs();
	}

	QString timestr;
	if (!option.gcHighlighting)
		alert=false;

	QDateTime time;
	if(!ts.isNull())
		time = ts;
	else
		time = QDateTime::currentDateTime();

	//timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
	timestr = time.time().toString(LocalDate);

	/*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
	if(y < 0)
		y = 0;
	bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/

	d->te_log->append(hr + QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(expandEntities(str)));

	if(atBottom)
		d->deferredScroll();

	if(alert)
		doAlert();
}

QString GCMainDlg::getNickColor(QString nick)
{
	int sender;
	if(nick == d->self||nick.isEmpty())
		sender = -1;
	else {
		if (!nicks.contains(nick)) {
			//not found in map
			nicks.insert(nick,nicknumber);
			nicknumber++;
		}
		sender=nicks[nick];
	}

	if(!option.gcNickColoring || option.gcNickColors.empty()) {
		return "#000000";
	}
	else if(sender == -1 || option.gcNickColors.size() == 1) {
		return option.gcNickColors[option.gcNickColors.size()-1];
	}
	else {
		int n = sender % (option.gcNickColors.size()-1);
		return option.gcNickColors[n];
	}
}

void GCMainDlg::appendMessage(const Message &m, bool alert)
{
	//QString who, color;
	if (!option.gcHighlighting)
		alert=false;
	QString who, textcolor, nickcolor,alerttagso,alerttagsc;

	bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();

	who = m.from().resource();
	QString hr = "";
	if (d->trackBar&&m.from().resource() != d->self&&!m.spooled()) {
		hr = QString("<hr>");
		d->trackBar = false;

		if ( d->trackBarParagraph ) {
			d->te_log->setUpdatesEnabled( false );
			d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
			d->te_log->removeSelectedText(1);
			d->te_log->setUpdatesEnabled( true );
		}
		d->trackBarParagraph = d->te_log->paragraphs();
	}
	/*if(local) {
		color = "#FF0000";
	}
	else {
		color = "#0000FF";
	}*/
	nickcolor = getNickColor(who);
	textcolor = d->te_log->palette().active().text().name();
	if(alert) {
		textcolor = "#FF0000";
		alerttagso = "<b>";
		alerttagsc = "</b>";
	}
	if(m.spooled())
		nickcolor = "#008000"; //color = "#008000";

	QString timestr;
	QDateTime time = m.timeStamp();
	//timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
	timestr = time.time().toString(LocalDate);

	/*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
	if(y < 0)
		y = 0;
	bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/

	bool emote = false;
	if(m.body().left(4) == "/me ")
		emote = true;

	QString txt;
	if(emote)
		txt = plain2rich(m.body().mid(4));
	else
		txt = plain2rich(m.body());

	txt = linkify(txt);

	if(option.useEmoticons)
		txt = emoticonify(txt);

	if(emote) {
		//d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + txt + "</font>");
		d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + alerttagso + txt + alerttagsc + "</font>");
	}
	else {
		if(option.chatSays) {
			//d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + QString("%1 says:").arg(expandEntities(who)) + "</font><br>" + txt);
			d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1] ").arg(timestr) + QString("%1 says:").arg(expandEntities(who)) + "</font><br>" + QString("<font color=\"%1\">").arg(textcolor) + alerttagso + txt + alerttagsc + "</font>");
		}
		else {
			//d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + expandEntities(who) + QString("&gt;</font> ") + txt);
			d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1] &lt;").arg(timestr) + expandEntities(who) + QString("&gt;</font> ") + QString("<font color=\"%1\">").arg(textcolor) + alerttagso + txt + alerttagsc +"</font>");
		}
	}

	//if(local || atBottom)
	if(m.from().resource() == d->self || atBottom)
		d->deferredScroll();

	// if we're not active, notify the user by changing the title
	if(!isActiveWindow()) {
		++d->pending;
		updateCaption();
	}

	//if someone directed their comments to us, notify the user
	if(alert)
		doAlert();

	//if the message spoke to us, alert the user before closing this window
	//except that keepopen doesn't seem to be implemented for this class yet.
	/*if(alert) {
		d->keepOpen = true;
		QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
        }*/
}

void GCMainDlg::doAlert()
{
	if(!isActiveWindow())
		doFlash(true);
}

void GCMainDlg::updateCaption()
{
	QString cap = "";

	if(d->pending > 0) {
		cap += "* ";
		if(d->pending > 1)
			cap += QString("[%1] ").arg(d->pending);
	}
	cap += d->jid.full();

	// if taskbar flash, then we need to erase first and redo
#ifdef Q_WS_WIN
	bool on = false;
	if(d->flashTimer)
		on = d->flashCount & 1;
	if(on)
		FlashWindow(winId(), true);
#endif
	setCaption(cap);
#ifdef Q_WS_WIN
	if(on)
		FlashWindow(winId(), true);
#endif
}

#ifdef Q_WS_WIN
void GCMainDlg::doFlash(bool yes)
{
	if(yes) {
		if(d->flashTimer)
			return;
		d->flashTimer = new QTimer(this);
		connect(d->flashTimer, SIGNAL(timeout()), SLOT(flashAnimate()));
		d->flashCount = 0;
		flashAnimate(); // kick the first one immediately
		d->flashTimer->start(500);
	}
	else {
		if(d->flashTimer) {
			delete d->flashTimer;
			d->flashTimer = 0;
			FlashWindow(winId(), false);
		}
	}
}
#else
void GCMainDlg::doFlash(bool)
{
}
#endif

void GCMainDlg::flashAnimate()
{
#ifdef Q_WS_WIN
	FlashWindow(winId(), true);
	++d->flashCount;
	if(d->flashCount == 5)
		d->flashTimer->stop();
#endif
}

void GCMainDlg::setLooks()
{
	// update the fonts
	QFont f;
	f.fromString(option.font[fChat]);
	d->te_log->setFont(f);
	d->mle->setFont(f);

	f.fromString(option.font[fRoster]);
	d->lv_users->QListView::setFont(f);

	if ( d->smallChat ) {
		d->toolbar->hide();
	}
	else {
		d->toolbar->show();
	}

	// update the widget icon
#ifndef Q_WS_MAC
	setIcon(IconsetFactory::icon("psi/groupChat"));
#endif
}

void GCMainDlg::optionsUpdate()
{
	/*QMimeSourceFactory *m = d->te_log->mimeSourceFactory();
	d->te_log->setMimeSourceFactory(is->emoticons.generateFactory());
	delete m;*/

	setLooks();

	// update status icons
	d->lv_users->updateAll();
}

void GCMainDlg::lv_action(const QString &nick, const Status &s, int x)
{
	if(x == 0) {
		d->pa->invokeGCMessage(d->jid.withResource(nick));
	}
	else if(x == 1) {
		d->pa->invokeGCChat(d->jid.withResource(nick));
	}
	else if(x == 2) {
		UserListItem u;
		u.setJid(d->jid.withResource(nick));
		u.setName(nick);

		// make a resource so the contact appears online
		UserResource ur;
		ur.setName(nick);
		ur.setStatus(s);
		u.userResourceList().append(ur);

		StatusShowDlg *w = new StatusShowDlg(u);
		w->show();
	}
	else if(x == 3) {
		d->pa->invokeGCInfo(d->jid.withResource(nick));
	}
	else if(x == 4) {
		d->pa->invokeGCFile(d->jid.withResource(nick));
	}
}

void GCMainDlg::contextMenuEvent(QContextMenuEvent *)
{
	d->pm_settings->exec(QCursor::pos());
}

void GCMainDlg::buildMenu()
{
	// Dialog menu
	d->pm_settings->clear();
	d->pm_settings->insertItem(tr("Toggle Compact/Full Size"), this, SLOT(toggleSmallChat()));

	d->act_clear->addTo( d->pm_settings );
	d->pm_settings->insertSeparator();

	d->act_icon->addTo( d->pm_settings );
}

void GCMainDlg::toggleSmallChat()
{
	d->smallChat = !d->smallChat;
	setLooks();
}

//----------------------------------------------------------------------------
// GCFindDlg
//----------------------------------------------------------------------------
GCFindDlg::GCFindDlg(int startPara, int startIndex, const QString &str, QWidget *parent, const char *name)
:QDialog(parent, name, false, WDestructiveClose)
{
	para = startPara;
	index = startIndex;

	setCaption(tr("Find"));
	QVBoxLayout *vb = new QVBoxLayout(this, 4);
	QHBoxLayout *hb = new QHBoxLayout(vb);
	QLabel *l = new QLabel(tr("Find:"), this);
	hb->addWidget(l);
	le_input = new QLineEdit(this);
	hb->addWidget(le_input);
	vb->addStretch(1);

	QFrame *Line1 = new QFrame(this);
	Line1->setFrameShape( QFrame::HLine );
	Line1->setFrameShadow( QFrame::Sunken );
	Line1->setFrameShape( QFrame::HLine );
	vb->addWidget(Line1);

	hb = new QHBoxLayout(vb);
	hb->addStretch(1);
	QPushButton *pb_close = new QPushButton(tr("&Close"), this);
	connect(pb_close, SIGNAL(clicked()), SLOT(close()));
	hb->addWidget(pb_close);
	QPushButton *pb_find = new QPushButton(tr("&Find"), this);
	pb_find->setDefault(true);
	connect(pb_find, SIGNAL(clicked()), SLOT(doFind()));
	hb->addWidget(pb_find);
	pb_find->setAutoDefault(true);

	resize(200, minimumSize().height());

	le_input->setText(str);
	le_input->setFocus();
}

GCFindDlg::~GCFindDlg()
{
}

void GCFindDlg::found(int _para, int _index)
{
	para = _para;
	index = _index;
}

void GCFindDlg::error(const QString &str)
{
	QMessageBox::warning(this, tr("Find"), tr("Search string '%1' not found.").arg(str));
	le_input->setFocus();
}

void GCFindDlg::doFind()
{
	emit find(para, index, le_input->text());
}

#include "groupchatdlg.moc"
