//=============================================================================
//
//   File : kvi_userlistview.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-2004 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 __KVIRC__
#define _KVI_USERLISTVIEW_CPP_

#include "kvi_debug.h"
#include "kvi_userlistview.h"
#include "kvi_settings.h"
#include "kvi_locale.h"
#include "kvi_options.h"
#include "kvi_defaults.h"
#include "kvi_iconmanager.h"
#include "kvi_regusersdb.h"
#include "kvi_parameterlist.h"
#include "kvi_window.h"
#include "kvi_console.h"
#include "kvi_app.h"
#include "kvi_useraction.h"
#include "kvi_qstring.h"
#include "kvi_frame.h"
#include "kvi_mdimanager.h"
#include "kvi_kvs_eventtriggers.h"
#include "kvi_toolwindows_container.h"
#include "kvi_doublebuffer.h"
#include "kvi_stringconversion.h"
#include "kvi_ircconnection.h"
#include "kvi_ircconnectionserverinfo.h"

#include <tqlabel.h>
#include <tqscrollbar.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqfontmetrics.h>
#include <tqdatetime.h>
#include <tqmime.h>
#include "kvi_styled_controls.h"
#include <tqevent.h>

#ifdef COMPILE_PSEUDO_TRANSPARENCY
	extern TQPixmap * g_pShadedChildGlobalDesktopBackground;
#endif


// kvi_app.cpp (loaded and destroyed by KviIconManager)
extern TQPixmap * g_pUserChanStatePixmap;

// Yet another really complex widget :)

#define KVI_USERLIST_BORDER_WIDTH 2

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

KviUserListToolTip::KviUserListToolTip(KviUserListView * v,KviUserListViewArea * a)
: KviTalToolTip(a)
{
	m_pListView = v;
}

KviUserListToolTip::~KviUserListToolTip()
{
}

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




KviUserListEntry::KviUserListEntry(KviUserListView * parent,const TQString &nick,
	KviIrcUserEntry * e,short int iFlags,bool bJoinTimeUnknown)
{
	m_pListView      = parent;
	m_szNick         = nick;
	m_pGlobalData    = e;
	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;
	
	recalcSize();
}

KviUserListEntry::~KviUserListEntry()
{
}

bool KviUserListEntry::color(TQColor& color)
{
	//
	// FIXME: Unused ?
	//
	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)
	{
		color=KVI_OPTION_COLOR(KviOption_colorUserListViewNormalForeground);
		return true;
	} else {
		color = KVI_OPTION_COLOR((m_iFlags & KVI_USERFLAG_CHANOWNER) ? \
			KviOption_colorUserListViewChanOwnerForeground : ((m_iFlags & KVI_USERFLAG_CHANADMIN) ? \
			KviOption_colorUserListViewChanAdminForeground : ((m_iFlags & KVI_USERFLAG_OP) ? \
			KviOption_colorUserListViewOpForeground : ((m_iFlags & KVI_USERFLAG_HALFOP) ? \
			KviOption_colorUserListViewHalfOpForeground : ((m_iFlags & KVI_USERFLAG_VOICE) ? \
			KviOption_colorUserListViewVoiceForeground : KviOption_colorUserListViewUserOpForeground)))));
			return true;
	}
	return true;
}

void KviUserListEntry::recalcSize()
{
	KviAvatar * av = m_pGlobalData->avatar();
	m_iHeight = m_pListView->m_iFontHeight;
	if(KVI_OPTION_BOOL(KviOption_boolShowUserChannelIcons) && (m_iHeight < 20))m_iHeight = 20;
	
	if(!KVI_OPTION_BOOL(KviOption_boolDisableAvatars))//G&N  2005
	{
		if(av)
		{
			if( KVI_OPTION_BOOL(KviOption_boolScaleAvatars) &&
					(
						!KVI_OPTION_BOOL(KviOption_boolDoNotStretchAvatars) ||
						(static_cast<unsigned int>(av->pixmap()->width()) > KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth)) ||
						(static_cast<unsigned int>(av->pixmap()->height()) > KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight))
					)
				)
			{
				TQPixmap * pix = av->scaledPixmap(KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth), KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight));
			
				m_iHeight += pix->height();
			}
			else
			{
				m_iHeight += av->pixmap()->height();
			}
		}
	}
	m_iHeight += 3;
}

///////////////////////////////////////////////



KviUserListView::KviUserListView(TQWidget * parent,KviWindowToolPageButton* button,KviIrcUserDataBase * db,KviWindow * pWnd,int dictSize,const TQString & label_text,const char * name)
: KviWindowToolWidget(parent,button)
{
	setAutoDelete(0);
	m_pKviWindow  = pWnd;
	m_pEntryDict  = new KviPointerHashTable<TQString,KviUserListEntry>(dictSize,false);
	m_pEntryDict->setAutoDelete(true);

	m_pUsersLabel = new TQLabel(this,"userslabel");
	KviTalToolTip::add(m_pUsersLabel,label_text);

	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 = db;
	m_iTotalHeight = 0;
	m_ibEntries   = 0;
	m_ieEntries   = 0;
	m_iIEntries   = 0;
	m_iSelectedCount = 0;
//	setBackgroundMode(TQWidget::NoBackground);
	applyOptions();
	registerSelf();
}

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

void KviUserListView::emitRightClick()
{
	int ev = -1;
	switch(m_pKviWindow->type())
	{
		case KVI_WINDOW_TYPE_CHANNEL: ev = KviEvent_OnChannelNickPopupRequest;  break;
		case KVI_WINDOW_TYPE_QUERY:   ev = KviEvent_OnQueryNickPopupRequest;    break;
		case KVI_WINDOW_TYPE_CONSOLE: ev = KviEvent_OnNotifyListPopupRequest;   break;
		default:                      ev = KviEvent_OnNickLinkPopupRequest;     break; // this should actually never happen
	}
	if(ev > -1)
	{
		if(KviKvsEventManager::instance()->hasAppHandlers(ev))
		{
			TQString nicks;
			appendSelectedNicknames(nicks);
			KviKvsVariantList vList;
			vList.append(nicks);
			KviKvsEventManager::instance()->trigger(ev,m_pKviWindow,&vList);
		} else {
			g_pApp->checkSuggestRestoreDefaultScript();
		}
	}
}

void KviUserListView::emitDoubleClick()
{
	int ev = -1;
	switch(m_pKviWindow->type())
	{
		case KVI_WINDOW_TYPE_CHANNEL: ev = KviEvent_OnChannelNickDefaultActionRequest;  break;
		case KVI_WINDOW_TYPE_QUERY:   ev = KviEvent_OnQueryNickDefaultActionRequest;    break;
		case KVI_WINDOW_TYPE_CONSOLE: ev = KviEvent_OnNotifyListDefaultActionRequest;   break;
		default:                      ev = KviEvent_OnNickLinkDefaultActionRequest;     break; // this should actually never happen
	}
	if(ev > -1)
	{
		if(KviKvsEventManager::instance()->hasAppHandlers(ev))
		{
			TQString nicks;
			appendSelectedNicknames(nicks);
			KviKvsVariantList vList;
			vList.append(nicks);
			KviKvsEventManager::instance()->trigger(ev,m_pKviWindow,&vList);
		} else {
			g_pApp->checkSuggestRestoreDefaultScript();
		}
	}
}

void KviUserListView::updateScrollBarRange()
{
	int max = m_iTotalHeight - (m_pViewArea->height() - (KVI_USERLIST_BORDER_WIDTH * 2));
	m_pViewArea->m_pScrollBar->setRange(0,max > 0 ? max : 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));
	TQFontMetrics fm(KVI_OPTION_FONT(KviOption_fontUserListView));
	m_iFontHeight = fm.lineSpacing();
	KviUserListEntry * e = m_pHeadItem;
	m_iTotalHeight = 0;
	while(e)
	{
		e->recalcSize();
		m_iTotalHeight += e->m_iHeight;
		e = e->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->isUpdatesEnabled();
	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 type, int num)
{
	switch (type)
	{
		case 'b':
			m_ibEntries = num;
		break;
		case 'e':
			m_ieEntries = num;
		break;
		case 'I':
			m_iIEntries = num;
		break;
	}
	updateUsersLabel();
}

void KviUserListView::completeNickBashLike(const TQString &begin,KviPointerList<TQString> *l,bool bAppendMask)
{
	KviUserListEntry * entry = m_pHeadItem;

	while(entry)
	{
		if(KviTQString::equalCIN(begin,entry->m_szNick,begin.length()))
		{
			if(bAppendMask)
			{
				TQString * s = new TQString();
				KviTQString::sprintf(*s,"%Q!%Q@%Q",&(entry->m_szNick),&(entry->m_pGlobalData->user()),&(entry->m_pGlobalData->host()));
				l->append(s);
			} else
				l->append(new TQString(entry->m_szNick));
		}
		entry = entry->m_pNext;
	}
}

bool KviUserListView::completeNickStandard(const TQString &begin,const TQString &skipAfter,TQString &buffer,bool bAppendMask)
{
	KviUserListEntry * entry = m_pHeadItem;

	if(!skipAfter.isEmpty())
	{
		while(entry)
		{
			if(KviTQString::equalCI(skipAfter,entry->m_szNick))
			{
				entry = entry->m_pNext;
				break;
			}
			entry = entry->m_pNext;
		}
	}

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

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

	// Ok...now the real completion
	while(entry)
	{
		if(entry->m_szNick.length() >= begin.length())
		{
			int result = KviTQString::cmpCIN(begin,entry->m_szNick,begin.length());
			if(result == 0)
			{
				// This is ok.
				buffer = entry->m_szNick;
				if(bAppendMask)
				{
					buffer += entry->m_pGlobalData->user();
					buffer += entry->m_pGlobalData->host();
				}
				return true;
			} else if(result < 0)
			{
				// No match...begin is lower than the current entry
				if(entry->m_iFlags == 0)return false;
				else {
					int flags = entry->m_iFlags;
					// skip the current flag
					while(entry)
					{
						if(entry->m_iFlags != flags)break;
						entry = entry->m_pNext;
					}
					continue;
				}
			}
		}
		entry = entry->m_pNext;
	}

	return false;
}


void KviUserListView::insertUserEntry(const TQString &nick,KviUserListEntry * e)
{
	// Complex insertion task :)

	m_pEntryDict->insert(nick,e);
	m_iTotalHeight += e->m_iHeight;

	bool bGotTopItem = false;

	int flag = 0;
	if(e->m_iFlags != 0)
	{
		if(e->m_iFlags & KVI_USERFLAG_USEROP)
		{
			flag = KVI_USERFLAG_USEROP;
			m_iUserOpCount++;
		}
		if(e->m_iFlags & KVI_USERFLAG_VOICE)
		{
			flag = KVI_USERFLAG_VOICE;
			m_iVoiceCount++;
		}
		if(e->m_iFlags & KVI_USERFLAG_HALFOP)
		{
			flag = KVI_USERFLAG_HALFOP;
			m_iHalfOpCount++;
		}
		if(e->m_iFlags & KVI_USERFLAG_OP)
		{
			flag = KVI_USERFLAG_OP;
			m_iOpCount++;
		}
		if(e->m_iFlags & KVI_USERFLAG_CHANADMIN)
		{
			flag = KVI_USERFLAG_CHANADMIN;
			m_iChanAdminCount++;
		}
		if(e->m_iFlags & KVI_USERFLAG_CHANOWNER)
		{
			flag = KVI_USERFLAG_CHANOWNER;
			m_iChanOwnerCount++;
		}
	}


	if(m_pHeadItem)
	{
		KviUserListEntry * entry = m_pHeadItem;

		if(!(e->m_iFlags & KVI_USERFLAG_CHANOWNER))
		{
			// the new user is not a channel owner...
			// skip the channel owners
			while(entry && (entry->m_iFlags & KVI_USERFLAG_CHANOWNER))
			{
				if(entry == m_pTopItem)bGotTopItem = true;
				entry = entry->m_pNext;
			}

			if(!(e->m_iFlags & KVI_USERFLAG_CHANADMIN))
			{
				// the new user is not a channel admin...
				// skip chan admins
				while(entry && (entry->m_iFlags & KVI_USERFLAG_CHANADMIN))
				{
					if(entry == m_pTopItem)bGotTopItem = true;
					entry = entry->m_pNext;
				}
	
				// is operator ?
				if(!(e->m_iFlags & KVI_USERFLAG_OP))
				{
					// the new user is not an op...
					// skip ops
					while(entry && (entry->m_iFlags & KVI_USERFLAG_OP))
					{
						if(entry == m_pTopItem)bGotTopItem = true;
						entry = entry->m_pNext;
					}
	
					// is half oped ?
					if(!(e->m_iFlags & KVI_USERFLAG_HALFOP))
					{
						// nope , skip halfops
						while(entry && (entry->m_iFlags & KVI_USERFLAG_HALFOP))
						{
							if(entry == m_pTopItem)bGotTopItem = true;
							entry = entry->m_pNext;
						}
	
						// is voiced ?
						if(!(e->m_iFlags & KVI_USERFLAG_VOICE))
						{
							// nope , not voiced so skip voiced users
							while(entry && (entry->m_iFlags & KVI_USERFLAG_VOICE))
							{
								if(entry == m_pTopItem)bGotTopItem = true;
								entry = entry->m_pNext;
							}
	
							// is userop'd?
							if(!(e->m_iFlags & KVI_USERFLAG_USEROP))
							{
								// nope , skip userops
								while(entry && (entry->m_iFlags & KVI_USERFLAG_USEROP))
								{
									if(entry == m_pTopItem)bGotTopItem = true;
									entry = entry->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: the chan owners are first in the list
		
		// now strcmp within the current user-flag group...
		while(entry && (KviTQString::cmpCI(entry->m_szNick,e->m_szNick) < 0) &&
				((entry->m_iFlags & flag) || (flag == 0)))
		{
			if(entry == m_pTopItem)bGotTopItem = true;
			entry = entry->m_pNext;
		}
		if(entry)
		{
			// inserting
			e->m_pNext = entry;
			e->m_pPrev = entry->m_pPrev;
			if(e->m_pPrev == 0)m_pHeadItem = e;
			else e->m_pPrev->m_pNext = e;
			entry->m_pPrev = e;
			// 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((e == m_pHeadItem) && (m_pTopItem == e->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 = e;
					triggerUpdate();
				} else {
					// invisible insertion
					m_pViewArea->m_bIgnoreScrollBar = true;
					m_pViewArea->m_iLastScrollBarVal += e->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 = e;
			e->m_pNext = 0;
			e->m_pPrev = m_pTailItem;
			m_pTailItem = e;
			triggerUpdate();
		}
	} else {
		// There were no items (is rather visible)
		m_pHeadItem = e;
		m_pTailItem = e;
		m_pTopItem = e;
		e->m_pNext = 0;
		e->m_pPrev = 0;
		triggerUpdate();
	}
	if(e->m_bSelected)
	{
		m_iSelectedCount++;
		if(m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pKviWindow,true);
	}
}

KviUserListEntry * KviUserListView::join(const TQString &nick,const TQString &user,
		const TQString &host,int iFlags)
{
	// Ok..an user joins the channel
	KviUserListEntry * it = m_pEntryDict->find(nick);
	if(it == 0)
	{
		// add an entry to the global dict
		KviIrcUserEntry * pGlobalData = m_pIrcUserDataBase->insertUser(nick,user,host);
		// calculate the flags and update the counters
		it = new KviUserListEntry(this,nick,pGlobalData,iFlags,(user == TQString()));
		insertUserEntry(nick,it);
	} 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 != it->m_iFlags)
			{
//// FIXME: #warning "Maybe say to the channel that we're oped : and the op is guessed from the names reply"
				if((iFlags & KVI_USERFLAG_CHANOWNER) != (it->m_iFlags & KVI_USERFLAG_CHANOWNER))setChanOwner(nick,iFlags & KVI_USERFLAG_CHANOWNER);
				if((iFlags & KVI_USERFLAG_CHANADMIN) != (it->m_iFlags & KVI_USERFLAG_CHANADMIN))setChanAdmin(nick,iFlags & KVI_USERFLAG_CHANADMIN);
				if((iFlags & KVI_USERFLAG_OP) != (it->m_iFlags & KVI_USERFLAG_OP))op(nick,iFlags & KVI_USERFLAG_OP);
				if((iFlags & KVI_USERFLAG_HALFOP) != (it->m_iFlags & KVI_USERFLAG_HALFOP))halfop(nick,iFlags & KVI_USERFLAG_HALFOP);
				if((iFlags & KVI_USERFLAG_VOICE) != (it->m_iFlags & KVI_USERFLAG_VOICE))voice(nick,iFlags & KVI_USERFLAG_VOICE);
				if((iFlags & KVI_USERFLAG_USEROP) != (it->m_iFlags & KVI_USERFLAG_USEROP))userop(nick,iFlags & KVI_USERFLAG_USEROP);
			}
//		}
	}
	return it;
}

void KviUserListView::triggerUpdate()
{
	// This stuff is useful on joins only
	if(m_pViewArea->isUpdatesEnabled())
	{
		//m_pViewArea->m_pScrollBar->setRange(0,m_iTotalHeight);
		updateScrollBarRange();
		m_pViewArea->update();
		updateUsersLabel();
	}
}

bool KviUserListView::avatarChanged(const TQString &nick)
{
	KviUserListEntry * it = m_pEntryDict->find(nick);
	if(it)
	{
		int oldH = it->m_iHeight;
		m_iTotalHeight -= it->m_iHeight;
		it->recalcSize();
		m_iTotalHeight += it->m_iHeight;
		// if this was "over" the top item , we must adjust the scrollbar value
		// otherwise scroll everything down
		KviUserListEntry * e = m_pHeadItem;
		bool bGotTopItem = false;
		while(e != it)
		{
			if(e == m_pTopItem)
			{
				bGotTopItem = true;
				e = it;
			} else e = e->m_pNext;
		}
		if(!bGotTopItem && (m_pTopItem != it))
		{
			// we're "over" the top item , so over the
			// upper side of the view...adjust the scroll bar value
			int hDiff = it->m_iHeight - oldH;
			m_pViewArea->m_iLastScrollBarVal += hDiff;
			m_pViewArea->m_bIgnoreScrollBar = true;
//			m_pViewArea->m_pScrollBar->setRange(0,m_iTotalHeight);
			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->m_pScrollBar->setRange(0,m_iTotalHeight);
			m_pViewArea->update();
		}
		return true;
	}
	return false;
}


bool KviUserListView::userActionVerifyMask(const TQString &nick,const TQString &user,const TQString &host,int actionTemperature,TQString &oldUser,TQString &oldHost)
{
	// This is called when an user "acts" in some visible way
	// on the channel, so we can keep track of his channeel
	// idle time.
	// This particular function version of userAction
	// will return false if there was a user or hostname
	// change (unless they were not known at all)
	// This will also update the username and hostname
	// if needed.
	KviUserListEntry * it = m_pEntryDict->find(TQString(nick));
	if(it)
	{
		it->m_lastActionTime = kvi_unixTime();
		bool bChanged = false;
		if(!(host.isEmpty() || (KviTQString::equalCS(host,"*"))))
		{
			if(!KviTQString::equalCI(it->m_pGlobalData->host(),host))
			{
				if(!(it->m_pGlobalData->host().isEmpty() || KviTQString::equalCS(it->m_pGlobalData->host(),"*")))
				{
					oldHost = it->m_pGlobalData->host();
					bChanged = true;
				}
				it->m_pGlobalData->setHost(host);
			}
		}
		if(!(user.isEmpty() || (KviTQString::equalCS(user,"*"))))
		{
			if(!KviTQString::equalCI(it->m_pGlobalData->user(),user))
			{
				if(!(it->m_pGlobalData->user().isEmpty() || KviTQString::equalCS(it->m_pGlobalData->user(),"*")))
				{
					oldUser = it->m_pGlobalData->user();
					bChanged = true;
				}
				it->m_pGlobalData->setUser(user);
			} 
		}
		it->m_iTemperature += actionTemperature;
		// Don't allow it to grow too much
		if(it->m_iTemperature > 300)it->m_iTemperature = 300;
		else if(it->m_iTemperature < -300)it->m_iTemperature = -300;
		if(itemVisible(it))triggerUpdate();
		return !bChanged;
	}
	return true; // no such nick so no change
}


void KviUserListView::userAction(const TQString &nick,const TQString &user,const TQString &host,int actionTemperature)
{
	// This is called when an user "acts" in some visible way
	// on the channel, so we can keep track of his channeel
	// idle time. This will also update the username and hostname
	// if needed.
	KviUserListEntry * it = m_pEntryDict->find(TQString(nick));
	if(it)
	{
		it->m_lastActionTime = kvi_unixTime();
		if(!(host.isEmpty() || (KviTQString::equalCS(host,"*"))))
			it->m_pGlobalData->setHost(host);
		if(!(user.isEmpty() || (KviTQString::equalCS(user,"*"))))
			it->m_pGlobalData->setUser(user);
		it->m_iTemperature += actionTemperature;
		// Don't allow it to grow too much
		if(it->m_iTemperature > 300)it->m_iTemperature = 300;
		else if(it->m_iTemperature < -300)it->m_iTemperature = -300;
		if(itemVisible(it))triggerUpdate();
	}
}


void KviUserListView::userAction(KviIrcMask *user,int actionTemperature)
{
	// This is called when an user "acts" in some visible way
	// on the channel, so we can keep track of his channeel
	// idle time. This will also update the username and hostname
	// if needed.
	KviUserListEntry * it = m_pEntryDict->find(TQString(user->nick()));
	if(it)
	{
		it->m_lastActionTime = kvi_unixTime();
		if(user->hasUser())it->m_pGlobalData->setUser(user->user());
		if(user->hasHost())it->m_pGlobalData->setHost(user->host());
		it->m_iTemperature += actionTemperature;
		// Don't allow it to grow too much
		if(it->m_iTemperature > 300)it->m_iTemperature = 300;
		else if(it->m_iTemperature < -300)it->m_iTemperature = -300;
		if(itemVisible(it))triggerUpdate();
	}
}

void KviUserListView::userAction(const TQString &nick,int actionTemperature)
{
	// This is called when an user "acts" in some visible way
	// on the channel, so we can keep track of his channeel
	// idle time. This will also update the username and hostname
	// if needed.
	KviUserListEntry * it = m_pEntryDict->find(nick);
	if(it)
	{
		it->m_lastActionTime = kvi_unixTime();
		it->m_iTemperature += actionTemperature;
		if(it->m_iTemperature > 300)it->m_iTemperature = 300;
		else if(it->m_iTemperature < -300)it->m_iTemperature = -300;
		if(itemVisible(it))triggerUpdate();
	}
}

kvi_time_t KviUserListView::getUserJoinTime(const TQString &szNick)
{
	KviUserListEntry * e = m_pEntryDict->find(szNick);
	if(!e)return (kvi_time_t)0;
	return e->m_joinTime;
}

kvi_time_t KviUserListView::getUserLastActionTime(const TQString &szNick)
{
	KviUserListEntry * e = m_pEntryDict->find(szNick);
	if(!e)return (kvi_time_t)0;
	return e->m_lastActionTime;
}


int KviUserListView::getUserModeLevel(const TQString &szNick)
{
	KviUserListEntry * e = m_pEntryDict->find(szNick);
	if(!e)return 0;
	if(e->m_iFlags & KVI_USERFLAG_MODEMASK)
	{
		if(e->m_iFlags & KVI_USERFLAG_CHANOWNER)return 60;
		if(e->m_iFlags & KVI_USERFLAG_CHANADMIN)return 50;
		if(e->m_iFlags & KVI_USERFLAG_OP)return 40;
		if(e->m_iFlags & KVI_USERFLAG_HALFOP)return 30;
		if(e->m_iFlags & KVI_USERFLAG_VOICE)return 20;
		if(e->m_iFlags & KVI_USERFLAG_USEROP)return 10;
	}
	return 0;
}

char KviUserListView::getUserFlag(KviUserListEntry * e)
{
	if(!e)return 0;
	return (char)m_pKviWindow->connection()->serverInfo()->modePrefixChar(e->m_iFlags).unicode();
}

void KviUserListView::prependUserFlag(const TQString &nick,TQString &buffer)
{
	char uFlag = getUserFlag(nick);
	if(uFlag)buffer.prepend(uFlag);
}

int KviUserListView::flags(const TQString &nick)
{
	KviUserListEntry * it = m_pEntryDict->find(nick);
	return it ? it->m_iFlags : 0;
}

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

SET_FLAG_FUNC(setChanOwner,KVI_USERFLAG_CHANOWNER)
SET_FLAG_FUNC(setChanAdmin,KVI_USERFLAG_CHANADMIN)
SET_FLAG_FUNC(op,KVI_USERFLAG_OP)
SET_FLAG_FUNC(halfop,KVI_USERFLAG_HALFOP)
SET_FLAG_FUNC(userop,KVI_USERFLAG_USEROP)
SET_FLAG_FUNC(voice,KVI_USERFLAG_VOICE)

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

GET_FLAG_FUNC(isChanOwner,KVI_USERFLAG_CHANOWNER)
GET_FLAG_FUNC(isChanAdmin,KVI_USERFLAG_CHANADMIN)
GET_FLAG_FUNC(isOp,KVI_USERFLAG_OP)
GET_FLAG_FUNC(isVoice,KVI_USERFLAG_VOICE)
GET_FLAG_FUNC(isHalfOp,KVI_USERFLAG_HALFOP)
GET_FLAG_FUNC(isUserOp,KVI_USERFLAG_USEROP)


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

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

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

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

	KviUserListEntry * entry = m_pEntryDict->find(nick);
	if(entry)
	{
		entry->m_bSelected = true;
		m_iSelectedCount = 1;
	} else {
		m_iSelectedCount = 0;
	}
	g_pFrame->childWindowSelectionStateChange(m_pKviWindow,true);
	m_pViewArea->update();
}

bool KviUserListView::partInternal(const TQString &nick,bool bRemove)
{
	KviUserListEntry * it = m_pEntryDict->find(nick);
	if(it)
	{
		// so, first of all..check if this item is over, or below the top item
		KviUserListEntry * e = m_pHeadItem;
		bool bGotTopItem = false;
		while(e != it)
		{
			if(e == m_pTopItem)
			{
				bGotTopItem = true;
				e = it;
			} else e = e->m_pNext;
		}
		if(bRemove)m_pIrcUserDataBase->removeUser(nick,it->m_pGlobalData);

		// now just remove it
		if(it->m_iFlags & KVI_USERFLAG_OP)m_iOpCount--;
		if(it->m_iFlags & KVI_USERFLAG_VOICE)m_iVoiceCount--;
		if(it->m_iFlags & KVI_USERFLAG_HALFOP)m_iHalfOpCount--;
		if(it->m_iFlags & KVI_USERFLAG_CHANADMIN)m_iChanAdminCount--;
		if(it->m_iFlags & KVI_USERFLAG_CHANOWNER)m_iChanOwnerCount--;
		if(it->m_iFlags & KVI_USERFLAG_USEROP)m_iUserOpCount--;
		if(it->m_bSelected)
		{
			m_iSelectedCount--;
			if(m_iSelectedCount == 0)g_pFrame->childWindowSelectionStateChange(m_pKviWindow,false);
		}
		if(it->m_pPrev)it->m_pPrev->m_pNext = it->m_pNext;
		if(it->m_pNext)it->m_pNext->m_pPrev = it->m_pPrev;
		if(m_pTopItem == it)
		{
			bGotTopItem = true; //!!! the previous while() does not handle it!
			m_pTopItem = it->m_pNext;
			if(m_pTopItem == 0)m_pTopItem = it->m_pPrev;
		}
		if(it == m_pHeadItem)m_pHeadItem = it->m_pNext;
		if(it == m_pTailItem)m_pTailItem = it->m_pPrev;
		m_iTotalHeight -= it->m_iHeight;

		int iHeight = it->m_iHeight;

		m_pEntryDict->remove(nick);

		if(bGotTopItem)
		{
			// removing after (or exactly) the top item, may be visible
			if(bRemove)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);
//			m_pViewArea->m_pScrollBar->setRange(0,m_iTotalHeight);
			updateScrollBarRange();
			m_pViewArea->m_bIgnoreScrollBar = false;
			if(bRemove)updateUsersLabel();
		}

		return true;
	}
	return false;
}

bool KviUserListView::nickChange(const TQString &oldNick,const TQString &newNick)
{
	KviUserListEntry * it = m_pEntryDict->find(oldNick);
	if(it)
	{
		TQString user      = it->m_pGlobalData->user();
		TQString host      = it->m_pGlobalData->host();
		int iFlags        = it->m_iFlags;
		kvi_time_t joint  = it->m_joinTime;
		bool bSelect      = it->m_bSelected;
		KviAvatar * av    = it->m_pGlobalData->forgetAvatar();
		KviIrcUserEntry::Gender gender = it->m_pGlobalData->gender();
		bool bBot = it->m_pGlobalData->isBot();
		part(oldNick);
		__range_invalid(m_pEntryDict->find(oldNick));
		it = join(newNick,user,host,iFlags);
		it->m_pGlobalData->setGender(gender);
		it->m_pGlobalData->setBot(bBot);
		it->m_joinTime = joint;
		it->m_lastActionTime = kvi_unixTime();
		it->m_bSelected = bSelect;
		it->m_iTemperature += KVI_USERACTION_NICK;
		if(av)
		{
			it->m_pGlobalData->setAvatar(av);
			avatarChanged(newNick);
		}
		return true;
	}
	return false;
}


void KviUserListView::updateUsersLabel()
{
	if(!KVI_OPTION_BOOL(KviOption_boolDisableUserListLabel))//G&N  2005
	{
		KviStr tmp;
		tmp.sprintf("<nobr><b>[%u]</b>",m_pEntryDict->count());
		if(m_iChanOwnerCount)tmp.append(KviStr::Format," q:%d",m_iChanOwnerCount);
		if(m_iChanAdminCount)tmp.append(KviStr::Format," a:%d",m_iChanAdminCount);
		if(m_iOpCount)tmp.append(KviStr::Format," o:%d",m_iOpCount);
		if(m_iHalfOpCount)tmp.append(KviStr::Format," h:%d",m_iHalfOpCount);
		if(m_iVoiceCount)tmp.append(KviStr::Format," v:%d",m_iVoiceCount);
		if(m_iUserOpCount)tmp.append(KviStr::Format," u:%d",m_iUserOpCount);
		if(m_ibEntries)tmp.append(KviStr::Format," b:%d",m_ibEntries);
		if(m_ieEntries)tmp.append(KviStr::Format," e:%d",m_ieEntries);
		if(m_iIEntries)tmp.append(KviStr::Format," I:%d",m_iIEntries);
		tmp.append("</nobr>");
		m_pUsersLabel->setText(tmp.ptr());
	}
}

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

void KviUserListView::removeAllEntries()
{
	KviPointerHashTableIterator<TQString,KviUserListEntry> it(*m_pEntryDict);
	while(it.current())
	{
		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_pFrame->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(TQResizeEvent *)
{
	int hght;
	if(!KVI_OPTION_BOOL(KviOption_boolDisableUserListLabel))//G&N  2005
	{
		hght = m_pUsersLabel->sizeHint().height();
		if(hght < 16)hght = 16; // at least
		m_pUsersLabel->setGeometry(0,0,width(),hght);
	}
	else
	{ 
		hght =0;
	}

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

	updateScrollBarRange();
}

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

KviUserListEntry * KviUserListView::itemAt(const TQPoint &pnt,TQRect * rct)
{
	if(!m_pTopItem)return 0;
	if(pnt.y() < 0)return 0;
	int curTop = KVI_USERLIST_BORDER_WIDTH - m_pViewArea->m_iTopItemOffset;
	KviUserListEntry * e = m_pTopItem;
	int curBottom = 0;
	while(e && (curTop <= m_pViewArea->height()))
	{
		curBottom = curTop + e->m_iHeight;
		if((pnt.y() >= curTop) && (pnt.y() < curBottom))
		{
			if(rct)
			{
				rct->setX(0);
				rct->setY(curTop);
				rct->setWidth(m_pViewArea->width());
				rct->setHeight(e->m_iHeight);
			}
			return e;
		}
		curTop = curBottom;
		e = e->m_pNext;
	}
	return 0;
}



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

	KviUserListEntry * e = m_pHeadItem;

	kvi_time_t curTime = kvi_unixTime();

	while(e)
	{
		if(e->m_lastActionTime)
		{
			unsigned int uTimeDiff = (((unsigned int)(curTime - e->m_lastActionTime)) >> 6);
			if(uTimeDiff < 10)
			{
				s->uActive++; // the user was alive in the last ~16 mins
				if(e->m_iFlags & (KVI_USERFLAG_OP | KVI_USERFLAG_CHANADMIN | KVI_USERFLAG_CHANOWNER))
				{
					s->uActiveOp++;
					if(e->m_iTemperature > 0)
					{
						s->uHot++;
						s->uHotOp++;
					}
				} else {
					if(e->m_iTemperature > 0)s->uHot++;
				}
				s->iAvgTemperature += e->m_iTemperature;
			}
		}
		if(e->m_iFlags & KVI_USERFLAG_CHANOWNER)s->uChanOwner++;
		else {
			if(e->m_iFlags & KVI_USERFLAG_CHANADMIN)s->uChanAdmin++;
			else {
				if(e->m_iFlags & KVI_USERFLAG_OP)s->uOp++;
				else {
					if(e->m_iFlags & KVI_USERFLAG_HALFOP)s->uHalfOp++;
					else {
						if(e->m_iFlags & KVI_USERFLAG_VOICE)s->uVoiced++;
						else {
							if(e->m_iFlags & KVI_USERFLAG_USEROP)s->uUserOp++;
						}
					}
				}
			}
		}
		e = e->m_pNext;
	}

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


void KviUserListView::maybeTip(KviUserListToolTip * tip,const TQPoint &pnt)
{
	if(!KVI_OPTION_BOOL(KviOption_boolShowUserListViewToolTips))return;
	TQRect itRect;
	KviUserListEntry * it = (KviUserListEntry *)itemAt(pnt,&itRect);
	if(it)
	{
		if(m_pKviWindow->console())
		{
			TQString buffer;
			m_pKviWindow->console()->getUserTipText(it->m_szNick,it->m_pGlobalData,buffer);

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

			if(it->m_joinTime != 0)
			{
				TQDateTime dt;
				dt.setTime_t(it->m_joinTime);
				buffer += "<tr><td bgcolor=\"#F0F0F0\"><nobr>";
				buffer += __tr2qs("Joined on <b>%1</b>").arg(dt.toString());
				buffer += "</nobr></td></tr>";
			}
	
			if(it->m_lastActionTime != 0)
			{
				int secs = kvi_unixTime() - it->m_lastActionTime;
				int mins = secs / 60;
				secs = secs % 60;
				int hors = mins / 60;
				mins = mins % 60;
				buffer += "<tr><td bgcolor=\"#F0F0F0\"><nobr>";
				buffer += __tr2qs("Quiet for <b>%1h %2m %3s</b>").arg(hors).arg(mins).arg(secs);
				buffer += "</nobr></td></tr>";
			}
			buffer += "</table>";
	
			tip->doTip(itRect,buffer);
		}
	}
}

///////////////////////////////////////////////////////////////

KviUserListViewArea::KviUserListViewArea(KviUserListView * par)
: TQWidget(par)
{
	m_pListView = par;
	setBackgroundMode(TQWidget::NoBackground);
	m_pScrollBar = new TQScrollBar(TQt::Vertical,this,"scrollbar");
	m_pScrollBar->setRange(0,0);
	m_pScrollBar->setValue(0);
	connect(m_pScrollBar,TQ_SIGNAL(valueChanged(int)),this,TQ_SLOT(scrollBarMoved(int)));
	m_pScrollBar->setPageStep(height());
	m_pScrollBar->setLineStep(m_pListView->m_iFontHeight);
	m_iLastScrollBarVal = 0;
	m_iTopItemOffset = 0;
	m_bIgnoreScrollBar = false;
	m_pLastEntryUnderMouse = 0;
}

KviUserListViewArea::~KviUserListViewArea()
{
}

void KviUserListViewArea::scrollBarMoved(int newVal)
{
	if(m_bIgnoreScrollBar)return;
	int diff = newVal - m_iLastScrollBarVal;
	if(m_pListView->m_pTopItem)
	{
		while(diff > 0)
		{
			int nextH = (m_pListView->m_pTopItem->m_iHeight - m_iTopItemOffset);
			if(diff >= nextH)
			{
				// the diff is greater than the top item visible part
				diff -= nextH;
				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;
					diff = 0;
				}
			} else {
				// just offset the top item
				m_iTopItemOffset += diff;
				diff = 0;
			}
		}
		while(diff < 0)
		{
			if((-diff) <= m_iTopItemOffset)
			{
				// just move the top item
				m_iTopItemOffset += diff;
				diff = 0;
			} else {
				diff += 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;
					diff = 0;
				}
			}
		}
	}
	m_iLastScrollBarVal = newVal;
	update();

}

void KviUserListViewArea::paintEvent(TQPaintEvent *ev)
{
	// update the scroll bar

	if(!isVisible())return;

	// if the mdiManager is in SDI mode
	// and this window is attacched but is not the toplevel one
	// then it is hidden completely behind the other windows
	// and we can avoid to paint it :)
	if(g_pFrame->mdiManager()->isInSDIMode() &&
		(m_pListView->window()->mdiParent() != g_pFrame->mdiManager()->topChild()) &&
		(m_pListView->window()->mdiParent()))
	{
		return; // totally hidden behind other windows
	}

	int wdth = width() - m_pScrollBar->width();

	TQRect r = ev->rect();
	if(r.right() > wdth)r.setRight(wdth);

	//tqDebug("PAINT EVENT %d,%d,%d,%d",r.left(),r.top(),r.width(),r.height());

	KviDoubleBuffer db(width(),height());
	TQPixmap * pMemBuffer = db.pixmap();

	TQPainter p(pMemBuffer);
	SET_ANTI_ALIASING(p);
	p.setFont(KVI_OPTION_FONT(KviOption_fontUserListView));


#ifdef COMPILE_PSEUDO_TRANSPARENCY
	if(g_pShadedChildGlobalDesktopBackground)
	{
		TQPoint pnt = mapToGlobal(TQPoint(r.left(),r.top()));
		p.drawTiledPixmap(r.left(),r.top(),r.width(),r.height(),*g_pShadedChildGlobalDesktopBackground,pnt.x(),pnt.y());
	} else {
#endif
		TQPixmap *pix = KVI_OPTION_PIXMAP(KviOption_pixmapUserListViewBackground).pixmap();
		p.fillRect(r.left(),r.top(),r.width(),r.height(),KVI_OPTION_COLOR(KviOption_colorUserListViewBackground));
		if(pix)
			KviPixmapUtils::drawPixmapWithPainter(&p,pix,KVI_OPTION_UINT(KviOption_uintUserListPixmapAlign),r,width(),height());
#ifdef COMPILE_PSEUDO_TRANSPARENCY
	}
#endif

	KviUserListEntry * e = m_pListView->m_pTopItem;

	int theY = 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(e && theY <= r.bottom())
	{
		int bottom = theY + e->m_iHeight;
		// theY is our top line
		// theX is our left corner
		// bottom is our bottom line
		// wdth is the width of the whole widget

		if(bottom >= r.top())
		{
			TQColor * pClrFore = 0;
			bool bColorAllocated=0; // FIXME: bool is true or false
			if(e->m_bSelected)
			{
				p.fillRect(0,theY,wdth,e->m_iHeight,KVI_OPTION_COLOR(KviOption_colorUserListViewSelectionBackground));
				pClrFore = &(KVI_OPTION_COLOR(KviOption_colorUserListViewSelectionForeground));
			} else if(KVI_OPTION_BOOL(KviOption_boolUseDifferentColorForOwnNick) && m_pListView->m_pKviWindow->connection())
			{
				if(e->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(e->m_szNick))
				{
					pClrFore = m_pListView->m_pKviWindow->connection()->userDataBase()->customColor(e->m_szNick);
				}
				if(!pClrFore)
				{
					if(e->m_iFlags == 0)
					{
						pClrFore = &(KVI_OPTION_COLOR(KviOption_colorUserListViewNormalForeground));
					} else {
						pClrFore = &(KVI_OPTION_COLOR((e->m_iFlags & KVI_USERFLAG_CHANOWNER) ? \
							KviOption_colorUserListViewChanOwnerForeground : ((e->m_iFlags & KVI_USERFLAG_CHANADMIN) ? \
							KviOption_colorUserListViewChanAdminForeground : ((e->m_iFlags & KVI_USERFLAG_OP) ? \
							KviOption_colorUserListViewOpForeground : ((e->m_iFlags & KVI_USERFLAG_HALFOP) ? \
							KviOption_colorUserListViewHalfOpForeground : ((e->m_iFlags & KVI_USERFLAG_VOICE) ? \
							KviOption_colorUserListViewVoiceForeground : KviOption_colorUserListViewUserOpForeground))))));
					}
				}
			}
			
			int theX = KVI_USERLIST_BORDER_WIDTH + 1;
	
			int iAvatarAndTextX = theX;
	
			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(TQPen(KVI_OPTION_COLOR(KviOption_colorUserListViewGrid),0,
							(KVI_OPTION_UINT(KviOption_uintUserListViewGridType) == KVI_USERLISTVIEW_GRIDTYPE_DOTGRID) ? TQPen::DotLine : TQPen::SolidLine));
						p.drawLine(0,bottom - 1,wdth,bottom - 1);
						if(bShowState || bShowIcons)
							p.drawLine(iAvatarAndTextX,bottom - 1,iAvatarAndTextX,theY);
					break;
					default: // KVI_USERLISTVIEW_GRIDTYPE_3DGRID and KVI_USERLISTVIEW_GRIDTYPE_3DBUTTONS
						if(!e->m_bSelected)
						{
							p.setPen(TQPen(KVI_OPTION_COLOR(KviOption_colorUserListViewGrid),0 /*,TQPen::DotLine*/));
							if((bShowState || bShowIcons) && (KVI_OPTION_UINT(KviOption_uintUserListViewGridType) == KVI_USERLISTVIEW_GRIDTYPE_3DGRID))
								p.drawLine(iAvatarAndTextX,bottom - 1,iAvatarAndTextX,theY);
							p.setPen(colorGroup().shadow());
							p.drawLine(0,bottom - 1,wdth,bottom - 1);
							p.setPen(colorGroup().light());
							p.drawLine(0,theY,wdth,theY);
							theY--;
						}
						theY++;
					break;
				}
				iAvatarAndTextX += 3;
			} else {
				iAvatarAndTextX += 1;
			}

			if(e->globalData()->isAway())
			{
				TQRgb rgb2 = pClrFore->rgb();
				TQRgb rgb1 = KVI_OPTION_COLOR(KviOption_colorUserListViewAwayForeground).rgb();
				p.setPen(TQColor(((tqRed(rgb1)*2) + tqRed(rgb2)) / 3,((tqGreen(rgb1)*2) + tqGreen(rgb2)) / 3,((tqBlue(rgb1)*2) + tqBlue(rgb2)) / 3));
			} else {
				p.setPen(*pClrFore);
			}
			theY+=2;
		
			if(!KVI_OPTION_BOOL(KviOption_boolDisableAvatars))//G&N  2005
			{
				KviAvatar * av = e->m_pGlobalData->avatar();
				if(av && KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth) && KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight))
				{
					TQPixmap * pix;
					if( KVI_OPTION_BOOL(KviOption_boolScaleAvatars) &&
						(
							!KVI_OPTION_BOOL(KviOption_boolDoNotStretchAvatars) ||
							(static_cast<unsigned int>(av->pixmap()->width()) > KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth)) ||
							(static_cast<unsigned int>(av->pixmap()->height()) > KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight))
						)
					)
					pix=av->scaledPixmap(KVI_OPTION_UINT(KviOption_uintAvatarScaleWidth),KVI_OPTION_UINT(KviOption_uintAvatarScaleHeight));
					else 
						pix=av->pixmap();
					p.drawPixmap(iAvatarAndTextX,theY,*pix);
					theY += pix->height() + 1;
				}
			}

			if(bShowGender)
			{
				if(e->globalData()->gender()!=KviIrcUserEntry::Unknown)
				{
					TQPixmap * ico = g_pIconManager->getBigIcon((e->globalData()->gender()==KviIrcUserEntry::Male) ? "kvi_icon_male.png" : "kvi_icon_female.png");
					p.drawPixmap(theX,theY+(m_pListView->m_iFontHeight-11)/2,*ico);
				}
				if(e->globalData()->isBot())
				{
					TQPixmap * ico = g_pIconManager->getBigIcon("kvi_icon_bot.png");
					p.drawPixmap(theX,theY+(m_pListView->m_iFontHeight-11)/2,*ico);
				}
				theX +=11;
			}

			if(bShowState)
			{
				if(e->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 - e->m_lastActionTime)) >> 6);
					if(uTimeDiff < 16)
					{
						//p.drawRect(theX,theY + 2,10,e->m_iHeight - 4);
						static int xOffTable[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(e->m_iTemperature > KVI_MID_TEMPERATURE)
						{
							if(e->m_iTemperature > KVI_HALF_HOT_TEMPERATURE)
							{
								if(e->m_iTemperature > KVI_HOT_TEMPERATURE)
								{
									// hot
									p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],0,8,16);
								} else {
									// half-hot
									p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],16,8,16);
								}
							} else {
								// bit-hot
								p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],32,8,16);
							}
						} else {
							if(e->m_iTemperature < KVI_HALF_COLD_TEMPERATURE)
							{
								if(e->m_iTemperature < KVI_COLD_TEMPERATURE)
								{
									// cold
									p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],80,8,16);
								} else {
									// half-cold
									p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],64,8,16);
								}
							} else {
								// bit-cold
								p.drawPixmap(theX,theY,*g_pUserChanStatePixmap,xOffTable[uTimeDiff],48,8,16);
							}
						} 
					}
				}
				theX += 11;
			}
			
			if(bShowIcons)
			{
				//p.drawRect(theX,theY + 2,18,e->m_iHeight - 4);
				if(e->m_iFlags != 0)
				{
					TQPixmap * ico = g_pIconManager->getSmallIcon( \
											e->globalData()->isAway() ? \
												( \
													(e->m_iFlags & KVI_USERFLAG_CHANOWNER) ? \
													KVI_SMALLICON_CHANOWNERAWAY : ((e->m_iFlags & KVI_USERFLAG_CHANADMIN) ? \
													KVI_SMALLICON_CHANADMINAWAY : ((e->m_iFlags & KVI_USERFLAG_OP) ? \
													KVI_SMALLICON_OPAWAY : ((e->m_iFlags & KVI_USERFLAG_HALFOP) ? \
													KVI_SMALLICON_HALFOPAWAY : ((e->m_iFlags & KVI_USERFLAG_VOICE) ? \
													KVI_SMALLICON_VOICEAWAY : KVI_SMALLICON_USEROPAWAY))))
												) \
											: \
												( \
													(e->m_iFlags & KVI_USERFLAG_CHANOWNER) ? \
													KVI_SMALLICON_CHANOWNER : ((e->m_iFlags & KVI_USERFLAG_CHANADMIN) ? \
													KVI_SMALLICON_CHANADMIN : ((e->m_iFlags & KVI_USERFLAG_OP) ? \
													KVI_SMALLICON_OP : ((e->m_iFlags & KVI_USERFLAG_HALFOP) ? \
													KVI_SMALLICON_HALFOP : ((e->m_iFlags & KVI_USERFLAG_VOICE) ? \
													KVI_SMALLICON_VOICE : KVI_SMALLICON_USEROP)))) \
												) \
										);
					p.drawPixmap(theX,theY+(m_pListView->m_iFontHeight-16/*size of small icon*/)/2,*ico);
				}
				theX +=18;
				p.drawText(iAvatarAndTextX,theY,wdth - theX,m_pListView->m_iFontHeight,TQt::AlignLeft|TQt::AlignVCenter,e->m_szNick);
			} else {

				char flag = m_pListView->getUserFlag(e);
				if(flag)
				{
					TQString ttt = TQChar(flag);
					ttt += e->m_szNick;
					p.drawText(iAvatarAndTextX,theY,wdth - theX,m_pListView->m_iFontHeight,TQt::AlignLeft|TQt::AlignVCenter,ttt);
				} else {
					p.drawText(iAvatarAndTextX,theY,wdth - theX,m_pListView->m_iFontHeight,TQt::AlignLeft|TQt::AlignVCenter,e->m_szNick);
				}
			}
			if(bColorAllocated) delete pClrFore;
		}

		theY = bottom;
		e = e->m_pNext;
	}

	//we really do not need any self-draw borders. 
	//if we will need it, we will draw a better one with system style

	//p.setPen(colorGroup().dark());
	//p.drawLine(0,0,wdth,0);
	//p.drawLine(0,0,0,height());
	//p.setPen(colorGroup().light());
	//p.drawLine(1,height()-1,wdth,height()-1);
	//p.drawLine(wdth - 1,1,wdth - 1,height());

	bitBlt(this,r.left(),r.top(),pMemBuffer,r.left(),r.top(),r.width(),r.height(),TQt::CopyROP,false);
}

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

void KviUserListViewArea::mousePressEvent(TQMouseEvent *e)
{
	setFocus();
	if(e->button() & TQt::LeftButton)
	{
		KviUserListEntry * entry = m_pListView->itemAt(e->pos());
		if(entry)
		{
			if(e->state() & TQt::ShiftButton)
			{
				// Multiselect mode
				if(!entry->m_bSelected)m_pListView->m_iSelectedCount++;
				entry->m_bSelected = true;
				if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			} else if(e->state() & TQt::ControlButton)
			{
				// Invert mode
				if(!entry->m_bSelected)m_pListView->m_iSelectedCount++;
				else m_pListView->m_iSelectedCount--;
				entry->m_bSelected = ! entry->m_bSelected;
				if(m_pListView->m_iSelectedCount == 0)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
				else if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			} else {
				// Single select mode
				bool bThereWasSelection = false;
				if(m_pListView->m_iSelectedCount > 0)
				{
					KviUserListEntry * aux = m_pListView->m_pHeadItem;
					while(aux)
					{
						aux->m_bSelected = false;
						aux = aux->m_pNext;
					}
					bThereWasSelection = true;
				}
				entry->m_bSelected = true;
				m_pListView->m_iSelectedCount = 1;
				if(!bThereWasSelection)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			}
		}
		m_pLastEntryUnderMouse = entry;
	} else if(e->button() & TQt::RightButton)
	{
		KviUserListEntry * entry = m_pListView->itemAt(e->pos());
		if(entry)
		{
			if(!entry->m_bSelected){
				entry->m_bSelected = true;
				m_pListView->m_iSelectedCount=1;
				KviPointerHashTableIterator<TQString,KviUserListEntry> it(*(m_pListView->m_pEntryDict));
				while(it.current())
				{
					if(it.current()!=entry)
						((KviUserListEntry *)it.current())->m_bSelected = false;
					++it;
				}
			}
			if(m_pListView->m_iSelectedCount == 1)
				g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
			update();
		}
		m_pListView->emitRightClick();
	}
}
void KviUserListViewArea::keyPressEvent( TQKeyEvent * e )
{
	if(e->key()==TQt::Key_Escape)
	{
		if(m_pListView->m_pKviWindow->input())
			((TQWidget*)(m_pListView->m_pKviWindow->input()))->setFocus();
	} else {
		TQString szKey=e->text();
		if(!szKey.isEmpty())
		{
			KviUserListEntry * nick=0;
			KviUserListEntry * aux = m_pListView->m_pHeadItem;
			while(aux)
			{
				//tqDebug("%s %s %i %s %i",__FILE__,__FUNCTION__,__LINE__,aux->nick().local8Bit().data(),aux->nick().find(szKey,0,0));
				if(aux->nick().find(szKey,0,0)==0) 
				{
					nick=aux;
					break;
				}
				aux = aux->m_pNext;
			}
			if(nick)
			{
				bool bThereWasSelection = false;
				if(m_pListView->m_iSelectedCount > 0)
				{
					aux = m_pListView->m_pHeadItem;
					while(aux)
					{
						aux->m_bSelected = false;
						aux = aux->m_pNext;
					}
					bThereWasSelection = true;
				}
				nick->m_bSelected = true;
				m_pListView->m_iSelectedCount = 1;
				if(!bThereWasSelection)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
				update();
			}
		}
	}
}

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

void KviUserListViewArea::mouseMoveEvent(TQMouseEvent *e)
{
	if(e->state() & TQt::LeftButton)
	{
		KviUserListEntry * entry = m_pListView->itemAt(e->pos());
		if(entry && (entry != m_pLastEntryUnderMouse))
		{
			if(e->state() & TQt::ControlButton)
			{
				if(entry->m_bSelected)m_pListView->m_iSelectedCount--;
				else m_pListView->m_iSelectedCount++;
				entry->m_bSelected = ! entry->m_bSelected;
				if(m_pListView->m_iSelectedCount == 0)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
				else if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
			} else {
				if(!entry->m_bSelected)m_pListView->m_iSelectedCount++;
				entry->m_bSelected = true;
				if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
			}
			update();
			m_pLastEntryUnderMouse = entry;
		} else {
			// out of the widget ?
			if(entry == m_pLastEntryUnderMouse)return;
			if(e->pos().y() < KVI_USERLIST_BORDER_WIDTH)
			{
				KviUserListEntry * top = m_pListView->m_pTopItem;
				if(top)
				{
					m_pScrollBar->setValue(m_pScrollBar->value() - top->m_iHeight);
					if(m_pListView->m_pTopItem != top)
					{
						if(e->state() & TQt::ControlButton)
						{
							if(m_pListView->m_pTopItem->m_bSelected)m_pListView->m_iSelectedCount--;
							else m_pListView->m_iSelectedCount++;
							m_pListView->m_pTopItem->m_bSelected = ! m_pListView->m_pTopItem->m_bSelected;
							if(m_pListView->m_iSelectedCount == 0)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
							else if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
						} else {
							if(!m_pListView->m_pTopItem->m_bSelected)m_pListView->m_iSelectedCount++;
							m_pListView->m_pTopItem->m_bSelected = true;
							if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
						}
						update();
					}
				}
				m_pLastEntryUnderMouse = top;
			} else if(e->pos().y() > (height() - KVI_USERLIST_BORDER_WIDTH))
			{
				KviUserListEntry * bottom = m_pListView->m_pTopItem;
				if(bottom)
				{
					int theY = KVI_USERLIST_BORDER_WIDTH - m_iTopItemOffset;
					while(bottom && (theY < height()))
					{
						theY+= bottom->m_iHeight;
						bottom = bottom->m_pNext;
					}
					if(!bottom)bottom = m_pListView->m_pTailItem;
					if(bottom)
					{
						m_pScrollBar->setValue(m_pScrollBar->value() + bottom->m_iHeight);
						if(bottom != m_pLastEntryUnderMouse)
						{
							if(e->state() & TQt::ControlButton)
							{
								if(bottom->m_bSelected)m_pListView->m_iSelectedCount--;
								else m_pListView->m_iSelectedCount++;
								bottom->m_bSelected = ! bottom->m_bSelected;
								if(m_pListView->m_iSelectedCount == 0)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,false);
								else if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
							} else {
								if(!bottom->m_bSelected)m_pListView->m_iSelectedCount++;
								bottom->m_bSelected = true;
								if(m_pListView->m_iSelectedCount == 1)g_pFrame->childWindowSelectionStateChange(m_pListView->m_pKviWindow,true);
							}
							update();
						}
					}
				}
				m_pLastEntryUnderMouse = bottom;
			} else m_pLastEntryUnderMouse = 0;
		}
	}
}

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

void KviUserListViewArea::wheelEvent(TQWheelEvent *e)
{
	g_pApp->sendEvent(m_pScrollBar,e);
}

#include "kvi_userlistview.moc"
