/*****************************************************************

Copyright (c) 1996-2003 the kicker authors. See file AUTHORS.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************/

#include <qapplication.h>
#include <qpainter.h>
#include <qstyle.h>

#include <kstandarddirs.h>
#include <kglobal.h>
#include <kmimetype.h>
#include <kglobalsettings.h>
#include <kcursor.h>
#include <kapplication.h>
#include <kipc.h>
#include <kdebug.h>

#include "panel.h"
#include "zoombutton.h"
#include "containerarea.h"

#include "panelbuttonbase.h"
#include "panelbuttonbase.moc"
#include "kicker.h"

namespace {
    ZoomButton* zoomButton = 0;
    int zoomDisabled = 0;
}

PanelButtonBase::PanelButtonBase(QWidget *parent, const char *name, WFlags f)
  : QButton(parent, name, f)
  , _valid(true)
  , _drawArrow(false)
  , _highlight(false)
  , _animated(false)
  , _changeCursorOverItem(true)
  , _hasAcceptedDrag(false)
  , _hasAnimation(true)
  , _tile(QString::null)
  , _iconName(QString::null)
  , _movie(0L)
  , _arrowDirection(::Bottom)
  , _popupDirection(::dUp)
  , _orientation(Horizontal)
  , _size((KIcon::StdSizes)-1), _zoom_size((KIcon::StdSizes)-1)
{
    _tileColor = QColor();
    calculateIconSizes();

    slotSettingsChanged(KApplication::SETTINGS_MOUSE);
    connect(kapp, SIGNAL(settingsChanged(int)), SLOT(slotSettingsChanged(int)));
    kapp->addKipcEventMask(KIPC::SettingsChanged);

    setAcceptDrops(true);
}

void PanelButtonBase::configure()
{
}

void PanelButtonBase::slotSettingsChanged(int category)
{
    if (category != KApplication::SETTINGS_MOUSE) return;

    _changeCursorOverItem = KGlobalSettings::changeCursorOverIcon();

    if (_changeCursorOverItem)
        setCursor(KCursor::handCursor());
    else
        unsetCursor();
}

void PanelButtonBase::setTile(const QString& tile, const QColor& color)
{
    if ( tile != _tile || _tileColor != color ) {
        _tile = tile;
        _tileColor = color;
        loadTiles();
        update();
    }
}

void PanelButtonBase::setIcon(const QString& icon)
{
    if( icon != _iconName ) {
        _iconName = icon;
        loadIcons();
        update();
        emit iconChanged();
    }
}

// return the (icon size, zoom size) that would be used if the panel were proposed_size
// if proposed_size==-1, use the current panel size instead
std::pair<int,int> PanelButtonBase::preferredIconSizes(int proposed_size) const
{

    // (re)calculates the icon sizes and report true if they have changed.
    // Get sizes from icontheme. We assume they are sorted.
    KIconTheme *ith = KGlobal::iconLoader()->theme();
    if (!ith)
       return std::pair<int,int>(-1,-1); // unknown icon size
    QValueList<int> sizes = ith->querySizes(KIcon::Panel);

    int sz = ith->defaultSize(KIcon::Panel);
    int zoom_sz = sz;

    int panelSize = (orientation() == Horizontal) ? height() : width();
    if (proposed_size<0) {
        proposed_size=panelSize;
    }

    // determine the upper limit on the size.  Normally, this is panelSize,
    // but if _limitSize is true, _maximum_size is used instead.
    int upperLimit=proposed_size;
    if ((proposed_size>maxButtonDim()) && (conserveSpace())) {
        upperLimit=maxButtonDim();
    }

    //kdDebug()<<endl<<endl<<flush;
    QValueListConstIterator<int> i = sizes.constBegin();
    while ( (i!=sizes.constEnd()) && ((*i)+2*iconMargin(*i)<=upperLimit))  {
        //kdDebug()<<"Considering icon size: "<<(*i)<<"   icon margin: "<<iconMargin(*i)<<"   button size: "<<(*i)+2*iconMargin(*i)<<"   upper limit: "<<upperLimit<<endl<<flush;
        sz=*i++;   // get the largest size under the limit
        zoom_sz=sz;
    }
    while ((i!=sizes.constEnd()) && ((sz*5)/4 > zoom_sz))  {
        zoom_sz=*i++;  // get the next largest size for zoom_sz
    }
    //kdDebug()<<"Using icon sizes: "<<sz<<"  "<<zoom_sz<<endl<<flush;
    return std::pair<int,int> (sz, zoom_sz);

}
bool PanelButtonBase::conserveSpace() const { return Kicker::kicker()->conserveSpace(); }
int PanelButtonBase::maxButtonDim() const { return  Kicker::kicker()->maxButtonDim(); }
int PanelButtonBase::maxIconDim() const { return  Kicker::kicker()->maxIconDim(); }
int PanelButtonBase::iconMargin(int iconDim) const {return  Kicker::kicker()->iconMargin(iconDim);}

// return the dimension that the button wants to be for a given panel dimension (panelDim)
int PanelButtonBase::preferredDimension(int panelDim) const
{
    // determine the upper limit on the size.  Normally, this is panelDim,
    // but if conserveSpace() is true, we restrict size to comfortably fit the icon
    if ((panelDim>maxButtonDim()) && (conserveSpace())) {
        std::pair<int,int> new_sizes=preferredIconSizes(panelDim);
        if (new_sizes.first>0) {
            return new_sizes.first+2*iconMargin(new_sizes.first);
        }
    }
    return panelDim;

}


// (re)calculates the icon sizes and report true if they have changed.
//      (false if we don't know, because theme couldn't be loaded?)
bool PanelButtonBase::calculateIconSizes()
{
    std::pair<int,int> new_sizes=preferredIconSizes();
    int sz=new_sizes.first;
    int zoom_sz=new_sizes.second;
    if (sz<0) { // sizes unknown
        return false;
    }

    if (_size != sz || _zoom_size != zoom_sz) {
        // Sizes have changed.  Update them
        _size = sz;
        _zoom_size = zoom_sz;
        return true;
     }
     return false;
}

void PanelButtonBase::loadIcons()
{
    KIconLoader * ldr = KGlobal::iconLoader();
    QString nm = _iconName;
    _hasAnimation = true;

    _icon = ldr->loadIcon(nm, KIcon::Panel, _size, KIcon::DefaultState, 0L, true);

    if (_icon.isNull()) {
        nm = defaultIcon();
        _icon = ldr->loadIcon(nm, KIcon::Panel, _size, KIcon::DefaultState);
    }

    _iconh = ldr->loadIcon(nm, KIcon::Panel, _size, KIcon::ActiveState, 0L, true);
    _iconz = ldr->loadIcon(nm, KIcon::Panel, _zoom_size, KIcon::ActiveState, 0L, true );
}

void PanelButtonBase::setIconURL(const KURL & u)
{
    QString name = KMimeType::iconForURL(u, 0);
    setIcon( name );
}

void PanelButtonBase::setTitle(const QString & t)
{
    _title = t;
}

void PanelButtonBase::setDrawArrow(bool v)
{
    if (_drawArrow != v) {
        _drawArrow = v;
        update();
    }
}

void PanelButtonBase::slotSetPopupDirection(Direction d)
{
    _popupDirection = d;

    switch (_popupDirection) {
    case ::dUp:    setArrowDirection(::Top);    break;
    case ::dDown:  setArrowDirection(::Bottom); break;
    case ::dLeft:  setArrowDirection(::Left);   break;
    case ::dRight: setArrowDirection(::Right);  break;
    }
}

void PanelButtonBase::setArrowDirection(Position dir)
{
    if (_arrowDirection != dir) {
        _arrowDirection = dir;
        update();
    }
}

void PanelButtonBase::setBackground()
{
    if( !parent() ) { //parent() is null for ZoomButtons
        _bg = QPixmap();
        return;
    }

    // inheritance is ButtonContainer - ScrollView - ContainerArea
    ContainerArea* area = static_cast<ContainerArea*>(parent()->parent()->parent());
    if (!area->transparent())
    {
        _bg = QPixmap();
        return;
    }
    // Get the pixmap from the container area object
    const QPixmap* containerBG = area->completeBackgroundPixmap();

    // Make sure the background pixmap exists
    if ( containerBG )
    {
        if( containerBG->isNull() ) {
            _bg = QPixmap();
            return;
        }
        // Create a pixmap the same size as the button to use as the bg
        QPixmap bgPix( width(), height() );

        QPoint p = mapTo( area, QPoint(0,0));
        copyBlt( &bgPix, 0, 0, containerBG, p.x(), p.y(), width(), height());
        _bg = bgPix;
    }
    else
    {
        // Conatainer palette pixmap not set yet
        _bg = QPixmap();
    }
}

void PanelButtonBase::resizeEvent(QResizeEvent*)
{
    // optimize: reload only when size really changes
    loadTiles();
    if(calculateIconSizes())
        loadIcons();
}

void PanelButtonBase::setZoomEnabled(bool b)
{
    if (b)
      zoomDisabled--;
    else
      zoomDisabled++;

    if (zoomDisabled && zoomButton)
    {
        zoomButton->unFocus();
    }
}

void PanelButtonBase::enterEvent(QEvent* e)
{
    if (!zoomButton)
        zoomButton = new ZoomButton;

    if (!zoomDisabled &&
        zoomButton->isZoomingEnabled() && /* _icon.width() < 32 && */
        !_iconz.isNull() && _iconz.width() > _icon.width() &&
        !mouseGrabber() && !qApp->activePopupWidget() )
    { // we can and should zoom
        if ( !zoomButton->isWatching( this ) )
        {
            zoomButton->watchMe( this );
            update();
        }
        return;
    }

    if (zoomButton->isAnimationEnabled() && _hasAnimation)
    {
        QMovie movie = KGlobal::iconLoader()->loadMovie(_iconName, KIcon::Panel, _size);
        if( !movie.isNull()) {
            delete _movie;
            _movie = new QMovie( movie );
            _movie->connectUpdate( this, SLOT( slotMovieUpdate(const QRect&)));
            _movie->connectStatus( this, SLOT( slotMovieStatus(int)));
            _animated = true;
            return;
        }
        else {
            _hasAnimation = false;
        }
    }

    _highlight = true;
    repaint(false);
    QButton::enterEvent(e);
}

void PanelButtonBase::leaveEvent(QEvent* e)
{

    if (_animated) {
        //  Should always disconnect before deletion,
	// because QMovie uses explicit sharing
	_movie->disconnectUpdate( this, SLOT( slotMovieUpdate(const QRect&)));
        _movie->disconnectStatus( this, SLOT( slotMovieStatus(int)));
    	delete _movie;
	_movie = 0L;
	_animated = false;
	repaint(false);
    }

    if (_highlight) {
	_highlight = false;
	repaint(false);
    }
    QButton::leaveEvent(e);
}

void PanelButtonBase::dragEnterEvent(QDragEnterEvent* e)
{
    if (e->isAccepted())
	_hasAcceptedDrag = true;
    update();
    QButton::dragEnterEvent( e );
}

void PanelButtonBase::dragLeaveEvent(QDragLeaveEvent* e)
{
    _hasAcceptedDrag = false;
    update();
    QButton::dragLeaveEvent( e );
}

void PanelButtonBase::dropEvent(QDropEvent* e)
{
    _hasAcceptedDrag = false;
    update();
    QButton::dropEvent( e );
}

void PanelButtonBase::loadTiles()
{
    if (_tileColor.isValid()) {
        setBackgroundOrigin( WidgetOrigin );
        _up = _down = QPixmap();
    }
    else if (_tile.isNull()) {
        setBackgroundOrigin( AncestorOrigin );
        _up = _down = QPixmap();
    } else {
        setBackgroundOrigin( WidgetOrigin );
        // If only the tiles were named a bit smarter we wouldn't have
        // to pass the up or down argument.
        _up   = QPixmap(loadTile(_tile, size(), "up"));
        _down = QPixmap(loadTile(_tile, size(), "down"));
    }
}

void PanelButtonBase::drawButton(QPainter *p)
{
    setBackground();

    // Draw the background. This is always needed, even when using tiles,
    // because they don't have to cover the entire button.
    if (!_bg.isNull())
        p->drawPixmap(0, 0, _bg);
    else if (_tileColor.isValid())
    {
        p->fillRect(rect(), _tileColor);
        style().drawPrimitive(QStyle::PE_Panel, p, rect(), colorGroup());
    }

    const QPixmap& tile = (isDown() || isOn()) ? _down : _up;
    if (!tile.isNull()) {
        // Draw the tile.
        p->drawPixmap(0, 0, tile);
    }
    else if (isDown() || isOn()) {
        // Draw shapes to indicate the down state.
        style().drawPrimitive(QStyle::PE_Panel, p, rect(), colorGroup(), QStyle::Style_Sunken);
    }

    drawButtonLabel(p);

    if (hasFocus() || _hasAcceptedDrag) {
        int x1, y1, x2, y2;
        rect().coords(&x1, &y1, &x2, &y2);
        QRect r(x1+2, y1+2, x2-x1-3, y2-y1-3);
        style().drawPrimitive(QStyle::PE_FocusRect, p, r, colorGroup(),
                              QStyle::Style_Default, colorGroup().button());
    }
}

void PanelButtonBase::drawButtonLabel(QPainter *p)
{
    if (isDown() || isOn())
	p->translate(2,2);

    const QPixmap& icon = labelIcon();
    if (!icon.isNull()
     && !(zoomButton && zoomButton->isWatching(this))) {
	int x = (width()  - icon.width() )/2;
	int y = (height() - icon.height())/2;
	p->drawPixmap(x, y, icon);
    }

    if (_drawArrow) {
	QStyle::PrimitiveElement e = QStyle::PE_ArrowUp;
	QRect r(0, 0, 8, 8);

	switch (_arrowDirection) {
	case ::Top:
	    e = QStyle::PE_ArrowUp;
	    break;
	case ::Bottom:
	    e = QStyle::PE_ArrowDown;
	    r.moveBy(0, height() - 8);
	    break;
	case ::Right:
	    e = QStyle::PE_ArrowRight;
	    r.moveBy(width() - 8 , 0);
	    break;
	case ::Left:
	    e = QStyle::PE_ArrowLeft;
	    break;
	}

	int flags = QStyle::Style_Enabled;
	if ( isDown() || isOn() )
	    flags |= QStyle::Style_Down;
	style().drawPrimitive(e, p, r, colorGroup(), flags);
    }

    if (isDown() || isOn())
	p->translate(-2,-2);
}

const QPixmap& PanelButtonBase::labelIcon() const
{
    if (_animated)
	return _movie->framePixmap();
    else
	return _highlight ? _iconh : _icon;
}

void PanelButtonBase::slotMovieUpdate(const QRect& /*rect*/)
{
    if (_animated) {
	emit iconChanged();
	update(); // update all since rect seems to be set incorrect
	// update(rect);
    }
}

void PanelButtonBase::slotMovieStatus( int status )
{
    if (status < 0) {
        // Error playing the MNG -> forget about it and do normal iconeffect
        if (_animated) {
	    _movie->disconnectUpdate( this, SLOT( slotMovieUpdate(const QRect&)));
            _movie->disconnectStatus( this, SLOT( slotMovieStatus(int)));
            delete _movie;
            _movie = 0;
            _animated =  false ;
            _hasAnimation = false;
	    emit iconChanged();
	    update();
        }
    }
    else if (status == QMovie::EndOfMovie) {
	// We could trigger on end of movie here, we don't because we wish to
	// view the last frame of the movie for the rest of the mouse-over .
    }
}

QImage loadTile(const QString& tile, const QSize& size, const QString& state)
{
    QString name = tile;

    if (size.height() < 42)
	name += "_tiny_";
    else if (size.height() < 54)
	name += "_normal_";
    else
	name += "_large_";

    name += state + ".png";

    QImage tileImg(KGlobal::dirs()->findResource("tiles", name));

    // scale if size does not match exactly
    if (!tileImg.isNull() && tileImg.size() != size)
	tileImg = tileImg.smoothScale(size);

    return tileImg;
}
