/*
    kopetelistviewitem.cpp - Kopete's modular QListViewItems

    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 "kopetecontact.h"
#include "kopetelistviewitem.h"
#include "kopeteemoticons.h"
#include "kopeteonlinestatus.h"

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

#include <qapplication.h>
#include <qpixmap.h>
#include <qpainter.h>
#include <qptrlist.h>
#include <qrect.h>
#include <qtimer.h>
#include <qheader.h>
#include <qstyle.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:
	QPtrList<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 QPoint &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<QString,QRect> ComponentBase::toolTip( const QPoint &relativePos )
{
	for ( uint n = 0; n < components(); ++n )
		if ( component( n )->rect().contains( relativePos ) )
			return component( n )->toolTip( relativePos );

	return std::make_pair( QString::null, QRect() );
}

void ComponentBase::updateAnimationPosition( int p, int s )
{
	for ( uint n = 0; n < components(); ++n )
	{
		Component *comp = component( n );
		QRect start = comp->startRect();
		QRect target = comp->targetRect();
		QRect 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;
	QRect rect;
	QRect 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<QString,QRect> Component::toolTip( const QPoint &relativePos )
{
	if ( !d->tipSource )
		return ComponentBase::toolTip( relativePos );

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

QRect Component::rect() { return d->rect; }
QRect Component::startRect() { return d->startRect; }
QRect 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 QRect &newRect )
{
	if ( rect().isNull() )
		d->startRect = QRect( newRect.topLeft(), newRect.topLeft() );
	else
		d->startRect = rect();
	d->targetRect = newRect;
	//kdDebug(14000) << k_funcinfo << "At " << rect << endl;
}

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

void Component::paint( QPainter *painter, const QColorGroup &cg )
{
	/*painter->setPen( Qt::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 = 2;
};

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 = QMAX( 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 = QMAX( 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 = QMAX( max, comp->minHeight() );
			sum += comp->minWidth();
		}
		else
		{
			max = QMAX( 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 QRect &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 = QMAX( rect.width(), minWidth() );
	else
		total = QMAX( 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 );
		
		QRect 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( QMIN( 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( QMIN( 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();
}

// ImageComponent --------

class ImageComponent::Private
{
public:
	QPixmap 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;
}

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

void ImageComponent::setPixmap( const QPixmap &img, bool adjustSize)
{
	d->image = img;
	if ( adjustSize )
	{
		setMinWidth( img.width() );
		setMinHeight( img.height() );
	}
	repaint();
}

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

/*static QPoint operator+( const QSize &sz, const QPoint &pt )
{
	return pt + sz;
}*/

void ImageComponent::paint( QPainter *painter, const QColorGroup & )
{
	QRect ourRc = rect();
	QRect rc = d->image.rect();
	// center rc within our rect
	rc.moveTopLeft( ourRc.topLeft() + (ourRc.size() - rc.size()) / 2 );
	// paint, shrunk to be within our rect
	painter->drawPixmap( rc & ourRc, d->image );
}

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

class TextComponent::Private
{
public:
	Private() : customColor( false ) {}
	QString text;
	bool customColor;
	QColor color;
	QFont font;
};

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

int TextComponent::RTTI = Rtti_TextComponent;

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

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

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

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

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

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

	if ( !d->text.isEmpty() )
		setMinHeight( QFontMetrics( 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 QFontMetrics( font() ).width( d->text ) + 2;
}

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

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

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

void TextComponent::paint( QPainter *painter, const QColorGroup &cg )
{
	if ( d->customColor )
		painter->setPen( d->color );
	else
		painter->setPen( cg.text() );
	QString dispStr = KStringHandler::rPixelSqueeze( d->text, QFontMetrics( font() ), rect().width() );
	painter->setFont( font() );
	painter->drawText( rect(), Qt::SingleLine, dispStr );
}

// DisplayNameComponent

class DisplayNameComponent::Private
{
public:
	QString text;
    QFont font;
};

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

int DisplayNameComponent::RTTI = Rtti_DisplayNameComponent;

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

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

	// finally, lay everything out
	QRect 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( QRect( usedWidth+ rect.left(), rect.top(),
							     totalWidth - usedWidth,
							     comp->heightForWidth( totalWidth - usedWidth ) ) );
				} else {
					comp->hide();
				}
			}
			else 
			{
				comp->show();
				comp->layout( QRect( 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 QString& text )
{
	if ( d->text == text ) 
		return;
	d->text = text;
	
	redraw();
}

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

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

	QFontMetrics 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( QPixmap( (*token).picPath ) );
			ic->scale( INT_MAX, fontHeight, QImage::ScaleMin );
		break;
		default:
			kdDebug( 14010 ) << k_funcinfo << "This should have not happened!" << endl;
		}
	}
	
	if(color.isValid())
		setColor( color );
}

void DisplayNameComponent::setFont( const QFont& 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 QColor& 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();
}

QString 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 iconSize) : ImageComponent( parent ) , d( new Private )
{
	d->contact = contact;
	d->iconSize = iconSize;
	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<QString,QRect> ContactComponent::toolTip( const QPoint &/*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 QTimer
{
	int period;
	int users;
public:
	SharedTimer( int period ) : period(period), users(0) {}
	void attach( QObject *target, const char *slot )
	{
		connect( this, SIGNAL(timeout()), target, slot );
		if( users++ == 0 )
			start( period );
		//kdDebug(14000) << "SharedTimer::attach: users is now " << users << "\n";
	}
	void detach( QObject *target, const char *slot )
	{
		disconnect( this, SIGNAL(timeout()), target, slot );
		if( --users == 0 )
			stop();
		//kdDebug(14000) << "SharedTimer::detach: users is now " << users << "\n";
	}
};

class SharedTimerRef
{
	SharedTimer &timer;
	QObject * const object;
	const char * const slot;
	bool attached;
public:
	SharedTimerRef( SharedTimer &timer, QObject *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, SLOT( slotLayoutAnimateItems() ) )
	 , animateLayout( true ), opacity( 1.0 )
	 , visibilityTimer( theVisibilityTimer(), item, SLOT( slotUpdateVisibility() ) )
	 , visibilityLevel( 0 ), visibilityTarget( false ), searchMatch( true )
	{
	}

	QTimer layoutTimer;

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

	float opacity;

	//QTimer 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( QListViewItem *parent, QObject *owner, const char *name )
 : QObject( owner, name ), KListViewItem( parent ), d( new Private(this) )
{
	initLVI();
}

Item::Item( QListView *parent, QObject *owner, const char *name )
 : QObject( owner, name ), KListViewItem( 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(), SIGNAL( sizeChange( int, int, int ) ), SLOT( slotColumnResized() ) );
	connect( &d->layoutTimer, SIGNAL( timeout() ), SLOT( slotLayoutItems() ) );
	//connect( &d->layoutAnimateTimer, SIGNAL( timeout() ), SLOT( slotLayoutAnimateItems() ) );
	//connect( &d->visibilityTimer, SIGNAL( timeout() ), 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( QRect( 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 = QMIN( 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()
{
	KListViewItem::setup();
	slotLayoutItems();
}

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

int Item::width( const QFontMetrics &, const QListView *lv, int c ) const
{
	// Qt 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( QPainter *p, const QColorGroup &cg, int column, int width, int align )
{
	QPixmap back( width, height() );
	QPainter paint( &back );
	//KListViewItem::paintCell( &paint, cg, column, width, align );
	// PASTED FROM KLISTVIEWITEM:
	// set the alternate cell background colour if necessary
	QColorGroup _cg = cg;
	if (isAlternate())
		if (listView()->viewport()->backgroundMode()==Qt::FixedColor)
			_cg.setColor(QColorGroup::Background, static_cast< KListView* >(listView())->alternateBackground());
		else
			_cg.setColor(QColorGroup::Base, static_cast< KListView* >(listView())->alternateBackground());
	// PASTED FROM QLISTVIEWITEM
	{
		QPainter *p = &paint;

		QListView *lv = listView();
		if ( !lv )
			return;
		QFontMetrics fm( p->fontMetrics() );

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

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

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

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

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

			//lv->paintEmptyArea( p, QRect( 0, 0, width, height() ) );
			QStyleOption opt( lv->sortColumn(), 0 ); // ### hack; in 3.1, add a property in QListView and QHeader
			QStyle::SFlags how = QStyle::Style_Default;
			if ( lv->isEnabled() )
				how |= QStyle::Style_Enabled;

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



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

		// removed icon drawing code from Qt

		// draw the tree gubbins
		if ( multiLinesEnabled() && column == 0 && isOpen() && childCount() ) {
			int textheight = fm.size( align, t ).height() + 2 * lv->itemMargin();
			textheight = QMAX( textheight, QApplication::globalStrut().height() );
			if ( textheight % 2 > 0 )
				textheight++;
			if ( textheight < height() ) {
				int w = lv->treeStepSize() / 2;
				lv->style().drawComplexControl( QStyle::CC_ListView, p, lv,
								QRect( 0, textheight, w + 1, height() - textheight + 1 ), _cg,
								lv->isEnabled() ? QStyle::Style_Enabled : QStyle::Style_Default,
								QStyle::SC_ListViewExpand,
								(uint)QStyle::SC_All, QStyleOption( 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(QColorGroup::Text , _cg.highlightedText() );
	
	if ( Component *comp = component( column ) )
		comp->paint( &paint, _cg );
	paint.end();

#ifdef HAVE_XRENDER
	QColor rgb = cg.base();//backgroundColor();
	float opac = 1.0;
	if ( d->visibilityTimer.isActive() && Private::fadeVisibility )
	{
		int vis = QMAX( 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"

// vim: set noet ts=4 sts=4 sw=4:
