/*
 * contactview.cpp - contact list widget
 * 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"contactview.h"

#include<qapplication.h>
#include<qptrlist.h>
#include<qheader.h>
#include<qtimer.h>
#include<qpainter.h>
#include<qpopupmenu.h>
#include<qmessagebox.h>
#include<qinputdialog.h>
#include<qiconset.h>
#include<qdragobject.h>
#include<qfiledialog.h>
#include<qlayout.h>
#include<stdlib.h>
#include"im.h"
#include"common.h"
#include"userlist.h"
#include"psiaccount.h"
#include"psicon.h"
#include"iconaction.h"
#include"alerticon.h"
#include"avatars.h"

//----------------------------------------------------------------------------
// ContactProfile
//----------------------------------------------------------------------------
class ContactProfile::Entry
{
public:
	Entry()
	{
		alerting = false;
		cvi.setAutoDelete(true);
	}
	~Entry()
	{
	}

	UserListItem u;
	QPtrList<ContactViewItem> cvi;
	bool alerting;
	Icon anim;
};

class ContactProfile::Private : public QObject
{
	Q_OBJECT
public:
	Private() {}

	QString name;
	ContactView *cv;
	ContactViewItem *cvi;
	ContactViewItem *self;
	UserListItem su;
	QPtrList<Entry> roster;
	QPtrList<ContactViewItem> groups;
	int oldstate;
	QTimer *t;
	PsiAccount *pa;
	bool v_enabled;

public slots:
	/*
	 * \brief This slot is toggled when number of active accounts is changed
	 *
	 * At the moment, it tries to recalculate the roster size.
	 */
	void numAccountsChanged()
	{
		cv->recalculateSize();
	}
};

ContactProfile::ContactProfile(PsiAccount *pa, const QString &name, ContactView *cv, bool unique)
{
	d = new Private;
	d->pa = pa;
	d->v_enabled = d->pa->enabled();
	d->name = name;
	d->cv = cv;
	d->cv->link(this);
	d->t = new QTimer;
	connect(d->t, SIGNAL(timeout()), SLOT(updateGroups()));
	connect(pa->psi(), SIGNAL(accountCountChanged()), d, SLOT(numAccountsChanged()));

	d->roster.setAutoDelete(true);

	d->self = 0;

	if(!unique)
		d->cvi = new ContactViewItem(name, this, d->cv);
	else
		d->cvi = 0;

	d->oldstate = -2;

	deferredUpdateGroups();
}

ContactProfile::~ContactProfile()
{
	// delete the roster
	clear();

	// clean up
	delete d->self;
	delete d->cvi;

	delete d->t;
	d->cv->unlink(this);

	delete d;
}

void ContactProfile::setEnabled(bool e)
{
	d->v_enabled = e;
	if(d->v_enabled){
		if(!d->cvi)
			d->cvi = new ContactViewItem(d->name, this, d->cv);
		addAllNeededContactItems();
	}
	else{
		if(d->self)
			removeSelf();

		removeAllUnneededContactItems();
		if(d->cvi)
			delete d->cvi;
		d->cvi = 0;
		d->self = 0;
	}
}

ContactView *ContactProfile::contactView() const
{
	return d->cv;
}

ContactViewItem *ContactProfile::self() const
{
	return d->self;
}

PsiAccount *ContactProfile::psiAccount() const
{
	return d->pa;
}

const QString & ContactProfile::name() const
{
	return d->name;
}

void ContactProfile::setName(const QString &name)
{
	d->name = name;
	if(d->cvi)
		d->cvi->setProfileName(name);
}

void ContactProfile::setName(const char *s)
{
	QObject::setName(s);
}

void ContactProfile::setState(int state)
{
	if(state == d->oldstate)
		return;
	d->oldstate = state;

	if(d->cvi) {
		d->cv->resetAnim();
		d->cvi->setProfileState(state);
	}
}

void ContactProfile::setUsingSSL(bool on)
{
	if(d->cvi)
		d->cvi->setProfileSSL(on);
}

ContactViewItem *ContactProfile::addGroup(int type)
{
	ContactViewItem *item;

	QString gname;
	if(type == ContactViewItem::gGeneral)
		gname = tr("General");
	else if(type == ContactViewItem::gNotInList)
		gname = tr("Not in list");
	else if(type == ContactViewItem::gAgents)
		gname = tr("Agents/Transports");
	else if(type == ContactViewItem::gPrivate)
		gname = tr("Private Messages");

	if(d->cvi)
		item = new ContactViewItem(gname, type, this, d->cvi);
	else
		item = new ContactViewItem(gname, type, this, d->cv);

	if(type == ContactViewItem::gAgents && !d->cv->isShowAgents())
		item->setVisible(false);

	d->groups.append(item);

	return item;
}

ContactViewItem *ContactProfile::addGroup(const QString &name)
{
	ContactViewItem *item;
	if(d->cvi)
		item = new ContactViewItem(name, ContactViewItem::gUser, this, d->cvi);
	else
		item = new ContactViewItem(name, ContactViewItem::gUser, this, d->cv);

	d->groups.append(item);

	return item;
}

// check for special group
ContactViewItem *ContactProfile::checkGroup(int type)
{
	ContactViewItem *item;
	if(d->cvi)
		item = (ContactViewItem *)d->cvi->firstChild();
	else
		item = (ContactViewItem *)d->cv->firstChild();

	for(; item; item = (ContactViewItem *)item->nextSibling()) {
		if(item->type() == ContactViewItem::Group && item->groupType() == type)
			return item;
	}

	return 0;
}

// make a tooltip with account information
QString ContactProfile::makeTip(bool trim, bool doLinkify) const
{
	if (d->cvi)
		return "<qt> <center> <b>" + d->cvi->text(0) + " " + d->cvi->groupInfo() + "</b> </center>" + d->su.makeBareTip(trim,doLinkify) + "</qt>";
	else
		return d->su.makeTip(trim,doLinkify);
}

// check for user group
ContactViewItem *ContactProfile::checkGroup(const QString &name)
{
	ContactViewItem *item;
	if(d->cvi)
		item = (ContactViewItem *)d->cvi->firstChild();
	else
		item = (ContactViewItem *)d->cv->firstChild();

	for(; item; item = (ContactViewItem *)item->nextSibling()) {
		if(item->type() == ContactViewItem::Group && item->groupType() == ContactViewItem::gUser && item->groupName() == name)
				return item;
	}

	return 0;
}

ContactViewItem *ContactProfile::ensureGroup(int type)
{
	ContactViewItem *group_item = checkGroup(type);
	if(!group_item)
		group_item = addGroup(type);

	return group_item;
}

ContactViewItem *ContactProfile::ensureGroup(const QString &name)
{
	ContactViewItem *group_item = checkGroup(name);
	if(!group_item)
		group_item = addGroup(name);

	return group_item;
}

void ContactProfile::checkDestroyGroup(const QString &group)
{
	ContactViewItem *group_item = checkGroup(group);
	if(group_item)
		checkDestroyGroup(group_item);
}

void ContactProfile::checkDestroyGroup(ContactViewItem *group)
{
	if(group->childCount() == 0) {
		d->groups.remove(group);
		delete group;
	}
}

void ContactProfile::updateEntry(const UserListItem &u)
{
	if (u.isSelf()) {
		// Update the self item
		d->su = u;

		// Show and/or update item if necessary
		if (d->cv->isShowSelf() || d->su.userResourceList().count() > 1) {
			if (d->self) {
				updateSelf();
			}
			else {
				addSelf();
			}
		}
		else {
			removeSelf();
		}
	}
	else {
		Entry *e = findEntry(u.jid());
		if(!e) {
			e = new Entry;
			d->roster.append(e);
			e->u = u;
		}
		else {
			e->u = u;
			removeUnneededContactItems(e);

			// update remaining items
			QPtrListIterator<ContactViewItem> it(e->cvi);
			for(ContactViewItem *i; (i = it.current()); ++it) {
				i->setContact(&e->u);
				if(!u.isAvailable())
					i->stopAnimateNick();
			}
		}

		deferredUpdateGroups();
		addNeededContactItems(e);
	}
}

void ContactProfile::updateSelf()
{
	if (d->self) {
		d->self->setContact(&d->su);
		if(!d->su.isAvailable())
			d->self->stopAnimateNick();
	}
}

void ContactProfile::addSelf()
{
	if(!d->self) {
		if(!d->cvi)
			return;
		d->self = new ContactViewItem(&d->su, this, d->cvi);
	}
}

void ContactProfile::removeSelf()
{
	if (d->self) {
		delete d->self;
		d->self = 0;
	}
}

ContactViewItem *ContactProfile::addContactItem(Entry *e, ContactViewItem *group_item)
{
	ContactViewItem *i = new ContactViewItem(&e->u, this, group_item);
	e->cvi.append(i);
	if(e->alerting)
		i->setAlert(&e->anim);
	deferredUpdateGroups();
	//printf("ContactProfile: adding [%s] to group [%s]\n", e->u.jid().full().latin1(), group_item->groupName().latin1());
	return i;
}

/*
 * \brief Ensures that specified Entry is present in contactlist
 *
 * \param e - Entry with the necessary data about item
 * \param group_item - ContactViewItem that will be the group for this item
 */
ContactViewItem *ContactProfile::ensureContactItem(Entry *e, ContactViewItem *group_item)
{
	d->cv->recalculateSize();

	QPtrListIterator<ContactViewItem> it(e->cvi);
	for(ContactViewItem *i; (i = it.current()); ++it) {
		ContactViewItem *g = (ContactViewItem *)static_cast<QListViewItem *>(i)->parent();
		if(g == group_item)
			return i;
	}
	return addContactItem(e, group_item);
}

/*
 * \brief Removes specified item from ContactView
 *
 * \param e - Entry with item's data
 * \param i - ContactViewItem corresponding to the e
 */
void ContactProfile::removeContactItem(Entry *e, ContactViewItem *i)
{
	d->cv->recalculateSize();

	ContactViewItem *group_item = (ContactViewItem *)static_cast<QListViewItem *>(i)->parent();
	//printf("ContactProfile: removing [%s] from group [%s]\n", e->u.jid().full().latin1(), group_item->groupName().latin1());
	e->cvi.removeRef(i);
	deferredUpdateGroups();
	checkDestroyGroup(group_item);
}

void ContactProfile::addNeededContactItems(Entry *e)
{
	if(!d->v_enabled)
		return;

	const UserListItem &u = e->u;

	if(u.inList()) {
		// don't add if we're not supposed to see it
		if(u.isTransport()) {
			if(!d->cv->isShowAgents())
				return;
		}
		else {
			if(!e->alerting) {
				if((!d->cv->isShowOffline() && !u.isAvailable()) || (!d->cv->isShowAway() && u.isAway()) || (!d->cv->isShowHidden() && u.isHidden()))
					return;
			}
		}
	}

	if(u.isPrivate())
		ensureContactItem(e, ensureGroup(ContactViewItem::gPrivate));
	else if(!u.inList())
		ensureContactItem(e, ensureGroup(ContactViewItem::gNotInList));
	else if(u.isTransport())
		ensureContactItem(e, ensureGroup(ContactViewItem::gAgents));
	else if(u.groups().isEmpty())
		ensureContactItem(e, ensureGroup(ContactViewItem::gGeneral));
	else {
		const QStringList &groups = u.groups();
		for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git)
			ensureContactItem(e, ensureGroup(*git));
	}
}

void ContactProfile::removeUnneededContactItems(Entry *e)
{
	const UserListItem &u = e->u;

	if(u.inList()) {
		bool delAll = !d->v_enabled;
		if(u.isTransport()) {
			if(!d->cv->isShowAgents())
				delAll = true;
		}
		else {
			if(!e->alerting) {
				if((!d->cv->isShowOffline() && !u.isAvailable()) || (!d->cv->isShowAway() && u.isAway()) || (!d->cv->isShowHidden() && u.isHidden()))
					delAll = true;
			}
		}
		if(delAll) {
			clearContactItems(e);
			return;
		}
	}

	QPtrListIterator<ContactViewItem> it(e->cvi);
	for(ContactViewItem *i; (i = it.current());) {
		bool del = false;
		ContactViewItem *g = (ContactViewItem *)static_cast<QListViewItem *>(i)->parent();

		if(g->groupType() == ContactViewItem::gNotInList && u.inList())
			del = true;
		else if(g->groupType() != ContactViewItem::gNotInList && g->groupType() != ContactViewItem::gPrivate && !u.inList())
			del = true;
		else if(g->groupType() == ContactViewItem::gAgents && !u.isTransport())
			del = true;
		else if(g->groupType() != ContactViewItem::gAgents && u.isTransport())
			del = true;
		else if(g->groupType() == ContactViewItem::gGeneral && !u.groups().isEmpty())
			del = true;
		else if(g->groupType() != ContactViewItem::gPrivate && g->groupType() != ContactViewItem::gGeneral && u.groups().isEmpty() && !u.isTransport() && u.inList())
			del = true;
		else if(g->groupType() == ContactViewItem::gUser) {
			const QStringList &groups = u.groups();
			if(!groups.isEmpty()) {
				bool found = false;
				for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git) {
					if(g->groupName() == *git) {
						found = true;
						break;
					}
				}
				if(!found)
					del = true;
			}
		}

		if(del)
			removeContactItem(e, i);
		else
			++it;
	}
}

void ContactProfile::clearContactItems(Entry *e)
{
	QPtrListIterator<ContactViewItem> it(e->cvi);
	for(ContactViewItem *i; (i = it.current());)
		removeContactItem(e, i);
}

void ContactProfile::addAllNeededContactItems()
{
	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it)
		addNeededContactItems(e);
}

void ContactProfile::removeAllUnneededContactItems()
{
	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it)
		removeUnneededContactItems(e);
}

void ContactProfile::removeEntry(const Jid &j)
{
	Entry *e = findEntry(j);
	if(e)
		removeEntry(e);
}

void ContactProfile::removeEntry(Entry *e)
{
	e->alerting = false;
	clearContactItems(e);
	d->roster.remove(e);
}

void ContactProfile::setAlert(const Jid &j, const Icon *anim)
{
	if(d->su.jid().compare(j)) {
		if(d->self)
			d->self->setAlert(anim);
	}
	else {
		Entry *e = findEntry(j);
		if(!e)
			return;

		if(!d->cv->isShowAgents() && e->u.isTransport() && e->u.inList())
			d->cv->setShowAgents(true);

		e->alerting = true;
		e->anim = *anim;
		addNeededContactItems(e);
		QPtrListIterator<ContactViewItem> it(e->cvi);
		for(ContactViewItem *i; (i = it.current()); ++it)
			i->setAlert(anim);

		if(option.scrollTo)
			ensureVisible(e);
	}
}

void ContactProfile::clearAlert(const Jid &j)
{
	if(d->su.jid().compare(j)) {
		if(d->self)
			d->self->clearAlert();
	}
	else {
		Entry *e = findEntry(j);
		if(!e)
			return;

		e->alerting = false;
		QPtrListIterator<ContactViewItem> it(e->cvi);
		for(ContactViewItem *i; (i = it.current()); ++it)
			i->clearAlert();
		if(e->u.inList())
			removeUnneededContactItems(e);
	}
}

void ContactProfile::clear()
{
	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current());)
		removeEntry(e);
}

ContactProfile::Entry *ContactProfile::findEntry(const Jid &jid) const
{
	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it) {
		if(e->u.jid().compare(jid))
			return e;
	}
	return 0;
}

ContactProfile::Entry *ContactProfile::findEntry(ContactViewItem *i) const
{
	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it) {
		QPtrListIterator<ContactViewItem> ci(e->cvi);
		for(ContactViewItem *cvi; (cvi = ci.current()); ++ci) {
			if(cvi == i)
				return e;
		}
	}
	return 0;
}

// return a list of contacts from a CVI group
JidList ContactProfile::contactListFromCVGroup(ContactViewItem *group) const
{
	JidList list;

	for(ContactViewItem *item = (ContactViewItem *)group->firstChild(); item ; item = (ContactViewItem *)item->nextSibling()) {
		if(item->type() != ContactViewItem::Contact)
			continue;

		list.append(item->u()->jid());
	}

	return list;
}

// return the number of contacts from a CVI group
int ContactProfile::contactSizeFromCVGroup(ContactViewItem *group) const
{
	int total = 0;

	for(ContactViewItem *item = (ContactViewItem *)group->firstChild(); item ; item = (ContactViewItem *)item->nextSibling()) {
		if(item->type() != ContactViewItem::Contact)
			continue;

		++total;
	}

	return total;
}

// return the number of contacts from a CVI group
int ContactProfile::contactsOnlineFromCVGroup(ContactViewItem *group) const
{
	int total = 0;

	for(ContactViewItem *item = (ContactViewItem *)group->firstChild(); item ; item = (ContactViewItem *)item->nextSibling()) {
		if(item->type() == ContactViewItem::Contact && item->u()->isAvailable())
			++total;
	}

	return total;
}

// return a list of contacts associated with "groupName"
JidList ContactProfile::contactListFromGroup(const QString &groupName) const
{
	JidList list;

	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it) {
		const UserListItem &u = e->u;
		if(u.isTransport())
			continue;
		const QStringList &g = u.groups();
		if(g.isEmpty()) {
			if(groupName.isEmpty())
				list.append(u.jid());
		}
		else {
			for(QStringList::ConstIterator git = g.begin(); git != g.end(); ++git) {
				if(*git == groupName) {
					list.append(u.jid());
					break;
				}
			}
		}
	}

	return list;
}

// return the number of contacts associated with "groupName"
int ContactProfile::contactSizeFromGroup(const QString &groupName) const
{
	int total = 0;

	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it) {
		const UserListItem &u = e->u;
		if(u.isTransport())
			continue;
		const QStringList &g = u.groups();
		if(g.isEmpty()) {
			if(groupName.isEmpty())
				++total;
		}
		else {
			for(QStringList::ConstIterator git = g.begin(); git != g.end(); ++git) {
				if(*git == groupName) {
					++total;
					break;
				}
			}
		}
	}

	return total;
}

void ContactProfile::updateGroupInfo(ContactViewItem *group)
{
	int type = group->groupType();
	if(type == ContactViewItem::gGeneral || type == ContactViewItem::gAgents || type == ContactViewItem::gPrivate || type == ContactViewItem::gUser) {
		int online = contactsOnlineFromCVGroup(group);
		int total;
		if(type == ContactViewItem::gGeneral || type == ContactViewItem::gUser) {
			QString gname;
			if(type == ContactViewItem::gUser)
				gname = group->groupName();
			else
				gname = "";
			total = contactSizeFromGroup(gname);
		}
		else {
			total = group->childCount();
		}
		if (option.showGroupCounts)
			group->setGroupInfo(QString("(%1/%2)").arg(online).arg(total));
	}
	else if (option.showGroupCounts) {
		int inGroup = contactSizeFromCVGroup(group);
		group->setGroupInfo(QString("(%1)").arg(inGroup));
	}
}

QStringList ContactProfile::groupList() const
{
	QStringList groupList;

	QPtrListIterator<Entry> it(d->roster);
	for(Entry *e; (e = it.current()); ++it) {
		const QStringList &groups = e->u.groups();
		for(QStringList::ConstIterator git = groups.begin(); git != groups.end(); ++git) {
			if(qstringlistmatch(groupList, *git) == -1)
				groupList.append(*git);
		}
	}

	groupList.sort();
	return groupList;
}

void ContactProfile::animateNick(const Jid &j)
{
	if(d->su.jid().compare(j)) {
		if(d->self)
			d->self->setAnimateNick();
	}

	Entry *e = findEntry(j);
	if(!e)
		return;
	QPtrListIterator<ContactViewItem> it(e->cvi);
	for(ContactViewItem *i; (i = it.current()); ++it)
		i->setAnimateNick();
}

void ContactProfile::deferredUpdateGroups()
{
	d->t->start(250, true);
}

void ContactProfile::updateGroups()
{
	int totalOnline = 0;
	{
		QPtrListIterator<Entry> it(d->roster);
		for(Entry *e; (e = it.current()); ++it) {
			if(e->u.isAvailable())
				++totalOnline;
		}
		if(d->cvi && option.showGroupCounts)
			d->cvi->setGroupInfo(QString("(%1/%2)").arg(totalOnline).arg(d->roster.count()));
	}

	{
		QPtrListIterator<ContactViewItem> it(d->groups);
		for(ContactViewItem *g; (g = it.current()); ++it)
			updateGroupInfo(g);
	}
}

void ContactProfile::ensureVisible(const Jid &j)
{
	Entry *e = findEntry(j);
	if(!e)
		return;
	ensureVisible(e);
}

void ContactProfile::ensureVisible(Entry *e)
{
	if(!e->alerting) {
		if(!d->cv->isShowAgents() && e->u.isTransport())
			d->cv->setShowAgents(true);
		if(!d->cv->isShowOffline() && !e->u.isAvailable())
			d->cv->setShowOffline(true);
		if(!d->cv->isShowAway() && e->u.isAway())
			d->cv->setShowAway(true);
		if(!d->cv->isShowHidden() && e->u.isHidden())
			d->cv->setShowHidden(true);
	}

	ContactViewItem *i = e->cvi.first();
	if(!i)
		return;
	d->cv->ensureItemVisible(i);
}

void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos)
{
	bool online = d->pa->loggedIn();

	if(i->type() == ContactViewItem::Profile) {
		QPopupMenu pm;

		QPopupMenu *am = new QPopupMenu(&pm);
		am->insertItem(IconsetFactory::iconPixmap("psi/disco"), tr("Online Users"), 5);
		am->insertItem(IconsetFactory::iconPixmap("psi/sendMessage"), tr("Send server message"), 1);
		am->insertSeparator();
		am->insertItem(/*IconsetFactory::iconPixmap("psi/edit"),*/ tr("Set MOTD"), 2);
		am->insertItem(/*IconsetFactory::iconPixmap("psi/edit/clear"),*/ tr("Update MOTD"), 3);
		am->insertItem(IconsetFactory::iconPixmap("psi/remove"), tr("Delete MOTD"), 4);

		const int status_start = 16;
		QPopupMenu *sm = new QPopupMenu(&pm);
		sm->insertItem(is->status(STATUS_ONLINE),	status2txt(STATUS_ONLINE),	STATUS_ONLINE		+ status_start);
		sm->insertItem(is->status(STATUS_CHAT),		status2txt(STATUS_CHAT),	STATUS_CHAT		+ status_start);
		sm->insertSeparator();
		sm->insertItem(is->status(STATUS_AWAY),		status2txt(STATUS_AWAY),	STATUS_AWAY		+ status_start);
		sm->insertItem(is->status(STATUS_XA),		status2txt(STATUS_XA),		STATUS_XA		+ status_start);
		sm->insertItem(is->status(STATUS_DND),		status2txt(STATUS_DND),		STATUS_DND		+ status_start);
		sm->insertSeparator();
		sm->insertItem(is->status(STATUS_INVISIBLE),	status2txt(STATUS_INVISIBLE),	STATUS_INVISIBLE	+ status_start);
		sm->insertSeparator();
		sm->insertItem(is->status(STATUS_OFFLINE),	status2txt(STATUS_OFFLINE),	STATUS_OFFLINE		+ status_start);
		pm.insertItem(tr("&Status"), sm);

		pm.insertSeparator();
		pm.insertItem(IconsetFactory::iconPixmap("psi/addContact"), tr("&Add a contact"), 7);
		pm.insertItem(IconsetFactory::iconPixmap("psi/disco"), tr("Service &Discovery"), 9);
		pm.insertItem(IconsetFactory::iconPixmap("psi/sendMessage"), tr("New &blank message"), 6);
		pm.insertSeparator();
		pm.insertItem(IconsetFactory::iconPixmap("psi/xml"), tr("&XML Console"), 10);
		pm.insertSeparator();
		pm.insertItem(IconsetFactory::iconPixmap("psi/account"), tr("&Modify Account..."), 0);
		pm.insertSeparator();
		pm.insertItem(tr("&Admin"), am);

		int x = pm.exec(pos);

		if(x == -1)
			return;

		if(x == 0)
			d->pa->modify();
		else if(x == 1) {
			Jid j = d->pa->jid().host() + '/' + "announce/online";
			actionSendMessage(j);
		}
		else if(x == 2) {
			Jid j = d->pa->jid().host() + '/' + "announce/motd";
			actionSendMessage(j);
		}
		else if(x == 3) {
			Jid j = d->pa->jid().host() + '/' + "announce/motd/update";
			actionSendMessage(j);
		}
		else if(x == 4) {
			Jid j = d->pa->jid().host() + '/' + "announce/motd/delete";
			Message m;
			m.setTo(j);
			d->pa->dj_sendMessage(m, false);
		}
		else if(x == 5) {
			// FIXME: will it still work on XMPP servers?
			Jid j = d->pa->jid().host() + '/' + "admin";
			actionDisco(j, "");
		}
		else if(x == 6) {
			actionSendMessage("");
		}
		else if(x == 7) {
			d->pa->openAddUserDlg();
		}
		else if(x == 9) {
			Jid j = d->pa->jid().host();
			actionDisco(j, "");
		}
		else if(x == 10) {
			d->pa->showXmlConsole();
		}
		else if(x >= status_start) {
			int status = x - status_start;
			d->pa->changeStatus(status);
		}
	}
	else if(i->type() == ContactViewItem::Group) {
		QString gname = i->groupName();
		QPopupMenu pm;

		pm.insertItem(IconsetFactory::iconPixmap("psi/sendMessage"), tr("Send message to group"), 0);
		if(!option.lockdown.roster) {
			// disable if it's not a user group
			if(!online || i->groupType() != ContactViewItem::gUser || gname == ContactView::tr("Hidden")) {
				d->cv->qa_ren->setEnabled(false);
				pm.setItemEnabled(2, false);
				pm.setItemEnabled(3, false);
			}
			else
				d->cv->qa_ren->setEnabled(true);

			d->cv->qa_ren->addTo(&pm);
			pm.insertSeparator();
			pm.insertItem(IconsetFactory::iconPixmap("psi/remove"), tr("Remove group"), 2);
			pm.insertItem(IconsetFactory::iconPixmap("psi/remove"), tr("Remove group and contacts"), 3);
		}

		if(i->groupType() == ContactViewItem::gAgents) {
			pm.insertSeparator();
			pm.insertItem(tr("Hide"), 4);
		}

		int x = pm.exec(pos);

		// restore actions
		if(option.lockdown.roster)
			d->cv->qa_ren->setEnabled(false);
		else
			d->cv->qa_ren->setEnabled(true);

		if(x == -1)
			return;

		if(x == 0) {
			JidList list = contactListFromCVGroup(i);

			// send multi
			actionSendMessage(list);
		}
		else if(x == 2 && online) {
			int n = QMessageBox::information(d->cv, tr("Remove Group"),tr(
			"This will cause all contacts in this group to be disassociated with it.\n"
			"\n"
			"Proceed?"), tr("&Yes"), tr("&No"));

			if(n == 0) {
				JidList list = contactListFromGroup(i->groupName());
				for(QValueList<Jid>::Iterator it = list.begin(); it != list.end(); ++it)
					actionGroupRemove(*it, gname);
			}
		}
		else if(x == 3 && online) {
			int n = QMessageBox::information(d->cv, tr("Remove Group and Contacts"),tr(
			"WARNING!  This will remove all contacts associated with this group!\n"
			"\n"
			"Proceed?"), tr("&Yes"), tr("&No"));

			if(n == 0) {
				JidList list = contactListFromGroup(i->groupName());
				for(QValueList<Jid>::Iterator it = list.begin(); it != list.end(); ++it) {
					removeEntry(*it);
					actionRemove(*it);
				}
			}
		}
		else if(x == 4) {
			if(i->groupType() == ContactViewItem::gAgents)
				d->cv->setShowAgents(false);
		}
	}
	else if(i->type() == ContactViewItem::Contact) {
		bool self = false;
		UserListItem *u;
		Entry *e = 0;
		if(i == d->self) {
			self = true;
			u = &d->su;
		}
		else {
			e = findEntry(i);
			if(!e)
				return;
			u = &e->u;
		}

		QStringList gl = groupList();
		qstringlistisort(gl); // caseless sort

		bool inList = e ? e->u.inList() : false;
		bool isPrivate = e ? e->u.isPrivate(): false;
		bool isAgent = e ? e->u.isTransport() : false;
		bool avail = e ? e->u.isAvailable() : false;
		QString groupNameCache = ((ContactViewItem *)static_cast<QListViewItem *>(i)->parent())->groupName();

		QPopupMenu pm;

		if(!self && !inList && !isPrivate && !option.lockdown.roster) {
			pm.insertItem(IconsetFactory::iconPixmap("psi/addContact"), tr("Add/Authorize to contact list"), 10);
			if(!online)
				pm.setItemEnabled(10, false);
			pm.insertSeparator();
		}

		if ( (self  && i->isAlerting()) ||
		     (!self && e->alerting) ) {
			d->cv->qa_recv->addTo(&pm);
			pm.insertSeparator();
		}

		d->cv->qa_send->addTo(&pm);

		//pm.insertItem(QIconSet(is->url), tr("Send &URL"), 2);

		const UserResourceList &rl = u->userResourceList();

		int base_sendto = 32;
		int at_sendto = 0;
		QPopupMenu *s2m = new QPopupMenu(&pm);
		QPopupMenu *c2m = new QPopupMenu(&pm);

		/*if ( rl.isEmpty() ) {
			d->cv->qa_send->addTo(&pm);
			d->cv->qa_chat->addTo(&pm);
		}
		else {
			d->cv->qa_send->addTo(s2m);
			s2m->insertSeparator();

			d->cv->qa_chat->addTo(c2m);
			c2m->insertSeparator();

			pm.insertItem(is->send.pixmap(), tr("Send &message"), s2m, 17);
			pm.insertItem(is->chat.base().pixmap(), tr("Open &chat window"), c2m, 18);
		}*/

		//s2m->insertItem(tr("Recipient Default"), base_sendto+at_sendto++);
		//c2m->insertItem(tr("Recipient Default"), base_sendto+at_sendto++);
		if(!rl.isEmpty()) {
			//s2m->insertSeparator();
			//c2m->insertSeparator();
			for(UserResourceList::ConstIterator it = rl.begin(); it != rl.end(); ++it) {
				const UserResource &r = *it;
				QString rname;
				if(r.name().isEmpty())
					rname = tr("[blank]");
				else
					rname = r.name();
				s2m->insertItem(is->status(r.status()), rname, base_sendto+at_sendto++);
				c2m->insertItem(is->status(r.status()), rname, base_sendto+at_sendto++);
			}
		}

		if(!isPrivate)
			pm.insertItem(tr("Send message to"), s2m, 17);

		d->cv->qa_chat->setIconSet(IconsetFactory::iconPixmap("psi/start-chat"));
		d->cv->qa_chat->addTo(&pm);

		if(!isPrivate)
			pm.insertItem(tr("Open chat to"), c2m, 18);

		if(!isPrivate) {
			if(rl.isEmpty()) {
				pm.setItemEnabled(17, false);
				pm.setItemEnabled(18, false);
			}
		}

		int base_hidden = base_sendto + at_sendto;
		int at_hidden = 0;
		QStringList hc;
		if(!isPrivate) {
			hc = d->pa->hiddenChats(u->jid());
			QPopupMenu *cm = new QPopupMenu(&pm);
			for(QStringList::ConstIterator it = hc.begin(); it != hc.end(); ++it) {
				QString rname;
				if((*it).isEmpty())
					rname = tr("[blank]");
				else
					rname = *it;

				// determine status
				int status;
				const UserResourceList &rl = u->userResourceList();
				UserResourceList::ConstIterator uit = rl.find(*it);
				if(uit != rl.end() || (uit = rl.priority()) != rl.end())
					status = makeSTATUS((*uit).status());
				else
					status = STATUS_OFFLINE;
				cm->insertItem(is->status(status), rname, base_hidden+at_hidden++);
			}
			pm.insertItem(tr("Active chats"), cm, 7);
			if(hc.isEmpty())
				pm.setItemEnabled(7, false);
		}

		if(!isAgent) {
			pm.insertSeparator();
			pm.insertItem(IconsetFactory::iconPixmap("psi/upload"), tr("Send &file"), 23);
			if(!online)
				pm.setItemEnabled(23, false);
		}

		// invites
		int base_gc = base_hidden + at_hidden;
		int at_gc = 0;
		QStringList groupchats;
		if(!isPrivate && !isAgent) {
			QPopupMenu *gm = new QPopupMenu(&pm);
			groupchats = d->pa->groupchats();
			for(QStringList::ConstIterator it = groupchats.begin(); it != groupchats.end(); ++it) {
				int id = gm->insertItem(*it, base_gc+at_gc++);
				if(!online)
					gm->setItemEnabled(id, false);
			}
			pm.insertItem(IconsetFactory::iconPixmap("psi/groupChat"), tr("Invite to"), gm, 14);
			if(groupchats.isEmpty())
				pm.setItemEnabled(14, false);
		}

		// weird?
		if(inList || !isAgent)
			pm.insertSeparator();

		int base_group = base_gc + at_gc;

		if(!self) {
			if(inList) {
				if(!option.lockdown.roster) {
					d->cv->qa_ren->setEnabled(online);
					d->cv->qa_ren->addTo(&pm);
				}
			}

			if(!isAgent) {
				if(inList && !option.lockdown.roster) {
					QPopupMenu *gm = new QPopupMenu(&pm);

					gm->setCheckable(true);
					gm->insertItem(tr("&None"), 8);
					gm->insertSeparator();

					QString g;
					if(e->u.groups().isEmpty())
						gm->setItemChecked(8, true);
					else
						g = groupNameCache;

					int n = 0;
					gl.remove(ContactView::tr("Hidden"));
					for(QStringList::ConstIterator it = gl.begin(); it != gl.end(); ++it) {
						QString str;
						if(n < 9)
							str = "&";
						str += QString("%1. %2").arg(n+1).arg(*it);
						gm->insertItem(str, n+base_group);

						if(*it == g)
							gm->setItemChecked(n+base_group, true);
						++n;
					}
					if(n > 0)
						gm->insertSeparator();

					gm->insertItem(ContactView::tr("Hidden"),n+base_group);
					if(g == ContactView::tr("Hidden"))
						gm->setItemChecked(n+base_group, true);
					gm->insertSeparator();
					gm->insertItem(/*IconsetFactory::iconPixmap("psi/edit/clear"),*/ tr("&Create new..."), 9);
					pm.insertItem(tr("&Group"), gm, 5);

					if(!online)
						pm.setItemEnabled(5, false);
				}
			}
			else {
				pm.insertSeparator();

				d->cv->qa_logon->setEnabled( !avail && online );

				d->cv->qa_logon->setIconSet(is->status(e->u.jid(), STATUS_ONLINE));
				d->cv->qa_logon->addTo(&pm);

				pm.insertItem(is->status(e->u.jid(), STATUS_OFFLINE), tr("Log off"), 16);
				if(!avail || !online)
					pm.setItemEnabled(16, false);
				pm.insertSeparator();
			}
		}

		if(inList && !option.lockdown.roster) {
			QPopupMenu *authm = new QPopupMenu (&pm);

			authm->insertItem(tr("Resend authorization to"), 6);
			authm->insertItem(tr("Rerequest authorization from"), 11);
			authm->insertItem(/*IconsetFactory::iconPixmap("psi/edit/delete"),*/ tr("Remove authorization from"), 15);

			pm.insertItem (IconsetFactory::iconPixmap("psi/register"), tr("Authorization"), authm, 20);
			if(!online)
				pm.setItemEnabled(20, false);
		}

		if(!self) {
			if(!option.lockdown.roster) {
				if(online || !inList)
					d->cv->qa_rem->setEnabled(true);
				else
					d->cv->qa_rem->setEnabled(false);

				d->cv->qa_rem->addTo(&pm);
			}
			pm.insertSeparator();
		}

		// Avatars
#ifdef AVATARS
		QPopupMenu *avpm = new QPopupMenu(&pm);
		d->cv->qa_assignAvatar->addTo(avpm);
		d->cv->qa_clearAvatar->setEnabled(d->pa->avatarFactory()->hasManualAvatar(u->jid()));
		d->cv->qa_clearAvatar->addTo(avpm);
		pm.insertItem(tr("&Avatar"), avpm);
		avpm->setEnabled(option.avatarsEnabled);
#endif

		if(d->pa->psi()->pgp()) {
			if(u->publicKeyID().isEmpty())
				pm.insertItem(IconsetFactory::iconPixmap("psi/gpg-yes"), tr("Assign Open&PGP key"), 21);
			else
				pm.insertItem(IconsetFactory::iconPixmap("psi/gpg-no"), tr("Unassign Open&PGP key"), 22);
		}

		d->cv->qa_vcard->addTo( &pm );

		if(!isPrivate) {
			d->cv->qa_hist->addTo(&pm);
		}

		//if(link_test) {
		//	pm.insertSeparator();
		//	pm.insertItem(tr("Link Test"), 13);
		//}

		QString name = u->jid().full();
		QString show = jidnick(u->jid().full(), u->name());
		if(name != show)
			name += QString(" (%1)").arg(u->name());

		int x = pm.exec(pos);

		// restore actions
		if(option.lockdown.roster) {
			d->cv->qa_ren->setEnabled(false);
			d->cv->qa_rem->setEnabled(false);
		}
		else {
			d->cv->qa_ren->setEnabled(true);
			d->cv->qa_rem->setEnabled(true);
		}

		if(x == -1)
			return;

		if(x == 0) {
			actionRecvEvent(u->jid());
		}
		else if(x == 1) {
			actionSendMessage(u->jid());
		}
		else if(x == 2) {
			actionSendUrl(u->jid());
		}
		else if(x == 6) {
			if(online) {
				actionAuth(u->jid());
				QMessageBox::information(d->cv, tr("Authorize"),
				tr("Sent authorization to <b>%1</b>.").arg(name));
			}
		}
		else if(x == 8) {
			if(online) {
				// if we have groups, but we are setting to 'none', then remove that particular group
				if(!u->groups().isEmpty()) {
					QString gname = groupNameCache;
					actionGroupRemove(u->jid(), gname);
				}
			}
		}
		else if(x == 9) {
			if(online) {
				while(1) {
					bool ok = false;
					QString newgroup = QInputDialog::getText(tr("Create New Group"), tr("Enter the new Group name:"), QLineEdit::Normal, QString::null, &ok, d->cv);
					if(!ok)
						break;
					if(newgroup.isEmpty())
						continue;

					// make sure we don't have it already
					bool found = false;
					const QStringList &groups = u->groups();
					for(QStringList::ConstIterator it = groups.begin(); it != groups.end(); ++it) {
						if(*it == newgroup) {
							found = true;
							break;
						}
					}
					if(!found) {
						QString gname = groupNameCache;
						actionGroupRemove(u->jid(), gname);
						actionGroupAdd(u->jid(), newgroup);
						break;
					}
				}
			}
		}
		else if(x == 10) {
			if(online) {
				actionAdd(u->jid());
				actionAuth(u->jid());

				QMessageBox::information(d->cv, tr("Add"),
				tr("Added/Authorized <b>%1</b> to the contact list.").arg(name));
			}
		}
		else if(x == 11) {
			if(online) {
				actionAuthRequest(u->jid());
				QMessageBox::information(d->cv, tr("Authorize"),
				tr("Rerequested authorization from <b>%1</b>.").arg(name));
			}
		}
		else if(x == 13) {
			actionTest(u->jid());
		}
		else if(x == 15) {
			if(online) {
				int n = QMessageBox::information(d->cv, tr("Remove"),
				tr("Are you sure you want to remove authorization from <b>%1</b>?").arg(name),
				tr("&Yes"), tr("&No"));

				if(n == 0)
					actionAuthRemove(u->jid());
			}
		}
		else if(x == 16) {
			if(online) {
				Status s=makeStatus(STATUS_OFFLINE,"");
				actionAgentSetStatus(u->jid(), s);
			}
		}
		else if(x == 21) {
			actionAssignKey(u->jid());
		}
		else if(x == 22) {
			actionUnassignKey(u->jid());
		}
		else if(x == 23) {
			if(online)
				actionSendFile(u->jid());
		}
		else if(x >= base_sendto && x < base_hidden) {
			int n = x - base_sendto;
			int res = n / 2;
			int type = n % 2;
			QString rname = "";
			//if(res > 0) {
				const UserResource &r = rl[res];
				rname = r.name();
			//}
			QString s = u->jid().userHost();
			if(!rname.isEmpty()) {
				s += '/';
				s += rname;
			}
			Jid j(s);

			if(type == 0)
				actionSendMessage(j);
			else if(type == 1)
				actionOpenChatSpecific(j);
		}
		else if(x >= base_hidden && x < base_gc) {
			int n = 0;
			int n2 = x - base_hidden;

			QString rname;
			for(QStringList::ConstIterator it = hc.begin(); it != hc.end(); ++it) {
				if(n == n2) {
					rname = *it;
					break;
				}
				++n;
			}

			QString s = u->jid().userHost();
			if(!rname.isEmpty()) {
				s += '/';
				s += rname;
			}
			Jid j(s);

			actionOpenChatSpecific(j);
		}
		else if(x >= base_gc && x < base_group) {
			if(online) {
				QString gc = groupchats[x - base_gc];
				actionInvite(u->jid(), gc);

				QMessageBox::information(d->cv, tr("Invitation"),
				tr("Sent groupchat invitation to <b>%1</b>.").arg(name));
			}
		}
		else if(x >= base_group) {
			if(online) {
				int n = 0;
				int n2 = x - base_group;

				QString newgroup;
				for(QStringList::Iterator it = gl.begin(); it != gl.end(); ++it) {
					if(n == n2) {
						newgroup = *it;
						break;
					}
					++n;
				}

				if(newgroup.isEmpty())
					newgroup = ContactView::tr("Hidden");

				if(n == n2) {
					// remove the group of this cvi if there is one
					if(!u->groups().isEmpty()) {
						//QString gname = ((ContactViewItem *)static_cast<QListViewItem *>(i)->parent())->groupName();
						QString gname = groupNameCache;
						actionGroupRemove(u->jid(), gname);
					}
					// add the group
					actionGroupAdd(u->jid(), newgroup);
				}
			}
		}
	}
}

void ContactProfile::scActionDefault(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionDefault(i->u()->jid());
}

void ContactProfile::scRecvEvent(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionRecvEvent(i->u()->jid());
}

void ContactProfile::scSendMessage(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionSendMessage(i->u()->jid());
}

void ContactProfile::scRename(ContactViewItem *i)
{
	if(!d->pa->loggedIn())
		return;

	if((i->type() == ContactViewItem::Contact && i->u()->inList()) ||
		(i->type() == ContactViewItem::Group && i->groupType() == ContactViewItem::gUser && i->groupName() != ContactView::tr("Hidden"))) {
		i->setRenameEnabled(0, true);
		i->startRename(0);
		i->setRenameEnabled(0, false);
	}
}

void ContactProfile::scVCard(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionInfo(i->u()->jid());
}

void ContactProfile::scHistory(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionHistory(i->u()->jid());
}

void ContactProfile::scOpenChat(ContactViewItem *i)
{
	if(i->type() == ContactViewItem::Contact)
		actionOpenChat(i->u()->jid());
}

void ContactProfile::scAgentSetStatus(ContactViewItem *i, Status &s)
{
	if(i->type() != ContactViewItem::Contact)
		return;
	if(!i->isAgent())
		return;

	if(i->u()->isAvailable() || !d->pa->loggedIn())
		return;

	actionAgentSetStatus(i->u()->jid(), s);
}

void ContactProfile::scRemove(ContactViewItem *i)
{
	if(i->type() != ContactViewItem::Contact)
		return;

	Entry *e = findEntry(i);
	if(!e)
		return;

	bool ok = true;
	if(!d->pa->loggedIn())
		ok = false;
	if(!i->u()->inList())
		ok = true;

	if(ok) {
		QString name = e->u.jid().full();
		QString show = jidnick(e->u.jid().full(), e->u.name());
		if(name != show)
			name += QString(" (%1)").arg(e->u.name());

		int n = 0;
		int gt = i->parentGroupType();
		if(gt != ContactViewItem::gNotInList && gt != ContactViewItem::gPrivate) {
			n = QMessageBox::information(d->cv, tr("Remove"),
			tr("Are you sure you want to remove <b>%1</b> from your contact list?").arg(name),
			tr("&Yes"), tr("&No"));
		}
		else
			n = 0;

		if(n == 0) {
			Jid j = e->u.jid();
			removeEntry(e);
			actionRemove(j);
		}
	}
}

void ContactProfile::doItemRenamed(ContactViewItem *i, const QString &text)
{
	if(i->type() == ContactViewItem::Contact) {
		Entry *e = findEntry(i);
		if(!e)
			return;

		// no change?
		//if(text == i->text(0))
		//	return;
		if(text.isEmpty()) {
			i->resetName();
			QMessageBox::information(d->cv, tr("Error"), tr("You can't set a blank name."));
			return;
		}

		//e->u.setName(text);
		//i->setContact(&e->u);
		actionRename(e->u.jid(), text);
	}
	else {
		// no change?
		if(text == i->groupName()) {
			i->resetGroupName();
			return;
		}
		if(text.isEmpty()) {
			i->resetGroupName();
			QMessageBox::information(d->cv, tr("Error"), tr("You can't set a blank group name."));
			return;
		}

		// make sure we don't have it already
		QStringList g = groupList();
		bool found = false;
		for(QStringList::ConstIterator it = g.begin(); it != g.end(); ++it) {
			if(*it == text) {
				found = true;
				break;
			}
		}
		if(found) {
			i->resetGroupName();
			QMessageBox::information(d->cv, tr("Error"), tr("You already have a group with that name."));
			return;
		}

		QString oldName = i->groupName();

		// set group name
		i->setGroupName(text);

		// send signal
		actionGroupRename(oldName, text);
	}
}

void ContactProfile::dragDrop(const QString &text, ContactViewItem *i)
{
	if(!d->pa->loggedIn())
		return;

	// get group
	ContactViewItem *gr;
	if(i->type() == ContactViewItem::Group)
		gr = i;
	else
		gr = (ContactViewItem *)static_cast<QListViewItem *>(i)->parent();

	Jid j(text);
	if(!j.isValid())
		return;
	Entry *e = findEntry(j);
	if(!e)
		return;
	const UserListItem &u = e->u;
	QStringList gl = u.groups();

	// already in the general group
	if(gr->groupType() == ContactViewItem::gGeneral && gl.isEmpty())
		return;
	// already in this user group
	if(gr->groupType() == ContactViewItem::gUser && u.inGroup(gr->groupName()))
		return;

	//printf("putting [%s] into group [%s]\n", u.jid().full().latin1(), gr->groupName().latin1());

	// remove all other groups from this contact
	for(QStringList::ConstIterator it = gl.begin(); it != gl.end(); ++it) {
		actionGroupRemove(u.jid(), *it);
	}
	if(gr->groupType() == ContactViewItem::gUser) {
		// add the new group
		actionGroupAdd(u.jid(), gr->groupName());
	}
}

void ContactProfile::dragDropFiles(const QStringList &files, ContactViewItem *i)
{
	if(files.isEmpty() || !d->pa->loggedIn() || i->type() != ContactViewItem::Contact)
		return;

	Entry *e = findEntry(i);
	if(!e)
		return;

	actionSendFiles(e->u.jid(),files);
}

//----------------------------------------------------------------------------
// ContactView
//----------------------------------------------------------------------------
class ContactView::Private : public QObject
{
	Q_OBJECT
public:
	Private(ContactView *_cv)
		: QObject(_cv)
	{
		cv = _cv;
		autoRosterResizeInProgress = false;

		// type ahead
		typeAheadTimer = new QTimer(cv);
		connect(typeAheadTimer, SIGNAL(timeout()), SLOT(resetTypeAhead()));
	}

	ContactView *cv;
	QTimer *animTimer, *recalculateSizeTimer;
	QPtrList<ContactProfile> profiles;
	QString typeAhead;
	QTimer *typeAheadTimer;
	QSize lastSize;
	bool autoRosterResizeInProgress;

	bool doTypeAhead(QKeyEvent *e)
	{
		typeAheadTimer->start(3000, TRUE); // reset type ahead in 3 seconds of inactivity

		QString text = e->text().lower();
		if ( text.isEmpty() ) {
			// Shift key could be used for entering '@', '#' and other extended chars
			// and other modifier keys could be used for layout changing

			//if ( e->key() != Qt::Key_Shift )
			//	typeAhead = QString::null;
			return false;
		}

		bool searchFromStart = true;
		if ( typeAhead.isEmpty() )
			searchFromStart = false;

		bool found = false;
		typeAhead += text;

		QListViewItemIterator it(cv);
		ContactViewItem *item;

		if ( !searchFromStart && cv->currentItem() ) {
			for ( ; (item = (ContactViewItem *)it.current()); ++it) {
				if ( item == cv->currentItem() ) {
					++it; // we want the search to start from next contact
					break;
				}
			}
		}

		for ( ; (item = (ContactViewItem *)it.current()); ++it) {
			//qWarning("->%s | %s | %s", text.latin1(), item->text(0).lower().latin1(), typeAhead.latin1());
			if ( item->text(0).lower().startsWith(typeAhead) ) {
				found = true;
				cv->setSelected(item, true);
				cv->ensureItemVisible(item);
				item->optionsUpdate();
				item->repaint();
				break;
			}
		}

		//qWarning("---------------\n");
		if ( !found ) {
			typeAhead = text;
			return false;
		}

		return true;
	}

public slots:
	/*
	 * \brief Recalculates the size of ContactView and resizes it accordingly
	 */
	void recalculateSize()
	{
		// save some CPU
		if ( !cv->allowResize() )
			return;

		if ( !cv->isUpdatesEnabled() )
			return;

		QSize oldSize = cv->size();
		QSize newSize = cv->sizeHint();

		if ( newSize.height() != oldSize.height() ) {
			lastSize = newSize;
			QWidget *topParent = cv->topLevelWidget();

			if ( cv->allowResize() ) {
				topParent->layout()->setEnabled( false ); // try to reduce some flicker

				int dh = newSize.height() - oldSize.height();
				topParent->resize( topParent->width(),
						   topParent->height() + dh );

				autoRosterResizeInProgress = true;

				QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );
				if ( option.autoRosterSizeGrowTop ) {
					int newy = topParent->y() - dh;

					// check, if we need to move roster lower
					if ( dh > 0 && ( topParent->frameGeometry().top() <= desktop.top() ) ) {
						newy = desktop.top();
					}

					topParent->move( topParent->x(), newy );
				}

				// check, if we need to move roster upper
				if ( dh > 0 && ( topParent->frameGeometry().bottom() >= desktop.bottom() ) ) {
					int newy = desktop.bottom() - topParent->frameGeometry().height();
					topParent->move( topParent->x(), newy );
				}

				QTimer::singleShot( 0, this, SLOT( resetAutoRosterResize() ) );

				topParent->layout()->setEnabled( true );
			}

			// issue a layout update
			topParent->layout()->activate();
		}
	}

	/*
	 * \brief Determine in which direction to grow Psi roster window when autoRosterSize is enabled
	 */
	void determineAutoRosterSizeGrowSide()
	{
		if ( autoRosterResizeInProgress )
			return;

		QWidget *topParent = cv->topLevelWidget();
		QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );

		int top_offs    = abs( desktop.top()    - topParent->frameGeometry().top() );
		int bottom_offs = abs( desktop.bottom() - topParent->frameGeometry().bottom() );

		option.autoRosterSizeGrowTop = bottom_offs < top_offs;
		//qWarning("growTop = %d", option.autoRosterSizeGrowTop);
	}

private slots:
	void resetAutoRosterResize()
	{
		//qWarning("resetAutoRosterResize");
		autoRosterResizeInProgress = false;
	}

	void resetTypeAhead()
	{
		typeAhead = QString::null;
	}
};

ContactView::ContactView(QWidget *parent, const char *name)
:QListView(parent, name, QListView::WRepaintNoErase | QListView::WResizeNoErase), QToolTip(viewport())
{
	d = new Private(this);

	// setup the QListView
	setAllColumnsShowFocus(true);
	setShowToolTips(false);
	setHScrollBarMode(AlwaysOff);
	setMinimumSize(96,32);
	setTreeStepSize(4);
	setSorting(0,true);

	topLevelWidget()->installEventFilter( this );

	// create the column and hide the header
	addColumn("");
	header()->hide();

	setResizeMode(QListView::LastColumn);
	setDefaultRenameAction(QListView::Accept);

	// catch signals
	lcto_active = false;
	connect(this, SIGNAL(itemRenamed(QListViewItem *, int, const QString &)), SLOT(qlv_itemRenamed(QListViewItem *, int, const QString &)));
	connect(this, SIGNAL(mouseButtonPressed(int, QListViewItem *, const QPoint &, int)),SLOT(qlv_singleclick(int, QListViewItem *, const QPoint &, int)) );
	connect(this, SIGNAL(doubleClicked(QListViewItem *)),SLOT(qlv_doubleclick(QListViewItem *)) );
	connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), SLOT(qlv_contextMenuRequested(QListViewItem *, const QPoint &, int)));

	v_showOffline = true;
	v_showAway = true;
	v_showHidden = true;
	v_showAgents = true;
	v_showSelf = true;

	d->lastSize = QSize( 0, 0 );

	// animation timer
	d->animTimer = new QTimer(this);
	d->animTimer->start(120 * 5);

	d->recalculateSizeTimer = new QTimer;
	connect(d->recalculateSizeTimer, SIGNAL(timeout()), d, SLOT(recalculateSize()));

	// actions
	qa_send = new IconAction("", "psi/sendMessage", tr("Send &message"), QListView::CTRL+QListView::Key_M, this);
	connect(qa_send, SIGNAL(activated()), SLOT(doSendMessage()));
	qa_ren = new IconAction("", /*"psi/edit/clear",*/ tr("Re&name"), QListView::Key_F2, this);
	connect(qa_ren, SIGNAL(activated()), SLOT(doRename()));
#ifdef AVATARS
	qa_assignAvatar = new IconAction("", tr("&Assign Custom Avatar"),QKeySequence(), this);
	connect(qa_assignAvatar, SIGNAL(activated()), SLOT(doAssignAvatar()));
	qa_clearAvatar = new IconAction("", tr("&Clear Custom Avatar"), QKeySequence(), this);
	connect(qa_clearAvatar, SIGNAL(activated()), SLOT(doClearAvatar()));
#endif
	qa_chat = new IconAction("", "psi/start-chat", tr("Open &chat window"), QListView::CTRL+QListView::Key_C, this);
	connect(qa_chat, SIGNAL(activated()), SLOT(doOpenChat()));
	qa_hist = new IconAction("", "psi/history", tr("&History"), QListView::CTRL+QListView::Key_H, this);
	connect(qa_hist, SIGNAL(activated()), SLOT(doHistory()));
	qa_logon = new IconAction("", tr("&Log on"), QListView::CTRL+QListView::Key_L, this);
	connect(qa_logon, SIGNAL(activated()), SLOT(doLogon()));
	qa_recv = new IconAction("", tr("&Receive incoming event"), QListView::CTRL+QListView::Key_R, this);
	connect(qa_recv, SIGNAL(activated()), SLOT(doRecvEvent()));
	qa_rem = new IconAction("", "psi/remove", tr("Rem&ove"), QListView::Key_Delete, this);
	connect(qa_rem, SIGNAL(activated()), SLOT(doRemove()));
	qa_vcard = new IconAction("", "psi/vCard", tr("User &Info"), QListView::CTRL+QListView::Key_I, this);
	connect(qa_vcard, SIGNAL(activated()), SLOT(doVCard()));

	if(option.lockdown.roster) {
		qa_ren->setEnabled(false);
		qa_rem->setEnabled(false);
	}

	optionsUpdate();
	setAcceptDrops(true);
	viewport()->setAcceptDrops(true);
}

ContactView::~ContactView()
{
	clear();
	delete d;
}

QTimer *ContactView::animTimer() const
{
	return d->animTimer;
}

void ContactView::clear()
{
	d->profiles.setAutoDelete(true);
	d->profiles.clear();
	d->profiles.setAutoDelete(false);
}

void ContactView::link(ContactProfile *cp)
{
	d->profiles.append(cp);
}

void ContactView::unlink(ContactProfile *cp)
{
	d->profiles.removeRef(cp);
}

void ContactView::maybeTip(const QPoint &pos)
{
	ContactViewItem *i = (ContactViewItem *)itemAt(pos);
	if(!i)
		return;
	QRect r(itemRect(i));
	if(i->type() == ContactViewItem::Contact)
		tip(r, i->u()->makeTip(true, false));
	else if(i->type() == ContactViewItem::Profile) {
		tip(r, i->contactProfile()->makeTip(true, false));
	}
	else
		tip(r, i->text(0) + " " + i->groupInfo());
}

void ContactView::keyPressEvent(QKeyEvent *e)
{
	int key = e->key();
	if(key == QListView::Key_Enter || key == QListView::Key_Return)
		doEnter();
	else if(key == QListView::Key_Space && d->typeAhead.isEmpty())
		doContext();
	else if (key == QListView::Key_Home   || key == QListView::Key_End      ||
		 key == QListView::Key_PageUp || key == QListView::Key_PageDown ||
		 key == QListView::Key_Up     || key == QListView::Key_Down     ||
		 key == QListView::Key_Left   || key == QListView::Key_Right) {

		d->typeAhead = QString::null;
		QListView::keyPressEvent(e);
	}
	else {
		if (!d->doTypeAhead(e))
			QListView::keyPressEvent(e);
	}
}

void ContactView::setShowOffline(bool x)
{
	bool oldstate = v_showOffline;
	v_showOffline = x;

	if(v_showOffline != oldstate) {
		showOffline(v_showOffline);

		QPtrListIterator<ContactProfile> it(d->profiles);
		for(ContactProfile *cp; (cp = it.current()); ++it) {
			if(!v_showOffline)
				cp->removeAllUnneededContactItems();
			else
				cp->addAllNeededContactItems();
		}
	}
}

void ContactView::setShowAway(bool x)
{
	bool oldstate = v_showAway;
	v_showAway = x;

	if(v_showAway != oldstate) {
		showAway(v_showAway);

		QPtrListIterator<ContactProfile> it(d->profiles);
		for(ContactProfile *cp; (cp = it.current()); ++it) {
			if(!v_showAway)
				cp->removeAllUnneededContactItems();
			else
				cp->addAllNeededContactItems();
		}
	}
}

void ContactView::setShowHidden(bool x)
{
	bool oldstate = v_showHidden;
	v_showHidden = x;

	if(v_showHidden != oldstate) {
		showHidden(v_showHidden);

		QPtrListIterator<ContactProfile> it(d->profiles);
		for(ContactProfile *cp; (cp = it.current()); ++it) {
			if(!v_showHidden)
				cp->removeAllUnneededContactItems();
			else
				cp->addAllNeededContactItems();
		}
	}
}

/*
 * \brief Shows/hides the self contact in roster
 *
 * \param x - boolean variable specifies whether to show self-contact or not
 */
void ContactView::setShowSelf(bool x)
{
	if (v_showSelf != x) {
		v_showSelf = x;
		showSelf(v_showSelf);

		QPtrListIterator<ContactProfile> it(d->profiles);
		for(ContactProfile *cp; (cp = it.current()); ++it) {
			if (v_showSelf && ! cp->self()) {
				cp->addSelf();
			}
			else if (!v_showSelf && cp->self() && cp->self()->u()->userResourceList().count() <= 1) {
				cp->removeSelf();
			}
		}

		recalculateSize();
	}
}

/*
 * \brief Event filter. Nuff said.
 */
bool ContactView::eventFilter( QObject *obj, QEvent *event )
{
	if ( event->type() == QEvent::Move )
		d->determineAutoRosterSizeGrowSide();

	return QListView::eventFilter( obj, event );
}


void ContactView::setShowAgents(bool x)
{
	bool oldstate = v_showAgents;
	v_showAgents = x;

	if(v_showAgents != oldstate) {
		showAgents(v_showAgents);

		QPtrListIterator<ContactProfile> it(d->profiles);
		for(ContactProfile *cp; (cp = it.current()); ++it) {
			if(!v_showAgents)
				cp->removeAllUnneededContactItems();
			else
				cp->addAllNeededContactItems();
		}
	}
}

// right-click context menu
void ContactView::qlv_contextMenuRequested(QListViewItem *lvi, const QPoint &pos, int c)
{
	if(option.useleft)
		return;

	qlv_contextPopup(lvi, pos, c);
}

void ContactView::qlv_contextPopup(QListViewItem *lvi, const QPoint &pos, int)
{
	ContactViewItem *i = (ContactViewItem *)lvi;
	if(!i)
		return;

	i->contactProfile()->doContextMenu(i, pos);
}

void ContactView::qlv_singleclick(int button, QListViewItem *i, const QPoint &pos, int c)
{
	lcto_active = false;
	QToolTip::hide();

	if(!i)
		return;

	ContactViewItem *item = (ContactViewItem *)i;
	setSelected(item, true);

	if(button == QListView::MidButton) {
		if(item->type() == ContactViewItem::Contact)
			item->contactProfile()->scActionDefault(item);
	}
	else {
		const QPixmap * pix = item->pixmap(0);			
		if (button == QListView::LeftButton && item->type() == ContactViewItem::Group && pix && viewport()->mapFromGlobal(pos).x() <= pix->width() + treeStepSize()) {
			setOpen(item, !item->isOpen());
		}
		else if(option.useleft) {
			if(button == QListView::LeftButton) {
				if(option.singleclick) {
					qlv_contextPopup(i, pos, c);
				}
				else {
					lcto_active = true;
					lcto_pos = pos;
					lcto_item = i;
					QTimer::singleShot(QApplication::doubleClickInterval()/2, this, SLOT(leftClickTimeOut()));
				}
			}
			else if(option.singleclick && button == QListView::RightButton) {
				if(item->type() == ContactViewItem::Contact)
					item->contactProfile()->scActionDefault(item);
			}
		}
		else {
			//if(button == QListView::RightButton) {
			//	qlv_contextPopup(i, pos, c);
			//}
			/*else*/if(button == QListView::LeftButton && option.singleclick) {
				if(item->type() == ContactViewItem::Contact)
					item->contactProfile()->scActionDefault(item);
			}
		}
	}

	d->typeAhead = "";
}

void ContactView::qlv_doubleclick(QListViewItem *i)
{
	lcto_active = false;

	if(!i)
		return;

	if(option.singleclick)
		return;

	ContactViewItem *item = (ContactViewItem *)i;
	item->contactProfile()->scActionDefault(item);

	d->typeAhead = "";
}

void ContactView::qlv_itemRenamed(QListViewItem *lvi, int, const QString &text)
{
	ContactViewItem *i = (ContactViewItem *)lvi;
	i->contactProfile()->doItemRenamed(i, text);
}

void ContactView::leftClickTimeOut()
{
	if(lcto_active) {
		QToolTip::hide();
		qlv_contextPopup(lcto_item, lcto_pos, 0);
		lcto_active = false;
	}
}

void ContactView::optionsUpdate()
{
	// set the font
	QFont f;
	f.fromString(option.font[fRoster]);
	QListView::setFont(f);

	// set the text and background colors
	QPalette mypal = QListView::palette();
	mypal.setColor(QColorGroup::Text, option.color[cOnline]);
	mypal.setColor(QColorGroup::Base, option.color[cListBack]);
	QListView::setPalette(mypal);

	// reload the icons
	QListViewItemIterator it(this);
	ContactViewItem *item;
	for(; it.current() ; ++it) {
		item = (ContactViewItem *)it.current();
		item->optionsUpdate();
	}

	// resize if necessary
	if (option.autoRosterSize)
		recalculateSize();

	update();
}

void ContactView::resetAnim()
{
	for(QListViewItemIterator it(this); it.current() ; ++it) {
		ContactViewItem *item = (ContactViewItem *)it.current();
		if(item->isAlerting())
			item->resetAnim();
	}
}

void ContactView::doRecvEvent()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scRecvEvent(i);
}

void ContactView::doRename()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scRename(i);
}

void ContactView::doAssignAvatar()
{
	// FIXME: Should check the supported filetypes dynamically
	QString file = QFileDialog::getOpenFileName("", tr("All files (*.png *.jpg *.gif)"), this, 0, tr("Choose an image"));
	if (!file.isNull()) {
		ContactViewItem *i = (ContactViewItem *)selectedItem();
		i->contactProfile()->psiAccount()->avatarFactory()->importManualAvatar(i->u()->jid(),file);
	}
}

void ContactView::doClearAvatar()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	i->contactProfile()->psiAccount()->avatarFactory()->removeManualAvatar(i->u()->jid());
}

void ContactView::doEnter()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scActionDefault(i);
}

void ContactView::doContext()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	ensureItemVisible(i);

	if(i->type() == ContactViewItem::Group)
		setOpen(i, !i->isOpen());
	else
		qlv_contextPopup(i, viewport()->mapToGlobal(QPoint(32, itemPos(i))), 0);
}

void ContactView::doSendMessage()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scSendMessage(i);
}

void ContactView::doOpenChat()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scOpenChat(i);
}

void ContactView::doHistory()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scHistory(i);
}

void ContactView::doVCard()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scVCard(i);
}

void ContactView::doLogon()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	Status s=i->contactProfile()->psiAccount()->status();
	i->contactProfile()->scAgentSetStatus(i, s);
}

void ContactView::doRemove()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return;
	i->contactProfile()->scRemove(i);
}

QDragObject *ContactView::dragObject()
{
	ContactViewItem *i = (ContactViewItem *)selectedItem();
	if(!i)
		return 0;
	if(i->type() != ContactViewItem::Contact)
		return 0;

	QDragObject *d = new QTextDrag(i->u()->jid().full(), this);
	d->setPixmap(IconsetFactory::iconPixmap("status/online"), QPoint(8,8));
	return d;
}

bool ContactView::allowResize() const
{
	if ( !option.autoRosterSize )
		return false;

	if ( topLevelWidget()->isMaximized() )
		return false;

	return true;
}

QSize ContactView::minimumSizeHint() const
{
	return QSize( minimumWidth(), minimumHeight() );
}

QSize ContactView::sizeHint() const
{
	// save some CPU
	if ( !allowResize() )
		return minimumSizeHint();

	QSize s( QListView::sizeHint().width(), 0 );
	int border = 5;
	int h = border;

	QListView *listView = (QListView *)this;
	QListViewItemIterator it( listView );
	while ( it.current() ) {
		if ( it.current()->isVisible() ) {
			// also we need to check whether the group is open or closed
			bool show = true;
			QListViewItem *item = it.current()->parent();
			while ( item ) {
				if ( !item->isOpen() ) {
					show = false;
					break;
				}
				item = item->parent();
			}

			if ( show )
				h += it.current()->height();
		}

		++it;
	}

	QWidget *topParent = topLevelWidget();
	QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );
	int dh = h - d->lastSize.height();

	// check that our dialog's height doesn't exceed the desktop's
	if ( allowResize() && dh > 0 && (topParent->frameGeometry().height() + dh) >= desktop.height() ) {
		h = desktop.height() - ( topParent->frameGeometry().height() - d->lastSize.height() );
	}

	int minH = minimumSizeHint().height();
	if ( h < minH )
		h = minH + border;
	s.setHeight( h );
	return s;
}

/*
 * \brief Adds the request to recalculate the ContactView size to the event queue
 */
void ContactView::recalculateSize()
{
	d->recalculateSizeTimer->start( 0, true );
}

//----------------------------------------------------------------------------
// ContactViewItem
//----------------------------------------------------------------------------
class ContactViewItem::Private
{
private:
	ContactViewItem *cvi;

public:
	Private(ContactViewItem *parent, ContactProfile *_cp) {
		cvi = parent;
		cp = _cp;
		u = 0;

		icon = lastIcon = 0;
	}

	~Private() {
	}

	void initGroupState() {
		UserAccount::GroupData gd = groupData();
		cvi->setOpen(gd.open);
	}

	QString getGroupName() {
		QString group;
		if ( type == Profile )
			group = "/\\/" + profileName + "\\/\\";
		else
			group = groupName;

		return group;
	}

	QMap<QString, UserAccount::GroupData> *groupState() {
		return (QMap<QString, UserAccount::GroupData> *)&cp->psiAccount()->userAccount().groupState;
	}

	UserAccount::GroupData groupData() {
		QMap<QString, UserAccount::GroupData> groupState = (QMap<QString, UserAccount::GroupData>)cp->psiAccount()->userAccount().groupState;
		QMap<QString, UserAccount::GroupData>::Iterator it = groupState.find(getGroupName());

		UserAccount::GroupData gd;
		gd.open = true;
		gd.rank = 0;

		if ( it != groupState.end() )
			gd = it.data();

		return gd;
	}

	int type;
	ContactProfile *cp;
	int status;

	// profiles
	QString profileName;
	bool ssl;

	// groups
	int groupType;
	QString groupName;
	QString groupInfo;

	// contact
	UserListItem *u;
	bool isAgent;
	bool alerting;
	bool animatingNick;

	Icon *icon, *lastIcon;
	int animateNickX, animateNickColor; // nick animation
};

ContactViewItem::ContactViewItem(const QString &profileName, ContactProfile *cp, ContactView *parent)
:QListViewItem(parent)
{
	d = new Private(this, cp);
	d->type = Profile;
	d->profileName = profileName;
	d->alerting = false;
	d->ssl = false;

	setProfileState(STATUS_OFFLINE);
	setText(0, profileName);

	d->initGroupState();
}

ContactViewItem::ContactViewItem(const QString &groupName, int groupType, ContactProfile *cp, ContactView *parent)
:QListViewItem(parent)
{
	d = new Private(this, cp);
	d->type = Group;
	d->groupName = groupName;
	d->groupType = groupType;
	d->alerting = false;

	drawGroupIcon();
	resetGroupName();
	setDropEnabled(true);

	d->initGroupState();
}

ContactViewItem::ContactViewItem(const QString &groupName, int groupType, ContactProfile *cp, ContactViewItem *parent)
:QListViewItem(parent)
{
	d = new Private(this, cp);
	d->type = Group;
	d->groupName = groupName;
	d->groupType = groupType;
	d->alerting = false;

	drawGroupIcon();
	resetGroupName();
	setDropEnabled(true);

	if(!parent->isVisible())
		setVisible(false);

	d->initGroupState();
}

ContactViewItem::ContactViewItem(UserListItem *u, ContactProfile *cp, ContactViewItem *parent)
:QListViewItem(parent)
{
	d = new Private(this, cp);
	d->cp = cp;
	d->type = Contact;
	d->u = u;
	d->alerting = false;
	d->animatingNick = false;

	cacheValues();

	resetStatus();
	resetName();

	setDragEnabled(true);
	setDropEnabled(true);

	if(!parent->isVisible())
		setVisible(false);
}

ContactViewItem::~ContactViewItem()
{
	setIcon( 0 );
	delete d;
}

void ContactViewItem::cacheValues()
{
	if ( d->u ) {
		if( !d->u->isAvailable() )
			d->status = STATUS_OFFLINE;
		else
			d->status = makeSTATUS((*d->u->priority()).status());
		d->isAgent = d->u->isTransport();
	}
}

ContactProfile *ContactViewItem::contactProfile() const
{
	return d->cp;
}

int ContactViewItem::type() const
{
	return d->type;
}

const QString & ContactViewItem::groupName() const
{
	return d->groupName;
}

const QString & ContactViewItem::groupInfo() const
{
	return d->groupInfo;
}

int ContactViewItem::groupType() const
{
	return d->groupType;
}

UserListItem *ContactViewItem::u() const
{
	return d->u;
}

int ContactViewItem::status() const
{
	return d->status;
}

bool ContactViewItem::isAgent() const
{
	return d->isAgent;
}

bool ContactViewItem::isAlerting() const
{
	return d->alerting;
}

bool ContactViewItem::isAnimatingNick() const
{
	return d->animatingNick;
}

int ContactViewItem::parentGroupType() const
{
	ContactViewItem *item = (ContactViewItem *)QListViewItem::parent();
	return item->groupType();
}

void ContactViewItem::drawGroupIcon()
{
	if ( d->type == Group ) {
		if ( childCount() == 0 )
			setIcon(IconsetFactory::iconPtr("psi/groupEmpty"));
		else if ( isOpen() )
			setIcon(IconsetFactory::iconPtr("psi/groupOpen"));
		else
			setIcon(IconsetFactory::iconPtr("psi/groupClosed"));
	}
	else if ( d->type == Profile ) {
		if ( !d->alerting )
			setProfileState(d->status);
	}
}

void ContactViewItem::paintFocus(QPainter *, const QColorGroup &, const QRect &)
{
	// re-implimented to do nothing.  selection is enough of a focus
}

void ContactViewItem::paintBranches(QPainter *p, const QColorGroup &cg, int w, int, int h)
{
	// paint a square of nothing
	p->fillRect(0, 0, w, h, cg.base());
}

void ContactViewItem::paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int alignment)
{
	if ( d->type == Contact ) {
		QColorGroup xcg = cg;

		if(d->status == STATUS_AWAY || d->status == STATUS_XA)
			xcg.setColor(QColorGroup::Text, option.color[cAway]);
		else if(d->status == STATUS_DND)
			xcg.setColor(QColorGroup::Text, option.color[cDND]);
		else if(d->status == STATUS_OFFLINE)
			xcg.setColor(QColorGroup::Text, option.color[cOffline]);

		if(d->animatingNick) {
			xcg.setColor(QColorGroup::Text, d->animateNickColor ? option.color[cAnimFront] : option.color[cAnimBack]);
			xcg.setColor(QColorGroup::HighlightedText, d->animateNickColor ? option.color[cAnimFront] : option.color[cAnimBack]);
		}

		QListViewItem::paintCell(p, xcg, column, width, alignment);

		QFontMetrics fm(p->font());
		const QPixmap *pix = pixmap(column);
		int x = fm.width(text(column)) + (pix ? pix->width() : 0) + 8;

		if ( d->u ) {
			UserResourceList::ConstIterator it = d->u->priority();
			if(it != d->u->userResourceList().end()) {
				if(d->u->isSecure((*it).name())) {
					const QPixmap &pix = IconsetFactory::iconPixmap("psi/cryptoYes");
					int y = (height() - pix.height()) / 2;
					p->drawPixmap(x, y, pix);
					x += 24;
				}
			}
		}
	}
	else if ( d->type == Group || d->type == Profile ) {
		QColorGroup xcg = cg;

		if(d->type == Profile) {
			xcg.setColor(QColorGroup::Text, option.color[cProfileFore]);
			xcg.setColor(QColorGroup::Base, option.color[cProfileBack]);
		}
		else if(d->type == Group) {
			QFont f = p->font();
			f.setPointSize(option.smallFontSize);
			p->setFont(f);
			xcg.setColor(QColorGroup::Text, option.color[cGroupFore]);
			if (!option.clNewHeadings) {
				xcg.setColor(QColorGroup::Base, option.color[cGroupBack]);
			}
		} 

		QListViewItem::paintCell(p, xcg, column, width, alignment);

		QFontMetrics fm(p->font());
		const QPixmap *pix = pixmap(column);
		int x = fm.width(text(column)) + (pix ? pix->width() : 0) + 8;

		if(d->type == Profile) {
			const QPixmap &pix = d->ssl ? IconsetFactory::iconPixmap("psi/cryptoYes") : IconsetFactory::iconPixmap("psi/cryptoNo");
			int y = (height() - pix.height()) / 2;
			p->drawPixmap(x, y, pix);
			x += 24;
		}

		if(isSelected())
			p->setPen(xcg.highlightedText());
		else
			p->setPen(xcg.text());

		QFont f_info = p->font();
		f_info.setPointSize(option.smallFontSize);
		p->setFont(f_info);
		QFontMetrics fm_info(p->font());
		//int info_x = width - fm_info.width(d->groupInfo) - 8;
		int info_x = x;
		int info_y = ((height() - fm_info.height()) / 2) + fm_info.ascent();
		p->drawText((info_x > x ? info_x : x), info_y, d->groupInfo);

		if(d->type == Group && option.clNewHeadings && !isSelected()) {
			x += fm.width(d->groupInfo) + 8;
			if(x < width - 8) {
				int h = (height() / 2) - 1;
				p->setPen(QPen(option.color[cGroupBack]));
				p->drawLine(x, h, width - 8, h);
				h++;
				p->setPen(QPen(option.color[cGroupFore]));
				/*int h = height() / 2;

				p->setPen(QPen(option.color[cGroupBack], 2));*/
				p->drawLine(x, h, width - 8, h);
			}
		} 
		else {
			if (option.outlineHeadings) {
				p->setPen(QPen(option.color[cGroupFore]));
				p->drawRect(0, 0, width, height());
			}
		}
	}
}

/*
 * \brief "Opens" or "closes the ContactViewItem
 *
 * When the item is in "open" state, all it's children items are visible.
 *
 * \param o - if true, the item will be "open"
 */
void ContactViewItem::setOpen(bool o)
{
	((ContactView *)listView())->recalculateSize();

	QListViewItem::setOpen(o);
	drawGroupIcon();

	// save state
	UserAccount::GroupData gd = d->groupData();
	gd.open = o;
	d->groupState()->insert(d->getGroupName(), gd);
}

void ContactViewItem::insertItem(QListViewItem *i)
{
	QListViewItem::insertItem(i);
	drawGroupIcon();
}

void ContactViewItem::takeItem(QListViewItem *i)
{
	QListViewItem::takeItem(i);
	drawGroupIcon();
}

int ContactViewItem::rankGroup(int groupType) const
{
	static int rankgroups[5] = {
		gGeneral,
		gUser,
		gPrivate,
		gAgents,
		gNotInList,
	};

	int n;
	for(n = 0; n < (int)sizeof(rankgroups); ++n) {
		if(rankgroups[n] == groupType)
			break;
	}
	if(n == sizeof(rankgroups))
		return sizeof(rankgroups)-1;

	return n;
}

int ContactViewItem::rankStatus(int status) const
{
	static int rankstatuses[7] = {
		STATUS_CHAT,
		STATUS_ONLINE,
		STATUS_AWAY,
		STATUS_XA,
		STATUS_DND,
		STATUS_INVISIBLE,
		STATUS_OFFLINE,
	};

	int n;
	for(n = 0; n < (int)sizeof(rankstatuses); ++n) {
		if(rankstatuses[n] == status)
			break;
	}
	if(n == sizeof(rankstatuses))
		return sizeof(rankstatuses)-1;

	return n;
}

int ContactViewItem::compare(QListViewItem *lvi, int, bool) const
{
	ContactViewItem *i = (ContactViewItem *)lvi;
	int ret = 0;

	if(d->type == Group || d->type == Profile) {
		// contacts always go before groups
		if(i->type() == Contact)
			ret = 1;
		else if(i->type() == Group) {
			if ( option.rosterGroupSortStyle == Options::GroupSortStyle_Rank ) {
				int ourRank   = d->groupData().rank;
				int theirRank = i->d->groupData().rank;

				ret = ourRank - theirRank;
			}
			else { // GroupSortStyle_Alpha
				ret = rankGroup(d->groupType) - rankGroup(i->groupType());
				if(ret == 0)
					ret = text(0).lower().localeAwareCompare(i->text(0).lower());
			}
		}
		else if(i->type() == Profile) {
			if ( option.rosterAccountSortStyle == Options::AccountSortStyle_Rank ) {
				int ourRank = d->groupData().rank;
				int theirRank = i->d->groupData().rank;

				ret = ourRank - theirRank;
			}
			else // AccountSortStyle_Alpha
				ret = text(0).lower().localeAwareCompare(i->text(0).lower());
		}
	}
	else if(d->type == Contact) {
		// contacts always go before groups
		if(i->type() == Group)
			ret = -1;
		else {
			if ( option.rosterContactSortStyle == Options::ContactSortStyle_Status ) {
				ret = rankStatus(d->status) - rankStatus(i->status());
				if(ret == 0)
					ret = text(0).lower().localeAwareCompare(i->text(0).lower());
			}
			else { // ContactSortStyle_Alpha
				ret = text(0).lower().localeAwareCompare(i->text(0).lower());
			}
		}
	}

	return ret;
}

void ContactViewItem::setProfileName(const QString &name)
{
	d->profileName = name;
	setText(0, d->profileName);
}

void ContactViewItem::setProfileState(int status)
{
	if ( status == -1 ) {
		setAlert( IconsetFactory::iconPtr("psi/connect") );
	}
	else {
		d->status = status;

		clearAlert();
		setIcon(is->statusPtr(status));
	}
}

void ContactViewItem::setProfileSSL(bool on)
{
	d->ssl = on;
	repaint();
}

void ContactViewItem::setGroupName(const QString &name)
{
	d->groupName = name;
	resetGroupName();

	updatePosition();
}

void ContactViewItem::setGroupInfo(const QString &info)
{
	d->groupInfo = info;
	repaint();
}

void ContactViewItem::resetStatus()
{
	if ( !d->alerting && d->u ) {
		setIcon(is->statusPtr(d->u));
	}
}

void ContactViewItem::resetName()
{
	resetStatus();
	if ( d->u ) {
		QString s = jidnick(d->u->jid().full(), d->u->name());
		if ( s != text(0) )
			setText(0, s);
	}
}

void ContactViewItem::resetGroupName()
{
	if ( d->groupName != text(0) )
		setText(0, d->groupName);
}

void ContactViewItem::resetAnim()
{
	if ( d->alerting ) {
		// TODO: think of how to reset animation frame
	}
}

void ContactViewItem::setAlert(const Icon *icon)
{
	bool reset = false;

	if ( !d->alerting ) {
		d->alerting = true;
		reset = true;
	}
	else {
		if ( d->lastIcon != icon )
			reset = true;
	}

	if ( reset )
		setIcon(icon, true);
}

void ContactViewItem::clearAlert()
{
	if ( d->alerting ) {
		d->alerting = false;
		//disconnect(static_cast<ContactView*>(QListViewItem::listView())->animTimer(), SIGNAL(timeout()), this, SLOT(animate()));
		resetStatus();
	}
}

void ContactViewItem::setIcon(const Icon *icon, bool alert)
{
	if ( d->lastIcon == icon ) {
		return; // cause less flicker. but still have to run calltree valgring skin on psi while online (mblsha).
	}
	else
		d->lastIcon = (Icon *)icon;

	if ( d->icon ) {
		disconnect(d->icon, 0, this, 0 );
		d->icon->stop();

		delete d->icon;
		d->icon = 0;
	}

	QPixmap pix;
	if ( icon ) {
		if ( !alert )
			d->icon = new Icon(*icon);
		else
			d->icon = new AlertIcon(icon);

		connect(d->icon, SIGNAL(pixmapChanged(const QPixmap &)), SLOT(iconUpdated(const QPixmap &)));
		d->icon->activated();

		pix = d->icon->pixmap();
	}

	setPixmap(0, pix);
}

void ContactViewItem::iconUpdated(const QPixmap &pix)
{
	setPixmap(0, pix);
}

void ContactViewItem::animateNick()
{
	d->animateNickColor = !d->animateNickColor;
	repaint();

	if(++d->animateNickX >= 16)
		stopAnimateNick();
}

void ContactViewItem::stopAnimateNick()
{
	if ( !d->animatingNick )
		return;

	disconnect(static_cast<ContactView*>(QListViewItem::listView())->animTimer(), SIGNAL(timeout()), this, SLOT(animateNick()));

	d->animatingNick = false;
	repaint();
}

void ContactViewItem::setAnimateNick()
{
	stopAnimateNick();

	connect(static_cast<ContactView*>(QListViewItem::listView())->animTimer(), SIGNAL(timeout()), SLOT(animateNick()));

	d->animatingNick = true;
	d->animateNickX = 0;
	animateNick();
}

void ContactViewItem::updatePosition()
{
	ContactViewItem *par = (ContactViewItem *)QListViewItem::parent();
	if(!par)
		return;

	ContactViewItem *after = 0;
	for(QListViewItem *i = par->firstChild(); i; i = i->nextSibling()) {
		ContactViewItem *item = (ContactViewItem *)i;
		// skip self
		if(item == this)
			continue;
		int x = compare(item, 0, true);
		if(x == 0)
			continue;
		if(x < 0)
			break;
		after = item;
	}

	if(after)
		moveItem(after);
	else {
		QListViewItem *i = par->firstChild();
		moveItem(i);
		i->moveItem(this);
	}
}

void ContactViewItem::optionsUpdate()
{
	if(d->type == Group || d->type == Profile) {
		drawGroupIcon();
	}
	else if(d->type == Contact) {
		if(!d->alerting)
			resetStatus();
		else
			resetAnim();
	}
}

void ContactViewItem::setContact(UserListItem *u)
{
	//int oldStatus = d->status;
	QString oldName = text(0);
	//bool wasAgent = d->isAgent;

	QString newName = jidnick(u->jid().full(), u->name());

	d->u = u;
	cacheValues();

	bool needUpdate = false;
	//if(d->status != oldStatus || d->isAgent != wasAgent || !u->presenceError().isEmpty()) {
		resetStatus();
		needUpdate = true;
	//}
	if(newName != oldName) {
		resetName();
		needUpdate = true;
	}

	if(needUpdate)
		updatePosition();

	repaint();
}

bool ContactViewItem::acceptDrop(const QMimeSource *m) const
{
	if ( d->type == Profile )
		return false;
	else if ( d->type == Group ) {
		if(d->groupType != gGeneral && d->groupType != gUser)
			return false;
	}
	else if ( d->type == Contact ) {
		if ( d->u && d->u->isSelf() )
			return false;
		ContactViewItem *par = (ContactViewItem *)QListViewItem::parent();
		if(par->groupType() != gGeneral && par->groupType() != gUser)
			return false;
	}

	// Files. Note that the QTextDrag test has to come after QUriDrag.
	if (d->type == Contact && QUriDrag::canDecode(m)) {
		QStringList l;
		if (QUriDrag::decodeLocalFiles(m,l) && !l.isEmpty())
			return true;
	}

	if(!QTextDrag::canDecode(m))
		return false;

	QString str;
	if(!QTextDrag::decode(m, str))
		return false;

	return true;
}

void ContactViewItem::dragEntered()
{
	//printf("entered\n");
}

void ContactViewItem::dragLeft()
{
	//printf("left\n");
}

void ContactViewItem::dropped(QDropEvent *i)
{
	if(!acceptDrop(i))
		return;

	// Files
	if (QUriDrag::canDecode(i)) {
		QStringList l;
		if (QUriDrag::decodeLocalFiles(i,l) && !l.isEmpty()) {
			d->cp->dragDropFiles(l, this);
			return;
		}
	}

	// Text
	if(QTextDrag::canDecode(i)) {
		QString text;
		if(QTextDrag::decode(i, text))
			d->cp->dragDrop(text, this);
	}
}

int ContactViewItem::rtti() const
{
	return 5103;
}

#include "contactview.moc"
