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

Copyright (c) 1996-2000 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 <qbitmap.h>
#include <qimage.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qstyle.h>
#include <qwidgetlist.h>

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

#include "kicker.h"
#include "panel.h"

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

static ZoomButton* zoomButton = 0;
static int zoomDisabled = 0;

ZoomButton::ZoomButton()
	: PanelButtonBase( 0, 0, WStyle_Customize | WX11BypassWM | WMouseNoMask)
{
    watch = 0;
    zooming = FALSE;
    locked = 0;
    hideTimer = 0;
    _oldCursor = cursor();
    qApp->installEventFilter( this );
    setMouseTracking( TRUE );
    connect( Kicker::kicker(), SIGNAL( configurationChanged() ),
	     this, SLOT(reconfigure() ) );
    reconfigure();
}

ZoomButton::~ZoomButton()
{
    qApp->removeEventFilter( this );
}

void ZoomButton::reconfigure()
{
    KConfig* config = KGlobal::config();
    config->setGroup("buttons");
    zooming = config->readBoolEntry("EnableIconZoom", false);
    readAnimatedIconsConfig();
}

void ZoomButton::readAnimatedIconsConfig()
{
    KConfigGroup cfgGroup( KGlobal::config(), "PanelIcons" );
    doAnimations = cfgGroup.readBoolEntry( "Animated", true );
}

void ZoomButton::updateIcon()
{
    if ( watch ) 
    {
        _icon = watch->zoomIcon();
        update();
    }	
}

// Overload because regular unsetCursor() does not work for toplevel widgets
void ZoomButton::unsetCursor()
{
    setCursor(_oldCursor);
}

bool ZoomButton::isWatching( PanelButtonBase* btn )
{
    return ( btn == watch );
}

void ZoomButton::watchMe( PanelButtonBase* btn )
{
    PanelButtonBase* watch_old = watch;
    watch = btn;
    _icon = watch->zoomIcon();
    if ( watch_old ) 
    {
	watch_old->repaint(false);
	disconnect(watch_old,0, this,0);
    }
    connect(watch, SIGNAL(iconChanged()), this, SLOT(updateIcon()));
    hide();
    resize( _icon.size() );
    int offset = 5;
    QPoint p = watch->center() - rect().center();
    if ( p.x() < offset )
	p.rx() = offset;
    if ( p.y() < offset )
	p.ry() = offset;
    if ( p.x() + width() > qApp->desktop()->width() - offset)
	p.rx() = qApp->desktop()->width() - width() - offset;
    if ( p.y() + height() > qApp->desktop()->height() - offset)
	p.ry() = qApp->desktop()->height() - height() - offset;
    move( p );
    mypos = p;
    setDown( false );

    if (_icon.mask())
	setMask(* _icon.mask());
    else	
        clearMask();

    raise();
    show();
    setEnabled(true);
}

void ZoomButton::drawButtonLabel(QPainter *p)
{
    if ( isDown() || isOn() )
	move ( mypos + QPoint(2,2) );
    else
	move ( mypos );
    if ( !_icon.isNull()) {
	int x = (width() - _icon.width())/2;
	int y = (height() - _icon.height())/2;
	p->drawPixmap(x, y, _icon);
    }
}

/* This event filter is very tricky and relies on Qt
   internals. It's written this way to make all panel buttons work
   without modification and to keep advanced functionality like tool
   tips for the buttons alive.

   Don't hack around in this filter unless you REALLY know what you
   are doing. In case of doubt, ask ettrich@kde.org.
*/
bool ZoomButton::eventFilter( QObject *o, QEvent * e)
{

    if ( !watch )
	return FALSE;
    if ( e == locked )
	return FALSE;
    if ( !isVisible() )
	return FALSE;
    if ( e->type() == QEvent::Leave ) {
	if ( !hideTimer )
	    hideTimer = startTimer( 200 );
    }

    if ( o == this ) {
	if ( e->type() == QEvent::Timer && ((QTimerEvent*)e)->timerId() == hideTimer ) {
	    if ( watch && watch->isDown() )
		return TRUE;
	    QPoint globalPos = QCursor::pos();
	    if ( !geometry().contains( globalPos ) ) {
		killTimer( hideTimer );
		hideTimer = 0;
		hide();
                PanelButtonBase* btn = watch;
                watch = 0;
		if ( btn )
		    btn->update();
	    }
	    return TRUE;
	}

	if ( e->type() == QEvent::MouseButtonPress )
	    raise();

	(void) event( e ); // send to us directly , below we'll  hide the event from the tooltip filters


	if ( e->type() == QEvent::MouseButtonPress ||
	     e->type() == QEvent::MouseButtonRelease ||
	     e->type() == QEvent::MouseMove ) {
	    QMouseEvent* ev = (QMouseEvent*) e;
	    if ( rect().contains( ev->pos() ) && watch && !watch->rect().contains( ev->pos() ) )
		ev = new QMouseEvent( ev->type(),
				      watch->rect().center(),
				      ev->globalPos(),
				      ev->button(),
				      ev->state() );
	    locked = e;
	    QApplication::sendEvent( watch, ev );
	    if ( ev != e )
		delete ev;
	    locked = 0;
	}

	if ( e->type() == QEvent::Enter || e->type() == QEvent::Leave ) {
	    locked = e;
	    QApplication::sendEvent( watch, e );
	    locked = 0;
	}



	return TRUE;
    }
    if ( watch == o && e != locked ) {
	if ( e->type() == QEvent::MouseButtonPress ||
	     e->type() == QEvent::MouseButtonRelease ||
	     e->type() == QEvent::MouseMove ||
	     e->type() == QEvent::Enter ||
	     e->type() == QEvent::Leave ) {
	    QApplication::sendEvent( this, e );
	    return TRUE;
	}
    }
    return FALSE;
}

void ZoomButton::unFocus() 
{
    hide();
    if (watch) {
	PanelButtonBase* tmp = watch;
        watch = 0;
	tmp->update();
    }	
    lower();
    setEnabled(false);
}

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

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

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)
{
    if( tile != _tile ) {
        _tile = tile;
        loadTiles();
        update();
    }
}

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

// (re)calculates the icon sizes and report true if they have changed.
bool PanelButtonBase::calculateIconSizes()
{
    KIcon::StdSizes sz;
    KIcon::StdSizes zoom_sz;

    if ( orientation() == Horizontal ) {
        if ( height() < 32 ) sz = KIcon::SizeSmall;
        else if ( height() < 48 ) sz = KIcon::SizeMedium;
        else sz = KIcon::SizeLarge;
    }
    else {
        if ( width() < 32 ) sz = KIcon::SizeSmall;
        else if ( width() < 48 ) sz = KIcon::SizeMedium;
        else sz = KIcon::SizeLarge;
    }

    if (sz == KIcon::SizeSmall)
        zoom_sz = KIcon::SizeMedium;
    else
        zoom_sz = KIcon::SizeLarge;

    if (_size != sz || _zoom_size != zoom_sz) {
        _size = sz;
        _zoom_size = zoom_sz;
	return true;
     }
     return false;
}

void PanelButtonBase::loadIcons()
{

    KIconLoader * ldr = KGlobal::iconLoader();
    QString nm = _iconName;

    _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()
{
    KConfig* config = KGlobal::config();
    config->setGroup("General");
    if (config->readBoolEntry("UseBackgroundTheme", false))
    {
	// Get the pixmap from the container area object
        QWidget* pContainer = Panel::the();
	QPalette pal = pContainer->palette();
	QBrush bgBrush = pal.brush( QPalette::Active, QColorGroup::Background );
	QPixmap* containerBG = bgBrush.pixmap();

	// Make sure the background pixmap exists
	if ( containerBG && parent() ) //parent() is null for ZoomButtons
	{
	    // Create a pixmap the same size as the button to use as the bg
	    QPixmap bgPix( width(), height() );

	    // Calculate which part of the container area background should be copied
	    QWidget* p = (QWidget*) parent();

	    int srcx, srcy;
	    if ( _arrowDirection == ::Bottom || _arrowDirection == ::Top )
	    {
		srcx = p->x() % containerBG->width();
		srcy = 0;
	    }
	    else
	    {
		srcx = 0;
		srcy = p->y() % containerBG->height();
	    }

	    // Check if we need to take care of a wrap-around
	    if ( srcx + p->width() <= containerBG->width() &&
		    srcy + p->height() <= containerBG->height() )
	    {
		// Nothing funny going on with overlaps - just copy

		QPoint dp( 0, 0 ); // destination point
		QRect sr( srcx, srcy, width(), height() );
		bitBlt( &bgPix, dp, containerBG, sr, CopyROP );
	    }
	    else
	    {
		// Need to do 2 seperate bitBlts to take care of the overlap

		// Copy the left/top of the image first up to the wrap-
		// around point
		int x = 0;
		int y = 0;
		int w = containerBG->width() - srcx;
		int h = containerBG->height() - srcy;
		QPoint dp( x, y ); // destination point
		QRect sr( srcx, srcy, w, h );
		bitBlt( &bgPix, dp, containerBG, sr, CopyROP );

		// Now copy the wrap-around bit
		if ( _arrowDirection == ::Bottom || _arrowDirection == ::Top )
		{
		    x = containerBG->width() - srcx;
		    y = 0;
		    w = srcx + p->width() - containerBG->width();
		    h = p->height();
		}
		else
		{
		    x = 0;
		    y = containerBG->height() - srcy;
		    w = p->width();
		    h = srcy + p->height() - containerBG->height();
		}
		dp = QPoint( x, y );
		sr = QRect( 0, 0, w, h );
		bitBlt( &bgPix, dp, containerBG, sr, CopyROP );
	    }

	    _bg = bgPix;
	}
	else
	{
	    // Conatainer palette pixmap not set yet
	    _bg = QPixmap();
	}
    }
    else
    {
	// No background pixmap to use
	_bg = (backgroundPixmap() && backgroundPixmap()->size()==size()) ? *backgroundPixmap() :  QPixmap();
    }
}

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

void PanelButtonBase::setZoomEnabled(bool b)
{
    if (!zoomButton) 
        zoomButton = new ZoomButton();
    
    if (!b)
      zoomDisabled++;
    else
      zoomDisabled--;

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

void PanelButtonBase::enterEvent(QEvent* e)
{

    if (!zoomDisabled)
    {
        if (!zoomButton)
            zoomButton = new ZoomButton;

        if (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( _tile == QString::null ) {
	_up = _down = QPixmap();
	return;
    }

    QString uptile, downtile;

    if ( height() < 42 ) {
	uptile = _tile + "_tiny_up.png";
	downtile = _tile + "_tiny_down.png";
    }
    else if ( height() < 54 ) {
	uptile = _tile + "_normal_up.png";
	downtile = _tile + "_normal_down.png";
    }
    else {
	uptile = _tile + "_large_up.png";
	downtile = _tile + "_large_down.png";
    }

    _up = QPixmap( KGlobal::dirs()->findResource("tiles", uptile) );
    _down = QPixmap ( KGlobal::dirs()->findResource("tiles", downtile) );

    // scale if size does not match exactly
    if ( !_up.isNull() ) {
	if ( _up.width() != width() || _up.height() != height() ) {
	    QImage upI = _up.convertToImage();
	    _up.convertFromImage( upI.smoothScale( width(), height() ) );
	}
    }

    // scale if size does not match exactly
    if ( !_down.isNull() ) {
	if ( _down.width() != width() || _down.height() != height() ) {
	    QImage downI = _down.convertToImage();
	    _down.convertFromImage( downI.smoothScale( width(), height() ) );
	}
    }
}

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

    QPixmap bg(size());
    QPainter pbg;
    pbg.begin(&bg);

    // Draw the background. This is always needed, even when using tiles,
    // because they don't have to cover the entire button.
    if ( !_bg.isNull() )
	pbg.drawPixmap( 0, 0, _bg );
    else
	pbg.fillRect(rect(), colorGroup().brush(QColorGroup::Background));

    QPixmap tile = (isDown() || isOn()) ? _down : _up;
    if (!tile.isNull()) { // Draw the tile
	int x = (width() -  tile.width() )/2;
	int y = (height() - tile.height())/2;
	pbg.drawPixmap(x, y, tile);
    }
    else if (isDown() || isOn()) { // Draw shapes to indicate the down state.
	pbg.setPen(Qt::black);
	pbg.drawLine(0, 0, width()-1, 0);
	pbg.drawLine(0, 0, 0, height()-1);
	pbg.setPen(colorGroup().light());
	pbg.drawLine(0, height()-1, width()-1, height()-1);
	pbg.drawLine(width()-1, 0, width()-1, height()-1);
    }

    pbg.end();
    p->drawPixmap(0,0,bg);
    drawButtonLabel(p);

    if ( hasFocus() || _hasAcceptedDrag ) {
	int x1, y1, x2, y2;
	rect().coords( &x1, &y1, &x2, &y2 );// get coordinates
	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)
{
    p->save();

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

    QPixmap icon = labelIcon();

    if ( (!zoomButton || !zoomButton->isWatching( this )) && !icon.isNull()) {
	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( QPoint( 0, 0 ), QSize( 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 );
    }

    p->restore();
}

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 ) {
            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 dont because we wish to
    // view the last frame of the movie for the rest of the mouse-over .
    }
}


