/***************************************************************************
 *   Copyright (C) 2007 by Sébastien Laoût                                 *
 *   slaout@linux62.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.                                   *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <kdeversion.h> // Detect KDE 3.5

#include "coverdisplay.h"
#include "progressbar.h"
#include "amarokapi.h"
#include "dcopinterface.h"
#include "playerinformation.h"
#include "tools.h"

#include <kapplication.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qbitmap.h>
#include <qimage.h>
#include <kimageeffect.h>
#include <qtooltip.h>
#include <qcursor.h>
#include <qdatetime.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kiconloader.h>
#include <qdir.h>
#include <krun.h>
#include <kurl.h>
#include <qstylesheet.h>

#include "applet.h"

#include <iostream>

const int FULL_SCREEN_TIMOUT = 3000;

const int CONTROL_BUTTONS_MAX_SIZE = 64;
const int STAR_IMAGE_SIZE          = 128;

const QRect HIDDEN_RECT = QRect(-100, -100, 1, 1);

const bool ANIMATE_BUTTONS = true;
const bool ANIMATE_TRACKS  = true;

CoverDisplay::CoverDisplay(QWidget *parent, Applet *applet/* = 0*/)
 : QWidget(parent, 0, Qt::WNoAutoErase),
   m_underMouse(false),
   m_shadowX(0), m_shadowWidth(0), m_progressBarOnLeft(false), m_withShadow(true),
   m_hoveredStar(-1), m_hoveredButton(-1), m_clickedButton(-1),
   m_orientation(Vertical), m_margin(0), m_progressMargin(0), m_buttonPadding(0), m_shadowMargin(0),
   m_buttonsAnimator(500/*ms*/), m_playPauseAnimator(500/*ms*/),
   m_trackAnimator(300/*ms*/), m_reverseTrackAnimation(false), m_oldRating(0), m_oldDuration(0), m_oldPosition(0),
   m_fullScreenBackgroundPixmap(0),
   m_applet(applet)
{
	// Make sure the next heightForWidth or widthForHeight will change the size:
	resize(1, 1);

	m_progressBar = new ProgressBar(this);
	m_progressBar->hide();
	connect( m_progressBar, SIGNAL(changePosition(int)), this, SLOT(setPosition(int)) );
	m_progressBar->installEventFilter(this);

	connect( &m_fullScreenTimer, SIGNAL(timeout()), this, SLOT(hideControls()) );

	m_infos = PlayerInformation::instance();
	connect( m_infos, SIGNAL(newInformation()),              this, SLOT(newInformation())              );
	connect( m_infos, SIGNAL(newRating()),                   this, SLOT(newRating())                   );
	connect( m_infos, SIGNAL(newProgress()),                 this, SLOT(newProgress())                 );
	connect( m_infos, SIGNAL(aboutToChangeTrack(int, bool)), this, SLOT(beforeAnimateTrack(int, bool)) );

	m_buttonsAnimator.setRange(0, 100);
	m_playPauseAnimator.setRange(0, 100);
	connect( &m_buttonsAnimator,   SIGNAL(newValue(int)), this, SLOT(updateStars())   );
	connect( &m_buttonsAnimator,   SIGNAL(newValue(int)), this, SLOT(updateButtons()) );
	connect( &m_playPauseAnimator, SIGNAL(newValue(int)), this, SLOT(updateButtons()) );

	m_trackAnimator.setRange(0, 100);
	connect( &m_trackAnimator, SIGNAL(beforeNewValue()), this, SLOT(updateCover())        );
	connect( &m_trackAnimator, SIGNAL(beforeNewValue()), this, SLOT(updateStars()  )      );
	connect( &m_trackAnimator, SIGNAL(beforeNewValue()), this, SLOT(updateText())         );
	connect( &m_trackAnimator, SIGNAL(beforeNewValue()), this, SLOT(updateProgressDraw()) ); // updateProgresBar() can only called to refresh animation (non-widget)
	connect( &m_trackAnimator, SIGNAL(newValue(int)),    this, SLOT(updateCover())        );
	connect( &m_trackAnimator, SIGNAL(newValue(int)),    this, SLOT(updateStars())        );
	connect( &m_trackAnimator, SIGNAL(newValue(int)),    this, SLOT(updateText())         );
	connect( &m_trackAnimator, SIGNAL(newValue(int)),    this, SLOT(updateProgressDraw()) ); // updateProgresBar() can only called to refresh animation (non-widget)

#if KDE_IS_VERSION( 3, 5, 0 )
	installEventFilter(KickerTip::the());
#endif
}

CoverDisplay::~CoverDisplay()
{
}

bool CoverDisplay::eventFilter(QObject */*object*/, QEvent *event)
{
	// Make sure we TOO get the mouse events to show/hide the mouse pointer when needed:
// 	if (dynamic_cast<QMouseEvent*>(event)/*->type() == QEvent::MouseEvent*/) {
// 		QMouseEvent *thisEvent = (QMouseEvent *) event;
// 		QMouseEvent *newEvent = new QMouseEvent(thisEvent->type(), mapFrom(m_progressBar, thisEvent->pos()/*)*/, thisEvent->button(), thisEvent->state());
// 		kapp->postEvent(this, newEvent);
// 	}
	if (event->type() == QEvent::MouseMove) {
		QMouseEvent *thisEvent = (QMouseEvent *) event;
		QMouseEvent *newEvent = new QMouseEvent(thisEvent->type(), thisEvent->pos() + m_progressBar->pos(), thisEvent->button(), thisEvent->state());
		mouseMoveEvent(newEvent);
		delete newEvent;
		//kapp->postEvent(this, newEvent);
	}

	// Not processed event, even if we effectively processed it (process it twice here and in the progressbar):
	return false;
}

void CoverDisplay::mouseMoveEvent(QMouseEvent *event)
{
	if (m_orientation == FullScreen && isVisible()) {
		// Update the "last moved mouse" time:
		m_fullScreenTimer.start(FULL_SCREEN_TIMOUT, /*singleShot=*/true);
		// Show controls if needed:
		if (m_underMouse == false) {
			m_underMouse = true;
			unsetCursor(); // Restore / show again the cursor
			if (ANIMATE_BUTTONS) {
				m_buttonsAnimator.toEnd();
				m_playPauseAnimator.toEnd();
			} else {
				updateButtons();
				updateStars();
			}
		}
	}

	// Get the number of the star that is under the mouse pointer:
	int hoveredStar = (areControlsShown() && m_underMouse && m_infos->status() != PlayerInformation::Stopped ? 0 : -1);
	if (hoveredStar != -1 && m_clickedButton == -1) {
		for (int i = 0; i < 5; i++) {
			QRect starRect(m_starsRect.x() + i * m_starWidth, m_starsRect.y(), m_starWidth, m_starWidth);
			if (starRect.contains(event->pos())) {
				hoveredStar = i + 1;
				break;
			}
		}
	}

	if (hoveredStar != m_hoveredStar) {
		m_hoveredStar = hoveredStar;
		updateStars();
		if (m_hoveredStar > 0)
			setCursor(QCursor(Qt::PointingHandCursor));
		else
			unsetCursor();
	}

	int hoveredButton = -1;
	if (m_underMouse) {
		if (areControlsShown() && m_prevRect.contains(event->pos()))
			hoveredButton = 0;
		else if (m_playRect.contains(event->pos()))
			hoveredButton = 1;
		else if (areControlsShown() && m_nextRect.contains(event->pos()))
			hoveredButton = 2;
		else if (m_infos->isPlaying() && m_fullRect.contains(event->pos()))
			hoveredButton = 3;
		else if (m_closeRect.contains(event->pos()))
			hoveredButton = 4;
	}

	if (m_hoveredButton != hoveredButton) {
		m_hoveredButton = hoveredButton;
		updateButtons();
		if (m_hoveredButton != -1)
			setCursor(QCursor(Qt::PointingHandCursor));
		else
			unsetCursor();
	}

	QWidget::mouseMoveEvent(event);
}

void CoverDisplay::enterEvent(QEvent *event)
{
	setMouseTracking(true);

	QWidget::enterEvent(event);

	if (m_orientation != FullScreen) {
		m_underMouse = true;
		if (ANIMATE_BUTTONS) {
			if (m_infos->isPlaying())
				m_buttonsAnimator.toEnd();
			m_playPauseAnimator.toEnd();
		} else {
			updateButtons();
			updateStars();
		}

		// To be able to show stars even if the mouse enter one pixel and do not move anymore:
		QMouseEvent *mouseEvent = new QMouseEvent(QEvent::MouseMove, mapFromGlobal(QCursor::pos()), 0, 0);
		mouseMoveEvent(mouseEvent);
		delete mouseEvent;
	}
}

void CoverDisplay::leaveEvent(QEvent *event)
{
	setMouseTracking(false);

	if (m_orientation != FullScreen) { // TODO: void setMouseHovered(bool)
		if (m_hoveredStar != -1) {
			m_hoveredStar = -1;
			updateStars();
			unsetCursor();
		}
		m_hoveredButton = -1;
		m_underMouse = false;
		if (ANIMATE_BUTTONS) {
			m_buttonsAnimator.toBegin();
			m_playPauseAnimator.toBegin();
		} else {
			updateButtons();
			updateStars();
		}
	}

	QWidget::leaveEvent(event);
}

void CoverDisplay::mousePressEvent(QMouseEvent *event)
{
	// Typically in fullscreen mode, the mouse & buttons just hidden and the user click:
	// the interface SHOULD responds, and not ask the user to move the mouse before:
	if (!m_underMouse) {
		QMouseEvent *mouseEvent = new QMouseEvent(QEvent::MouseMove, mapFromGlobal(QCursor::pos()), 0, 0);
		mouseMoveEvent(mouseEvent);
		delete mouseEvent;
	}

	if (m_hoveredStar > 0) {
		int newRating = m_hoveredStar * 2;
		if (m_infos->rating() == 1)
			newRating = 0;
		else if (m_infos->rating() == newRating)
			newRating -= 1;
		m_infos->changeRating(newRating);
	}

	int clickedButton = -1;
	if (m_underMouse) {
		if (areControlsShown() && m_prevRect.contains(event->pos()))
			clickedButton = 0;
		else if (m_playRect.contains(event->pos()))
			clickedButton = 1;
		else if (areControlsShown() && m_nextRect.contains(event->pos()))
			clickedButton = 2;
		else if (m_infos->isPlaying() && m_fullRect.contains(event->pos()))
			clickedButton = 3;
		else if (m_closeRect.contains(event->pos()))
			clickedButton = 4;
	}


	if (m_clickedButton != clickedButton) {
		m_clickedButton = clickedButton;
		updateButtons();
	}

	QWidget::mousePressEvent(event);
}

void CoverDisplay::wheelEvent(QWheelEvent *event)
{
	if (areControlsShown()) {
		if (event->orientation() == Qt::Vertical) {
			int deltaSeconds = 10 * (event->delta() > 0 ? 1 : -1);
			m_infos->seekRelative(deltaSeconds);
		} else {
			if (event->delta() > 0)
				AmarokApi::volumeUp();
			else
				AmarokApi::volumeDown();
		}
	}
}

void CoverDisplay::resizeEvent(QResizeEvent *event)
{
	if (m_orientation == Horizontal) {
		m_textRect.setWidth(width() - m_textRect.x());
		if (m_progressBarOnLeft) {
			m_progressRect.setWidth(width() - m_progressRect.x() - m_progressMargin);
			m_progressBar->resize(m_progressRect.size());
		}
	}

	QWidget::resizeEvent(event);
}

int CoverDisplay::progressHeightForSize(int size)
{
	return (size < 110 ? (size < 70 ? (size < 58 ? (size < 30 ? 3 : 4) : 5) : 6) : (6 + (size - 110 + 1) * 10 / (256 - 110)));
}

int CoverDisplay::heightForWidth(int width)
{
//	QTimer::singleShot( 100, this, SLOT(update()) );

//	if (!m_infos->isPlaying())
//		return 0;

	// Recompute only if there are changes to do:
	if (this->width() == width && m_orientation == Vertical)
		return minimumHeight();

	// Compute general values:
	m_orientation    = Vertical;
	m_margin         = (width < 58 ? (width < 30 ? 0 : 1) : 2);
	m_progressMargin = (width < 58 ? (width < 30 ? 1 : 2) : 3);
	m_buttonPadding  = m_margin;
	m_shadowMargin   = m_margin;

	// Compute progress rectangle:
	m_progressRect.setX(      m_margin + m_progressMargin    );
	m_progressRect.setY(      m_margin                       );
	m_progressRect.setWidth(  width - 2 * m_progressRect.x() );
	m_progressRect.setHeight( progressHeightForSize(width)   );

	m_withShadow = true;

	// Compute cover rectangle:
	m_coverRect.setX(      m_margin                               );
	m_coverRect.setY(      m_progressRect.bottom() + 1 + m_margin );
	m_coverRect.setWidth(  width - 2 * m_margin                   );
	m_coverRect.setHeight( m_coverRect.width()                    );

	// Compute the stars rectangle:
	m_starWidth = m_coverRect.width() / 5;
	m_starsRect.setX(      (width - 5 * m_starWidth) / 2                    );
	m_starsRect.setY(      m_coverRect.bottom() + 1 - (m_starWidth * 2 / 3) );
	m_starsRect.setWidth(  5 * m_starWidth                                  );
	m_starsRect.setHeight( m_starWidth                                      );

	// Compute text rectangle:
	QPainter painter(this);
	m_textFont = font();
	m_textFont.setPointSize(m_textFont.pointSize() - 2);
	painter.setFont(m_textFont);
	QRect textBoundingRect = painter.boundingRect(0, 0, 10000, 10000, 0, "A\nA\nA");
	m_textRect.setX(      m_margin                            );
	m_textRect.setY(      m_starsRect.bottom() + 1 + m_margin );
	m_textRect.setWidth(  width - m_margin                    );
	m_textRect.setHeight( textBoundingRect.height()           );

	computeTextColor();

	int appletHeight = m_textRect.bottom() + 1 + m_margin;
	setMinimumSize(width, appletHeight);
	resize(minimumSize());

	computeButtonRects(width);
	initPixmaps();

	// Recompute cover size:
	newInformation();

	return appletHeight;
}

int CoverDisplay::widthForHeight(int height)
{
//	QTimer::singleShot( 100, this, SLOT(update()) );

//	if (!m_infos->isPlaying())
//		return 0;

	// Recompute only if there are changes to do:
	if (this->height() == height && m_orientation == Horizontal)
		return minimumWidth();

	// Compute general values:
	m_orientation    = Horizontal;
	m_margin         = (height < 58 ? (height < 30 ? 0 : 1) : 2);
	m_progressMargin = (height < 58 ? (height < 30 ? 1 : 1) : 3);
	m_buttonPadding  = m_margin;
	m_shadowMargin   = m_margin;

	// Compute the text size hint:
	QPainter painter(this);
	m_textFont = font();
	m_textFont.setPointSize(m_textFont.pointSize() - 2);
	painter.setFont(m_textFont);
	QString newLine = (m_orientation == FullScreen ? "\n\n" : "\n");
	QRect textBoundingRect = painter.boundingRect(0, 0, 10000, 10000, 0, "The avgerage width" + newLine + "of an album" + newLine + "name");

	// Compute progress position and height if positionned on top of the cover:
	m_progressBarOnLeft = (textBoundingRect.width() > height);
	if (!m_progressBarOnLeft) {
		m_progressRect.setX(      m_margin + m_progressMargin   );
		m_progressRect.setY(      m_margin                      );
		m_progressRect.setHeight( progressHeightForSize(height) );
	}

	m_withShadow = (height > 110);

	// Compute cover rectangle:
	int coverTop = (m_progressBarOnLeft ? m_margin : m_progressRect.bottom() + 1 + m_margin);
	m_starWidth = (height - coverTop - m_margin) * 2 / (5 * 2 + 1);
	if (!m_withShadow)
		m_starWidth = (height - coverTop - m_margin) / 5;
	m_coverRect.setX(      m_margin                                                      );
	m_coverRect.setY(      coverTop                                                      );
	m_coverRect.setWidth(  m_withShadow ? 5 * m_starWidth : height - coverTop - m_margin );
	m_coverRect.setHeight( m_coverRect.width()                                           );

	// Compute progress width, determined by the cover width:
	if (!m_progressBarOnLeft)
		m_progressRect.setWidth( m_coverRect.width() - 2 * m_progressMargin );

	// Compute progress position and height if positionned on top of the text:
	if (m_progressBarOnLeft) {
		m_progressRect.setX(      m_coverRect.right() + 1 + m_progressMargin   );
		m_progressRect.setY(      m_margin                                     );
		m_progressRect.setHeight( progressHeightForSize(height)                );
	}

	int textTop = (m_progressBarOnLeft ? m_progressRect.bottom() + 1 + m_margin : m_margin);

	int availableHeightUnderText = height - textTop - textBoundingRect.height() - m_margin - m_margin;
	int possibleStarWidth = availableHeightUnderText;
	if (possibleStarWidth > textBoundingRect.width() / 5)
		possibleStarWidth = textBoundingRect.width() / 5;
	bool starsOnLeft = (!m_withShadow && availableHeightUnderText > m_starWidth && possibleStarWidth > m_starWidth);

	// Compute the stars rectangle:
	if (starsOnLeft) {
		m_starWidth = possibleStarWidth;
		m_starsRect.setX( m_coverRect.right() + 1 + m_margin );
	} else {
		m_starsRect.setX( (m_coverRect.right() + 1 + m_margin - 5 * m_starWidth) / 2 );
	}
		m_starsRect.setY( m_withShadow ? m_coverRect.bottom() + 1 - (m_starWidth * 2 / 3) : height - m_margin - m_starWidth );
	m_starsRect.setWidth(  5 * m_starWidth );
	m_starsRect.setHeight( m_starWidth     );

	// Compute text rectangle:
	m_textRect.setX(      m_coverRect.right() + (m_margin == 0 ? 1 : m_margin)      );
	m_textRect.setY(      textTop + (height - textTop - textBoundingRect.height() - (starsOnLeft ? m_starWidth + m_margin : 0)) / 2 - 1 );
	m_textRect.setWidth(  textBoundingRect.width()                                  );
	m_textRect.setHeight( textBoundingRect.height() + (m_withShadow ? 0 : m_margin) ); // Add margin for Fitt's law to work with those small Kickers!

	computeTextColor();

	int appletWidth = m_textRect.right() + m_margin;
	setMinimumSize(appletWidth, height);
	resize(minimumSize());

	computeButtonRects(height);
	initPixmaps();

	if (m_progressBarOnLeft) {
		m_progressRect.setWidth(width() - m_progressRect.x() - m_progressMargin);
		m_progressBar->resize(m_progressRect.size());
	}

	// Recompute cover size:
	newInformation();

	return appletWidth;
}

void CoverDisplay::setFullScreen()
{
	// Recompute only if there are changes to do:
	if (m_orientation == FullScreen)
		return;

	m_fullScreenBackgroundPixmap = new QPixmap(*(QPixmap*)paletteBackgroundPixmap());
//	m_fullScreenBackgroundPixmap->detach();
	setBackgroundMode(Qt::NoBackground);

	// Compute general values:
	m_orientation    = FullScreen;
	m_margin         = 100;
	m_progressMargin = 50;
	m_buttonPadding  = 2;
	m_shadowMargin   = 5;

	// Compute cover rectangle:
	int coverSize = 300;
	m_coverRect.setX(      m_margin                   );
	m_coverRect.setY(      (height() - coverSize) / 2 );
	m_coverRect.setWidth(  coverSize                  );
	m_coverRect.setHeight( coverSize                  );

	// Compute progress position and height:
	int progressHeight = 30;
	m_progressRect.setX(      m_margin + m_progressMargin                   );
	m_progressRect.setY(      (m_coverRect.y() - progressHeight) * 2 / 3    );
	m_progressRect.setWidth(  width() - 2 * m_margin - 2 * m_progressMargin );
	m_progressRect.setHeight( progressHeight                                );

	// Compute the stars rectangle:
	m_starWidth = m_coverRect.width() / 5;
	m_starsRect.setX(      m_coverRect.x() + (m_coverRect.width() - 5 * m_starWidth) / 2 );
	m_starsRect.setY(      m_coverRect.bottom() + 1 - (m_starWidth * 2 / 3) );
	m_starsRect.setWidth(  5 * m_starWidth                                  );
	m_starsRect.setHeight( m_starWidth                                      );

	// Compute text rectangle:
	QPainter painter(this);
	m_textFont = font();
	m_textFont.setBold(true);
	m_textFont.setPointSize(m_textFont.pointSize() * 4);
	painter.setFont(m_textFont);
	QString newLine = (m_orientation == FullScreen ? "\n\n" : "\n");
	QRect textBoundingRect = painter.boundingRect(0, 0, 10000, 10000, 0, "The avgerage width" + newLine + "of an album" + newLine + "name");
	m_textRect.setX(      m_coverRect.right() + m_margin / 3                                       );
	m_textRect.setY(      m_coverRect.y() + (m_coverRect.height() - textBoundingRect.height()) / 2 );
	m_textRect.setWidth(  width() - m_textRect.x()                                                 );
	m_textRect.setHeight( textBoundingRect.height()                                                );

	// Computing button positions:
	int buttonWidth = CONTROL_BUTTONS_MAX_SIZE + 2 * m_buttonPadding;
	int x = (width() - 5 * buttonWidth) / 2;
	int y = m_coverRect.bottom() + 1 + (height() - m_coverRect.bottom() - 1 - buttonWidth) / 3;

	m_prevRect.setX(      x           );
	m_prevRect.setY(      y           );
	m_prevRect.setWidth(  buttonWidth );
	m_prevRect.setHeight( buttonWidth );

	m_playRect.setX(      x + 2 * buttonWidth );
	m_playRect.setY(      y                   );
	m_playRect.setWidth(  buttonWidth         );
	m_playRect.setHeight( buttonWidth         );

	m_nextRect.setX(      x + 4 * buttonWidth );
	m_nextRect.setY(      y                   );
	m_nextRect.setWidth(  buttonWidth         );
	m_nextRect.setHeight( buttonWidth         );

	m_fullRect = HIDDEN_RECT;

	int closeSize = 22 + 2 * m_buttonPadding;
	m_closeRect.setX(      width() - closeSize );
	m_closeRect.setY(      0                   );
	m_closeRect.setWidth(  closeSize           );
	m_closeRect.setHeight( closeSize           );

	initPixmaps();

	// Recompute cover size:
	newInformation();
}

void CoverDisplay::computeButtonRects(int size)
{
	// One horizontal line:
	if (m_orientation == Horizontal && size < 46/*30*/) {
		int x = m_coverRect.x();
		int y = m_progressRect.bottom() + 1 + m_margin;
		int buttonWidth = (/*m_coverRect.*/height() - y - (m_coverRect.bottom() + 1 - m_starsRect.y()));
		buttonWidth = QMIN(buttonWidth, CONTROL_BUTTONS_MAX_SIZE + 2 * m_buttonPadding);
		if (buttonWidth > (width() - 2 * m_margin - 3) / 4) // Do not put buttons outside (on the right) of the applet
			buttonWidth = (width() - 2 * m_margin - 3) / 4;

		m_prevRect.setX(      x           );
		m_prevRect.setY(      y           );
		m_prevRect.setWidth(  buttonWidth );
		m_prevRect.setHeight( buttonWidth );

		m_playRect.setX(      x + buttonWidth + 1 );
		m_playRect.setY(      y                   );
		m_playRect.setWidth(  buttonWidth         );
		m_playRect.setHeight( buttonWidth         );

		m_nextRect.setX(      x + 2 * buttonWidth + 2 );
		m_nextRect.setY(      y                       );
		m_nextRect.setWidth(  buttonWidth             );
		m_nextRect.setHeight( buttonWidth             );

		m_fullRect.setX(      x + 3 * buttonWidth + 3 );
		m_fullRect.setY(      y                       );
		m_fullRect.setWidth(  buttonWidth             );
		m_fullRect.setHeight( buttonWidth             );

		m_closeRect = HIDDEN_RECT;
	// A square:
	} else if (size < 76) {
		int availableHeight = m_coverRect.height() - (m_coverRect.bottom() + 1 - m_starsRect.y());
		if (m_starsRect.x() >= m_coverRect.right()) // Stars are on left, buttons can span the whole cover:
			availableHeight = m_coverRect.height();
		int buttonWidth = availableHeight / 2 - 1;
		buttonWidth = QMIN(buttonWidth, CONTROL_BUTTONS_MAX_SIZE + 2 * m_buttonPadding);
		int x = m_coverRect.x() + (m_coverRect.width() - 2 * buttonWidth - 1) / 2; // Have the same button spacing (1 pixel) horizontally and vertically
		int y = m_coverRect.y();

		m_prevRect.setX(      x           );
		m_prevRect.setY(      y           );
		m_prevRect.setWidth(  buttonWidth );
		m_prevRect.setHeight( buttonWidth );

		m_playRect.setX(      x               );
		m_playRect.setY(      y + 1 + buttonWidth );
		m_playRect.setWidth(  buttonWidth     );
		m_playRect.setHeight( buttonWidth     );

		m_nextRect.setX(      x + 1 + buttonWidth );
		m_nextRect.setY(      y                   );
		m_nextRect.setWidth(  buttonWidth         );
		m_nextRect.setHeight( buttonWidth         );

		m_fullRect.setX(      x + 1 + buttonWidth );
		m_fullRect.setY(      y + 1 + buttonWidth );
		m_fullRect.setWidth(  buttonWidth         );
		m_fullRect.setHeight( buttonWidth         );

		m_closeRect = HIDDEN_RECT;
	// A "arrow keys"-like arrangement with 3 top buttons:
	} else {
		int buttonWidth = QMIN(m_coverRect.width() / 3, CONTROL_BUTTONS_MAX_SIZE + 2 * m_buttonPadding);
		int x = m_coverRect.x();
		int y = m_coverRect.y();

		m_prevRect.setX(      x           );
		m_prevRect.setY(      y           );
		m_prevRect.setWidth(  buttonWidth );
		m_prevRect.setHeight( buttonWidth );

		m_playRect.setX(      x + buttonWidth + (m_coverRect.width() - 3 * buttonWidth) / 2 );
		m_playRect.setY(      y                                                             );
		m_playRect.setWidth(  buttonWidth                                                   );
		m_playRect.setHeight( buttonWidth                                                   );

		m_nextRect.setX(      x + m_coverRect.width() - buttonWidth );
		m_nextRect.setY(      y                                     );
		m_nextRect.setWidth(  buttonWidth                           );
		m_nextRect.setHeight( buttonWidth                           );

		int fullSize = QMIN(128, m_coverRect.height() - buttonWidth - (m_coverRect.bottom() + 1 - m_starsRect.y()));
		m_fullRect.setX(      m_coverRect.x() + (m_coverRect.width() - fullSize) / 2 );
		m_fullRect.setY(      m_coverRect.y() + buttonWidth                          );
		m_fullRect.setWidth(  fullSize                                               );
		m_fullRect.setHeight( fullSize                                               );

		m_closeRect = HIDDEN_RECT;
	}
}

QPixmap CoverDisplay::getAmarokImage(const QString &imageName)
{
// 	QDir dir;
// 	QStringList folders = kapp->dirs()->resourceDirs("data");
// 	for (QStringList::Iterator it = folders.begin(); it != folders.end(); ++it) {
// 		QString imagePath = *it + "/amarok/images/" + imageName + ".png";
// 		if (dir.exists(imagePath))
// 			return QPixmap(imagePath);
// 	}

	// Not found in the Amarok folder, use a standard available star icon:
	return kapp->iconLoader()->loadIcon(imageName/*"services"*//*"favorites"*/, KIcon::Desktop, STAR_IMAGE_SIZE/*CONTROL_BUTTONS_MAX_SIZE*/, KIcon::DefaultState);
}

void CoverDisplay::initPixmaps()
{
	m_progressBar->move(m_progressRect.topLeft());
	m_progressBar->resize(m_progressRect.size());

	// Load normal and mini stars at the display size:
	m_starIcon     = getAmarokImage("star");
	m_miniStarIcon = getAmarokImage("smallstar");
	m_starIcon     = m_starIcon.smoothScale(m_starWidth, m_starWidth);
	m_miniStarIcon = m_miniStarIcon.smoothScale(m_starWidth, m_starWidth);

	// Compute images for other states of the stars:
	m_hoveredEmptyStarIcon = m_starIcon;
	m_hoveredEmptyStarIcon.detach();
	m_hoveredMiniStarIcon = m_miniStarIcon;
	m_hoveredMiniStarIcon.detach();
	m_hoveredStarIcon = m_starIcon;
	m_hoveredStarIcon.detach();

	KImageEffect::intensity(m_hoveredStarIcon, 0.5);
	KImageEffect::intensity(m_hoveredMiniStarIcon, 0.5);
	KImageEffect::toGray(m_hoveredEmptyStarIcon, /*fast=*/false);

	m_hoveredHoveredEmptyStarIcon = m_hoveredEmptyStarIcon;
	m_hoveredHoveredEmptyStarIcon.detach();
	KImageEffect::intensity(m_hoveredHoveredEmptyStarIcon, 0.5);

	// Load the button icons:
	loadButtonImages(m_prevIcon,  "amarok_back",  "player_start",           m_prevRect.width() - 2 * m_buttonPadding);
	loadButtonImages(m_nextIcon,  "amarok_next",  "player_end",             m_nextRect.width() - 2 * m_buttonPadding);
	loadButtonImages(m_playIcon,  "amarok_play",  "player_play",            m_playRect.width() - 2 * m_buttonPadding);
	loadButtonImages(m_pauseIcon, "amarok_pause", "player_pause",           m_playRect.width() - 2 * m_buttonPadding);
	loadButtonImages(m_fullIcon,  "NON_FINDABLE_ICON", "window_fullscreen", m_fullRect.width() - 2 * m_buttonPadding);
	loadButtonImages(m_closeIcon, "NON_FINDABLE_ICON", "fileclose",         m_closeRect.width() - 2 * m_buttonPadding);
}

void CoverDisplay::loadButtonImages(QImage icons[3], const QString &iconName, const QString &fallbackIconName, int width)
{
	QPixmap pixmap;
	QDir dir;
	QStringList folders = kapp->dirs()->resourceDirs("data");
	for (QStringList::Iterator it = folders.begin(); it != folders.end(); ++it) {
		QString imagePath = *it + "/amarok/icons/hicolor/" + QString::number(CONTROL_BUTTONS_MAX_SIZE) + "x" + QString::number(CONTROL_BUTTONS_MAX_SIZE) + "/actions/" + iconName + ".png";
		if (dir.exists(imagePath))
			pixmap = QPixmap(imagePath);
	}

	if (fallbackIconName == "window_fullscreen")
		pixmap = getAmarokImage("fullscreen");

	if (pixmap.isNull())
		pixmap = kapp->iconLoader()->loadIcon(fallbackIconName, KIcon::Desktop, 32, KIcon::DefaultState);

	QImage icon = pixmap.convertToImage();
	icon = icon.smoothScale(width, width);

	// We take take rect, but it can be any other: they are the same size (by now):
	QImage hoverImage = icon;
	hoverImage.detach();
	QImage clickedImage = icon;
	clickedImage.detach();

	KImageEffect::intensity(hoverImage, 0.5);
	KImageEffect::blend(Qt::black, clickedImage, 0.5);

	icons[0] = icon;
	icons[1] = hoverImage;
	icons[2] = clickedImage;
}

QString CoverDisplay::informationText(int numberOfLines)
{
	QString newLine = (m_orientation == FullScreen ? "\n\n" : "\n");

	QString title = m_infos->title();
	if (title.isEmpty()) {
		title = KURL(m_infos->path()).fileName();
		int index = title.findRev(".");
		if (index >= 0)
			title = title.left(index);
	}

	QString line1 = m_infos->artist();
	QString line2 = (m_infos->album().isEmpty() || m_infos->year().isEmpty() || m_infos->year() == "0" ? m_infos->album() : i18n("%1 (%2)").arg(m_infos->album(), m_infos->year()));
	QString line3 = (m_infos->track().isEmpty() ? title : (title.isEmpty() ? m_infos->track() : i18n("%1: %2").arg(m_infos->track(), title)));

	if (numberOfLines == 3)
		return line1 + newLine + line2 + newLine + line3;
	else // 2 lines:
		return (line1.isEmpty() ? line2 : (line2.isEmpty() ? line1 : i18n("%1 - %2").arg(line1, line2))) + newLine + line3;
}

void CoverDisplay::computeTextColor()
{
	QColor backgroundColor = paletteBackgroundColor();

	if (backgroundPixmap()) {
		QRect textRect = rect().intersect(m_textRect);
		if (textRect.isValid() && !textRect.isNull()) {
			// Get the background image of the text:
			QPixmap pixmap(textRect.size());
			QPainter painter(&pixmap);
			int backgroundTextX = textRect.x() - ((QWidget *) parent())->x();
			int backgroundTextY = textRect.y() - ((QWidget *) parent())->y();
			painter.drawTiledPixmap(0, 0, textRect.width(), textRect.height(), *backgroundPixmap(), backgroundTextX, backgroundTextY);
			painter.end();
			// Get the average of the color of every pixels:
			QImage image = pixmap.convertToImage();
			image = image.smoothScale(1, 1);
			uint *p = (uint *)image.scanLine(0) + 0;
			backgroundColor = QColor((QRgb)*p);
		}
	}
	if (qGray(backgroundColor.rgb()) < 100)
		m_textColor = QColor(255, 255, 255);
	else
		m_textColor = QColor(0, 0, 0);
}

void CoverDisplay::setPalette(const QPalette &palette)
{
	QWidget::setPalette(palette);

	if (m_orientation != FullScreen)
		computeTextColor();
}

void CoverDisplay::paintEvent(QPaintEvent *event)
{
// 	QTime begin = QTime::currentTime();

	// The progressbar is a QWidget, but for the sake of code simplicity, do like if it were drawn with other elements:
	bool showProgressBar = m_infos->isPlaying() || (m_orientation == FullScreen && !m_infos->path().isEmpty());
	if (m_trackAnimator.isInIntermediateState())
		showProgressBar = false;
	bool progressBarWillBeShown = !m_progressBar->isShown() && showProgressBar;
	m_progressBar->setShown(showProgressBar);

	// Initialize the drawing:
	QRect clipRect = event->rect();
	QMemArray<QRect> regionRects = event->region().rects();
	QPixmap buffer(clipRect.width(), clipRect.height());
	QPainter painterBuffer(&buffer);
	painterBuffer.translate(-clipRect.x(), -clipRect.y());

	// Draw background:
	if (backgroundPixmap()) {
		//painterBuffer.drawTiledPixmap(0, 0, width(), height(), *backgroundPixmap());
		for (uint i = 0; i < regionRects.size(); i++) {
			QRect r = regionRects[i];
			painterBuffer.drawTiledPixmap(r, *backgroundPixmap(), r.topLeft());
		}
	} else
		painterBuffer.fillRect(0, 0, width(), height(), paletteBackgroundColor());

	int buttonsOpacity = (ANIMATE_BUTTONS ? m_buttonsAnimator.value() : 100);

	// stage == 0: Draw old cover & information
	// stage == 1: Draw new cover & information
	for (int stage = 0; stage < 2; stage++) {
		// Figure out if it is necessary to draw the old information (when not animated or animation finished):
		bool oldTrack = (stage == 0);
		if (oldTrack && !m_trackAnimator.isInIntermediateState())
			continue;

		// Translate the painter, so that the drawing code draw both at the same place:
		int xTranslation = (oldTrack ? oldCoverDelta() : coverDelta());
		painterBuffer.translate(xTranslation, 0);

		// Draw the progressbar intermediate position during track animations (not a widget to not flicker):
		if (m_trackAnimator.isInIntermediateState() || progressBarWillBeShown) {
			QPixmap progressPixmap;
			if (oldTrack) {
				if (m_oldDuration > 0)
					progressPixmap = m_progressBar->progressPixmap(/*underMouse*/false, m_oldDuration, m_oldPosition, m_oldLeftText, m_oldRightText);
			} else {
				if (m_infos->isPlaying())
					progressPixmap = m_progressBar->progressPixmap(m_progressBar->underMouse(), m_infos->duration(), m_infos->position(), m_progressBar->leftText(), m_progressBar->rightText());
			}
			if (!progressPixmap.isNull())
				painterBuffer.drawPixmap(m_progressRect.x(), m_progressRect.y(), progressPixmap);
		}

		// Draw the cover:
		QRect effectiveCoverRect = m_coverRect;
		effectiveCoverRect.setHeight(m_coverRect.height() * 2 + m_shadowMargin);
		if ((areControlsShown() || m_trackAnimator.isInIntermediateState()) && clipRect.intersects(effectiveCoverRect)) {
			// Draw cover:
			QImage coverImage = (oldTrack ? m_oldCoverScalledImage : (m_infos->isPlaying() || m_orientation == FullScreen ? m_coverScalledImage : QImage()));
			QRect r = m_coverRect; // Do not draw the cover if out of screen. And, more importantly, do not compute the shadow in this case.
			r.moveLeft(r.left() + xTranslation);
			if (r.intersects(clipRect)) {
				painterBuffer.drawImage(m_coverRect.topLeft(), coverImage);
			}

			r = m_coverRect;
			r.moveTop(r.bottom() + 1 + m_shadowMargin);
			r.moveLeft(r.left() + xTranslation);
			if (m_withShadow && !coverImage.isNull() && r.intersects(clipRect)) {
				// Draw a copy of the background image to draw the shadow on it ????????????????,
				QPixmap transparent(m_coverRect.size());
				QPainter transparentPainter(&transparent);
				if (backgroundPixmap())
					transparentPainter.drawTiledPixmap(0, 0, m_coverRect.width(), m_coverRect.height(), *backgroundPixmap(), m_coverRect.x() + xTranslation, m_coverRect.bottom() + 1 + m_shadowMargin);
				else
					transparentPainter.fillRect(0, 0, m_coverRect.width(), m_coverRect.height(), paletteBackgroundColor());
				transparentPainter.end();
				QImage transparentImage = transparent.convertToImage();

				// Create the shadow image:
				QImage copyOfCoverImage = (oldTrack ? m_oldCoverInvertedImage : m_coverInvertedImage);
				copyOfCoverImage.detach(); // We will modify it
				// KImageEffect::blend is buggy: it does not take into account alpha buffer,
				// if a pixel is half-transparent white, then the reflexion is fully-opaque white!
				// We correct that by not having transparency anymore:
				if (copyOfCoverImage.hasAlphaBuffer()) {
					QPixmap pixmap(copyOfCoverImage.size());
					QPainter pixmapPainter(&pixmap);
					pixmapPainter.drawPixmap(0, 0, transparent);
					pixmapPainter.drawImage(0, 0, copyOfCoverImage);
					pixmapPainter.end();
					copyOfCoverImage = pixmap.convertToImage();
				}
				QImage shadowImage = KImageEffect::blend(copyOfCoverImage, transparentImage, KImageEffect::VerticalGradient, /*xf=*/0, /*yf=*/ -300);
				shadowImage = KImageEffect::blend(shadowImage, transparentImage, 0.5);

				// Draw cover shadow:
				painterBuffer.drawImage(m_coverRect.bottomLeft() + QPoint(m_shadowX, 1 + m_shadowMargin), shadowImage, QRect(m_shadowX, 0, m_shadowWidth, shadowImage.width()));
			}
		}

//QTime begin = QTime::currentTime();

		// Draw stars:
		if ((areControlsShown() || m_trackAnimator.isInIntermediateState()) && clipRect.intersects(m_starsRect)) {
			int rating = (oldTrack ? m_oldRating : ((m_orientation != FullScreen && m_infos->isPlaying()) || (m_orientation == FullScreen && m_infos->status() != PlayerInformation::Stopped) ? m_infos->rating() : -1));
			int hoveredStar = (oldTrack ? -1 : (m_infos->isPlaying() || m_orientation == FullScreen ? m_hoveredStar * 2 : -1));
			for (int i = 1; i <= 10 && rating >= 0; i += 2) { // Rating is -1 when drawing the old track, and the old track was "paused" or "stopped"
				int opacity = 100;
				// Compute the star icon to paint:
				QImage icon;
				if (i == rating) { // Mini star
					if (hoveredStar > i)
						icon = m_hoveredMiniStarIcon;
					else
						icon = m_miniStarIcon;
				} else if (i < rating) { // Big star
					if (hoveredStar > i)
						icon = m_hoveredStarIcon;
					else
						icon = m_starIcon;
				} else if (hoveredStar > -1 || !m_buttonsAnimator.atBegin()) { // Gray star
					opacity = buttonsOpacity;
					if (i + 1 <= hoveredStar)
						icon = m_hoveredHoveredEmptyStarIcon;
					else
						icon = m_hoveredEmptyStarIcon;
				}
				// Paint the star icon:
				int thisStarX = m_starsRect.x() + (i - i % 2) * m_starWidth / 2;
				//painterBuffer.drawImage(thisStarX, m_starsRect.y(), icon);

				QRect r = QRect(thisStarX + xTranslation, m_starsRect.y(), m_starWidth, m_starWidth); // Do not draw the star if out of screen. And, more importantly, do not compute the shadow in this case.
				if (r.intersects(clipRect)) {
					drawIcon(painterBuffer, icon, thisStarX, m_starsRect.y(), buffer, clipRect.x() - xTranslation, clipRect.y(), opacity);
				}

				r.moveTop(r.bottom() + 1 + m_shadowMargin / 2);

				if (m_withShadow && r.intersects(clipRect)) {
					// Create star shadow:
					int starShadowY = m_starsRect.bottom() + 1 + m_shadowMargin / 2;
					QPixmap transparent(m_starWidth, m_starWidth);
					QPainter transparentPainter(&transparent);
					transparentPainter.drawPixmap(0, 0, buffer, thisStarX - clipRect.x() + xTranslation, starShadowY - clipRect.y(), m_starWidth, m_starWidth);
					transparentPainter.end();
					QImage transparentImage = transparent.convertToImage();

					// Create the shadow image:
					QImage copyOfStarImage = icon.mirror();
					QImage shadowImage = KImageEffect::blend(copyOfStarImage, transparentImage, KImageEffect::VerticalGradient, /*xf=*/0, /*yf=*/ -200);

					// Draw star shadow:
					//painterBuffer.drawImage(thisStarX, starShadowY, shadowImage);
					drawIcon(painterBuffer, shadowImage, thisStarX, starShadowY, buffer, clipRect.x() - xTranslation, clipRect.y(), opacity);
				}
			}
		}

// 		QPixmap transparentPixmap(m_prevRect.width() - 2 * m_buttonPadding, m_prevRect.height() - 2 * m_buttonPadding);
// 		QPainter transparentPainter(&transparentPixmap);
// 		transparentPainter.drawPixmap(0, 0, buffer, m_prevRect.x() + m_buttonPadding - clipRect.x(), m_prevRect.y() + m_buttonPadding - clipRect.y());
// 		transparentPainter.end();
// 		QPixmap icon = transparentPixmap;//m_prevIcon[index];
// 		icon.detach();
// 		transparentPainter.begin(&icon);
// 		transparentPainter.drawPixmap(0, 0, m_prevIcon[index]);
// 		transparentPainter.end();
// 		QImage transparentImage = transparentPixmap.convertToImage();
// 		QImage iconImage = icon.convertToImage();
// 		QImage translucentIcon = KImageEffect::blend(transparentImage, iconImage, 0.5);
// 		painterBuffer.drawImage(m_prevRect.x() + m_buttonPadding, m_prevRect.y() + m_buttonPadding, translucentIcon);

//QTime endStars = QTime::currentTime();
//std::cout << "STARS PAINTED IN " << beginStars.msecsTo(endStars) << std::endl;


		// Draw texts (before buttons because buttons can overlap text with very small horizontal Kicker):
		if ((areControlsShown() || m_trackAnimator.isInIntermediateState()) && clipRect.intersects(m_textRect)) {
			// Get the text to display:
			int numberOfLines = (m_orientation == Horizontal && height() - 2 * m_margin < m_textRect.height() ? 2 : 3);
			QString text = (oldTrack ? m_oldText : (m_infos->isPlaying() || m_orientation == FullScreen ? informationText(numberOfLines) : ""));
			// Get the font in which to display the text:
			QFont font = m_textFont;
			painterBuffer.setFont(font);
			if (m_orientation == FullScreen) {
				while (font.pointSize() > this->font().pointSize() && painterBuffer.boundingRect(0, 0, 10000, 10000, 0, text).width() > m_textRect.width() - 16) {
					font.setPointSize(font.pointSize() - 1);
					painterBuffer.setFont(font);
				}
			}
			// Get the text color:
			QColor textColor = m_textColor;
			if (m_orientation == FullScreen)
				textColor = Qt::white;
			// Draw the text shadow:
			if (m_orientation != FullScreen || !ANIMATE_TRACKS) { // Too time consuming and too little useless in full screen
				QColor shadowColor;
				if (qGray(m_textColor.rgb()) < 100)
					shadowColor = QColor(255, 255, 255);
				else
					shadowColor = QColor(0,0,0);
				painterBuffer.setPen(shadowColor);
				painterBuffer.drawText(QRect(m_textRect.x() - 1, m_textRect.y() - 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x() - 1, m_textRect.y(),     m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x() - 1, m_textRect.y() + 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x() + 1, m_textRect.y() - 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x() + 1, m_textRect.y(),     m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x() + 1, m_textRect.y() + 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x(),     m_textRect.y() - 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x(),     m_textRect.y(),     m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
				painterBuffer.drawText(QRect(m_textRect.x(),     m_textRect.y() + 1, m_textRect.width(), m_textRect.height()), Qt::AlignLeft | Qt::AlignVCenter, text);
			}
			// And draw that information text:
			painterBuffer.setPen(textColor);
			painterBuffer.drawText(m_textRect, Qt::AlignLeft | Qt::AlignVCenter, text);
		}

		// Remove the translation for other draws to be correct:
		painterBuffer.translate(-xTranslation, 0);
	}

//QTime begin = QTime::currentTime();

	// Draw the buttons:
	bool showButtons = ((m_underMouse && areControlsShown()) || m_buttonsAnimator.isInIntermediateState());
	if (showButtons && clipRect.intersects(m_prevRect)) {
		// Prev button:
		int index = (m_clickedButton == 0 && m_hoveredButton == 0 ? 2 : (m_clickedButton == -1 && m_hoveredButton == 0 ? 1 : 0));
		//painterBuffer.drawPixmap(m_prevRect.x() + m_buttonPadding, m_prevRect.y() + m_buttonPadding, m_prevIcon[index]);
		drawIcon(painterBuffer, m_prevIcon[index], m_prevRect.x() + m_buttonPadding, m_prevRect.y() + m_buttonPadding, buffer, clipRect.x(), clipRect.y(), buttonsOpacity);
	}
	if ((showButtons || !m_infos->isPlaying()) && clipRect.intersects(m_playRect)) {
		// Play/pause button:
		int index = (m_clickedButton == 1 && m_hoveredButton == 1 ? 2 : (m_clickedButton == -1 && m_hoveredButton == 1 ? 1 : 0));
		QImage playPauseIcon = (m_infos->isPlaying() ? m_pauseIcon[index] : m_playIcon[index]);
		//painterBuffer.drawPixmap(m_playRect.x() + m_buttonPadding, m_playRect.y() + m_buttonPadding, playPauseIcon);
		int playOpacity = (ANIMATE_BUTTONS ? m_playPauseAnimator.value() : 100);
		drawIcon(painterBuffer, playPauseIcon, m_playRect.x() + m_buttonPadding, m_playRect.y() + m_buttonPadding, buffer, clipRect.x(), clipRect.y(), playOpacity);
	}
	if (showButtons && clipRect.intersects(m_nextRect)) {
		// Next button:
		int index = (m_clickedButton == 2 && m_hoveredButton == 2 ? 2 : (m_clickedButton == -1 && m_hoveredButton == 2 ? 1 : 0));
		//painterBuffer.drawPixmap(m_nextRect.x() + m_buttonPadding, m_nextRect.y() + m_buttonPadding, m_nextIcon[index]);
		drawIcon(painterBuffer, m_nextIcon[index], m_nextRect.x() + m_buttonPadding, m_nextRect.y() + m_buttonPadding, buffer, clipRect.x(), clipRect.y(), buttonsOpacity);
	}
	if (showButtons && clipRect.intersects(m_fullRect)) {
		// Fullscreen button:
		int index = (m_clickedButton == 3 && m_hoveredButton == 3 ? 2 : (m_clickedButton == -1 && m_hoveredButton == 3 ? 1 : 0));
		//painterBuffer.drawPixmap(m_fullRect.x() + m_buttonPadding, m_fullRect.y() + m_buttonPadding, m_fullIcon[index]);
		drawIcon(painterBuffer, m_fullIcon[index], m_fullRect.x() + m_buttonPadding, m_fullRect.y() + m_buttonPadding, buffer, clipRect.x(), clipRect.y(), buttonsOpacity);
	}
	if (showButtons && clipRect.intersects(m_closeRect)) {
		// Close button:
		int index = (m_clickedButton == 4 && m_hoveredButton == 4 ? 2 : (m_clickedButton == -1 && m_hoveredButton == 4 ? 1 : 0));
		//painterBuffer.drawPixmap(m_closeRect.x() + m_buttonPadding, m_closeRect.y() + m_buttonPadding, m_closeIcon[index]);
		drawIcon(painterBuffer, m_closeIcon[index], m_closeRect.x() + m_buttonPadding, m_closeRect.y() + m_buttonPadding, buffer, clipRect.x(), clipRect.y(), buttonsOpacity);
	}

//QTime end = QTime::currentTime();
//std::cout << "BUTTONS PAINTED IN " << begin.msecsTo(end) << std::endl;

// 	// Test debug:
// 	int length = 17;
// 	QColor colors[] = { Qt::white, Qt::black, Qt::red, Qt::darkRed, Qt::green,
// 	                    Qt::darkGreen, Qt::blue, Qt::darkBlue, Qt::cyan, Qt::darkCyan,
// 	                    Qt::magenta, Qt::darkMagenta, Qt::yellow, Qt::darkYellow, Qt::gray,
// 	                    Qt::darkGray, Qt::lightGray };
// 	static int i = -1;
// 	i++;
// 	painterBuffer.setPen(colors[i % length]);
// 	painterBuffer.drawRect(clipRect);
// 	QRect r = clipRect;
// 	r = QRect(r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2);
// 	painterBuffer.drawRect(r);
	//painterBuffer.fillRect(0, 0, width(), height(), colors[i]);

// 	for (int i = 0; i < regionRects.size(); i++) {
// 		QRect r = regionRects[i];
// 		painterBuffer.drawRect(r);
// 	}

	// End:
	painterBuffer.end();
	QPainter painter(this);
//	painter.drawPixmap(clipRect.x(), clipRect.y(), buffer);
	for (uint i = 0; i < regionRects.size(); i++) {
		QRect r = regionRects[i];
		painter.drawPixmap(r.x(), r.y(), buffer, r.x() - clipRect.x(), r.y() - clipRect.y(), r.width(), r.height());
	}
	painter.end();

// 	QTime end = QTime::currentTime();
// 	std::cout << m_orientation << " : Painted in " << begin.msecsTo(end) << "  ";
// 	if (m_underMouse && areControlsShown() && clipRect.intersects(m_prevRect))   std::cout << " Previous";
// 	if (m_underMouse && clipRect.intersects(m_playRect))                         std::cout << " Play";
// 	if (m_underMouse && areControlsShown() && clipRect.intersects(m_nextRect))   std::cout << " Next";
// 	if (m_underMouse && m_infos->isPlaying() && clipRect.intersects(m_fullRect)) std::cout << " FullScreen";
// 	if (m_underMouse && clipRect.intersects(m_closeRect))                        std::cout << " Close";
// 	if (areControlsShown() && clipRect.intersects(m_progressRect))               std::cout << " Progress";
// 	if (areControlsShown() && clipRect.intersects(effectiveCoverRect))           std::cout << " Cover";
// 	if (areControlsShown() && clipRect.intersects(m_starsRect))                  std::cout << " Stars";
// 	if (areControlsShown() && clipRect.intersects(m_textRect))                   std::cout << " Text";
// 	std::cout << std::endl;
}


void CoverDisplay::drawIcon(QPainter &painter, const QImage &iconImage, int x, int y, const QPixmap &buffer, int bufferX, int bufferY, int percentVisible)
{
	if (percentVisible >= 100)
		painter.drawImage(x, y, iconImage);
	else if (percentVisible > 0) {
		// Create a pixmap that copies the background of the button:
		QPixmap backgroundPixmap(iconImage.size());
		QPainter backgroundPainter(&backgroundPixmap);
		backgroundPainter.drawPixmap(0, 0, buffer, x - bufferX, y - bufferY);
		backgroundPainter.end();
		// Create images in order to do the composition:
		QImage backgroundImage = backgroundPixmap.convertToImage();
//		QImage iconImage = iconPixmap.convertToImage(); // TODO: Pre-convert to image!
		KImageEffect::blendOnLower(iconImage, QPoint(0, 0), backgroundImage, QRect(0, 0, backgroundImage.width(), backgroundImage.height()), percentVisible / 100.0);
		painter.drawImage(x, y, backgroundImage); // FIXME: Directly blend on buffer pixmap?
	}

// 		QPixmap transparentPixmap(m_prevRect.width() - 2 * m_buttonPadding, m_prevRect.height() - 2 * m_buttonPadding);
// 		QPainter transparentPainter(&transparentPixmap);
// 		transparentPainter.drawPixmap(0, 0, buffer, m_prevRect.x() + m_buttonPadding - clipRect.x(), m_prevRect.y() + m_buttonPadding - clipRect.y());
// 		transparentPainter.end();
// 		QPixmap icon = transparentPixmap;//m_prevIcon[index];
// 		icon.detach();
// 		transparentPainter.begin(&icon);
// 		transparentPainter.drawPixmap(0, 0, m_prevIcon[index]);
// 		transparentPainter.end();
// 		QImage transparentImage = transparentPixmap.convertToImage();
// 		QImage iconImage = icon.convertToImage();
// 		QImage translucentIcon = KImageEffect::blend(transparentImage, iconImage, 0.5);
// 		painterBuffer.drawImage(m_prevRect.x() + m_buttonPadding, m_prevRect.y() + m_buttonPadding, translucentIcon);

}


void CoverDisplay::mouseReleaseEvent(QMouseEvent *event)
{
	bool justClosed = false;

	if (m_clickedButton != -1) {
		// If the button has been clicked, and released, execute it:
		if (m_clickedButton == m_hoveredButton) {
			switch (m_clickedButton) {
				case 0: AmarokApi::previousTrack(); break;
				case 1:
					// Pause if it is playing:
					if (m_infos->isPlaying())
						AmarokApi::playPause();
					// Try to play:
					else if (!AmarokApi::playPause())
						// Not successful, amarok is not started yet. Start it and play:
						KRun::runCommand("amarok --play", "amarok", "amarok"); // Application name and icon name is for startup feedback mechanism
					break;
				case 2:
					AmarokApi::nextTrack();
					break;
				case 3:
					emit fullScreenAsked();
					break;
				case 4:
					emit closeAsked();
					justClosed = true;
					break;
				default: ;
			}
		}
		// Visually unpress the button:
		m_clickedButton = -1;
		updateButtons();
	}

	if (m_orientation == FullScreen && !justClosed) {
		// The buttons were still shown during click, considere that mouse release as another mouse activity, to not hide the buttons immediatly:
		m_fullScreenTimer.start(FULL_SCREEN_TIMOUT, /*singleShot=*/true);
	}

	QWidget::mouseReleaseEvent(event);
}

void CoverDisplay::updateProgressBar()
{
	QString time = i18n("%1:%2").arg(m_infos->position() / 60).arg(QString::number(m_infos->position() % 60).rightJustify(2, '0'));
	QString left = i18n("%1:%2").arg((m_infos->duration() - m_infos->position()) / 60).arg(QString::number((m_infos->duration() - m_infos->position()) % 60).rightJustify(2, '0'));

	m_progressBar->setCurrent(m_infos->position());
	m_progressBar->setLeftText(m_infos->duration() == 0 ? "" : time);
	m_progressBar->setRightText(m_infos->duration() == 0 ? "" : "-" + left);

	if (m_infos->isPlaying() && m_orientation != FullScreen) {
		m_timesString = i18n("%1, %2 left").arg(time, left);
		QString newLine = (m_orientation == FullScreen ? "\n\n" : "\n");
		QString text = m_timesString + "\n\n" + informationText(3);
		QToolTip::add(this, text);
	} else {
		m_timesString = "";
		QToolTip::add(this, "");
	}
#if KDE_IS_VERSION( 3, 5, 0 )
	Client::updateKickerTip();
#endif
}

void CoverDisplay::setPosition(int position)
{
	if (areControlsShown())
		m_infos->changePosition(position);
		//AmarokApi::seek(position);
}

// bool CoverDisplay::close(bool alsoDelete)
// {
// 	// Same as in hideEvent() but for times where is called by code (close()). There should be no updateButtons()!
// 	// This "event handler" does not take into account minimization of the window, in contrary to hideEvent().
// 	hideControls(/*update=*/false);
//
// 	return QWidget::close(alsoDelete);
// }

void CoverDisplay::hideEvent(QHideEvent *event)
{
	if (ANIMATE_BUTTONS) {
		m_buttonsAnimator.setValueAndState(0, ShowAnimator::NotRunning);
		m_playPauseAnimator.setValueAndState(0, ShowAnimator::NotRunning);
	}

	// Do not hide the mouse & buttons a few seconds later
	// if the user hide the window and show it again less than a few seconds later:
	hideControls(/*update=false*/);

	QWidget::hideEvent(event);
}

void CoverDisplay::hideControls(/*bool update = true*/)
{
	if (m_clickedButton == -1) {
		m_fullScreenTimer.stop(); // In case it is called outside of a timeout (eg. when hiding window)
		m_underMouse = false;
		m_hoveredStar = -1;
		setCursor(Qt::BlankCursor);
		m_progressBar->removeHighlighting();
		m_hoveredButton = -1;
//		if (false/*update*/) {
			if (ANIMATE_BUTTONS) {
				m_buttonsAnimator.toBegin();
				m_playPauseAnimator.toBegin();
			} else if (isVisible()) {
				updateButtons();
				updateStars();
			}
//		}
	}
}

int CoverDisplay::coverDelta()
{
	int value = m_trackAnimator.value();

	if (value == 100 || value == 0)
		return 0;

	if (m_reverseTrackAnimation)
		return (width() * value / 100) - width();
	else
		return width() - (width() * value / 100);
}

int CoverDisplay::oldCoverDelta()
{
	int value = m_trackAnimator.value();

	if (value == 100 || value == 0)
		return -1000;

	if (m_reverseTrackAnimation)
		return coverDelta() + width();
	else
		return coverDelta() - width();
}

void CoverDisplay::updateCover()
{
	QRect rect = m_coverRect;
	rect.setHeight(rect.height() * 2 + m_shadowMargin);
	rect.moveLeft(rect.left() + coverDelta());
	update(rect);
	if (m_trackAnimator.isInIntermediateState()) {
		rect.moveLeft(rect.left() - coverDelta() + oldCoverDelta());
		update(rect);
	}
}

void CoverDisplay::updateStars()
{
	if (!isVisible())
		return;

	QRect rect = m_starsRect;
	rect.setHeight(rect.height() * 2 + m_shadowMargin / 2);
	rect.moveLeft(rect.left() + coverDelta());
	update(rect);
	if (m_trackAnimator.isInIntermediateState()) {
		rect.moveLeft(rect.left() - coverDelta() + oldCoverDelta());
		update(rect);
	}
}

void CoverDisplay::updateButtons()
{
	if (!isVisible())
		return;

	int length = 5;
	QRect rects[] = { m_prevRect, m_nextRect, m_playRect, m_fullRect, m_closeRect };

	for (int i = 0; i < length; i++)
		update(rects[i]);
}

void CoverDisplay::updateText()
{
	QRect rect(m_textRect.x() - 1, m_textRect.y() - 1, m_textRect.width() + 2, m_textRect.height() + 2);
	rect.moveLeft(rect.left() + coverDelta());
	update(rect);
	if (m_trackAnimator.isInIntermediateState()) {
		rect.moveLeft(rect.left() - coverDelta() + oldCoverDelta());
		update(rect);
	}

//	if (m_orientation == FullScreen && !m_trackAnimator.isInIntermediateState()) // Optimisation, to not loss 100 ms in every timeout of Amarok DCOP calls
//		m_infos->resumePolling(); // Animation finished
}

void CoverDisplay::updateProgressDraw()
{
	QRect rect = m_progressRect;
	rect.moveLeft(rect.left() + coverDelta());
	update(rect);
	if (m_trackAnimator.isInIntermediateState()) {
		rect.moveLeft(rect.left() - coverDelta() + oldCoverDelta());
		update(rect);
	}
}

bool CoverDisplay::areControlsShown() // FIXME: bool canShowControls()
{
	return (m_infos->isPlaying() || m_orientation == FullScreen);
}

void CoverDisplay::newInformation()
{
	// Update mouse hover effect:
	if (m_underMouse) {
		m_hoveredStar   = -1;
		m_hoveredButton = -1;
		m_clickedButton = -1;
		QMouseEvent *mouseEvent = new QMouseEvent(QEvent::MouseMove, mapFromGlobal(QCursor::pos()), 0, 0);
		kapp->postEvent(this, mouseEvent);
	}

	QPixmap coverPixmap = m_infos->coverPixmap();
	m_shadowX     = 0;
	m_shadowWidth = m_coverRect.width();
	if (coverPixmap.width() > 0 && coverPixmap.width() != coverPixmap.height()) {
		if (coverPixmap.width() > coverPixmap.height()) {
			coverPixmap = Tools::addTransparentPixelsOnTop(coverPixmap, coverPixmap.width() - coverPixmap.height());
		} else {
			int delta     = coverPixmap.height() - coverPixmap.width();
			m_shadowX     = delta / 2 - 1;
			m_shadowWidth = m_coverRect.width() - delta + 2;
			coverPixmap   = Tools::addTransparentPixelsOnLeftRight(coverPixmap, delta / 2, delta - delta / 2);
		}
	}

	// Cache an image of the cover, at the right size:
	m_coverScalledImage = coverPixmap.convertToImage();
	if (!coverPixmap.isNull()) {
		m_coverScalledImage = m_coverScalledImage.smoothScale(m_coverRect.width(), m_coverRect.height());
		m_coverInvertedImage = m_coverScalledImage.mirror();
	}

	// Update animations:
	if (ANIMATE_BUTTONS && m_underMouse && m_orientation != FullScreen) {
		if (m_infos->isPlaying())
			m_buttonsAnimator.toEnd();
		else
			m_buttonsAnimator.toBegin();
	}

	// Update the progressbar data (it does not take its information from m_infos);
	newProgress();

	// Update the display:
	updateCover();
	updateStars();
	updateText();
}

void CoverDisplay::newRating()
{
	updateStars();
}

void CoverDisplay::newProgress()
{
	m_progressBar->setTotal(m_infos->duration());
	m_progressBar->setCurrent(m_infos->position());
	updateProgressBar();
}

void CoverDisplay::beforeAnimateTrack(int nextStatus, bool isPrevious)
{
	if (!ANIMATE_TRACKS)
		return;

	// Full-screen always show information. No transition for playing / not playing:
	if (m_orientation == FullScreen && (m_infos->status() == PlayerInformation::Paused || nextStatus == PlayerInformation::Paused))//m_infos->isPlaying() != (nextStatus != PlayerInformation::Stopped))
		return;

	m_trackAnimator.setValueAndState(0, ShowAnimator::MoveForward);
	m_reverseTrackAnimation = isPrevious/* || (!m_infos->isPlaying() && willPlay)*/;

	int numberOfLines = (m_orientation == Horizontal && height() - 2 * m_margin < m_textRect.height() ? 2 : 3);

	m_oldCoverScalledImage  = ( m_infos->isPlaying() ? m_coverScalledImage            : QImage() );
	m_oldCoverInvertedImage = ( m_infos->isPlaying() ? m_coverInvertedImage           : QImage() );
	m_oldText               = ( m_infos->isPlaying() ? informationText(numberOfLines) : ""       );
	m_oldRating             = ( m_infos->isPlaying() ? m_infos->rating()              : -1       ); // -1 to not draw any star, because it relates to no track
	m_oldDuration           = ( m_infos->isPlaying() ? m_infos->duration()            : 0        );
	m_oldPosition           = ( m_infos->isPlaying() ? m_infos->position()            : 0        );
	m_oldLeftText           = ( m_infos->isPlaying() ? m_progressBar->leftText()      : ""       );
	m_oldRightText          = ( m_infos->isPlaying() ? m_progressBar->rightText()     : ""       );

//	if (m_orientation == FullScreen) // Optimisation, to not loss 100 ms in every timeout of Amarok DCOP calls
//		m_infos->suspendPolling();
}

const QPixmap* CoverDisplay::backgroundPixmap()
{
	if (m_orientation == FullScreen)
		return m_fullScreenBackgroundPixmap;
	else
		return paletteBackgroundPixmap();
}

#if KDE_IS_VERSION( 3, 5, 0 )
void CoverDisplay::updateKickerTip(KickerTip::Data &data)
{
	if (!m_infos->isPlaying() || m_orientation == FullScreen)
		return;

	data.message   = m_timesString;
	data.subtext   = QStyleSheet::escape(informationText(3)).replace('\n', "<br>");
	data.icon      = kapp->iconLoader()->loadIcon("kirocker", KIcon::Desktop, 48, KIcon::DefaultState);
	data.direction = m_applet->popupDirection();
}
#endif

void CoverDisplay::fontChange(const QFont &oldFont)
{
	QWidget::fontChange(oldFont);

	if (m_orientation == Horizontal || m_orientation == Vertical) {
		m_orientation = FullScreen; // This is temporar. In a few milliseconds, widthForHeight or heightForWidth will be called
		emit updateLayout();        //  We set to a different orientation to be sure the whole layout is recomputed (do not return the cached geometry)
	}
}

#include "coverdisplay.moc"
