/****************************************************************************
** $Id: qt/src/widgets/qslider.cpp   2.3.3   edited 2001-06-26 $
**
** Implementation of QSlider class
**
** Created : 961019
**
** Copyright (C) 1992-2000 Trolltech AS.  All rights reserved.
**
** This file is part of the widgets module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses may use this file in accordance with the Qt Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

#include "seekbar.h"
#include "qstyle.h"
#include "qpainter.h"
#include "qdrawutil.h"
#include "qtimer.h"
#include "qbitmap.h"
#include "qapplication.h"
#include "skin.h"
#include "skinpanel.h"

static const int motifBorder = 0;
static const int thresholdTime = 500;
static const int repeatTime    = 100;

static const bool funnyWindowsStyle = FALSE;

static int sliderStartVal = 0; //##### class member?


/*!
  Constructs a vertical slider.

  The \e parent and \e name arguments are sent to the QWidget constructor.
*/

SeekBar::SeekBar(Skin::PartsType type, const SkinManager *skinmgr, QWidget *parent, const char *name)
    : QWidget( parent, name  ),
      type_(type)
{
    orient = Horizontal;
    
    setSkin(skinmgr->skin());
    connect(skinmgr, SIGNAL(skinChanged(const Skin *)), this, SLOT(setSkin(const Skin *)));

    if (parent && parent->inherits("SkinPanel"))
		connect(((SkinPanel *)parent), SIGNAL(resized()), this, SLOT(skinPanelResized()));
    
    init();
}

void SeekBar::init()
{
    extra = 0;
    timer = 0;
    sliderPos = 0;
    sliderVal = 0;
    clickOffset = 0;
    state = Idle;
    track = TRUE;
    setFocusPolicy( TabFocus  );
    setBackgroundMode(NoBackground);
	setWFlags(Qt::WRepaintNoErase | Qt::WResizeNoErase);
//    setAutoMask(true);
}


/*!
  Enables slider tracking if \e enable is TRUE, or disables tracking
  if \e enable is FALSE.

  If tracking is enabled (default), the slider emits the
  valueChanged() signal whenever the slider is being dragged.  If
  tracking is disabled, the slider emits the valueChanged() signal
  when the user releases the mouse button (unless the value happens to
  be the same as before).

  \sa tracking()
*/

void SeekBar::setTracking( bool enable )
{
    track = enable;
}


/*!
  \fn bool SeekBar::tracking() const
  Returns TRUE if tracking is enabled, or FALSE if tracking is disabled.

  Tracking is initially enabled.

  \sa setTracking()
*/


/*!
  \fn void SeekBar::valueChanged( int value )
  This signal is emitted when the slider value is changed, with the
  new slider value as an argument.
*/

/*!
  \fn void SeekBar::sliderPressed()
  This signal is emitted when the user presses the slider with the mouse.
*/

/*!
  \fn void SeekBar::sliderMoved( int value )
  This signal is emitted when the slider is dragged, with the
  new slider value as an argument.
*/

/*!
  \fn void SeekBar::sliderReleased()
  This signal is emitted when the user releases the slider with the mouse.
*/

/*!
  Calculates slider position corresponding to value \a v.
*/

int SeekBar::positionFromValue( int v ) const
{
    int  a = available();
    return QRangeControl::positionFromValue( v, a );
}

/*!
  Returns the available space in which the slider can move.
*/

int SeekBar::available() const
{
    return (orient == Horizontal) ? width() - image_->width()
	: height() - image_->height();
}

/*!
  Calculates value corresponding to slider position \a p.
*/

int SeekBar::valueFromPosition( int p ) const
{
    int a = available();
    return QRangeControl::valueFromPosition( p, a );

}

/*!
  Implements the virtual QRangeControl function.
*/

void SeekBar::rangeChange()
{
    int newPos = positionFromValue( value() );
    if ( newPos != sliderPos ) {
	reallyMoveSlider( newPos );
    }
}

/*!
  Implements the virtual QRangeControl function.
*/

void SeekBar::valueChange()
{
    if ( sliderVal != value() ) {
	int newPos = positionFromValue( value() );
	sliderVal = value();
	reallyMoveSlider( newPos );
    }
    emit valueChanged(value());
}


/*!\reimp
*/
void SeekBar::resizeEvent( QResizeEvent * )
{
    rangeChange();
    if ( autoMask() )
	updateMask();
    
        
}


/*!
  Reimplements the virtual function QWidget::setPalette().

  Sets the background color to the mid color for Motif style sliders.
*/

void SeekBar::setPalette( const QPalette &p )
{
    QWidget::setPalette( p );
}



/*!
  Sets the slider orientation.  The \e orientation must be
  SeekBar::Vertical or SeekBar::Horizontal.
  \sa orientation()
*/

void SeekBar::setOrientation( Orientation orientation )
{
    orient = orientation;
    rangeChange();
    update();
}


/*!
  \fn Orientation SeekBar::orientation() const
  Returns the slider orientation; SeekBar::Vertical or
  SeekBar::Horizontal.
  \sa setOrientation()
*/


/*!
  Returns the slider handle rectangle. (The actual moving-around thing.)
*/

QRect SeekBar::sliderRect() const
{
    QRect r;
    if (orient == Horizontal )
	r.setRect( sliderPos, 0,
		   image_->width(), image_->height()  );
    else
	r.setRect ( 0, sliderPos,
		    image_->width(), image_->height()  );
    return r;
}


/*!
  Performs the actual moving of the slider.
*/

void SeekBar::reallyMoveSlider( int newPos )
{
    QRect oldR = sliderRect();
    sliderPos = newPos;
    QRect newR = sliderRect();
    //since sliderRect isn't virtual, I know that oldR and newR
    // are the same size.
    if ( orient == Horizontal ) {
	if ( oldR.left() < newR.left() )
	    oldR.setRight( QMIN ( oldR.right(), newR.left()));
	else           //oldR.right() >= newR.right()
	    oldR.setLeft( QMAX ( oldR.left(), newR.right()));
    } else {
	if ( oldR.top() < newR.top() )
	    oldR.setBottom( QMIN ( oldR.bottom(), newR.top()));
	else           //oldR.bottom() >= newR.bottom()
	    oldR.setTop( QMAX ( oldR.top(), newR.bottom()));
    }
    repaint( oldR );
    repaint( newR, FALSE );
    if ( autoMask() )
	updateMask();
}

/*!\reimp
*/
void SeekBar::paintEvent( QPaintEvent * )
{
    QPainter p( this );
    
    if (!autoMask())
    {
    	const QWidget *parent = parentWidget();

		if (parent->inherits("SkinPanel"))
		{
			if (background_.height() != this->height() || background_.width() != this->width())
			{
				background_.resize(this->width(), this->height());

				QPainter backpainter(&background_);
				backpainter.translate(-x(), -y());
				((SkinPanel *)parent)->paintSkin(backpainter);
				backpainter.translate(x(), y());
				backpainter.end();
			}
			
			//p.translate(-x(), -y());
			//((SkinPanel *)parent)->paintSkin(p);
			//p.translate(x(), y());
			
			p.drawPixmap(0, 0, background_);
		}
    }
    
    if (orient == Horizontal) {
    	p.drawPixmap(sliderPos, 0, *image_);
    } else {
    	p.drawPixmap(0, sliderPos, *image_);
    }
}


/*!

 Reimplementation of QWidget::updateMask(). Draws the mask of the
 slider when transparency is required.

 \sa QWidget::setAutoMask()
*/
void SeekBar::updateMask()
{
    if (image_) {
	QBitmap bm(size());
	bm.fill(color0);

	QPainter p( &bm, this );
	p.setPen(color1);
	if (orient == Horizontal) {
	    p.fillRect(sliderPos, 0, image_->width(), image_->height(), QBrush(color1));
	} else {
	    p.fillRect(0, sliderPos, image_->width(), image_->height(), QBrush(color1));
	}
	setMask(bm);
    }
}

/*!\reimp
*/
void SeekBar::mousePressEvent( QMouseEvent *e )
{
    resetState();
    sliderStartVal = sliderVal;
    QRect r = sliderRect();

    if ( e->button() == RightButton ) {
	return;
    } else if ( r.contains( e->pos() ) ) {
	state = Dragging;
	clickOffset = (QCOORD)( goodPart( e->pos() ) - sliderPos );
	emit sliderPressed();
    } else if ( e->button() == MidButton ||
		(funnyWindowsStyle /*&& style() == WindowsStyle*/) ) {
	int pos = goodPart( e->pos() );
	moveSlider( pos - slideLength() / 2 );
	state = Dragging;
	clickOffset = slideLength() / 2;
    } else if ( orient == Horizontal && e->pos().x() < r.left() //### goodPart
		|| orient == Vertical && e->pos().y() < r.top() ) {
	state = TimingDown;
	subtractStep();
	if ( !timer )
	    timer = new QTimer( this );
	connect( timer, SIGNAL(timeout()), SLOT(repeatTimeout()) );
	timer->start( thresholdTime, TRUE );
    } else if ( orient == Horizontal && e->pos().x() > r.right() //### goodPart
		|| orient == Vertical && e->pos().y() > r.bottom() ) {
	state = TimingUp;
	addStep();
	if ( !timer )
	    timer = new QTimer( this );
	connect( timer, SIGNAL(timeout()), SLOT(repeatTimeout()) );
	timer->start( thresholdTime, TRUE );
    }
}

/*!\reimp
*/
void SeekBar::mouseMoveEvent( QMouseEvent *e )
{
    if ( state != Dragging )
	return;

    //if ( style() == WindowsStyle ) {
	QRect r = rect();
	//int m = style().maximumSliderDragDistance();
	int m = 10;
	
	if ( m >= 0 ) {
	    if ( orientation() == Horizontal )
		r.setRect( r.x() - m, r.y() - 2*m/3,
			   r.width() + 2*m, r.height() + 3*m );
	    else
		r.setRect( r.x() - 2*m/3, r.y() - m,
			   r.width() + 3*m, r.height() + 2*m );
	    if ( !r.contains( e->pos() ) ) {
		moveSlider( positionFromValue( sliderStartVal) );
		return;
	    }
	}
    //}

    int pos = goodPart( e->pos() );
    moveSlider( pos - clickOffset );
}

/*!\reimp
*/
void SeekBar::wheelEvent( QWheelEvent * e){
    static float offset = 0;
    static SeekBar* offset_owner = 0;
    if (offset_owner != this){
	offset_owner = this;
	offset = 0;
    }
    offset += -e->delta()*QMAX(pageStep(),lineStep())/120;
    if (QABS(offset)<1)
	return;
    setValue( value() + int(offset) );
    offset -= int(offset);
    e->accept();
}


/*!\reimp
*/
void SeekBar::mouseReleaseEvent( QMouseEvent * )
{
    resetState();
}

/*!\reimp
*/
void SeekBar::focusInEvent( QFocusEvent * e)
{
    QWidget::focusInEvent( e );
}

/*!\reimp
*/
void SeekBar::focusOutEvent( QFocusEvent * e )
{
    QWidget::focusOutEvent( e );
}

/*!
  Moves the left (or top) edge of the slider to position
  \a pos. Performs snapping.
*/

void SeekBar::moveSlider( int pos )
{
    int  a = available();
    int newPos = QMIN( a, QMAX( 0, pos ) );
    int newVal = valueFromPosition( newPos );
    if ( sliderVal != newVal ) {
	sliderVal = newVal;
	emit sliderMoved( sliderVal );
    }
    if ( tracking() && sliderVal != value() ) {
	setValue( sliderVal );
	// ### Why do we emit the valueChanged signal here?  It will get emitted in 
	// valueChange() anyway...
	//emit valueChanged( sliderVal );
    }

    newPos = positionFromValue( newVal );

    if ( sliderPos != newPos )
	reallyMoveSlider( newPos );
}


/*!
  Resets all state information and stops my timer.
*/

void SeekBar::resetState()
{
    if ( timer ) {
	timer->stop();
	timer->disconnect();
    }
    switch ( state ) {
    case TimingUp:
    case TimingDown:
	break;
    case Dragging: {
	setValue( valueFromPosition( sliderPos ) );
	emit sliderReleased();
	break;
    }
    case Idle:
	break;
    default:
	qWarning("SeekBar: (%s) in wrong state", name( "unnamed" ) );
    }
    state = Idle;
}


/*!\reimp
*/
void SeekBar::keyPressEvent( QKeyEvent *e )
{
    bool sloppy = false;
    switch ( e->key() ) {
    case Key_Left:
	if ( sloppy || orient == Horizontal )
	    subtractLine();
	break;
    case Key_Right:
	if ( sloppy || orient == Horizontal )
	    addLine();
	break;
    case Key_Up:
	if ( sloppy || orient == Vertical )
	    subtractLine();
	break;
    case Key_Down:
	if ( sloppy || orient == Vertical )
	    addLine();
	break;
    case Key_Prior:
	subtractStep();
	break;
    case Key_Next:
	addStep();
	break;
    case Key_Home:
	setValue( minValue() );
	break;
    case Key_End:
	setValue( maxValue() );
	break;
    default:
	e->ignore();
	return;
    }
}


/*!
  Returns the length of the slider.
*/

int SeekBar::slideLength() const
{
	return 5;
}


/*!
  Makes QRangeControl::setValue() available as a slot.
*/

void SeekBar::setValue( int value )
{
    QRangeControl::setValue( value );
}


/*!
  Moves the slider one pageStep() upwards.
*/

void SeekBar::addStep()
{
    addPage();
    emit sliderStepped( QRangeControl::value() );
}


/*!
  Moves the slider one pageStep() downwards.
*/

void SeekBar::subtractStep()
{
    subtractPage();
    emit sliderStepped( QRangeControl::value() );
}


/*!
  Waits for autorepeat.
*/

void SeekBar::repeatTimeout()
{
    ASSERT( timer );
    timer->disconnect();
    if ( state == TimingDown )
	connect( timer, SIGNAL(timeout()), SLOT(subtractStep()) );
    else if ( state == TimingUp )
	connect( timer, SIGNAL(timeout()), SLOT(addStep()) );
    timer->start( repeatTime, FALSE );
}


/*!
  Returns the relevant dimension of \a p.
*/

int SeekBar::goodPart( const QPoint &p ) const
{
    return (orient == Horizontal) ?  p.x() : p.y();
}

/*!\reimp
*/
QSize SeekBar::sizeHint() const
{
    constPolish();
    const int length = 84;

    if ( orient == Horizontal )
	return QSize( length, image_->height() ).expandedTo( QApplication::globalStrut() );
    else
	return QSize( image_->width(), length ).expandedTo( QApplication::globalStrut() );
}



/*!
  \reimp
*/

QSize SeekBar::minimumSizeHint() const
{
    QSize s = sizeHint();
    int length = 5;
    if ( orient == Horizontal )
	s.setWidth( length );
    else
	s.setHeight( length );

    return s;
}



/*!\reimp
*/
QSizePolicy SeekBar::sizePolicy() const
{
    if ( orient == Horizontal )
	return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
    else
	return QSizePolicy(  QSizePolicy::Fixed, QSizePolicy::Expanding );
}


/*!
  \reimp
*/
int SeekBar::minValue() const
{
    return QRangeControl::minValue();
}

/*!
  \reimp
*/
int SeekBar::maxValue() const
{
    return QRangeControl::maxValue();
}

/*!
  A convenience function which just calls
  setRange( i, maxValue() )

  \sa setRange()
*/
void SeekBar::setMinValue( int i )
{
    setRange( i, maxValue() );
}

/*!
  A convenience function which just calls
  setRange( minValue(), i )

  \sa setRange()
*/
void SeekBar::setMaxValue( int i )
{
    setRange( minValue(), i );
}

/*!
  \reimp
*/
int SeekBar::lineStep() const
{
    return QRangeControl::lineStep();
}

/*!
  \reimp
*/
int SeekBar::pageStep() const
{
    return QRangeControl::pageStep();
}

/*!
  Sets the line step to \e i.

  Calls the virtual stepChange() function if the new line step is
  different from the previous setting.

  \sa lineStep() QRangeControl::setSteps() setPageStep() setRange()
*/
void SeekBar::setLineStep( int i )
{
    setSteps( i, pageStep() );
}

/*!
  Sets the page step to \e i.

  Calls the virtual stepChange() function if the new page step is
  different from the previous setting.

  \sa pageStep() QRangeControl::setSteps() setLineStep() setRange()
*/
void SeekBar::setPageStep( int i )
{
    setSteps( lineStep(), i );
}

/*!
  \reimp
*/
int SeekBar::value() const
{
    return QRangeControl::value();
}

void SeekBar::setSkin(const Skin *skin)
{
	parts_ = skin->lookup(type_);
    image_ = &parts_->image();
    
	if (parentWidget() && parentWidget()->inherits("SkinPanel"))
		skinPanelResized();
	else
		setGeometry(parts_->geometry().x(), parts_->geometry().y(), parts_->length(), parts_->geometry().height());
}

void SeekBar::skinPanelResized()
{
	SkinPanel *parent = (SkinPanel *)parentWidget();
	setGeometry(parent->calculateGeometry(parts_));
}

