/*
    kopetelistviewitem.cpp - Kopete's modular TQListViewItems

    Copyright (c) 2005      by Engin AYDOGAN          <engin@bzzzt.biz>
    Copyright (c) 2004      by Richard Smith          <kde@metafoo.co.uk>

    Kopete    (c) 2002-2004 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * This program is free software; you can redistribute it and/or modify  *
    * it under the terms of the GNU General Public License as published by  *
    * the Free Software Foundation; either version 2 of the License, or     *
    * (at your option) any later version.                                   *
    *                                                                       *
    *************************************************************************
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "kopeteprefs.h"
#include "kopetecontact.h"
#include "kopetelistviewitem.h"
#include "kopeteemoticons.h"
#include "kopeteonlinestatus.h"

#include <kdebug.h>
#include <kiconloader.h>
#include <kstringhandler.h>

#include <tqapplication.h>
#include <tqpixmap.h>
#include <tqpainter.h>
#include <tqptrlist.h>
#include <tqrect.h>
#include <tqtimer.h>
#include <tqheader.h>
#include <tqstyle.h>

#ifdef HAVE_XRENDER
#  include <X11/Xlib.h>
#  include <X11/extensions/Xrender.h>
#endif

#include <limits.h>

namespace Kopete {
namespace UI {
namespace ListView {

// ComponentBase --------

class ComponentBase::Private
{
public:
	TQPtrList<Component> components;
};

ComponentBase::ComponentBase()
 : d( new Private )
{
}

ComponentBase::~ComponentBase()
{
	d->components.setAutoDelete( true );
	delete d;
}

uint ComponentBase::components() { return d->components.count(); }
Component *ComponentBase::component( uint n ) { return d->components.at( n ); }

Component *ComponentBase::componentAt( const TQPoint &pt )
{
	for ( uint n = 0; n < components(); ++n )
	{
		if ( component( n )->rect().contains( pt ) )
		{
			if ( Component *comp = component( n )->componentAt( pt ) )
				return comp;
			return component( n );
		}
	}
	return 0;
}

void ComponentBase::componentAdded( Component *component )
{
	d->components.append( component );
}

void ComponentBase::componentRemoved( Component *component )
{
	//TODO: make sure the component is in d->components once and only once.
	// if not, the situation is best referred to as 'very very broken indeed'.
	d->components.remove( component );
}

void ComponentBase::clear()
{
	/* I'm switching setAutoDelete back and forth instead of turning it
	 * on permenantly, because original author of this class set it to
	 * auto delete in the dtor, that might have a reason that I can't
	 * imagine right now */
	bool tmp = d->components.autoDelete();
	d->components.setAutoDelete( true );
	d->components.clear();
	d->components.setAutoDelete( tmp );
}

void ComponentBase::componentResized( Component * )
{
}

std::pair<TQString,TQRect> ComponentBase::toolTip( const TQPoint &relativePos )
{
	for ( uint n = 0; n < components(); ++n )
		if ( component( n )->rect().contains( relativePos ) )
			return component( n )->toolTip( relativePos );

	return std::make_pair( TQString(), TQRect() );
}

void ComponentBase::updateAnimationPosition( int p, int s )
{
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		TQRect start = comp->startRect();
		TQRect target = comp->targetRect();
		TQRect rc( start.left() + ((target.left() - start.left()) * p) / s,
		          start.top() + ((target.top() - start.top()) * p) / s,
		          start.width() + ((target.width() - start.width()) * p) / s,
		          start.height() + ((target.height() - start.height()) * p) / s );
		comp->setRect( rc );
		comp->updateAnimationPosition( p, s );
	}
}

// Component --------

class Component::Private
{
public:
	Private( ComponentBase *parent )
	 : parent( parent ), minWidth( 0 ), minHeight( 0 )
	 , growHoriz( false ), growVert( false )
	 , tipSource( 0 )
	{
	}
	ComponentBase *parent;
	TQRect rect;
	TQRect startRect, targetRect;
	int minWidth, minHeight;
	bool growHoriz, growVert;
	bool show; /** @since 23-03-2005 */
	ToolTipSource *tipSource;
};

Component::Component( ComponentBase *parent )
 : d( new Private( parent ) )
{
	d->parent->componentAdded( this );
	d->show = true;
}

int Component::RTTI = Rtti_Component;

Component::~Component()
{
	d->parent->componentRemoved( this );
	delete d;
}


void Component::hide()
{
	d->show = false;
}

void Component::show()
{
	d->show = true;
}

bool Component::isShown()
{
	return d->show;
}

bool Component::isHidden()
{
	return !d->show;
}

void Component::setToolTipSource( ToolTipSource *source )
{
	d->tipSource = source;
}

std::pair<TQString,TQRect> Component::toolTip( const TQPoint &relativePos )
{
	if ( !d->tipSource )
		return ComponentBase::toolTip( relativePos );

	TQRect rc = rect();
	TQString result = (*d->tipSource)( this, relativePos, rc );
	return std::make_pair(result, rc);
}

TQRect Component::rect() { return d->rect; }
TQRect Component::startRect() { return d->startRect; }
TQRect Component::targetRect() { return d->targetRect; }

int Component::minWidth() { return d->minWidth; }
int Component::minHeight() { return d->minHeight; }
int Component::widthForHeight( int ) { return minWidth(); }
int Component::heightForWidth( int ) { return minHeight(); }

bool Component::setMinWidth( int width )
{
	if ( d->minWidth == width ) return false;
	d->minWidth = width;

	d->parent->componentResized( this );
	return true;
}
bool Component::setMinHeight( int height )
{
	if ( d->minHeight == height ) return false;
	d->minHeight = height;

	d->parent->componentResized( this );
	return true;
}

void Component::layout( const TQRect &newRect )
{
	if ( rect().isNull() )
		d->startRect = TQRect( newRect.topLeft(), newRect.topLeft() );
	else
		d->startRect = rect();
	d->targetRect = newRect;
	//kdDebug(14000) << k_funcinfo << "At " << rect << endl;
}

void Component::setRect( const TQRect &rect )
{
	d->rect = rect;
}

void Component::paint( TQPainter *painter, const TQColorGroup &cg )
{
	/*painter->setPen( TQt::red );
	painter->drawRect( rect() );*/
	for ( uint n = 0; n < components(); ++n )
	{
		if( component( n )->isShown() )
			component( n )->paint( painter, cg );
	}
}

void Component::repaint()
{
	d->parent->repaint();
}

void Component::relayout()
{
	d->parent->relayout();
}

void Component::componentAdded( Component *component )
{
	ComponentBase::componentAdded( component );
	//update( Relayout );
}

void Component::componentRemoved( Component *component )
{
	ComponentBase::componentRemoved( component );
	//update( Relayout );
}

// BoxComponent --------

class BoxComponent::Private
{
public:
	Private( BoxComponent::Direction dir ) : direction( dir ) {}
	BoxComponent::Direction direction;

	static const int padding = 0;
};

BoxComponent::BoxComponent( ComponentBase *parent, Direction dir )
 : Component( parent ), d( new Private( dir ) )
{
}

int BoxComponent::RTTI = Rtti_BoxComponent;

BoxComponent::~BoxComponent()
{
	delete d;
}

int BoxComponent::widthForHeight( int height )
{
	if ( d->direction != Horizontal )
	{
		int width = 0;
		for ( uint n = 0; n < components(); ++n )
			width = TQMAX( width, component( n )->widthForHeight( height ) );
		return width;
	}
	else
	{
		int width = (components() - 1) * Private::padding;
		for ( uint n = 0; n < components(); ++n )
			width += component( n )->widthForHeight( height );
		return width;
	}
}

int BoxComponent::heightForWidth( int width )
{
	if ( d->direction == Horizontal )
	{
		int height = 0;
		for ( uint n = 0; n < components(); ++n )
			height = TQMAX( height, component( n )->heightForWidth( width ) );
		return height;
	}
	else
	{
		int height = (components() - 1) * Private::padding;
		for ( uint n = 0; n < components(); ++n )
			height += component( n )->heightForWidth( width );
		return height;
	}
}

void BoxComponent::calcMinSize()
{
	int sum = (components() - 1) * Private::padding, max = 0;
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		if ( d->direction == Horizontal )
		{
			max = TQMAX( max, comp->minHeight() );
			sum += comp->minWidth();
		}
		else
		{
			max = TQMAX( max, comp->minWidth() );
			sum += comp->minHeight();
		}
	}

	bool sizeChanged = false;
	if ( d->direction == Horizontal )
	{
		if ( setMinWidth( sum ) ) sizeChanged = true;
		if ( setMinHeight( max ) ) sizeChanged = true;
	}
	else
	{
		if ( setMinWidth( max ) ) sizeChanged = true;
		if ( setMinHeight( sum ) ) sizeChanged = true;
	}

	if ( sizeChanged )
		repaint();
	else
		relayout();
}

void BoxComponent::layout( const TQRect &rect )
{
	Component::layout( rect );

	bool horiz = (d->direction == Horizontal);
	int fixedSize = 0;
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		if ( horiz )
			fixedSize += comp->minWidth();
		else
			fixedSize += comp->minHeight();
	}

	// remaining space after all fixed items have been allocated
	int padding = Private::padding;

	// ensure total is at least minXXX. the only time the rect
	// will be smaller than that is when we don't fit, and in
	// that cases we should pretend that we're wide/high enough.
	int total;
	if ( horiz )
		total = TQMAX( rect.width(), minWidth() );
	else
		total = TQMAX( rect.height(), minHeight() );

	int remaining = total - fixedSize - padding * (components() - 1);

	// finally, lay everything out
	int pos = 0;
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		
		TQRect rc;
		if ( horiz )
		{
			rc.setLeft( rect.left() + pos );
			rc.setTop( rect.top() );
			rc.setHeight( rect.height() );
			int minWidth = comp->minWidth();
			int desiredWidth = comp->widthForHeight( rect.height() );
			rc.setWidth( TQMIN( remaining + minWidth, desiredWidth ) );
			pos += rc.width();
			remaining -= rc.width() - minWidth;
		}
		else
		{
			rc.setLeft( rect.left() );
			rc.setTop( rect.top() + pos );
			rc.setWidth( rect.width() );
			int minHeight = comp->minHeight();
			int desiredHeight = comp->heightForWidth( rect.width() );
			rc.setHeight( TQMIN( remaining + minHeight, desiredHeight ) );
			pos += rc.height();
			remaining -= rc.height() - minHeight;
		}
		comp->layout( rc & rect );
		pos += padding;
	}
}

void BoxComponent::componentAdded( Component *component )
{
	Component::componentAdded( component );
	calcMinSize();
}

void BoxComponent::componentRemoved( Component *component )
{
	Component::componentRemoved( component );
	calcMinSize();
}

void BoxComponent::componentResized( Component *component )
{
	Component::componentResized( component );
	calcMinSize();
}



/*= ContactBoxComponent =====================================================*/

class ContactBoxComponent::Private
{
public:
	TQRect				sub;

	TQPixmap 			back_pixmap;

	TQPixmap 			corner_tl_pixmap;
	TQPixmap 			corner_bl_pixmap;
	TQPixmap 			corner_tr_pixmap;
	TQPixmap 			corner_br_pixmap;

	TQPixmap 			top_pixmap;
	TQPixmap 			left_pixmap;
	TQPixmap 			right_pixmap;
	TQPixmap 			bottom_pixmap;
};

ContactBoxComponent::ContactBoxComponent(ComponentBase *parent, Direction dir)
 : BoxComponent(parent, dir), d(new Private())
{}

ContactBoxComponent::~ContactBoxComponent()
{
	delete d;
}

void	ContactBoxComponent::reloadTheme()
{
	TQString	path = KopetePrefs::prefs()->themeURL();
	TQString	str;

	str = path + "ContactBackground.png";
	d->back_pixmap.load(str);

	str = path + "ContactTopLeft.png";
	d->corner_tl_pixmap.load(str);
	str = path + "ContactBottomLeft.png";
	d->corner_bl_pixmap.load(str);
	str = path + "ContactTopRight.png";
	d->corner_tr_pixmap.load(str);
	str = path + "ContactBottomRight.png";
	d->corner_br_pixmap.load(str);

	str = path + "ContactTop.png";
	d->top_pixmap.load(str);
	str = path + "ContactLeft.png";
	d->left_pixmap.load(str);
	str = path + "ContactRight.png";
	d->right_pixmap.load(str);
	str = path + "ContactBottom.png";
	d->bottom_pixmap.load(str);
}

void	ContactBoxComponent::layout(const TQRect &rect)
{
	d->sub.setLeft(rect.left() +
			d->left_pixmap.width());
	d->sub.setTop(rect.top() +
			d->top_pixmap.height());
	d->sub.setRight(rect.right() -
			d->right_pixmap.width());
	d->sub.setBottom(rect.bottom() -
			d->bottom_pixmap.height());

	BoxComponent::layout(d->sub);
	Component::layout(rect);
}

int	ContactBoxComponent::widthForHeight(int height)
{
	return BoxComponent::widthForHeight(height) +
			d->left_pixmap.width() +
			d->right_pixmap.width();
}

int	ContactBoxComponent::heightForWidth(int width)
{
	return BoxComponent::heightForWidth(width) +
			d->top_pixmap.height() +
			d->bottom_pixmap.height();
}

void	ContactBoxComponent::paint(TQPainter *painter, const TQColorGroup &cg)
{
	painter->drawPixmap(0,
							  0,
							  d->corner_tl_pixmap);

	painter->drawPixmap(0,
							  d->sub.bottom()+1,
							  d->corner_bl_pixmap);

	painter->drawPixmap(d->sub.right()+1,
							  0,
							  d->corner_tr_pixmap);

	painter->drawPixmap(d->sub.right()+1,
							  d->sub.bottom()+1,
							  d->corner_br_pixmap);

	painter->drawTiledPixmap(0,
									 d->sub.top(),
									 d->left_pixmap.width(),
									 d->sub.height(),
									 d->left_pixmap);

	painter->drawTiledPixmap(d->sub.left(),
									 0,
									 d->sub.width(),
									 d->top_pixmap.height(),
									 d->top_pixmap);

	painter->drawTiledPixmap(d->sub.left(),
									 d->sub.bottom()+1,
									 d->sub.width(),
									 d->bottom_pixmap.height(),
									 d->bottom_pixmap);

	painter->drawTiledPixmap(d->sub.right()+1,
									 d->sub.top(),
									 d->right_pixmap.width(),
									 d->sub.height(),
									 d->right_pixmap);

	painter->drawTiledPixmap(d->sub,
									 d->back_pixmap);

	return BoxComponent::paint(painter, cg);
}



/*= GroupBoxComponent =======================================================*/

class GroupBoxComponent::Private
{
public:
	TQRect				sub;

	TQPixmap 			back_pixmap;

	TQPixmap			open_pixmap;
	TQPixmap			closed_pixmap;

	TQPixmap 			corner_tl_pixmap;
	TQPixmap 			corner_bl_pixmap;
	TQPixmap 			corner_tr_pixmap;
	TQPixmap 			corner_br_pixmap;

	TQPixmap 			top_pixmap;
	TQPixmap 			left_pixmap;
	TQPixmap 			right_pixmap;
	TQPixmap 			bottom_pixmap;
};

GroupBoxComponent::GroupBoxComponent(ComponentBase *parent, Direction dir)
 : BoxComponent(parent, dir), d(new Private())
{}

GroupBoxComponent::~GroupBoxComponent()
{
	delete d;
}

void	GroupBoxComponent::reloadTheme()
{
	TQString	path = KopetePrefs::prefs()->themeURL();
	TQString	str;

	str = path + "GroupBackground.png";
	d->back_pixmap.load(str);

	str = path + "GroupOpen.png";
	d->open_pixmap.load(str);
	str = path + "GroupClosed.png";
	d->closed_pixmap.load(str);

	str = path + "GroupTopLeft.png";
	d->corner_tl_pixmap.load(str);
	str = path + "GroupBottomLeft.png";
	d->corner_bl_pixmap.load(str);
	str = path + "GroupTopRight.png";
	d->corner_tr_pixmap.load(str);
	str = path + "GroupBottomRight.png";
	d->corner_br_pixmap.load(str);

	str = path + "GroupTop.png";
	d->top_pixmap.load(str);
	str = path + "GroupLeft.png";
	d->left_pixmap.load(str);
	str = path + "GroupRight.png";
	d->right_pixmap.load(str);
	str = path + "GroupBottom.png";
	d->bottom_pixmap.load(str);
}

void	GroupBoxComponent::layout(const TQRect &rect)
{
	d->sub.setLeft(rect.left() +
			d->left_pixmap.width());
	d->sub.setTop(rect.top() +
			d->top_pixmap.height());
	d->sub.setRight(rect.right() -
			d->right_pixmap.width());
	d->sub.setBottom(rect.bottom() -
			d->bottom_pixmap.height());

	BoxComponent::layout(d->sub);
	Component::layout(rect);
}

int	GroupBoxComponent::widthForHeight(int height)
{
	return BoxComponent::widthForHeight(height) +
			d->left_pixmap.width() +
			d->right_pixmap.width();
}

int	GroupBoxComponent::heightForWidth( int width )
{
	return BoxComponent::heightForWidth(width) +
			d->top_pixmap.height() +
			d->bottom_pixmap.height();
}

void	GroupBoxComponent::paint( TQPainter *painter, const TQColorGroup &cg )
{
	painter->drawPixmap(0,
							  0,
							  d->corner_tl_pixmap);

	painter->drawPixmap(0,
							  d->sub.bottom()+1,
							  d->corner_bl_pixmap);

	painter->drawPixmap(d->sub.right()+1,
							  0,
							  d->corner_tr_pixmap);

	painter->drawPixmap(d->sub.right()+1,
							  d->sub.bottom()+1,
							  d->corner_br_pixmap);

	painter->drawTiledPixmap(0,
									 d->sub.top(),
									 d->left_pixmap.width(),
									 d->sub.height(),
									 d->left_pixmap);

	painter->drawTiledPixmap(d->sub.left(),
									 0,
									 d->sub.width(),
									 d->top_pixmap.height(),
									 d->top_pixmap);

	painter->drawTiledPixmap(d->sub.left(),
									 d->sub.bottom()+1,
									 d->sub.width(),
									 d->bottom_pixmap.height(),
									 d->bottom_pixmap);

	painter->drawTiledPixmap(d->sub.right()+1,
									 d->sub.top(),
									 d->right_pixmap.width(),
									 d->sub.height(),
									 d->right_pixmap);

	painter->drawTiledPixmap(d->sub,
									 d->back_pixmap);

	return BoxComponent::paint(painter, cg);
}

// ImageComponent --------

class ImageComponent::Private
{
public:
	TQPixmap image;
};

ImageComponent::ImageComponent( ComponentBase *parent )
 : Component( parent ), d( new Private )
{
}

int ImageComponent::RTTI = Rtti_ImageComponent;

ImageComponent::ImageComponent( ComponentBase *parent, int minW, int minH )
 : Component( parent ), d( new Private )
{
	setMinWidth( minW );
	setMinHeight( minH );
	repaint();
}

ImageComponent::~ImageComponent()
{
	delete d;
}

TQPixmap ImageComponent::pixmap()
{
	return d->image;
}

void ImageComponent::setPixmap( const TQPixmap &img, bool)
{
	d->image = img;
	setMinWidth(d->image.width());
	setMinHeight(d->image.height());

	repaint();
}

void ImageComponent::paint( TQPainter *painter, const TQColorGroup & )
{
	TQRect ourRc = rect();
	TQRect rc = d->image.rect();
	// center rc within our rect
	rc.moveTopLeft(ourRc.topLeft());
	// paint, shrunk to be within our rect
	painter->drawPixmap( rc & ourRc, d->image );
}

void ImageComponent::scale( int w, int h, TQImage::ScaleMode mode )
{
	TQImage im = d->image.convertToImage();
	setPixmap( TQPixmap( im.smoothScale( w, h, mode ) ) );
}



/*= FaceComponent ===========================================================*/

void		FaceComponent::setPixmap(const TQPixmap &img, bool)
{
	d->image = img;

	setMinWidth(d->image.width());
	setMinHeight(d->image.height());

	if (img.width() >= 30)
	{
		d->image = TQPixmap(img.convertToImage().smoothScale(30, 30));
		setMinWidth(d->image.width() + 4);
		setMinHeight(d->image.height() + 4);
	}

	repaint();
}

static TQPoint	operator+(const TQPoint &pt, const TQSize &sz)
{
	return TQPoint(pt.x() + sz.width(), pt.y() + sz.height());
}

void	FaceComponent::paint(TQPainter *painter, const TQColorGroup &)
{
	TQRect	outRc = rect();
	TQRect pixRc = d->image.rect();

	pixRc.moveTopLeft(outRc.topLeft() + (outRc.size() - pixRc.size()) / 2);

	if (d->image.width() == 30)
	{
		TQPixmap	pixBorder;
		TQString	path = KopetePrefs::prefs()->themeURL();
		TQString	str = path + "ContactFace.png";

		pixBorder.load(str);
		TQRect pixRc2 =  pixBorder.rect();

		pixRc2.moveTopLeft(outRc.topLeft() + (outRc.size() - pixRc2.size()) / 2);
		painter->drawPixmap(pixRc2, pixBorder);
	}

	painter->drawPixmap(pixRc, d->image);
}


// TextComponent

class TextComponent::Private
{
public:
	Private() : customColor( false ) {}
	TQString text;
	bool customColor;
	TQColor color;
	TQFont font;
};

TextComponent::TextComponent( ComponentBase *parent, const TQFont &font, const TQString &text )
 : Component( parent ), d( new Private )
{
	setFont( font );
	setText( text );
}

int TextComponent::RTTI = Rtti_TextComponent;

TextComponent::~TextComponent()
{
	delete d;
}

TQString TextComponent::text()
{
	return d->text;
}

void TextComponent::setText( const TQString &text )
{
	if ( text == d->text ) return;
	d->text = text;
	relayout();
	calcMinSize();
}

TQFont TextComponent::font()
{
	return d->font;
}

void TextComponent::setFont( const TQFont &font )
{
	if ( font == d->font ) return;
	d->font = font;
	calcMinSize();
}

void TextComponent::calcMinSize()
{
	setMinWidth( 0 );

	if ( !d->text.isEmpty() )
		setMinHeight( TQFontMetrics( font() ).height() );
	else
		setMinHeight( 0 );

	repaint();
}

int TextComponent::widthForHeight( int )
{
	// add 2 to place an extra gap between the text and things to its right.
	// allegedly if this is not done the protocol icons overlap the text.
	// i however have never seen this problem (which would almost certainly
	// be a bug somewhere else).
	return TQFontMetrics( font() ).width( d->text ) + 2;
}

TQColor TextComponent::color()
{
	return d->customColor ? d->color : TQColor();
}

void TextComponent::setColor( const TQColor &color )
{
	d->color = color;
	d->customColor = true;
	repaint();
}

void TextComponent::setDefaultColor()
{
	d->customColor = false;
	repaint();
}

void TextComponent::paint( TQPainter *painter, const TQColorGroup &cg )
{
	if ( d->customColor )
		painter->setPen( d->color );
	else
		painter->setPen( cg.text() );
	TQString dispStr = KStringHandler::rPixelSqueeze( d->text, TQFontMetrics( font() ), rect().width() );
	painter->setFont( font() );
	painter->drawText( rect(), TQt::SingleLine, dispStr );
}

// DisplayNameComponent

class DisplayNameComponent::Private
{
public:
	TQString text;
    TQFont font;
};

DisplayNameComponent::DisplayNameComponent( ComponentBase *parent )
 : BoxComponent( parent ), d( new Private )
{
}

int DisplayNameComponent::RTTI = Rtti_DisplayNameComponent;

DisplayNameComponent::~DisplayNameComponent()
{
	delete d;
}

void DisplayNameComponent::layout( const TQRect &rect )
{
	Component::layout( rect );

	// finally, lay everything out
	TQRect rc;
	int totalWidth = rect.width();
	int usedWidth = 0;
	bool exceeded = false;
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		if ( !exceeded )
		{
			if ( ( usedWidth + comp->widthForHeight( rect.height() ) ) > totalWidth )
			{
				exceeded = true;
				// TextComponents can squeeze themselves
				if ( comp->rtti() == Rtti_TextComponent )
				{
					comp->show();
					comp->layout( TQRect( usedWidth+ rect.left(), rect.top(),
							     totalWidth - usedWidth,
							     comp->heightForWidth( totalWidth - usedWidth ) ) );
				} else {
					comp->hide();
				}
			}
			else 
			{
				comp->show();
				comp->layout( TQRect( usedWidth+ rect.left(), rect.top(),
						     comp->widthForHeight( rect.height() ),
						     comp->heightForWidth( rect.width() ) ) );
			}
			usedWidth+= comp->widthForHeight( rect.height() );
		}
		else
		{
			// Shall we implement a hide()/show() in Component class ?
			comp->hide();
		}
	}
}

void DisplayNameComponent::setText( const TQString& text )
{
	if ( d->text == text ) 
		return;
	d->text = text;
	
	redraw();
}

void DisplayNameComponent::redraw()
{
	TQColor color;
	for ( uint n = 0; n < components(); ++n )
		if( component( n )->rtti() == Rtti_TextComponent )
	{
		((TextComponent*)component(n))->color();
	}
	
	TQValueList<Kopete::Emoticons::Token> tokens;
	TQValueList<Kopete::Emoticons::Token>::const_iterator token;
	
	clear(); // clear childs

	tokens = Kopete::Emoticons::tokenizeEmoticons( d->text );
	ImageComponent *ic;
	TextComponent *t;

	TQFontMetrics fontMetrics( d->font );
	int fontHeight = fontMetrics.height();
	for ( token = tokens.begin(); token != tokens.end(); ++token )
	{
		switch ( (*token).type )
		{
		case Kopete::Emoticons::Text:
			t = new TextComponent( this,  d->font, (*token).text );
		break;
		case Kopete::Emoticons::Image:
			ic = new ImageComponent( this );
			ic->setPixmap( TQPixmap( (*token).picPath ) );
			ic->scale( INT_MAX, fontHeight, TQImage::ScaleMin );
		break;
		default:
			kdDebug( 14010 ) << k_funcinfo << "This should have not happened!" << endl;
		}
	}
	
	if(color.isValid())
		setColor( color );
}

void DisplayNameComponent::setFont( const TQFont& font )
{
	for ( uint n = 0; n < components(); ++n )
		if( component( n )->rtti() == Rtti_TextComponent )
			((TextComponent*)component(n))->setFont( font );
	d->font = font;
}

void DisplayNameComponent::setColor( const TQColor& color )
{
	for ( uint n = 0; n < components(); ++n )
		if( component( n )->rtti() == Rtti_TextComponent )
			((TextComponent*)component(n))->setColor( color );
}

void DisplayNameComponent::setDefaultColor()
{
	for ( uint n = 0; n < components(); ++n )
		if( component( n )->rtti() == Rtti_TextComponent )
			((TextComponent*)component(n))->setDefaultColor();
}

TQString DisplayNameComponent::text()
{
	return d->text;
}

// HSpacerComponent --------

HSpacerComponent::HSpacerComponent( ComponentBase *parent )
 : Component( parent )
{
	setMinWidth( 0 );
	setMinHeight( 0 );
}

int HSpacerComponent::RTTI = Rtti_HSpacerComponent;

int HSpacerComponent::widthForHeight( int )
{
	return INT_MAX;
}

// VSpacerComponent --------

VSpacerComponent::VSpacerComponent( ComponentBase *parent )
 : Component( parent )
{
	setMinWidth( 0 );
	setMinHeight( 0 );
}

int VSpacerComponent::RTTI = Rtti_VSpacerComponent;

int VSpacerComponent::heightForWidth( int )
{
	return INT_MAX;
}

//////////////////  ContactComponent /////////////////////////

class ContactComponent::Private
{
public:
	Kopete::Contact *contact;
	int iconSize;
};

ContactComponent::ContactComponent( ComponentBase *parent, Kopete::Contact *contact, int) : ImageComponent( parent ) , d( new Private )
{
	d->contact = contact;
	d->iconSize = 12; // size of the image is fixed to 12 pixels
	updatePixmap();
}

ContactComponent::~ContactComponent()
{
	delete d;
}

void ContactComponent::updatePixmap()
{
	setPixmap( contact()->onlineStatus().iconFor( contact(), d->iconSize ) );
}
Kopete::Contact *ContactComponent::contact()
{
	return d->contact;
}

// we don't need to use a tooltip source here - this way is simpler
std::pair<TQString,TQRect> ContactComponent::toolTip( const TQPoint &/*relativePos*/ )
{
	return std::make_pair(d->contact->toolTip(),rect());
}

//////////////////  SpacerComponent /////////////////////////

SpacerComponent::SpacerComponent( ComponentBase *parent, int w, int h ) : Component( parent )
{
	setMinWidth(w);
	setMinHeight(h);
}

// Item --------

/**
 * A periodic timer intended to be shared amongst multiple objects. Will run only
 * if an object is attached to it.
 */
class SharedTimer : private TQTimer
{
	int period;
	int users;
public:
	SharedTimer( int period ) : period(period), users(0) {}
	void attach( TQObject *target, const char *slot )
	{
		connect( this, TQT_SIGNAL(timeout()), target, slot );
		if( users++ == 0 )
			start( period );
		//kdDebug(14000) << "SharedTimer::attach: users is now " << users << "\n";
	}
	void detach( TQObject *target, const char *slot )
	{
		disconnect( this, TQT_SIGNAL(timeout()), target, slot );
		if( --users == 0 )
			stop();
		//kdDebug(14000) << "SharedTimer::detach: users is now " << users << "\n";
	}
};

class SharedTimerRef
{
	SharedTimer &timer;
	TQObject * const object;
	const char * const slot;
	bool attached;
public:
	SharedTimerRef( SharedTimer &timer, TQObject *obj, const char *slot )
	 : timer(timer), object(obj), slot(slot), attached(false)
	{
	}
	void start()
	{
		if( attached ) return;
		timer.attach( object, slot );
		attached = true;
	}
	void stop()
	{
		if( !attached ) return;
		timer.detach( object, slot );
		attached = false;
	}
	bool isActive()
	{
		return attached;
	}
};

class Item::Private
{
public:
	Private( Item *item )
	 : layoutAnimateTimer( theLayoutAnimateTimer(), item, TQT_SLOT( slotLayoutAnimateItems() ) )
	 , animateLayout( true ), opacity( 1.0 )
	 , visibilityTimer( theVisibilityTimer(), item, TQT_SLOT( slotUpdateVisibility() ) )
	 , visibilityLevel( 0 ), visibilityTarget( false ), searchMatch( true )
	{
	}

	TQTimer layoutTimer;

	//TQTimer layoutAnimateTimer;
	SharedTimerRef layoutAnimateTimer;
	SharedTimer &theLayoutAnimateTimer()
	{
		static SharedTimer timer( 10 );
		return timer;
	}
	
	bool animateLayout;
	int layoutAnimateSteps;
	static const int layoutAnimateStepsTotal = 10;

	float opacity;

	//TQTimer visibilityTimer;
	SharedTimerRef visibilityTimer;
	SharedTimer &theVisibilityTimer()
	{
		static SharedTimer timer( 40 );
		return timer;
	}

	int visibilityLevel;
	bool visibilityTarget;
	static const int visibilityFoldSteps = 7;
#ifdef HAVE_XRENDER
	static const int visibilityFadeSteps = 7;
#else
	static const int visibilityFadeSteps = 0;
#endif
	static const int visibilityStepsTotal = visibilityFoldSteps + visibilityFadeSteps;
	
	bool searchMatch;
		
	static bool animateChanges;
	static bool fadeVisibility;
	static bool foldVisibility;
};

bool Item::Private::animateChanges = true;
bool Item::Private::fadeVisibility = true;
bool Item::Private::foldVisibility = true;

Item::Item( TQListViewItem *parent, TQObject *owner, const char *name )
 : TQObject( owner, name ), TDEListViewItem( parent ), d( new Private(this) )
{
	initLVI();
}

Item::Item( TQListView *parent, TQObject *owner, const char *name )
 : TQObject( owner, name ), TDEListViewItem( parent ), d( new Private(this) )
{
	initLVI();
}

Item::~Item()
{
	delete d;
}

void Item::setEffects( bool animation, bool fading, bool folding )
{
	Private::animateChanges = animation;
	Private::fadeVisibility = fading;
	Private::foldVisibility = folding;
}

void Item::initLVI()
{
	connect( listView()->header(), TQT_SIGNAL( sizeChange( int, int, int ) ), TQT_SLOT( slotColumnResized() ) );
	connect( &d->layoutTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotLayoutItems() ) );
	//connect( &d->layoutAnimateTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotLayoutAnimateItems() ) );
	//connect( &d->visibilityTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotUpdateVisibility() ) );
	setVisible( false );
	setTargetVisibility( true );
}

void Item::slotColumnResized()
{
	scheduleLayout();
	// if we've been resized, don't animate the layout
	d->animateLayout = false;
}

void Item::scheduleLayout()
{
	// perform a delayed layout in order to speed it all up
	if ( ! d->layoutTimer.isActive() )
		d->layoutTimer.start( 30, true );
}

void Item::slotLayoutItems()
{
	d->layoutTimer.stop();

	for ( uint n = 0; n < components(); ++n )
	{
		int width = listView()->columnWidth(n);
		if ( n == 0 )
		{
			int d = depth() + (listView()->rootIsDecorated() ? 1 : 0);
			width -= d * listView()->treeStepSize();
		}

		int height = component( n )->heightForWidth( width );
		component( n )->layout( TQRect( 0, 0, width, height ) );
		//kdDebug(14000) << k_funcinfo << "Component " << n << " is " << width << " x " << height << endl;
	}

	if ( Private::animateChanges && d->animateLayout && !d->visibilityTimer.isActive() )
	{
		d->layoutAnimateTimer.start();
		//if ( !d->layoutAnimateTimer.isActive() )
		//	d->layoutAnimateTimer.start( 10 );
		d->layoutAnimateSteps = 0;
	}
	else
	{
		d->layoutAnimateSteps = Private::layoutAnimateStepsTotal;
		d->animateLayout = true;
	}
	slotLayoutAnimateItems();
}

void Item::slotLayoutAnimateItems()
{
	if ( ++d->layoutAnimateSteps >= Private::layoutAnimateStepsTotal )
		d->layoutAnimateTimer.stop();

	const int s = Private::layoutAnimateStepsTotal;
	const int p = TQMIN( d->layoutAnimateSteps, s );

	updateAnimationPosition( p, s );
	setHeight(0);
	repaint();
}

float Item::opacity()
{
	return d->opacity;
}

void Item::setOpacity( float opacity )
{
	if ( d->opacity == opacity ) return;
	d->opacity = opacity;
	repaint();
}

void Item::setSearchMatch( bool match )
{
	d->searchMatch = match;
	
	if ( !match )
		setVisible( false );
	else
	{
		kdDebug(14000) << k_funcinfo << " match: " << match << ", vis timer active: " << d->visibilityTimer.isActive()
		               << ", target visibility: " << targetVisibility() << endl;
		if ( d->visibilityTimer.isActive() )
			setVisible( true );
		else
			setVisible( targetVisibility() );
	}
}

bool Item::targetVisibility()
{
	return d->visibilityTarget;
}

void Item::setTargetVisibility( bool vis )
{
	if ( d->visibilityTarget == vis )
	{
		// in case we're getting called because our parent was shown and
		// we need to be rehidden
		if ( !d->visibilityTimer.isActive() )
			setVisible( vis && d->searchMatch );
		return;
	}
	d->visibilityTarget = vis;
	d->visibilityTimer.start();
	//d->visibilityTimer.start( 40 );
	if ( targetVisibility() )
		setVisible( d->searchMatch );
	slotUpdateVisibility();
}

void Item::slotUpdateVisibility()
{
	if ( targetVisibility() )
		++d->visibilityLevel;
	else
		--d->visibilityLevel;

	if ( !Private::foldVisibility && !Private::fadeVisibility )
		d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : 0;
	else if ( !Private::fadeVisibility && d->visibilityLevel >= Private::visibilityFoldSteps )
		d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : Private::visibilityFoldSteps - 1;
	else if ( !Private::foldVisibility && d->visibilityLevel <= Private::visibilityFoldSteps )
		d->visibilityLevel = targetVisibility() ? Private::visibilityFoldSteps + 1 : 0;

	if ( d->visibilityLevel >= Private::visibilityStepsTotal )
	{
		d->visibilityLevel = Private::visibilityStepsTotal;
		d->visibilityTimer.stop();
	}
	else if ( d->visibilityLevel <= 0 )
	{
		d->visibilityLevel = 0;
		d->visibilityTimer.stop();
		setVisible( false );
	}
	setHeight( 0 );
	repaint();
}

void Item::repaint()
{
	// if we're about to relayout, don't bother painting yet.
	if ( d->layoutTimer.isActive() )
		return;
	listView()->repaintItem( this );
}

void Item::relayout()
{
	scheduleLayout();
}

void Item::setup()
{
	TDEListViewItem::setup();
	slotLayoutItems();
}

void Item::setHeight( int )
{
	int minHeight = 0;
	for ( uint n = 0; n < components(); ++n )
		minHeight = TQMAX( minHeight, component( n )->rect().height() );
	//kdDebug(14000) << k_funcinfo << "Height is " << minHeight << endl;
	if ( Private::foldVisibility && d->visibilityTimer.isActive() )
	{
		int vis = TQMIN( d->visibilityLevel, Private::visibilityFoldSteps );
		minHeight = (minHeight * vis) / Private::visibilityFoldSteps;
	}
	TDEListViewItem::setHeight( minHeight );
}

int Item::width( const TQFontMetrics &, const TQListView *lv, int c ) const
{
	// TQt computes the itemRect from this. we want the whole item to be
	// clickable, so we return the widest we could possibly be.
	return lv->header()->sectionSize( c );
}

void Item::paintCell( TQPainter *p, const TQColorGroup &cg, int column, int width, int align )
{
	TQPixmap back( width, height() );
	TQPainter paint( &back );
	//TDEListViewItem::paintCell( &paint, cg, column, width, align );
	// PASTED FROM KLISTVIEWITEM:
	// set the alternate cell background colour if necessary
	TQColorGroup _cg = cg;
	if (isAlternate())
		if (listView()->viewport()->backgroundMode()==TQt::FixedColor)
			_cg.setColor(TQColorGroup::Background, static_cast< TDEListView* >(listView())->alternateBackground());
		else
			_cg.setColor(TQColorGroup::Base, static_cast< TDEListView* >(listView())->alternateBackground());
	// PASTED FROM TQLISTVIEWITEM
	{
		TQPainter *p = &paint;

		TQListView *lv = listView();
		if ( !lv )
			return;
		TQFontMetrics fm( p->fontMetrics() );

		// any text we render is done by the Components, not by this class, so make sure we've nothing to write
		TQString t;

		// removed text truncating code from TQt - we do that differently, further on

		int marg = lv->itemMargin();
		int r = marg;
	//	const TQPixmap * icon = pixmap( column );

		const BackgroundMode bgmode = lv->viewport()->backgroundMode();
		const TQColorGroup::ColorRole crole = TQPalette::backgroundRoleFromMode( bgmode );

		if ( _cg.brush( crole ) != lv->colorGroup().brush( crole ) )
			p->fillRect( 0, 0, width, height(), _cg.brush( crole ) );
		else
		{
			// all copied from TQListView::paintEmptyArea

			//lv->paintEmptyArea( p, TQRect( 0, 0, width, height() ) );
			TQStyleOption opt( lv->sortColumn(), 0 ); // ### hack; in 3.1, add a property in TQListView and TQHeader
			TQStyle::SFlags how = TQStyle::Style_Default;
			if ( lv->isEnabled() )
				how |= TQStyle::Style_Enabled;

			lv->style().drawComplexControl( TQStyle::CC_ListView,
						p, lv, TQRect( 0, 0, width, height() ), lv->colorGroup(),
						how, TQStyle::SC_ListView, TQStyle::SC_None,
						opt );
		}



		if ( isSelected() &&
		(column == 0 || lv->allColumnsShowFocus()) ) {
			p->fillRect( r - marg, 0, width - r + marg, height(),
					_cg.brush( TQColorGroup::Highlight ) );
	// removed text pen setting code from TQt
		}

		// removed icon drawing code from TQt

		// draw the tree gubbins
		if ( multiLinesEnabled() && column == 0 && isOpen() && childCount() ) {
			int textheight = fm.size( align, t ).height() + 2 * lv->itemMargin();
			textheight = TQMAX( textheight, TQApplication::globalStrut().height() );
			if ( textheight % 2 > 0 )
				textheight++;
			if ( textheight < height() ) {
				int w = lv->treeStepSize() / 2;
				lv->style().drawComplexControl( TQStyle::CC_ListView, p, lv,
								TQRect( 0, textheight, w + 1, height() - textheight + 1 ), _cg,
								lv->isEnabled() ? TQStyle::Style_Enabled : TQStyle::Style_Default,
								TQStyle::SC_ListViewExpand,
								(uint)TQStyle::SC_All, TQStyleOption( this ) );
			}
		}
	}
	// END OF PASTE
	
	
	//do you see a better way to tell the TextComponent we are selected ?  - Olivier 2004-09-02
	if ( isSelected() ) 
		_cg.setColor(TQColorGroup::Text , _cg.highlightedText() );
	
	if ( Component *comp = component( column ) )
		comp->paint( &paint, _cg );
	paint.end();

#ifdef HAVE_XRENDER
	TQColor rgb = cg.base();//backgroundColor();
	float opac = 1.0;
	if ( d->visibilityTimer.isActive() && Private::fadeVisibility )
	{
		int vis = TQMAX( d->visibilityLevel - Private::visibilityFoldSteps, 0 );
		opac = float(vis) / Private::visibilityFadeSteps;
	}
	opac *= opacity();
	const int alpha = 257 - int(opac * 257);
	if ( alpha != 0 )
	{
		XRenderColor clr = { alpha * rgb.red(), alpha * rgb.green(), alpha * rgb.blue(), alpha * 0xff };
		XRenderFillRectangle( back.x11Display(), PictOpOver, back.x11RenderHandle(),
		                      &clr, 0, 0, width, height() );
	}
#endif

	p->drawPixmap( 0, 0, back );
}

void Item::componentAdded( Component *component )
{
	ComponentBase::componentAdded( component );
	scheduleLayout();
}

void Item::componentRemoved( Component *component )
{
	ComponentBase::componentRemoved( component );
	scheduleLayout();
}

void Item::componentResized( Component *component )
{
	ComponentBase::componentResized( component );
	scheduleLayout();
}

} // END namespace ListView
} // END namespace UI
} // END namespace Kopete

#include "kopetelistviewitem.moc"
