//=============================================================================
//
//   File : KviUserListView.cpp
//   Creation date : Tue Aug 1 2000 21:05:22 by Szymon Stefanek
//
//   This file is part of the KVIrc irc client distribution
//   Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   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 opinion) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================


#define _KVI_USERLISTVIEW_CPP_

#include "kvi_debug.h"
#include "KviUserListView.h"
#include "kvi_settings.h"
#include "KviLocale.h"
#include "KviOptions.h"
#include "kvi_defaults.h"
#include "KviIconManager.h"
#include "KviRegisteredUserDataBase.h"
#include "KviParameterList.h"
#include "KviWindow.h"
#include "KviConsoleWindow.h"
#include "KviApplication.h"
#include "KviUserAction.h"
#include "KviQString.h"
#include "KviMainWindow.h"
#include "KviMdiManager.h"
#include "KviKvsEventTriggers.h"
#include "KviWindowToolWidget.h"
#include "KviStringConversion.h"
#include "KviIrcConnection.h"
#include "KviIrcConnectionServerInfo.h"
#include "KviPixmapUtils.h"

#include <QLabel>
#include <QScrollBar>
#include <QPainter>
#include <QPixmap>
#include <QFontMetrics>
#include <QDateTime>
#include <QEvent>
#include <QPaintEvent>
#include <QScrollBar>

#ifdef COMPILE_PSEUDO_TRANSPARENCY
	extern QPixmap * g_pShadedChildGlobalDesktopBackground;
#endif


// KviApplication.cpp (loaded and destroyed by KviIconManager)
extern QPixmap * g_pUserChanStatePixmap;

// Yet another really complex widget :)

#define KVI_USERLIST_BORDER_WIDTH 2

// FIXME: #warning "We want to be able to navigate the list with the keyboard!"

KviUserListToolTip::KviUserListToolTip(KviUserListView * pView, KviUserListViewArea * pArea)
: KviTalToolTip(pArea)
{
	m_pListView = pView;
}

KviUserListToolTip::~KviUserListToolTip()
{
}

void KviUserListToolTip::maybeTip(const QPoint & pnt)
{
	m_pListView->maybeTip(this,pnt);
}


KviUserListEntry::KviUserListEntry(KviUserListView * pParent, const QString & szNick, KviIrcUserEntry * pEntry, short int iFlags, bool bJoinTimeUnknown)
: QObject()
{
	m_pListView       = pParent;
	m_szNick          = szNick;
	m_pGlobalData     = pEntry;
	m_iFlags          = iFlags;
	m_lastActionTime  = (kvi_time_t)0;
	m_joinTime        = bJoinTimeUnknown ? (kvi_time_t)0 : kvi_unixTime();
	m_iTemperature    = bJoinTimeUnknown ? 0 : KVI_USERACTION_JOIN;

	m_bSelected       = false;
	m_pAvatarPixmap   = NULL;

	updateAvatarData();
	recalcSize();
}

KviUserListEntry::~KviUserListEntry()
{
	if(m_pAvatarPixmap)
		detachAvatarData();
}

void KviUserListEntry::detachAvatarData()
{
	if(!m_pAvatarPixmap)
		return;

	m_pAvatarPixmap->stop();
	//FIXME these checks did not work since these are m_pAvatarPixmap's SIGNALs
// 	if (receivers(SIGNAL(frameChanged())) > 0)
	QObject::disconnect(m_pAvatarPixmap,SIGNAL(frameChanged()),this,SLOT(avatarFrameChanged()));
// 	if (receivers(SIGNAL(destroyed())) > 0)
	QObject::disconnect(m_pAvatarPixmap,SIGNAL(destroyed()),this,SLOT(avatarDestroyed()));
	m_pAvatarPixmap = NULL;
}

void KviUserListEntry::updateAvatarData()
{
	detachAvatarData();

	if(!KVI_OPTION_BOOL(KviOption_boolShowAvatarsInUserlist))
		return;

	KviAvatar * pAv = m_pGlobalData->avatar();

	if(!pAv)
		return;

	if(pAv)
	{
		if(
				KVI_OPTION_BOOL(KviOption_boolScaleAvatars) &&
				(
					(!KVI_OPTION_BOOL(KviOption_boolDoNotUpscaleAvatars)) ||
					((unsigned int)pAv->size().width() > KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth)) ||
					((unsigned int)pAv->size().height() > KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight))
				)
			)
		{
			m_pAvatarPixmap = pAv->forSize(
					KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth),
					KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight)
				);
		} else {
			m_pAvatarPixmap = pAv->animatedPixmap();
		}
	}

	if(!m_pAvatarPixmap)
		return;

	if(!KVI_OPTION_BOOL(KviOption_boolEnableAnimatedAvatars))
		return;

	QObject::connect(m_pAvatarPixmap,SIGNAL(frameChanged()),this,SLOT(avatarFrameChanged()));
	QObject::connect(m_pAvatarPixmap,SIGNAL(destroyed()),this,SLOT(avatarDestroyed()));

	m_pAvatarPixmap->start();
}

void KviUserListEntry::avatarFrameChanged()
{
	m_pListView->animatedAvatarUpdated(this);
}

void KviUserListEntry::avatarDestroyed()
{
	m_pAvatarPixmap = NULL;
}

bool KviUserListEntry::color(QColor & color)
{
	//
	// Used in KviIrcView::getTextLine
	//
	KviRegisteredUser * pRegisteredUser = m_pListView->m_pKviWindow->connection()->userDataBase()->registeredUser(m_szNick);
	if(pRegisteredUser)
	{
		if(m_pListView->m_pKviWindow->connection()->userDataBase()->haveCustomColor(m_szNick))
		{
			color = *(m_pListView->m_pKviWindow->connection()->userDataBase()->customColor(m_szNick));
			return true;
		}
	}

	if(KVI_OPTION_BOOL(KviOption_boolUseDifferentColorForOwnNick) && m_pListView->m_pKviWindow->connection())
	{
		if(m_szNick == m_pListView->m_pKviWindow->connection()->currentNickName())
		{
			color = KVI_OPTION_COLOR(KviOption_colorUserListViewOwnForeground);
			return true;
		}
	}

	if(m_iFlags == 0 && !globalData()->isIrcOp())
	{
		color = KVI_OPTION_COLOR(KviOption_colorUserListViewNormalForeground);
		return true;
	} else {
		color = KVI_OPTION_COLOR((globalData()->isIrcOp()) ? \
			KviOption_colorUserListViewIrcOpForeground : ((m_iFlags & KviIrcUserEntry::ChanOwner) ? \
			KviOption_colorUserListViewChanOwnerForeground : ((m_iFlags & KviIrcUserEntry::ChanAdmin) ? \
			KviOption_colorUserListViewChanAdminForeground : ((m_iFlags & KviIrcUserEntry::Op) ? \
			KviOption_colorUserListViewOpForeground : ((m_iFlags & KviIrcUserEntry::HalfOp) ? \
			KviOption_colorUserListViewHalfOpForeground : ((m_iFlags & KviIrcUserEntry::Voice) ? \
			KviOption_colorUserListViewVoiceForeground : KviOption_colorUserListViewUserOpForeground))))));
			return true;
	}
	return true;
}

void KviUserListEntry::recalcSize()
{
	m_iHeight = m_pListView->m_iFontHeight;

	if(KVI_OPTION_BOOL(KviOption_boolShowUserChannelIcons) && (m_iHeight < 20))
		m_iHeight = 20;

	if(m_pAvatarPixmap)
		m_iHeight += m_pAvatarPixmap->size().height();

	m_iHeight += 3;
}


KviUserListView::KviUserListView(QWidget * pParent, KviWindowToolPageButton * pButton, KviIrcUserDataBase * pDb, KviWindow * pWnd, int iDictSize, const QString & szTextLabel, const char * pName)
: KviWindowToolWidget(pParent,pButton)
{
	setObjectName(pName);

	m_pKviWindow  = pWnd;
	m_pEntryDict  = new KviPointerHashTable<QString,KviUserListEntry>(iDictSize,false);
	m_pEntryDict->setAutoDelete(true);

	m_pUsersLabel = new QLabel(this);
	m_pUsersLabel->setObjectName("userslabel");
	KviTalToolTip::add(m_pUsersLabel,szTextLabel);

	m_pViewArea        = new KviUserListViewArea(this);
	m_pToolTip         = new KviUserListToolTip(this,m_pViewArea);
	m_pTopItem         = 0;
	m_pHeadItem        = 0;
	m_pTailItem        = 0;
	m_iOpCount         = 0;
	m_iHalfOpCount     = 0;
	m_iVoiceCount      = 0;
	m_iChanAdminCount  = 0;
	m_iChanOwnerCount  = 0;
	m_iUserOpCount     = 0;
	m_pIrcUserDataBase = pDb;
	m_iTotalHeight     = 0;
	m_ibEntries        = 0;
	m_ieEntries        = 0;
	m_iIEntries        = 0;
	m_iSelectedCount   = 0;

	applyOptions();
}

KviUserListView::~KviUserListView()
{
	removeAllEntries();
	delete m_pEntryDict;
	delete m_pToolTip;
	delete m_pViewArea;
	delete m_pUsersLabel;
}

void KviUserListView::emitRightClick()
{
	int iEv = -1;
	switch(m_pKviWindow->type())
	{
		case KviWindow::Channel: iEv = KviEvent_OnChannelNickPopupRequest;  break;
		case KviWindow::Query:   iEv = KviEvent_OnQueryNickPopupRequest;    break;
		case KviWindow::Console: iEv = KviEvent_OnNotifyListPopupRequest;   break;
		default:                      iEv = KviEvent_OnNickLinkPopupRequest;     break; // this should actually never happen
	}

	if(iEv > -1)
	{
		if(KviKvsEventManager::instance()->hasAppHandlers(iEv))
		{
			QString szNicks;
			appendSelectedNicknames(szNicks);
			KviKvsVariantList vList;
			vList.append(szNicks);
			KviKvsEventManager::instance()->trigger(iEv,m_pKviWindow,&vList);
		} else {
			g_pApp->checkSuggestRestoreDefaultScript();
		}
	}
}

void KviUserListView::emitDoubleClick()
{
	int iEv = -1;
	switch(m_pKviWindow->type())
	{
		case KviWindow::Channel: iEv = KviEvent_OnChannelNickDefaultActionRequest;  break;
		case KviWindow::Query:   iEv = KviEvent_OnQueryNickDefaultActionRequest;    break;
		case KviWindow::Console: iEv = KviEvent_OnNotifyListDefaultActionRequest;   break;
		default:                      iEv = KviEvent_OnNickLinkDefaultActionRequest;     break; // this should actually never happen
	}

	if(iEv > -1)
	{
		if(KviKvsEventManager::instance()->hasAppHandlers(iEv))
		{
			QString szNicks;
			appendSelectedNicknames(szNicks);
			KviKvsVariantList vList;
			vList.append(szNicks);
			KviKvsEventManager::instance()->trigger(iEv,m_pKviWindow,&vList);
		} else {
			g_pApp->checkSuggestRestoreDefaultScript();
		}
	}
}

void KviUserListView::updateScrollBarRange()
{
	int iMax = m_iTotalHeight - (m_pViewArea->height() - (KVI_USERLIST_BORDER_WIDTH * 2));
	m_pViewArea->m_pScrollBar->setRange(0,iMax > 0 ? iMax : 0);
	m_pViewArea->m_pScrollBar->setVisible(iMax > 0);
}

void KviUserListView::applyOptions()
{
/*	if(!KVI_OPTION_BOOL(KviOption_boolDisableUserListLabel)){
		m_pUsersLabel->hide();
		m_pViewArea->setGeometry(0,0,width(),height());
	}
	if(KVI_OPTION_BOOL(KviOption_boolDisableUserListLabel)){
		m_pUsersLabel->show();
		updateUsersLabel();
	}*/
	setFont(KVI_OPTION_FONT(KviOption_fontUserListView));
	QFontMetrics fm(font());

	m_iFontHeight = fm.lineSpacing();
	m_pViewArea->m_pScrollBar->setSingleStep(m_iFontHeight);

	KviUserListEntry * pEntry = m_pHeadItem;

	//reset scrollarea position and scrollbar position
	m_pTopItem = m_pHeadItem;
	m_pViewArea->m_pScrollBar->setValue(0);

	m_iTotalHeight = 0;
	while(pEntry)
	{
		pEntry->updateAvatarData();
		pEntry->recalcSize();
		m_iTotalHeight += pEntry->m_iHeight;
		pEntry = pEntry->m_pNext;
	}
	updateScrollBarRange();
	m_pUsersLabel->setFont(KVI_OPTION_FONT(KviOption_fontUserListView));
	setMinimumWidth(100);
	resizeEvent(0); // this will call update() too
	repaint();
}

void KviUserListView::updateArea()
{
	bool bEnable = m_pViewArea->updatesEnabled();
	if(!bEnable)
		m_pViewArea->setUpdatesEnabled(true);

	triggerUpdate();

	if(!bEnable)
		m_pViewArea->setUpdatesEnabled(false);
}

void KviUserListView::enableUpdates(bool bEnable)
{
	m_pViewArea->setUpdatesEnabled(bEnable);
	if(bEnable)
		triggerUpdate();
}

void KviUserListView::setMaskEntries(char cType, int iNum)
{
	switch(cType)
	{
		case 'b':
			m_ibEntries = iNum;
		break;
		case 'e':
			m_ieEntries = iNum;
		break;
		case 'I':
			m_iIEntries = iNum;
		break;
	}
	updateUsersLabel();
}

void KviUserListView::animatedAvatarUpdated(KviUserListEntry *e)
{
	// FIXME: This sucks
	if(!m_pTopItem)
		return;

	if(!KVI_OPTION_BOOL(KviOption_boolShowAvatarsInUserlist))
		return;

	QRect rct;
	KviUserListEntry * pEntry = m_pTopItem;
	int iCurTop = KVI_USERLIST_BORDER_WIDTH - m_pViewArea->m_iTopItemOffset;
	int iCurBottom = 0;

	int iBaseX = KVI_USERLIST_BORDER_WIDTH + 1;
	int iBaseY = 2;
	if(KVI_OPTION_BOOL(KviOption_boolDrawGenderIcons))
		iBaseX += 11;
	if(KVI_OPTION_BOOL(KviOption_boolShowUserChannelIcons))
		iBaseX += 18;
	if(KVI_OPTION_BOOL(KviOption_boolShowUserChannelState))
		iBaseX += 11;
	if(KVI_OPTION_BOOL(KviOption_boolUserListViewDrawGrid))
	{
		switch(KVI_OPTION_UINT(KviOption_uintUserListViewGridType))
		{
			case KVI_USERLISTVIEW_GRIDTYPE_PLAINGRID:
			case KVI_USERLISTVIEW_GRIDTYPE_DOTGRID:
			break;
			default: // KVI_USERLISTVIEW_GRIDTYPE_3DGRID and KVI_USERLISTVIEW_GRIDTYPE_3DBUTTONS
				if(pEntry->m_bSelected)
					iBaseY++;
			break;
		}
		iBaseX += 3;
	} else {
		iBaseX += 1;
	}

	while(pEntry && (iCurTop <= m_pViewArea->height()))
	{
		iCurBottom = iCurTop + pEntry->m_iHeight;
		if(pEntry == e)
		{
			rct.setX(iBaseX);
			rct.setY(iCurTop + iBaseY);
			rct.setWidth(pEntry->m_pAvatarPixmap->pixmap()->size().width());
			rct.setHeight(pEntry->m_pAvatarPixmap->pixmap()->size().height());

			m_pViewArea->update(rct);
			break;
		}
		iCurTop = iCurBottom;
		pEntry = pEntry->m_pNext;
	}
}

void KviUserListView::completeNickBashLike(const QString & szBegin, KviPointerList<QString> * pList, bool bAppendMask)
{
	KviUserListEntry * pEntry = m_pHeadItem;

	while(pEntry)
	{
		if(KviQString::equalCIN(szBegin,pEntry->m_szNick,szBegin.length()))
		{
			if(bAppendMask)
			{
				QString * szTmp = new QString(QString("%1!%2@%3").arg(pEntry->m_szNick,pEntry->m_pGlobalData->user(),pEntry->m_pGlobalData->host()));
				pList->append(szTmp);
			} else
				pList->append(new QString(pEntry->m_szNick));
		}
		pEntry = pEntry->m_pNext;
	}
}

bool KviUserListView::completeNickStandard(const QString & szBegin, const QString & szSkipAfter, QString & szBuffer, bool bAppendMask)
{
	KviUserListEntry * pEntry = m_pHeadItem;

	if(!szSkipAfter.isEmpty())
	{
		while(pEntry)
		{
			if(KviQString::equalCI(szSkipAfter,pEntry->m_szNick))
			{
				pEntry = pEntry->m_pNext;
				break;
			}
			pEntry = pEntry->m_pNext;
		}
	}

	// FIXME: completion should skip my own nick or place it as last entry in the chain (?)

	//	if(KviConsoleWindow * c = m_pKviWindow->console())
	//	{
	//		if(kvi_strEqualCI(entry->m_szNick.ptr(),c->currentNickName())
	//	}

	// Ok...now the real completion
	while(pEntry)
	{
		if(pEntry->m_szNick.length() >= szBegin.length())
		{
			int iResult = KviQString::cmpCIN(szBegin,pEntry->m_szNick,szBegin.length());
			if(iResult == 0)
			{
				// This is ok.
				szBuffer = pEntry->m_szNick;
				if(bAppendMask)
				{
					szBuffer += "!";
					szBuffer += pEntry->m_pGlobalData->user();
					szBuffer += "@";
					szBuffer += pEntry->m_pGlobalData->host();
				}
				return true;
			}
			/*
			 * This part of code has been commented out to fix #236
			 * This code takes some absurt assterion about the first character of nicknames
			 * and their order in the userlist.

			else if(iResult < 0)
			{
				// No match...begin is lower than the current entry
				if(pEntry->m_iFlags == 0) return false;
				else {
					int iFlags = pEntry->m_iFlags;
					// skip the current flag
					while(pEntry)
					{
						if(pEntry->m_iFlags != iFlags)
							break;
						pEntry = pEntry->m_pNext;
					}
					continue;
				}
			}
			*/
		}
		pEntry = pEntry->m_pNext;
	}

	return false;
}

void KviUserListView::insertUserEntry(const QString & szNnick, KviUserListEntry * pUserEntry)
{
	// Complex insertion task :)

	m_pEntryDict->insert(szNnick,pUserEntry);
	m_iTotalHeight += pUserEntry->m_iHeight;

	bool bGotTopItem = false;
	bool bModeqHasPrefix = m_pKviWindow->connection()->serverInfo()->isSupportedModeFlag('q');
	bool bModeaHasPrefix = m_pKviWindow->connection()->serverInfo()->isSupportedModeFlag('a');

	int iFlag = 0;
	if(pUserEntry->m_iFlags != 0)
	{
		if(pUserEntry->m_iFlags & KviIrcUserEntry::UserOp)
		{
			iFlag = KviIrcUserEntry::UserOp;
			m_iUserOpCount++;
		}

		if(pUserEntry->m_iFlags & KviIrcUserEntry::Voice)
		{
			iFlag = KviIrcUserEntry::Voice;
			m_iVoiceCount++;
		}

		if(pUserEntry->m_iFlags & KviIrcUserEntry::HalfOp)
		{
			iFlag = KviIrcUserEntry::HalfOp;
			m_iHalfOpCount++;
		}

		if(pUserEntry->m_iFlags & KviIrcUserEntry::Op)
		{
			iFlag = KviIrcUserEntry::Op;
			m_iOpCount++;
		}

		if(pUserEntry->m_iFlags & KviIrcUserEntry::ChanAdmin)
		{
			if(bModeaHasPrefix) iFlag = KviIrcUserEntry::ChanAdmin;
			m_iChanAdminCount++;
		}

		if(pUserEntry->m_iFlags & KviIrcUserEntry::ChanOwner)
		{
			if(bModeqHasPrefix) iFlag = KviIrcUserEntry::ChanOwner;
			m_iChanOwnerCount++;
		}
	}

	//FIXME this should probably be handled in a different way (place)
	if(pUserEntry->globalData()->isIrcOp())
	{
		m_iIrcOpCount++;
	}

	if(m_pHeadItem)
	{
		KviUserListEntry * pEntry = m_pHeadItem;

		if(!(pUserEntry->m_iFlags & KviIrcUserEntry::ChanOwner) || !bModeqHasPrefix)
		{
			// the new user is not a channel owner...
			// skip the channel owners
			while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::ChanOwner) && bModeqHasPrefix)
			{
				if(pEntry == m_pTopItem)
					bGotTopItem = true;
				pEntry = pEntry->m_pNext;
			}

			if(!(pUserEntry->m_iFlags & KviIrcUserEntry::ChanAdmin) || !bModeaHasPrefix)
			{
				// the new user is not a channel admin...
				// skip chan admins
				while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin) && bModeaHasPrefix)
				{
					if(pEntry == m_pTopItem)
						bGotTopItem = true;
					pEntry = pEntry->m_pNext;
				}

				// is operator ?
				if(!(pUserEntry->m_iFlags & KviIrcUserEntry::Op))
				{
					// the new user is not an op...
					// skip ops
					while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::Op))
					{
						if(pEntry == m_pTopItem)
							bGotTopItem = true;
						pEntry = pEntry->m_pNext;
					}

					// is half oped ?
					if(!(pUserEntry->m_iFlags & KviIrcUserEntry::HalfOp))
					{
						// nope, skip halfops
						while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::HalfOp))
						{
							if(pEntry == m_pTopItem)
								bGotTopItem = true;
							pEntry = pEntry->m_pNext;
						}

						// is voiced ?
						if(!(pUserEntry->m_iFlags & KviIrcUserEntry::Voice))
						{
							// nope, not voiced so skip voiced users
							while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::Voice))
							{
								if(pEntry == m_pTopItem)
									bGotTopItem = true;
								pEntry = pEntry->m_pNext;
							}

							// is userop'd?
							if(!(pUserEntry->m_iFlags & KviIrcUserEntry::UserOp))
							{
								// nope, skip userops
								while(pEntry && (pEntry->m_iFlags & KviIrcUserEntry::UserOp))
								{
									if(pEntry == m_pTopItem)
										bGotTopItem = true;
									pEntry = pEntry->m_pNext;
								}
							} // else is userop, ops, halfops, and voiced are skipped
						} // else it is voiced, ops and halfops are skipped
					} // else it is halfop,  ops are skipped
				} // else it is op, chan admins are skipped
			} // else it is chan admin, chan owners are skipped
		} // else it is chan owner, so nothing to skip: they are first in the list

		// now strcmp within the current user-flag group...
		while(pEntry && (KviQString::cmpCI(pEntry->m_szNick,pUserEntry->m_szNick,
				KVI_OPTION_BOOL(KviOption_boolPlaceNickWithNonAlphaCharsAtEnd)
				) < 0) &&
				((pEntry->m_iFlags & iFlag) || (iFlag == 0)))
		{
			if(pEntry == m_pTopItem)
				bGotTopItem = true;
			pEntry = pEntry->m_pNext;
		}

		if(pEntry)
		{
			// inserting
			pUserEntry->m_pNext = pEntry;
			pUserEntry->m_pPrev = pEntry->m_pPrev;
			if(pUserEntry->m_pPrev == 0)
				m_pHeadItem = pUserEntry;
			else pUserEntry->m_pPrev->m_pNext = pUserEntry;

			pEntry->m_pPrev = pUserEntry;
			// need to adjust the item offsets now...
			// ok... if we're inserting something after
			// the top item, we move everything down
			// otherwise we only update the scrollbar values
			if(!bGotTopItem)
			{
				// Inserting BEFORE the top item
				if((pUserEntry == m_pHeadItem) && (m_pTopItem == pUserEntry->m_pNext) && (m_pViewArea->m_iTopItemOffset == 0))
				{
					// special case...the top item is the head one
					// and it has zero offset...change the top item too
					m_pTopItem = pUserEntry;
					triggerUpdate();
				} else {
					// invisible insertion
					m_pViewArea->m_bIgnoreScrollBar = true;
					m_pViewArea->m_iLastScrollBarVal += pUserEntry->m_iHeight;
					updateScrollBarRange();
					m_pViewArea->m_pScrollBar->setValue(m_pViewArea->m_iLastScrollBarVal);
					m_pViewArea->m_bIgnoreScrollBar = false;
					updateUsersLabel();
				}
			} else {
				triggerUpdate();
			}
		} else {
			// appending to the end (may be visible)
			m_pTailItem->m_pNext = pUserEntry;
			pUserEntry->m_pNext = 0;
			pUserEntry->m_pPrev = m_pTailItem;
			m_pTailItem = pUserEntry;
			triggerUpdate();
		}
	} else {
		// There were no items (is rather visible)
		m_pHeadItem = pUserEntry;
		m_pTailItem = pUserEntry;
		m_pTopItem = pUserEntry;
		pUserEntry->m_pNext = 0;
		pUserEntry->m_pPrev = 0;
		triggerUpdate();
	}

	if(pUserEntry->m_bSelected)
	{
		m_iSelectedCount++;
		if(m_iSelectedCount == 1)
			g_pMainWindow->childWindowSelectionStateChange(m_pKviWindow,true);
	}
}

KviUserListEntry * KviUserListView::join(const QString & szNick, const QString & szUser, const QString & szHost, int iFlags)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(!pEntry)
	{
		// add an entry to the global dict
		KviIrcUserEntry * pGlobalData = m_pIrcUserDataBase->insertUser(szNick,szUser,szHost);
		// calculate the flags and update the counters
		pEntry = new KviUserListEntry(this,szNick,pGlobalData,iFlags,(szUser == QString()));
		insertUserEntry(szNick,pEntry);
	} else {
//		if(!host.isEmpty()) - it can be UHNAMES with host or NAMEX(X) w/o it
//		{
			// Ok...the user was already on...
			// Probably this is a NAMES(X) reply, and the user IS_ME
			// (already joined after the JOIN message)
			if(iFlags != pEntry->m_iFlags)
			{
//// FIXME: #warning "Maybe say to the channel that we're oped : and the op is guessed from the names reply"
				if((iFlags & KviIrcUserEntry::ChanOwner) != (pEntry->m_iFlags & KviIrcUserEntry::ChanOwner))
					setChanOwner(szNick,iFlags & KviIrcUserEntry::ChanOwner);

				if((iFlags & KviIrcUserEntry::ChanAdmin) != (pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin))
					setChanAdmin(szNick,iFlags & KviIrcUserEntry::ChanAdmin);

				if((iFlags & KviIrcUserEntry::Op) != (pEntry->m_iFlags & KviIrcUserEntry::Op))
					setOp(szNick,iFlags & KviIrcUserEntry::Op);

				if((iFlags & KviIrcUserEntry::HalfOp) != (pEntry->m_iFlags & KviIrcUserEntry::HalfOp))
					setHalfOp(szNick,iFlags & KviIrcUserEntry::HalfOp);

				if((iFlags & KviIrcUserEntry::Voice) != (pEntry->m_iFlags & KviIrcUserEntry::Voice))
					setVoice(szNick,iFlags & KviIrcUserEntry::Voice);

				if((iFlags & KviIrcUserEntry::UserOp) != (pEntry->m_iFlags & KviIrcUserEntry::UserOp))
					setUserOp(szNick,iFlags & KviIrcUserEntry::UserOp);
			}
//		}
	}
	return pEntry;
}

void KviUserListView::triggerUpdate()
{
	// This stuff is useful on joins only
	if(m_pViewArea->updatesEnabled())
	{
		updateScrollBarRange();
		m_pViewArea->update();
		updateUsersLabel();
	}
}

bool KviUserListView::avatarChanged(const QString & szNick)
{
	KviUserListEntry * pUserEntry = m_pEntryDict->find(szNick);
	if(!pUserEntry)
		return false;

	int iOldHeight = pUserEntry->m_iHeight;
	m_iTotalHeight -= pUserEntry->m_iHeight;
	pUserEntry->updateAvatarData();
	pUserEntry->recalcSize();
	m_iTotalHeight += pUserEntry->m_iHeight;
	// if this was "over" the top item, we must adjust the scrollbar value
	// otherwise scroll everything down
	KviUserListEntry * pEntry = m_pHeadItem;
	bool bGotTopItem = false;
	while(pEntry != pUserEntry)
	{
		if(pEntry == m_pTopItem)
		{
			bGotTopItem = true;
			pEntry = pUserEntry;
		} else pEntry = pEntry->m_pNext;
	}

	if(!bGotTopItem && (m_pTopItem != pUserEntry))
	{
		// we're "over" the top item, so over the
		// upper side of the view...adjust the scroll bar value
		int iHeightDiff = pUserEntry->m_iHeight - iOldHeight;
		m_pViewArea->m_iLastScrollBarVal += iHeightDiff;
		m_pViewArea->m_bIgnoreScrollBar = true;
		updateScrollBarRange();
		m_pViewArea->m_pScrollBar->setValue(m_pViewArea->m_iLastScrollBarVal);
		m_pViewArea->m_bIgnoreScrollBar = false;
	} else {
		// the item may be visible!
		// the scroll bar should take care of the case
		// in that the current value runs out of the allowed
		// range.... it should set the value to a good one
		// and emit the signal
		updateScrollBarRange();
		m_pViewArea->update();
	}
	return true;
}

bool KviUserListView::userActionVerifyMask(const QString & szNick, const QString & szUser, const QString & szHost, int iActionTemperature, QString & szOldUser, QString & szOldHost)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(pEntry)
	{
		pEntry->m_lastActionTime = kvi_unixTime();
		bool bChanged = false;

		if(!(szHost.isEmpty() || (KviQString::equalCS(szHost,"*"))))
		{
			if(!KviQString::equalCI(pEntry->m_pGlobalData->host(),szHost))
			{
				if(!(pEntry->m_pGlobalData->host().isEmpty() || KviQString::equalCS(pEntry->m_pGlobalData->host(),"*")))
				{
					szOldHost = pEntry->m_pGlobalData->host();
					bChanged = true;
				}
				pEntry->m_pGlobalData->setHost(szHost);
			}
		}

		if(!(szUser.isEmpty() || (KviQString::equalCS(szUser,"*"))))
		{
			if(!KviQString::equalCI(pEntry->m_pGlobalData->user(),szUser))
			{
				if(!(pEntry->m_pGlobalData->user().isEmpty() || KviQString::equalCS(pEntry->m_pGlobalData->user(),"*")))
				{
					szOldUser = pEntry->m_pGlobalData->user();
					bChanged = true;
				}
				pEntry->m_pGlobalData->setUser(szUser);
			}
		}

		pEntry->m_iTemperature += iActionTemperature;
		// Don't allow it to grow too much
		if(pEntry->m_iTemperature > 300)
			pEntry->m_iTemperature = 300;
		else if(pEntry->m_iTemperature < -300)
			pEntry->m_iTemperature = -300;

		if(itemVisible(pEntry))
			triggerUpdate();

		return !bChanged;
	}
	return true; // no such nick so no change
}

void KviUserListView::userAction(const QString & szNick, const QString & szUser, const QString & szHost, int iActionTemperature)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(pEntry)
	{
		pEntry->m_lastActionTime = kvi_unixTime();
		if(!(szHost.isEmpty() || (KviQString::equalCS(szHost,"*"))))
			pEntry->m_pGlobalData->setHost(szHost);
		if(!(szUser.isEmpty() || (KviQString::equalCS(szUser,"*"))))
			pEntry->m_pGlobalData->setUser(szUser);
		pEntry->m_iTemperature += iActionTemperature;

		// Don't allow it to grow too much
		if(pEntry->m_iTemperature > 300)
			pEntry->m_iTemperature = 300;
		else if(pEntry->m_iTemperature < -300)
			pEntry->m_iTemperature = -300;

		if(itemVisible(pEntry))
			triggerUpdate();
	}
}

void KviUserListView::userAction(KviIrcMask * pUser, int iActionTemperature)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(pUser->nick());
	if(pEntry)
	{
		pEntry->m_lastActionTime = kvi_unixTime();
		if(pUser->hasUser())
			pEntry->m_pGlobalData->setUser(pUser->user());
		if(pUser->hasHost())
			pEntry->m_pGlobalData->setHost(pUser->host());
		pEntry->m_iTemperature += iActionTemperature;

		// Don't allow it to grow too much
		if(pEntry->m_iTemperature > 300)
			pEntry->m_iTemperature = 300;
		else if(pEntry->m_iTemperature < -300)
			pEntry->m_iTemperature = -300;

		if(itemVisible(pEntry))
			triggerUpdate();
	}
}

void KviUserListView::userAction(const QString & szNick, int iActionTemperature)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(pEntry)
	{
		pEntry->m_lastActionTime = kvi_unixTime();
		pEntry->m_iTemperature += iActionTemperature;

		if(pEntry->m_iTemperature > 300)
			pEntry->m_iTemperature = 300;
		else if(pEntry->m_iTemperature < -300)
			pEntry->m_iTemperature = -300;

		if(itemVisible(pEntry))
			triggerUpdate();
	}
}

kvi_time_t KviUserListView::getUserJoinTime(const QString & szNick)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(!pEntry)
		return (kvi_time_t)0;

	return pEntry->m_joinTime;
}

kvi_time_t KviUserListView::getUserLastActionTime(const QString & szNick)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(!pEntry)
		return (kvi_time_t)0;

	return pEntry->m_lastActionTime;
}

int KviUserListView::getUserModeLevel(const QString & szNick)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(!pEntry)
		return 0;

	if(pEntry->m_iFlags & KviIrcUserEntry::ModeMask)
	{
		if(pEntry->m_iFlags & KviIrcUserEntry::ChanOwner)
			return 60;
		if(pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin)
			return 50;
		if(pEntry->m_iFlags & KviIrcUserEntry::Op)
			return 40;
		if(pEntry->m_iFlags & KviIrcUserEntry::HalfOp)
			return 30;
		if(pEntry->m_iFlags & KviIrcUserEntry::Voice)
			return 20;
		if(pEntry->m_iFlags & KviIrcUserEntry::UserOp)
			return 10;
	}
	return 0;
}

char KviUserListView::getUserFlag(KviUserListEntry * pEntry)
{
	if(!pEntry)
		return 0;

	return (char)m_pKviWindow->connection()->serverInfo()->modePrefixChar(pEntry->m_iFlags).unicode();
}

void KviUserListView::prependUserFlag(const QString & szNick, QString & szBuffer)
{
	char uFlag = getUserFlag(szNick);
	if(uFlag)
		szBuffer.prepend(uFlag);
}

int KviUserListView::flags(const QString & szNick)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	return pEntry ? pEntry->m_iFlags : 0;
}

#define SET_FLAG_FUNC(__funcname,__flag) \
	bool KviUserListView::__funcname(const QString & szNick, bool bYes) \
	{ \
		KviUserListEntry * pEntry = m_pEntryDict->find(szNick); \
		if(!pEntry) \
			return false; \
		m_pEntryDict->setAutoDelete(false); \
		partInternal(szNick,false); \
		m_pEntryDict->setAutoDelete(true); \
		if(bYes) \
		{ \
			if(!(pEntry->m_iFlags & __flag)) \
				pEntry->m_iFlags |= __flag; \
		} else { \
			if(pEntry->m_iFlags & __flag) \
				pEntry->m_iFlags &= ~__flag; \
		} \
		updateScrollBarRange(); \
		insertUserEntry(szNick,pEntry); \
		m_pViewArea->update(); \
		return true; \
	}

SET_FLAG_FUNC(setChanOwner,KviIrcUserEntry::ChanOwner)
SET_FLAG_FUNC(setChanAdmin,KviIrcUserEntry::ChanAdmin)
SET_FLAG_FUNC(setOp,KviIrcUserEntry::Op)
SET_FLAG_FUNC(setHalfOp,KviIrcUserEntry::HalfOp)
SET_FLAG_FUNC(setUserOp,KviIrcUserEntry::UserOp)
SET_FLAG_FUNC(setVoice,KviIrcUserEntry::Voice)

#define GET_FLAG_FUNC(__funcname,__flag) \
	bool KviUserListView::__funcname(const QString & szNick, bool bAtLeast) \
	{ \
		KviUserListEntry * pEntry = m_pEntryDict->find(szNick); \
		return pEntry ? (bAtLeast ? (pEntry->m_iFlags >= __flag) : (pEntry->m_iFlags & __flag)) : false; \
	}

GET_FLAG_FUNC(isChanOwner,KviIrcUserEntry::ChanOwner)
GET_FLAG_FUNC(isChanAdmin,KviIrcUserEntry::ChanAdmin)
GET_FLAG_FUNC(isOp,KviIrcUserEntry::Op)
GET_FLAG_FUNC(isVoice,KviIrcUserEntry::Voice)
GET_FLAG_FUNC(isHalfOp,KviIrcUserEntry::HalfOp)
GET_FLAG_FUNC(isUserOp,KviIrcUserEntry::UserOp)

QString * KviUserListView::firstSelectedNickname()
{
	m_pIterator = m_pHeadItem;
	while(m_pIterator)
	{
		if(m_pIterator->m_bSelected)
		{
			QString * szTmp = &(m_pIterator->m_szNick);
			m_pIterator = m_pIterator->m_pNext;
			return szTmp;
		}
		m_pIterator = m_pIterator->m_pNext;
	}
	return 0;
}

QString * KviUserListView::nextSelectedNickname()
{
	while(m_pIterator)
	{
		if(m_pIterator->m_bSelected)
		{
			QString * szTmp = &(m_pIterator->m_szNick);
			m_pIterator = m_pIterator->m_pNext;
			return szTmp;
		}
		m_pIterator = m_pIterator->m_pNext;
	}
	return 0;
}

void KviUserListView::appendSelectedNicknames(QString & szBuffer)
{
	KviUserListEntry * pEntry = m_pHeadItem;
	bool bFirst = true;
	while(pEntry)
	{
		if(pEntry->m_bSelected)
		{
			if(!bFirst)
				szBuffer.append(',');
			else bFirst = false;
			szBuffer.append(pEntry->m_szNick);
		}
		pEntry = pEntry->m_pNext;
	}
}

void KviUserListView::select(const QString & szNick)
{
	KviPointerHashTableIterator<QString,KviUserListEntry> it(*m_pEntryDict);
	while(it.current())
	{
		((KviUserListEntry *)it.current())->m_bSelected = false;
		++it;
	}

	KviUserListEntry * pEntry = m_pEntryDict->find(szNick);
	if(pEntry)
	{
		pEntry->m_bSelected = true;
		m_iSelectedCount = 1;
	} else {
		m_iSelectedCount = 0;
	}

	g_pMainWindow->childWindowSelectionStateChange(m_pKviWindow,true);
	m_pViewArea->update();
}

void KviUserListView::ensureVisible(const QString & szNick)
{
	KviUserListEntry * pUserEntry = m_pEntryDict->find(szNick);
	if(!pUserEntry)
		return;

	// so, first of all..check if this item is over, or below the top item
	KviUserListEntry * pEntry = m_pHeadItem;
	bool bGotTopItem = false;
	bool bGotUserEntry = false;
	int iHeight = 0;
	while(pEntry)
	{
		if(pEntry == pUserEntry)
		{
			if(bGotTopItem)
			{
				iHeight+=pEntry->m_iHeight;
				break;
			} else {
				bGotUserEntry=true;
			}
		}

		if(pEntry == m_pTopItem)
		{
			if(bGotUserEntry)
			{
				iHeight-=pEntry->m_iHeight;
				break;
			} else {
				bGotTopItem = true;
			}
		}

		if(bGotTopItem) iHeight+=pEntry->m_iHeight;
		if(bGotUserEntry) iHeight-=pEntry->m_iHeight;
		pEntry = pEntry->m_pNext;
	}

	if(iHeight > m_pViewArea->height())
	{
		iHeight-= m_pViewArea->height();
	} else if (iHeight>=0) {
		//already visible
		return;
	}


	//the scrollbar will clamp this value itself
	m_pViewArea->m_pScrollBar->setValue(m_pViewArea->m_pScrollBar->value() + iHeight);
	m_pViewArea->m_iLastScrollBarVal = m_pViewArea->m_pScrollBar->value();
}

bool KviUserListView::partInternal(const QString & szNick, bool bRemoveDefinitively)
{
	KviUserListEntry * pUserEntry = m_pEntryDict->find(szNick);
	if(!pUserEntry)
		return false; // not there

	// so, first of all..check if this item is over, or below the top item
	KviUserListEntry * pEntry = m_pHeadItem;
	bool bGotTopItem = false;
	while(pEntry != pUserEntry)
	{
		if(pEntry == m_pTopItem)
		{
			bGotTopItem = true;
			pEntry = pUserEntry;
		} else pEntry = pEntry->m_pNext;
	}

	// decrease counts first
	if(pUserEntry->m_pGlobalData->isIrcOp())
		m_iIrcOpCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::ChanOwner)
		m_iChanOwnerCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::ChanAdmin)
		m_iChanAdminCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::Op)
		m_iOpCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::HalfOp)
		m_iHalfOpCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::Voice)
		m_iVoiceCount--;
	if(pUserEntry->m_iFlags & KviIrcUserEntry::UserOp)
		m_iUserOpCount--;

	if(bRemoveDefinitively)
	{
		pUserEntry->detachAvatarData();
		m_pIrcUserDataBase->removeUser(szNick,pUserEntry->m_pGlobalData);
	}

	if(pUserEntry->m_bSelected)
	{
		m_iSelectedCount--;
		if(m_iSelectedCount == 0)
			g_pMainWindow->childWindowSelectionStateChange(m_pKviWindow,false);
	}
	if(pUserEntry->m_pPrev)
		pUserEntry->m_pPrev->m_pNext = pUserEntry->m_pNext;
	if(pUserEntry->m_pNext)
		pUserEntry->m_pNext->m_pPrev = pUserEntry->m_pPrev;
	if(m_pTopItem == pUserEntry)
	{
		bGotTopItem = true; // !!! the previous while() does not handle it!
		m_pTopItem = pUserEntry->m_pNext;
		if(m_pTopItem == 0)
			m_pTopItem = pUserEntry->m_pPrev;
	}
	if(pUserEntry == m_pHeadItem)
		m_pHeadItem = pUserEntry->m_pNext;
	if(pUserEntry == m_pTailItem)
		m_pTailItem = pUserEntry->m_pPrev;
	m_iTotalHeight -= pUserEntry->m_iHeight;

	int iHeight = pUserEntry->m_iHeight;

	m_pEntryDict->remove(szNick);

	if(bGotTopItem)
	{
		// removing after (or exactly) the top item, may be visible
		if(bRemoveDefinitively)
			triggerUpdate();
	} else {
		// removing over (before) the top item...not visible
		m_pViewArea->m_bIgnoreScrollBar = true;
		m_pViewArea->m_iLastScrollBarVal -= iHeight;
		m_pViewArea->m_pScrollBar->setValue(m_pViewArea->m_iLastScrollBarVal);
		updateScrollBarRange();
		m_pViewArea->m_bIgnoreScrollBar = false;
		if(bRemoveDefinitively)
			updateUsersLabel();
	}

	return true;
}

bool KviUserListView::nickChange(const QString & szOldNick, const QString & szNewNick)
{
	KviUserListEntry * pEntry = m_pEntryDict->find(szOldNick);
	if(pEntry)
	{
		QString szUser    = pEntry->m_pGlobalData->user();
		QString szHost    = pEntry->m_pGlobalData->host();
		int iFlags        = pEntry->m_iFlags;
		kvi_time_t joint  = pEntry->m_joinTime;
		bool bSelect      = pEntry->m_bSelected;

		KviAvatar * pAv   = pEntry->m_pGlobalData->forgetAvatar();

		KviIrcUserEntry::Gender gender = pEntry->m_pGlobalData->gender();
		bool bBot = pEntry->m_pGlobalData->isBot();
		part(szOldNick);
		KVI_ASSERT(!m_pEntryDict->find(szOldNick));

		pEntry = join(szNewNick,szUser,szHost,iFlags);
		pEntry->m_pGlobalData->setGender(gender);
		pEntry->m_pGlobalData->setBot(bBot);
		pEntry->m_joinTime = joint;
		pEntry->m_lastActionTime = kvi_unixTime();
		pEntry->m_bSelected = bSelect;
		pEntry->m_iTemperature += KVI_USERACTION_NICK;

		if(pAv)
		{
			pEntry->m_pGlobalData->setAvatar(pAv);
			avatarChanged(szNewNick);
		}
		return true;
	}
	return false;
}

void KviUserListView::updateUsersLabel()
{
	if(!KVI_OPTION_BOOL(KviOption_boolShowUserListStatisticLabel))
		return;

	KviCString tmp;
	tmp.sprintf("<nobr><b>[%u]</b>",m_pEntryDict->count());
	if(m_iChanOwnerCount)
		tmp.append(KviCString::Format," q:%d",m_iChanOwnerCount);
	if(m_iChanAdminCount)
		tmp.append(KviCString::Format," a:%d",m_iChanAdminCount);
	if(m_iOpCount)
		tmp.append(KviCString::Format," o:%d",m_iOpCount);
	if(m_iHalfOpCount)
		tmp.append(KviCString::Format," h:%d",m_iHalfOpCount);
	if(m_iVoiceCount)
		tmp.append(KviCString::Format," v:%d",m_iVoiceCount);
	if(m_iUserOpCount)
		tmp.append(KviCString::Format," u:%d",m_iUserOpCount);
	if(m_ibEntries)
		tmp.append(KviCString::Format," b:%d",m_ibEntries);
	if(m_ieEntries)
		tmp.append(KviCString::Format," e:%d",m_ieEntries);
	if(m_iIEntries)
		tmp.append(KviCString::Format," I:%d",m_iIEntries);
	tmp.append("</nobr>");
	m_pUsersLabel->setText(tmp.ptr());
}

// FIXME: this could be done really better
void KviUserListView::partAllButOne(const QString & szWhoNot)
{
	QStringList list;
	KviPointerHashTableIterator<QString,KviUserListEntry> it(*m_pEntryDict);
	while(it.current())
	{
		if(!KviQString::equalCI(szWhoNot,it.currentKey()))
			list.append(it.currentKey());
		++it;
	}
	for(QStringList::Iterator it2 = list.begin();it2 != list.end();it2++)
	{
		part(*it2);
	}
}

void KviUserListView::removeAllEntries()
{
	KviPointerHashTableIterator<QString,KviUserListEntry> it(*m_pEntryDict);
	while(it.current())
	{
		//it.current()->resetAvatarConnection();
		m_pIrcUserDataBase->removeUser(it.currentKey(),
			((KviUserListEntry *)it.current())->m_pGlobalData);
		++it;
	}

	m_pEntryDict->clear();
	m_pHeadItem = 0;
	m_pTopItem = 0;
	m_iVoiceCount = 0;
	m_iHalfOpCount = 0;
	m_iChanAdminCount = 0;
	m_iChanOwnerCount = 0;
	m_iOpCount = 0;
	m_iUserOpCount = 0;

	if(m_iSelectedCount != 0)
	{
		m_iSelectedCount = 0;
		g_pMainWindow->childWindowSelectionStateChange(m_pKviWindow,false);
	}

	m_pViewArea->m_iTopItemOffset = 0;
	m_pViewArea->m_iLastScrollBarVal = 0;
	m_pViewArea->m_bIgnoreScrollBar = true;
	m_pViewArea->m_pScrollBar->setValue(0);
	m_iTotalHeight = 0;
	updateScrollBarRange();
	m_pViewArea->m_bIgnoreScrollBar = false;  // gfgf
}

void KviUserListView::partAll()
{
	removeAllEntries();
	triggerUpdate();
}

void KviUserListView::resizeEvent(QResizeEvent *)
{
	int iHeight;
	if(KVI_OPTION_BOOL(KviOption_boolShowUserListStatisticLabel))//G&N  2005
	{
		iHeight = m_pUsersLabel->sizeHint().height();
		if(iHeight < 16)
			iHeight = 16; // at least
		m_pUsersLabel->setGeometry(0,0,width(),iHeight);
	} else {
		iHeight = 0;
	}

	m_pViewArea->setGeometry(0,iHeight,width(),height() - iHeight);

	updateScrollBarRange();
}

bool KviUserListView::itemVisible(KviUserListEntry * e)
{
	KviUserListEntry * pEntry = m_pTopItem;
	int iCurTop = KVI_USERLIST_BORDER_WIDTH - m_pViewArea->m_iTopItemOffset;
	int iHeight = height();
	while(pEntry && (iCurTop < iHeight))
	{
		if(pEntry == e)
			return true;
		iCurTop += pEntry->m_iHeight;
		pEntry = pEntry->m_pNext;
	}
	return false;
}

KviUserListEntry * KviUserListView::itemAt(const QPoint & pnt, QRect * pRect)
{
	if(!m_pTopItem)
		return 0;
	if(pnt.y() < 0)
		return 0;

	int iCurTop = KVI_USERLIST_BORDER_WIDTH - m_pViewArea->m_iTopItemOffset;
	int iCurBottom = 0;
	KviUserListEntry * pEntry = m_pTopItem;
	while(pEntry && (iCurTop <= m_pViewArea->height()))
	{
		iCurBottom = iCurTop + pEntry->m_iHeight;
		if((pnt.y() >= iCurTop) && (pnt.y() < iCurBottom))
		{
			if(pRect)
			{
				pRect->setX(0);
				pRect->setY(iCurTop);
				pRect->setWidth(m_pViewArea->width());
				pRect->setHeight(pEntry->m_iHeight);
			}
			return pEntry;
		}
		iCurTop = iCurBottom;
		pEntry = pEntry->m_pNext;
	}
	return 0;
}

void KviUserListView::userStats(KviUserListViewUserStats * pStats)
{
	pStats->uTotal = m_pEntryDict->count();
	pStats->uHot = 0;
	pStats->uHotOp = 0;
	pStats->uActive = 0;
	pStats->uActiveOp = 0;
	pStats->uChanAdmin = 0;
	pStats->uChanOwner = 0;
	pStats->iAvgTemperature = 0;
	pStats->uOp = 0;
	pStats->uVoiced = 0;
	pStats->uHalfOp = 0;
	pStats->uUserOp = 0;
	pStats->uIrcOp = 0;

	KviUserListEntry * pEntry = m_pHeadItem;

	kvi_time_t curTime = kvi_unixTime();

	while(pEntry)
	{
		if(pEntry->m_lastActionTime)
		{
			unsigned int uTimeDiff = (((unsigned int)(curTime - pEntry->m_lastActionTime)) >> 6);
			if(uTimeDiff < 10)
			{
				pStats->uActive++; // the user was alive in the last ~16 mins
				if(pEntry->m_iFlags & (KviIrcUserEntry::Op | KviIrcUserEntry::ChanAdmin | KviIrcUserEntry::ChanOwner))
				{
					pStats->uActiveOp++;
					if(pEntry->m_iTemperature > 0)
					{
						pStats->uHot++;
						pStats->uHotOp++;
					}
				} else {
					if(pEntry->m_iTemperature > 0)
						pStats->uHot++;
				}
				pStats->iAvgTemperature += pEntry->m_iTemperature;
			}
		}

		if(pEntry->m_pGlobalData->isIrcOp())
			pStats->uIrcOp++;

		if(pEntry->m_iFlags & KviIrcUserEntry::ChanOwner)
			pStats->uChanOwner++;
		else {
			if(pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin)
				pStats->uChanAdmin++;
			else {
				if(pEntry->m_iFlags & KviIrcUserEntry::Op)
					pStats->uOp++;
				else {
					if(pEntry->m_iFlags & KviIrcUserEntry::HalfOp)
						pStats->uHalfOp++;
					else {
						if(pEntry->m_iFlags & KviIrcUserEntry::Voice)
							pStats->uVoiced++;
						else {
							if(pEntry->m_iFlags & KviIrcUserEntry::UserOp)
								pStats->uUserOp++;
						}
					}
				}
			}
		}
		pEntry = pEntry->m_pNext;
	}

	if(pStats->uActive > 0)
		pStats->iAvgTemperature /= ((int)pStats->uActive);
}

void KviUserListView::maybeTip(KviUserListToolTip * pTip, const QPoint & pnt)
{
	if(!KVI_OPTION_BOOL(KviOption_boolShowUserListViewToolTips))
		return;
	QRect itRect;
	KviUserListEntry * pEntry = (KviUserListEntry *)itemAt(pnt,&itRect);
	if(pEntry)
	{
		if(m_pKviWindow->console())
		{
			QString szBuffer;
			m_pKviWindow->console()->getUserTipText(pEntry->m_szNick,pEntry->m_pGlobalData,szBuffer);

			szBuffer += "<table width=\"100%\">";

			if(pEntry->m_joinTime != 0)
			{
				QString szTmp;
				QDateTime date;
				date.setTime_t(pEntry->m_joinTime);

				switch(KVI_OPTION_UINT(KviOption_uintOutputDatetimeFormat))
				{
					case 0:
						szTmp = date.toString();
						break;
					case 1:
						szTmp = date.toString(Qt::ISODate);
						break;
					case 2:
						szTmp = date.toString(Qt::SystemLocaleDate);
						break;
				}

				szBuffer += "<tr><td bgcolor=\"#F0F0F0\"><nobr><font color=\"#000000\">";
				szBuffer += __tr2qs("Joined on <b>%1</b>").arg(szTmp);
				szBuffer += "</font></nobr></td></tr>";
			}

			if(pEntry->m_lastActionTime != 0)
			{
				int iSecs = kvi_unixTime() - pEntry->m_lastActionTime;
				int iMins = iSecs / 60;
				iSecs = iSecs % 60;
				int iHours = iMins / 60;
				iMins = iMins % 60;
				szBuffer += "<tr><td bgcolor=\"#F0F0F0\"><nobr><font color=\"#000000\">";
				szBuffer += __tr2qs("Quiet for <b>%1h %2m %3s</b>").arg(iHours).arg(iMins).arg(iSecs);
				szBuffer += "</font></nobr></td></tr>";
			}

			if(pEntry->m_pGlobalData->isIrcOp())
			{
				szBuffer += "<tr><td bgcolor=\"#F0F0F0\"><nobr><font color=\"#000000\">";
				szBuffer += __tr2qs("%1 is an <b>IrcOp</b>").arg(pEntry->m_szNick);
				szBuffer += "</font></nobr></td></tr>";
			}
			szBuffer += "</table>";

			pTip->doTip(itRect,szBuffer);
		}
	}
}

KviUserListViewArea::KviUserListViewArea(KviUserListView * pPar)
: QWidget(pPar)
{
	m_pListView = pPar;
	setAutoFillBackground(false);

	m_pScrollBar = new QScrollBar(Qt::Vertical,this);
	m_pScrollBar->setObjectName("scrollbar");
	m_pScrollBar->setRange(0,0);
	m_pScrollBar->setVisible(false);
	m_pScrollBar->setValue(0);
	connect(m_pScrollBar,SIGNAL(valueChanged(int)),this,SLOT(scrollBarMoved(int)));
	m_pScrollBar->setPageStep(height());
	m_iLastScrollBarVal = 0;
	m_iTopItemOffset = 0;
	m_bIgnoreScrollBar = false;
	m_pLastEntryUnderMouse = 0;
}

KviUserListViewArea::~KviUserListViewArea()
{
}

void KviUserListViewArea::scrollBarMoved(int iNewVal)
{
	if(m_bIgnoreScrollBar)
		return;
	int iDiff = iNewVal - m_iLastScrollBarVal;
	if(m_pListView->m_pTopItem)
	{
		while(iDiff > 0)
		{
			int iNextH = (m_pListView->m_pTopItem->m_iHeight - m_iTopItemOffset);
			if(iDiff >= iNextH)
			{
				// the diff is greater than the top item visible part
				iDiff -= iNextH;
				if(m_pListView->m_pTopItem->m_pNext)
				{
					// There is a next item (offset to 0)
					m_pListView->m_pTopItem = m_pListView->m_pTopItem->m_pNext;
					m_iTopItemOffset = 0;
				} else {
					// No next item (rather a bug) (offset to the top item size)
					m_iTopItemOffset = m_pListView->m_pTopItem->m_iHeight;
					iDiff = 0;
				}
			} else {
				// just offset the top item
				m_iTopItemOffset += iDiff;
				iDiff = 0;
			}
		}

		while(iDiff < 0)
		{
			if((-iDiff) <= m_iTopItemOffset)
			{
				// just move the top item
				m_iTopItemOffset += iDiff;
				iDiff = 0;
			} else {
				iDiff += m_iTopItemOffset;
				if(m_pListView->m_pTopItem->m_pPrev)
				{
					// There is a prev item (offset to 0)
					m_pListView->m_pTopItem = m_pListView->m_pTopItem->m_pPrev;
					m_iTopItemOffset = m_pListView->m_pTopItem->m_iHeight;
				} else {
					// No prev item (rather a bug) (offset to the top item size)
					m_iTopItemOffset = 0;
					iDiff = 0;
				}
			}
		}
	}
	m_iLastScrollBarVal = iNewVal;
	update();
}

void KviUserListViewArea::paintEvent(QPaintEvent * e)
{
	// update the scroll bar
	int iWidth = width();
	if(m_pScrollBar->isVisible())
		iWidth-= m_pScrollBar->width();

	QRect r = e->rect();
	if(r.right() > iWidth)
		r.setRight(iWidth);

	QPainter p(this);

	QFontMetrics fm(p.fontMetrics());

#ifdef COMPILE_PSEUDO_TRANSPARENCY
	if(KVI_OPTION_BOOL(KviOption_boolUseCompositingForTransparency) && g_pApp->supportsCompositing())
	{
		p.save();
		p.setCompositionMode(QPainter::CompositionMode_Source);
		QColor col=KVI_OPTION_COLOR(KviOption_colorGlobalTransparencyFade);
		col.setAlphaF((float)((float)KVI_OPTION_UINT(KviOption_uintGlobalTransparencyChildFadeFactor) / (float)100));
		p.fillRect(r, col);
		p.restore();
	} else if(g_pShadedChildGlobalDesktopBackground)
	{
		QPoint pnt = m_pListView->window()->mdiParent() ? mapTo(g_pMainWindow, r.topLeft() + g_pMainWindow->mdiManager()->scrollBarsOffset()) : mapTo(m_pListView->window(), r.topLeft());
		p.drawTiledPixmap(r,*(g_pShadedChildGlobalDesktopBackground), pnt);
	} else {
#endif
		QPixmap * pix = KVI_OPTION_PIXMAP(KviOption_pixmapUserListViewBackground).pixmap();
		if(pix)
		{
			KviPixmapUtils::drawPixmapWithPainter(&p,pix,KVI_OPTION_UINT(KviOption_uintUserListPixmapAlign),r,r.width(),r.height());
		} else {
			p.fillRect(r,KVI_OPTION_COLOR(KviOption_colorUserListViewBackground));
		}
#ifdef COMPILE_PSEUDO_TRANSPARENCY
	}
#endif

	p.setClipRect(r);

	KviUserListEntry * pEntry = m_pListView->m_pTopItem;

	int iTheY = KVI_USERLIST_BORDER_WIDTH - m_iTopItemOffset;

	kvi_time_t curTime = kvi_unixTime();

	bool bShowIcons = KVI_OPTION_BOOL(KviOption_boolShowUserChannelIcons);
	bool bShowState = KVI_OPTION_BOOL(KviOption_boolShowUserChannelState);
	bool bShowGender = KVI_OPTION_BOOL(KviOption_boolDrawGenderIcons);

	while(pEntry && iTheY <= r.bottom())
	{
		int iBottom = iTheY + pEntry->m_iHeight;
		// iTheY is our top line
		// iTheX is our left corner
		// iBottom is our bottom line
		// iWidth is the width of the whole widget

		if(iBottom >= r.top())
		{
			QColor * pClrFore = 0;
			if(pEntry->m_bSelected)
			{
				QColor col = KVI_OPTION_COLOR(KviOption_colorUserListViewSelectionBackground);
				col.setAlpha(150);
				p.fillRect(0,iTheY,width(),pEntry->m_iHeight,col);
				pClrFore = &(KVI_OPTION_COLOR(KviOption_colorUserListViewSelectionForeground));
			} else if(KVI_OPTION_BOOL(KviOption_boolUseDifferentColorForOwnNick) && m_pListView->m_pKviWindow->connection())
			{
				if(pEntry->m_szNick==m_pListView->m_pKviWindow->connection()->currentNickName())
				{
					pClrFore = &(KVI_OPTION_COLOR(KviOption_colorUserListViewOwnForeground));
				}
			}

			if(!pClrFore)
			{
				// FIXME:
				//
				// this is slow... VERY slow when one has a lot of registered users.
				//   (this is NOT a simple lookup in the user db... it is a mask match)
				// if we REALLY need to use custom colors for regged users then
				// they should be updated ONCE and stored (cached) in the KviUserListEntry structure
				//
				if(m_pListView->m_pKviWindow->connection()->userDataBase()->haveCustomColor(pEntry->m_szNick))
				{
					pClrFore = m_pListView->m_pKviWindow->connection()->userDataBase()->customColor(pEntry->m_szNick);
				}

				if(!pClrFore)
				{
					if(pEntry->m_iFlags == 0 && !pEntry->globalData()->isIrcOp())
					{
						pClrFore = &(KVI_OPTION_COLOR(KviOption_colorUserListViewNormalForeground));
					} else {
						pClrFore = &(KVI_OPTION_COLOR((pEntry->globalData()->isIrcOp()) ? \
							KviOption_colorUserListViewIrcOpForeground :
							((pEntry->m_iFlags & KviIrcUserEntry::ChanOwner) ? \
							KviOption_colorUserListViewChanOwnerForeground : ((pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin) ? \
							KviOption_colorUserListViewChanAdminForeground : ((pEntry->m_iFlags & KviIrcUserEntry::Op) ? \
							KviOption_colorUserListViewOpForeground : ((pEntry->m_iFlags & KviIrcUserEntry::HalfOp) ? \
							KviOption_colorUserListViewHalfOpForeground : ((pEntry->m_iFlags & KviIrcUserEntry::Voice) ? \
							KviOption_colorUserListViewVoiceForeground : KviOption_colorUserListViewUserOpForeground)))))));
					}
				}
			}

			int iTheX = KVI_USERLIST_BORDER_WIDTH + 1;

			int iAvatarAndTextX = iTheX;

			if(bShowGender)
				iAvatarAndTextX += 11;
			if(bShowIcons)
				iAvatarAndTextX += 18;
			if(bShowState)
				iAvatarAndTextX += 11;

			if(KVI_OPTION_BOOL(KviOption_boolUserListViewDrawGrid))
			{
				// the grid
				switch(KVI_OPTION_UINT(KviOption_uintUserListViewGridType))
				{
					case KVI_USERLISTVIEW_GRIDTYPE_PLAINGRID:
					case KVI_USERLISTVIEW_GRIDTYPE_DOTGRID:
						p.setPen(QPen(KVI_OPTION_COLOR(KviOption_colorUserListViewGrid),0,
							(KVI_OPTION_UINT(KviOption_uintUserListViewGridType) == KVI_USERLISTVIEW_GRIDTYPE_DOTGRID) ? Qt::DotLine : Qt::SolidLine));
						p.drawLine(0,iBottom - 1,iWidth,iBottom - 1);
						if(bShowState || bShowIcons)
							p.drawLine(iAvatarAndTextX,iBottom - 1,iAvatarAndTextX,iTheY);
					break;
					default: // KVI_USERLISTVIEW_GRIDTYPE_3DGRID and KVI_USERLISTVIEW_GRIDTYPE_3DBUTTONS
						if(!pEntry->m_bSelected)
						{
							p.setPen(QPen(KVI_OPTION_COLOR(KviOption_colorUserListViewGrid),0 /*,QPen::DotLine*/));
							if((bShowState || bShowIcons) && (KVI_OPTION_UINT(KviOption_uintUserListViewGridType) == KVI_USERLISTVIEW_GRIDTYPE_3DGRID))
								p.drawLine(iAvatarAndTextX,iBottom - 1,iAvatarAndTextX,iTheY);
							p.setPen(palette().shadow().color());
							p.drawLine(0,iBottom - 1,iWidth,iBottom - 1);
							p.setPen(palette().light().color());
							p.drawLine(0,iTheY,iWidth,iTheY);
							iTheY--;
						}
						iTheY++;
					break;
				}
				iAvatarAndTextX += 3;
			} else {
				iAvatarAndTextX += 1;
			}

			if(pEntry->globalData()->isAway())
			{
				QRgb rgb2 = pClrFore->rgb();
				QRgb rgb1 = KVI_OPTION_COLOR(KviOption_colorUserListViewAwayForeground).rgb();
				p.setPen(QColor(
					((qRed(rgb1)*2) + qRed(rgb2)) / 3,
					((qGreen(rgb1)*2) + qGreen(rgb2)) / 3,
					((qBlue(rgb1)*2) + qBlue(rgb2)) / 3)
				);
			} else {
				p.setPen(*pClrFore);
			}
			iTheY += 2;

			if(KVI_OPTION_BOOL(KviOption_boolShowAvatarsInUserlist))
			{
				if(pEntry->m_pAvatarPixmap)
				{
					QPixmap * pPix = pEntry->m_pAvatarPixmap->pixmap();
					p.drawPixmap(iAvatarAndTextX,iTheY,*pPix);
					iTheY += pPix->height() + 1;
				}
			}

			if(bShowGender)
			{
				if(pEntry->globalData()->isIrcOp())
				{
					QPixmap * pIco = g_pIconManager->getSmallIcon(KviIconManager::SexIrcOp);
					p.drawPixmap(iTheX,iTheY+(m_pListView->m_iFontHeight-11)/2,*pIco);
				} else if(pEntry->globalData()->gender()!=KviIrcUserEntry::Unknown) {
					QPixmap * pIco = g_pIconManager->getSmallIcon((pEntry->globalData()->gender()==KviIrcUserEntry::Male) ? KviIconManager::SexMale : KviIconManager::SexFemale);
					p.drawPixmap(iTheX,iTheY+(m_pListView->m_iFontHeight-11)/2,*pIco);
				} else if(pEntry->globalData()->isBot()) {
					QPixmap * pIco = g_pIconManager->getSmallIcon(KviIconManager::SexBot);
					p.drawPixmap(iTheX,iTheY+(m_pListView->m_iFontHeight-11)/2,*pIco);
				}
				iTheX +=11;
			}

			if(bShowState)
			{
				if(pEntry->m_lastActionTime)
				{
					// the g_pUserChanStatePixmap is 36 x 80 pixels
					// divided into 6 rows of 5 pixmaps
					// row 0 is hot, row 5 is cold
					// left is most active, right is least active
					// e->m_iTemperature is a signed short, negative values are cold
					// e->m_lastActionTime is the time of the last action (eventually 0, if not known)
					// 6 bit right shift is an aprox division for 64 : really aprox minutes
					unsigned int uTimeDiff = (((unsigned int)(curTime - pEntry->m_lastActionTime)) >> 6);
					if(uTimeDiff < 16)
					{
						//p.drawRect(iTheX,iTheY + 2,10,e->m_iHeight - 4);
						static int iXOffTable[16] =
						{
							0  , 8  , 16 , 16 ,
							24 , 24 , 24 , 24 ,
							32 , 32 , 32 , 32 ,
							32 , 32 , 32 , 32
						};
						// the temperature now
						// temp > 100 is hot (offset y = 0)
						// temp < -100 is cold (offset y = 80)
						// temp > 30 is half-hot (offset y = 16)
						// temp < -30 is half-cold (offset y = 64)
						// temp > 0 is a-bit-hot (offset y = 32)
						// temp < 0 is a-bit-cold (offset y = 48)

						if(pEntry->m_iTemperature > KVI_MID_TEMPERATURE)
						{
							if(pEntry->m_iTemperature > KVI_HALF_HOT_TEMPERATURE)
							{
								if(pEntry->m_iTemperature > KVI_HOT_TEMPERATURE)
								{
									// hot
									p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],0,8,16);
								} else {
									// half-hot
									p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],16,8,16);
								}
							} else {
								// bit-hot
								p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],32,8,16);
							}
						} else {
							if(pEntry->m_iTemperature < KVI_HALF_COLD_TEMPERATURE)
							{
								if(pEntry->m_iTemperature < KVI_COLD_TEMPERATURE)
								{
									// cold
									p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],80,8,16);
								} else {
									// half-cold
									p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],64,8,16);
								}
							} else {
								// bit-cold
								p.drawPixmap(iTheX,iTheY,*g_pUserChanStatePixmap,iXOffTable[uTimeDiff],48,8,16);
							}
						}
					}
				}
				iTheX += 11;
			}

			if(bShowIcons)
			{
				//p.drawRect(iTheX,iTheY + 2,18,e->m_iHeight - 4);
				if(pEntry->m_iFlags != 0)
				{
					QPixmap * pIco = g_pIconManager->getSmallIcon( \
											pEntry->globalData()->isAway() ? \
												( \
													(pEntry->m_iFlags & KviIrcUserEntry::ChanOwner) ? \
													KviIconManager::ChanOwnerAway : ((pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin) ? \
													KviIconManager::ChanAdminAway : ((pEntry->m_iFlags & KviIrcUserEntry::Op) ? \
													KviIconManager::OpAway : ((pEntry->m_iFlags & KviIrcUserEntry::HalfOp) ? \
													KviIconManager::HalfOpAway : ((pEntry->m_iFlags & KviIrcUserEntry::Voice) ? \
													KviIconManager::VoiceAway : KviIconManager::UserOpAway))))
												) \
											: \
												( \
													(pEntry->m_iFlags & KviIrcUserEntry::ChanOwner) ? \
													KviIconManager::ChanOwner : ((pEntry->m_iFlags & KviIrcUserEntry::ChanAdmin) ? \
													KviIconManager::ChanAdmin : ((pEntry->m_iFlags & KviIrcUserEntry::Op) ? \
													KviIconManager::Op : ((pEntry->m_iFlags & KviIrcUserEntry::HalfOp) ? \
													KviIconManager::HalfOp : ((pEntry->m_iFlags & KviIrcUserEntry::Voice) ? \
													KviIconManager::Voice : KviIconManager::UserOp)))) \
												) \
										);
					p.drawPixmap(iTheX,iTheY+(fm.lineSpacing()-16/*size of small icon*/)/2,*pIco);
				}
				iTheX +=18;
				p.drawText(iAvatarAndTextX,iTheY+fm.lineSpacing()-1,pEntry->m_szNick);
			} else {

				char cFlag = m_pListView->getUserFlag(pEntry);
				if(cFlag)
				{
					QString szTmp = QChar(cFlag);
					szTmp += pEntry->m_szNick;
					p.drawText(iAvatarAndTextX,iTheY+fm.lineSpacing()-1,szTmp);
				} else {
					p.drawText(iAvatarAndTextX,iTheY+fm.lineSpacing()-1,pEntry->m_szNick);
				}
			}
		}

		iTheY = iBottom;
		pEntry = pEntry->m_pNext;
	}

}

void KviUserListViewArea::resizeEvent(QResizeEvent *)
{
	int iScr = m_pScrollBar->sizeHint().width();
	m_pScrollBar->setGeometry(width() - iScr,0,iScr,height());
	m_pScrollBar->setPageStep(height());
	m_pScrollBar->setSingleStep(m_pListView->m_iFontHeight - 1);
}

void KviUserListViewArea::mousePressEvent(QMouseEvent * e)
{
	setFocus();
	if(e->button() & Qt::LeftButton)
	{
		KviUserListEntry * pEntry = m_pListView->itemAt(e->pos());
		if(pEntry)
		{
			if(e->modifiers() & Qt::ShiftModifier)
			{
				// Multiselect mode
				if(!pEntry->m_bSelected)
					m_pListView->m_iSelectedCount++;
				pEntry->m_bSelected = true;

				if(m_pListView->m_iSelectedCount == 1)
					g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			} else if(e->modifiers() & Qt::ControlModifier)
			{
				// Invert mode
				if(!pEntry->m_bSelected)
					m_pListView->m_iSelectedCount++;
				else m_pListView->m_iSelectedCount--;

				pEntry->m_bSelected = !pEntry->m_bSelected;
				if(m_pListView->m_iSelectedCount == 0)
					g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
				else if(m_pListView->m_iSelectedCount == 1)
					g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			} else {
				// Single select mode
				bool bThereWasSelection = false;
				if(m_pListView->m_iSelectedCount > 0)
				{
					KviUserListEntry * pAux = m_pListView->m_pHeadItem;
					while(pAux)
					{
						pAux->m_bSelected = false;
						pAux = pAux->m_pNext;
					}
					bThereWasSelection = true;
				}
				pEntry->m_bSelected = true;
				m_pListView->m_iSelectedCount = 1;

				if(!bThereWasSelection)
					g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			}
		}
		m_pLastEntryUnderMouse = pEntry;
	} else if(e->button() & Qt::RightButton)
	{
		KviUserListEntry * pEntry = m_pListView->itemAt(e->pos());
		if(pEntry)
		{
			if(!pEntry->m_bSelected){
				pEntry->m_bSelected = true;
				m_pListView->m_iSelectedCount=1;

				KviPointerHashTableIterator<QString,KviUserListEntry> it(*(m_pListView->m_pEntryDict));
				while(it.current())
				{
					if(it.current() != pEntry)
						((KviUserListEntry *)it.current())->m_bSelected = false;
					++it;
				}
			}
			if(m_pListView->m_iSelectedCount == 1)
				g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
			update();
		}
		m_pListView->emitRightClick();
	}
}

void KviUserListViewArea::keyPressEvent(QKeyEvent * e)
{
	if(e->key() == Qt::Key_Escape)
	{
		if(m_pListView->m_pKviWindow->input())
			((QWidget*)(m_pListView->m_pKviWindow->input()))->setFocus();
	} else if(e->key() == Qt::Key_Up)
	{
		if(m_pListView->m_iSelectedCount == 0)
		{
			KviUserListEntry * pNick = m_pListView->m_pHeadItem;
			if(pNick)
			{
				pNick->m_bSelected = true;
				m_pListView->ensureVisible(pNick->nick());
			}
		} else {
			KviUserListEntry * pAux = m_pListView->m_pHeadItem;
			KviUserListEntry * pNick=0;
			while(pAux)
			{
				if(pAux->m_bSelected)
				{
					if(e->modifiers() & Qt::ShiftModifier)
					{
						if(pAux != m_pListView->m_pHeadItem)
						{
							m_pListView->m_iSelectedCount++;
							pNick = pAux->m_pPrev;
						}
						break;
					} else {
						pAux->m_bSelected=false;

						if(!pNick)
						{
							if(pAux == m_pListView->m_pHeadItem)
								pNick=m_pListView->m_pTailItem;
							else
								pNick = pAux->m_pPrev;
						}
					}
				}
				pAux = pAux->m_pNext;
			}

			if(pNick)
			{
				pNick->m_bSelected=true;
				m_pListView->ensureVisible(pNick->nick());
			}
		}
		g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
		update();
	} else if(e->key() == Qt::Key_Down)
	{
		if(m_pListView->m_iSelectedCount == 0)
		{
			KviUserListEntry * pNick = m_pListView->m_pHeadItem;
			if(pNick)
			{
				pNick->m_bSelected = true;
				m_pListView->ensureVisible(pNick->nick());
			}
		} else {
			KviUserListEntry * pAux = m_pListView->m_pTailItem;
			KviUserListEntry * pNick=0;
			while(pAux)
			{
				if(pAux->m_bSelected)
				{
					if(e->modifiers() & Qt::ShiftModifier)
					{
						if(pAux != m_pListView->m_pTailItem)
						{
							m_pListView->m_iSelectedCount++;
							pNick = pAux->m_pNext;
						}
						break;
					} else {
						pAux->m_bSelected=false;

						if(!pNick)
						{
							if(pAux == m_pListView->m_pTailItem)
								pNick=m_pListView->m_pHeadItem;
							else
								pNick = pAux->m_pNext;
						}
					}
				}
				pAux = pAux->m_pPrev;
			}

			if(pNick)
			{
				pNick->m_bSelected=true;
				m_pListView->ensureVisible(pNick->nick());
			}
		}
		g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
		update();
	} else if(e->key() == Qt::Key_PageUp)
	{
		if(m_pListView->m_iSelectedCount == 0)
		{
			KviUserListEntry * pNick = m_pListView->m_pHeadItem;
			if(pNick)
			{
				pNick->m_bSelected = true;
				m_pListView->ensureVisible(pNick->nick());
			}
		} else {
			KviUserListEntry * pAux = m_pListView->m_pHeadItem;
			KviUserListEntry * pNick=0;
			while(pAux)
			{
				if(pAux->m_bSelected)
				{
					if(e->modifiers() & Qt::ShiftModifier)
					{
						int i=10;
						pNick = pAux;
						while(pNick != m_pListView->m_pHeadItem && i > 0)
						{
							m_pListView->m_iSelectedCount++;
							pNick->m_bSelected=true;
							pNick = pNick->m_pPrev;
							i--;
						}
						if(pNick)
							pNick->m_bSelected=true;
						break;
					} else {
						pAux->m_bSelected=false;

						if(!pNick)
						{
							pNick = pAux;
							for(int i=10; i>0; i--)
							{
								if(pNick == m_pListView->m_pHeadItem)
								{
									pNick=m_pListView->m_pTailItem;
									i = 0;
								} else {
									pNick = pNick->m_pPrev;
								}
							}
						}
					}
					if(pNick)
						pNick->m_bSelected=true;
				}
				pAux = pAux->m_pNext;
			}

			if(pNick)
				m_pListView->ensureVisible(pNick->nick());
		}
		g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
		update();
	} else if(e->key() == Qt::Key_PageDown)
	{
		if(m_pListView->m_iSelectedCount == 0)
		{
			KviUserListEntry * pNick = m_pListView->m_pHeadItem;
			if(pNick)
			{
				pNick->m_bSelected = true;
				m_pListView->ensureVisible(pNick->nick());
			}
		} else {
			KviUserListEntry * pAux = m_pListView->m_pTailItem;
			KviUserListEntry * pNick=0;
			while(pAux)
			{
				if(pAux->m_bSelected)
				{
					if(e->modifiers() & Qt::ShiftModifier)
					{
						int i=10;
						pNick = pAux;
						while(pNick != m_pListView->m_pTailItem && i > 0)
						{
							m_pListView->m_iSelectedCount++;
							pNick->m_bSelected=true;
							pNick = pNick->m_pNext;
							i--;
						}
						if(pNick)
							pNick->m_bSelected=true;
						break;
					} else {
						pAux->m_bSelected=false;

						if(!pNick)
						{
							pNick = pAux;
							for(int i=10; i>0; i--)
							{
								if(pNick == m_pListView->m_pTailItem)
								{
									pNick=m_pListView->m_pHeadItem;
									i = 0;
								} else {
									pNick = pNick->m_pNext;
								}
							}
						}
					}
					if(pNick)
						pNick->m_bSelected=true;
				}
				pAux = pAux->m_pPrev;
			}

			if(pNick)
				m_pListView->ensureVisible(pNick->nick());
		}
		g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
		update();
	} else if(e->matches(QKeySequence::SelectAll)) {
		KviUserListEntry * pAux = m_pListView->m_pHeadItem;
		while(pAux)
		{
			pAux->m_bSelected = true;
			pAux = pAux->m_pNext;
		}

		g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
		update();
	} else {
		QString szKey = e->text();
		if(!szKey.isEmpty())
		{
			KviUserListEntry * pNick = 0;
			KviUserListEntry * pAux = m_pListView->m_pHeadItem;

			if(m_pListView->selectedCount() == 1)
			{
				bool bFoundSelected=false;
				while(pAux)
				{
					if(pAux->nick().indexOf(szKey,0,Qt::CaseInsensitive)==0)
					{
						//match
						if(bFoundSelected)
							break;
						if(pAux->m_bSelected)
							bFoundSelected=true;
					}
					pAux = pAux->m_pNext;
				}
				if(!pAux)
					pAux = m_pListView->m_pHeadItem;
			}

			while(pAux)
			{
				//qDebug("%s %s %i %s %i",__FILE__,__FUNCTION__,__LINE__,aux->nick().toUtf8().data(),aux->nick().find(szKey,0,0));
				if(pAux->nick().indexOf(szKey,0,Qt::CaseInsensitive)==0)
				{
					pNick = pAux;
					break;
				}
				pAux = pAux->m_pNext;
			}

			if(pNick)
			{
				m_pListView->select(pNick->nick());
				m_pListView->ensureVisible(pNick->nick());
			}
		}
	}
}

void KviUserListViewArea::mouseDoubleClickEvent(QMouseEvent *)
{
	m_pListView->emitDoubleClick();
}

void KviUserListViewArea::mouseMoveEvent(QMouseEvent * e)
{
	if(!(e->buttons() & Qt::LeftButton))
		return;

	KviUserListEntry * pEntry = m_pListView->itemAt(e->pos());
	if(!pEntry)
	{
		if(e->pos().y() < KVI_USERLIST_BORDER_WIDTH)
		{
			KviUserListEntry * pTop = m_pListView->m_pTopItem;
			if(pTop)
			{
				pEntry=pTop;
				m_pScrollBar->setValue(m_pScrollBar->value() - pTop->m_iHeight);
				if(m_pListView->m_pTopItem != pTop)
				{
					pEntry=m_pListView->m_pTopItem;
				}
			}

		} else if(e->pos().y() > (height() - KVI_USERLIST_BORDER_WIDTH))
		{
			KviUserListEntry * pBottom = m_pListView->m_pTopItem;
			if(pBottom)
			{
				int iTheY = KVI_USERLIST_BORDER_WIDTH - m_iTopItemOffset;
				while(pBottom && (iTheY < height()))
				{
					iTheY += pBottom->m_iHeight;
					pBottom = pBottom->m_pNext;
				}

				if(!pBottom)
					pBottom = m_pListView->m_pTailItem;
				if(pBottom)
				{
					pEntry=pBottom;
					m_pScrollBar->setValue(m_pScrollBar->value() + pBottom->m_iHeight);
				}
			}
		}
	}

	if(!pEntry || !m_pLastEntryUnderMouse || pEntry == m_pLastEntryUnderMouse)
		return;

	if(e->modifiers() & Qt::ControlModifier)
	{
		if(pEntry->m_bSelected)
			m_pListView->m_iSelectedCount--;
		else m_pListView->m_iSelectedCount++;
			pEntry->m_bSelected = ! pEntry->m_bSelected;
		if(m_pListView->m_iSelectedCount == 0)
			g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
		else if(m_pListView->m_iSelectedCount == 1)
				g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);

		m_pLastEntryUnderMouse=pEntry;
	} else {
		KviUserListEntry * pCurEntry = m_pListView->m_pHeadItem;
		for(uint uFound=0; pCurEntry &&  uFound<2; pCurEntry=pCurEntry->next())
		{
			if(pCurEntry == pEntry || pCurEntry == m_pLastEntryUnderMouse)
				uFound++;
			if(uFound)
			{
				if(!pCurEntry->m_bSelected)
				{
					m_pListView->m_iSelectedCount++;
					pCurEntry->m_bSelected = true;
					if(m_pListView->m_iSelectedCount == 1)
					g_pMainWindow->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				}
			}
		}
	}
	update();
}

void KviUserListViewArea::mouseReleaseEvent(QMouseEvent *)
{
	m_pLastEntryUnderMouse = 0;
}

void KviUserListViewArea::wheelEvent(QWheelEvent * e)
{
	static bool bHere = false; // Qt4(<= 4.2.2) has a nasty bug that makes the re-sent wheelEvent to cause infinite recursion
	if(bHere)
		return;
	bHere = true;
	g_pApp->sendEvent(m_pScrollBar,e);
	bHere = false;
}

#ifndef COMPILE_USE_STANDALONE_MOC_SOURCES
#include "KviUserListView.moc"
#endif //!COMPILE_USE_STANDALONE_MOC_SOURCES
