/****************************************************************************
**
** Implementation of TQSlider class
**
** Created : 961019
**
** Copyright (C) 2010 Timothy Pearson and (C) 1992-2008 Trolltech ASA.
**
** This file is part of the widgets module of the TQt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free TQt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.TQPL
** included in the packaging of this file.  Licensees holding valid TQt
** Commercial licenses may use this file in accordance with the TQt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/

#include "tqslider.h"
#ifndef TQT_NO_SLIDER
#include "tqpainter.h"
#include "tqdrawutil.h"
#include "tqtimer.h"
#include "tqbitmap.h"
#include "tqapplication.h"
#include "tqstyle.h"
#if defined(TQT_ACCESSIBILITY_SUPPORT)
#include "tqaccessible.h"
#endif

static const int thresholdTime = 300;
static const int repeatTime = 100;

struct TQSliderPrivate
{
    // ### move these to TQSlider in TQt 4.0
    int sliderStartVal;
    TQSliderPrivate() : sliderStartVal( 0 ) { }
};


/*!
    \class TQSlider
    \brief The TQSlider widget provides a vertical or horizontal slider.

    \ingroup basic
    \mainclass

    The slider is the classic widget for controlling a bounded value.
    It lets the user move a slider along a horizontal or vertical
    groove and translates the slider's position into an integer value
    within the legal range.

    TQSlider inherits TQRangeControl, which provides the "integer" side
    of the slider. setRange() and value() are likely to be used by
    practically all slider users; see the \l TQRangeControl
    documentation for information about the many other functions that
    class provides.

    The main functions offered by the slider itself are tickmark and
    orientation control; you can use setTickmarks() to indicate where
    you want the tickmarks to be, setTickInterval() to indicate how
    many of them you want and setOrientation() to indicate whether the
    slider is to be horizontal or vertical.

    A slider accepts focus on Tab and uses the mouse wheel and a
    suitable keyboard interface.

    <img src=qslider-m.png> <img src=qslider-w.png>

    \important setRange

    \sa TQScrollBar TQSpinBox
    \link guibooks.html#fowler GUI Design Handbook: Slider\endlink
*/


/*!
    \enum TQSlider::TickSetting

    This enum specifies where the tickmarks are to be drawn relative
    to the slider's groove and the handle the user moves.

    \value NoMarks do not draw any tickmarks.
    \value Both draw tickmarks on both sides of the groove.
    \value Above draw tickmarks above the (horizontal) slider
    \value Below draw tickmarks below the (horizontal) slider
    \value Left draw tickmarks to the left of the (vertical) slider
    \value Right draw tickmarks to the right of the (vertical) slider
*/


/*!
    Constructs a vertical slider.

    The \a parent and \a name arguments are sent on to the TQWidget
    constructor.
*/

TQSlider::TQSlider( TQWidget *parent, const char *name )
    : TQWidget( parent, name  )
{
    orient = Qt::Vertical;
    init();
}

/*!
    Constructs a slider.

    The \a orientation must be \l Qt::Vertical or \l Qt::Horizontal.

    The \a parent and \a name arguments are sent on to the TQWidget
    constructor.
*/

TQSlider::TQSlider( Qt::Orientation orientation, TQWidget *parent, const char *name )
    : TQWidget( parent, name )
{
    orient = orientation;
    init();
}

/*!
    Constructs a slider whose value can never be smaller than \a
    minValue or greater than \a maxValue, whose page step size is \a
    pageStep and whose value is initially \a value (which is
    guaranteed to be in range using bound()).

    If \a orientation is \c Qt::Vertical the slider is vertical and if it
    is \c Qt::Horizontal the slider is horizontal.

    The \a parent and \a name arguments are sent on to the TQWidget
    constructor.
*/

TQSlider::TQSlider( int minValue, int maxValue, int pageStep,
		  int value, Qt::Orientation orientation,
		  TQWidget *parent, const char *name )
    : TQWidget( parent, name ),
      TQRangeControl( minValue, maxValue, 1, pageStep, value )
{
    orient = orientation;
    init();
    sliderVal = value;
}

/*!
    Destructor.
*/
TQSlider::~TQSlider()
{
    delete d;
}

void TQSlider::init()
{
    d = new TQSliderPrivate;
    timer = 0;
    sliderPos = 0;
    sliderVal = 0;
    clickOffset = 0;
    state = Idle;
    track = TRUE;
    ticks = NoMarks;
    tickInt = 0;
    setFocusPolicy( Qt::TabFocus  );
    initTicks();

    TQSizePolicy sp( TQSizePolicy::Expanding, TQSizePolicy::Fixed );
    if ( orient == Qt::Vertical )
	sp.transpose();
    tqsetSizePolicy( sp );
    clearWState( TQt::WState_OwnSizePolicy );
}


/*
    Does what's needed when someone changes the tickmark status.
*/

void TQSlider::initTicks()
{
    tickOffset = tqstyle().tqpixelMetric( TQStyle::PM_SliderTickmarkOffset, this );
}


/*!
    \property TQSlider::tracking
    \brief whether slider tracking is enabled

    If tracking is enabled (the 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).
*/

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


/*!
    \fn void TQSlider::valueChanged( int value )

    This signal is emitted when the slider value is changed, with the
    new slider \a value as its argument.
*/

/*!
    \fn void TQSlider::sliderPressed()

    This signal is emitted when the user presses the slider with the
    mouse.
*/

/*!
    \fn void TQSlider::sliderMoved( int value )

    This signal is emitted when the slider is dragged, with the new
    slider \a value as its argument.
*/

/*!
    \fn void TQSlider::sliderReleased()

    This signal is emitted when the user releases the slider with the mouse.
*/

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

int TQSlider::positionFromValue( int v ) const
{
    int  a = available();
    int x = TQRangeControl::positionFromValue( v, a );
    if ( orient == Qt::Horizontal && TQApplication::reverseLayout() )
	x = a - x;
    return x;
}

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

int TQSlider::available() const
{
    return tqstyle().tqpixelMetric( TQStyle::PM_SliderSpaceAvailable, this );
}

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

int TQSlider::valueFromPosition( int p ) const
{
    int a = available();
    int x = TQRangeControl::valueFromPosition( p, a );
    if ( orient == Qt::Horizontal && TQApplication::reverseLayout() )
	x = maxValue() + minValue() - x;
    return x;
}

/*!
    Implements the virtual TQRangeControl function.
*/

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

/*!
    Implements the virtual TQRangeControl function.
*/

void TQSlider::valueChange()
{
    if ( sliderVal != value() ) {
	int newPos = positionFromValue( value() );
	sliderVal = value();
	reallyMoveSlider( newPos );
    }
    emit valueChanged(value());
#if defined(TQT_ACCESSIBILITY_SUPPORT)
    TQAccessible::updateAccessibility( this, 0, TQAccessible::ValueChanged );
#endif
}


/*!
    \reimp
*/
void TQSlider::resizeEvent( TQResizeEvent * )
{
    rangeChange();
    initTicks();
}


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

    Sets the background color to the mid color for Motif style sliders
    using palette \a p.
*/

void TQSlider::setPalette( const TQPalette &p )
{
    TQWidget::setPalette( p );
}



/*!
    \property TQSlider::orientation
    \brief the slider's orientation

    The orientation must be \l Qt::Vertical (the default) or \l
    Qt::Horizontal.
*/

void TQSlider::setOrientation( Qt::Orientation orientation )
{
    if ( orientation == orient )
	return;

    if ( !testWState( TQt::WState_OwnSizePolicy ) ) {
	TQSizePolicy sp = sizePolicy();
	sp.transpose();
	tqsetSizePolicy( sp );
	clearWState( TQt::WState_OwnSizePolicy );
    }

    orient = orientation;

    rangeChange();
    update();
}

/*!
    \fn int TQSlider::sliderStart() const

    Returns the start position of the slider.
*/


/*!
    Returns the slider handle rectangle. (This is the visual marker
    that the user can move.)
*/

TQRect TQSlider::sliderRect() const
{
    return tqstyle().querySubControlMetrics( TQStyle::CC_Slider, this,
					   TQStyle::SC_SliderHandle );
}

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

void TQSlider::reallyMoveSlider( int newPos )
{
    TQRegion oldR(sliderRect());
    sliderPos = newPos;
    TQRegion newR(sliderRect());

    /* just the one tqrepaint if no background */
    if (backgroundMode() == TQt::NoBackground)
	tqrepaint(newR | oldR, FALSE);
    else {
	tqrepaint(oldR.subtract(newR));
	tqrepaint(newR, FALSE);
    }
}


/*!
    \reimp
*/
void TQSlider::paintEvent( TQPaintEvent * )
{
    TQPainter p( this );

    TQStyle::SFlags flags = TQStyle::Style_Default;
    if (isEnabled())
	flags |= TQStyle::Style_Enabled;
    if (hasFocus())
	flags |= TQStyle::Style_HasFocus;

    TQStyle::SCFlags sub = TQStyle::SC_SliderGroove | TQStyle::SC_SliderHandle;
    if ( tickmarks() != NoMarks )
	sub |= TQStyle::SC_SliderTickmarks;

    tqstyle().tqdrawComplexControl( TQStyle::CC_Slider, &p, this, rect(), tqcolorGroup(),
				flags, sub, state == Dragging ? TQStyle::SC_SliderHandle : TQStyle::SC_None );
}


/*!
    \reimp
*/
void TQSlider::mousePressEvent( TQMouseEvent *e )
{
    int slideLength = tqstyle().tqpixelMetric( TQStyle::PM_SliderLength, this );
    resetState();
    d->sliderStartVal = sliderVal;
    TQRect r = sliderRect();

    if ( e->button() == Qt::RightButton )
	return;

    if ( r.contains( e->pos() ) ) {
	state = Dragging;
	clickOffset = (TQCOORD)( goodPart( e->pos() ) - sliderPos );
	emit sliderPressed();
    } else if ( e->button() == Qt::MidButton ) {
	int pos = goodPart( e->pos() );
	moveSlider( pos - slideLength / 2 );
	state = Dragging;
	clickOffset = slideLength / 2;
    } else if ( (orient == Qt::Horizontal && e->pos().x() < r.left()) //### goodPart
		|| (orient == Qt::Vertical && e->pos().y() < r.top()) ) {
	if ( orient == Qt::Horizontal && TQApplication::reverseLayout() ) {
	    state = TimingUp;
	    addPage();
	} else {
	    state = TimingDown;
	    subtractPage();
	}
	if ( !timer )
	    timer = new TQTimer( this );
	connect( timer, TQT_SIGNAL(timeout()), TQT_SLOT(repeatTimeout()) );
	timer->start( thresholdTime, TRUE );
    } else if ( (orient == Qt::Horizontal && e->pos().x() > r.right()) //### goodPart
		|| (orient == Qt::Vertical && e->pos().y() > r.bottom()) ) {
	if ( orient == Qt::Horizontal && TQApplication::reverseLayout() ) {
	    state = TimingDown;
	    subtractPage();
	} else {
	    state = TimingUp;
	    addPage();
	}
	if ( !timer )
	    timer = new TQTimer( this );
	connect( timer, TQT_SIGNAL(timeout()), TQT_SLOT(repeatTimeout()) );
	timer->start( thresholdTime, TRUE );
    }
    update( sliderRect() );
}

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

    TQRect r = rect();
    int m = tqstyle().tqpixelMetric( TQStyle::PM_MaximumDragDistance,
				 this );
    if ( m >= 0 ) {
	if ( orientation() == Qt::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(d->sliderStartVal) );
	    return;
	}
    }

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

/*!
    \reimp
*/
#ifndef TQT_NO_WHEELEVENT
void TQSlider::wheelEvent( TQWheelEvent * e )
{
    if ( e->orientation() != orientation() && !TQT_TQRECT_OBJECT(rect()).contains(e->pos()) )
	return;

    static float offset = 0;
    static TQSlider* offset_owner = 0;
    if (offset_owner != this){
	offset_owner = this;
	offset = 0;
    }
    offset += -e->delta()*TQMAX(pageStep(),lineStep())/120;
    if (TQABS(offset)<1)
	return;
    setValue( value() + int(offset) );
    offset -= int(offset);
    e->accept();
}
#endif

/*!
    \reimp
*/
void TQSlider::mouseReleaseEvent( TQMouseEvent * )
{
    resetState();
    update( sliderRect() );
}

/*!
    \reimp
*/
void TQSlider::focusInEvent( TQFocusEvent * e)
{
    TQWidget::focusInEvent( e );
}

/*!
    \reimp
*/
void TQSlider::focusOutEvent( TQFocusEvent * e )
{
    TQWidget::focusOutEvent( e );
}

/*!
    Moves the left (or top) edge of the slider to position \a pos. The
    slider is actually moved to the step position nearest the given \a
    pos.
*/

void TQSlider::moveSlider( int pos )
{
    int  a = available();
    int newPos = TQMIN( a, TQMAX( 0, pos ) );
    int newVal = valueFromPosition( newPos );
    if (tqstyle().tqstyleHint(TQStyle::SH_Slider_SnapToValue, this))
	newPos = positionFromValue( newVal );
    if ( sliderPos != newPos )
	reallyMoveSlider( newPos );
    if ( sliderVal != newVal ) {
	sliderVal = newVal;
	emit sliderMoved( sliderVal );
    }
    if ( tracking() && sliderVal != value() )
	setValue( sliderVal );

}


/*
    Resets all state information and stops the timer.
*/

void TQSlider::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("TQSlider: (%s) in wrong state", name( "unnamed" ) );
    }
    state = Idle;
}


/*!
    \reimp
*/
void TQSlider::keyPressEvent( TQKeyEvent *e )
{
    bool sloppy = bool(tqstyle().tqstyleHint(TQStyle::SH_Slider_SloppyKeyEvents, this));
    switch ( e->key() ) {
    case Qt::Key_Left:
	if ( sloppy || orient == Qt::Horizontal ) {
	    if (TQApplication::reverseLayout())
		addLine();
	    else
		subtractLine();
	}
	break;
    case Qt::Key_Right:
	if ( sloppy || orient == Qt::Horizontal ) {
	    if (TQApplication::reverseLayout())
		subtractLine();
	    else
		addLine();
	}
	break;
    case Qt::Key_Up:
	if ( sloppy || orient == Qt::Vertical )
	    subtractLine();
	break;
    case Qt::Key_Down:
	if ( sloppy || orient == Qt::Vertical )
	    addLine();
	break;
    case TQt::Key_Prior:
	subtractPage();
	break;
    case TQt::Key_Next:
	addPage();
	break;
    case Qt::Key_Home:
	setValue( minValue() );
	break;
    case Qt::Key_End:
	setValue( maxValue() );
	break;
    default:
	e->ignore();
	return;
    }
}

void TQSlider::setValue( int value )
{
    TQRangeControl::setValue( value );
#if defined(TQT_ACCESSIBILITY_SUPPORT)
    TQAccessible::updateAccessibility( this, 0, TQAccessible::ValueChanged );
#endif
}


/*! \reimp
*/

void TQSlider::addLine()
{
    TQRangeControl::addLine();
}

/*! \reimp
*/

void TQSlider::subtractLine()
{
    TQRangeControl::subtractLine();
}

/*!
    Moves the slider one pageStep() up or right.
*/

void TQSlider::addStep()
{
    addPage();
}


/*!
    Moves the slider one pageStep() down or left.
*/

void TQSlider::subtractStep()
{
    subtractPage();
}


/*
  Waits for autorepeat.
*/

void TQSlider::repeatTimeout()
{
    TQ_ASSERT( timer );
    timer->disconnect();
    if ( state == TimingDown )
	connect( timer, TQT_SIGNAL(timeout()), TQT_SLOT(subtractStep()) );
    else if ( state == TimingUp )
	connect( timer, TQT_SIGNAL(timeout()), TQT_SLOT(addStep()) );
    timer->start( repeatTime, FALSE );
}


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

int TQSlider::goodPart( const TQPoint &p ) const
{
    return (orient == Qt::Horizontal) ?  p.x() : p.y();
}

/*!
    \reimp
*/
TQSize TQSlider::tqsizeHint() const
{
    constPolish();
    const int length = 84, tickSpace = 5;
    int thick = tqstyle().tqpixelMetric( TQStyle::PM_SliderThickness, this );
    if ( ticks & Above )
	thick += tickSpace;
    if ( ticks & Below )
	thick += tickSpace;
    int w = thick, h = length;
    if ( orient == Qt::Horizontal ) {
	w = length;
	h = thick;
    }
    return (tqstyle().tqsizeFromContents(TQStyle::CT_Slider, this,
				     TQSize(w, h)).expandedTo(TQApplication::globalStrut()));
}



/*!
    \reimp
*/

TQSize TQSlider::tqminimumSizeHint() const
{
    TQSize s = tqsizeHint();
    int length = tqstyle().tqpixelMetric(TQStyle::PM_SliderLength, this);
    if ( orient == Qt::Horizontal )
	s.setWidth( length );
    else
	s.setHeight( length );

    return s;
}

/*! \fn void TQSlider::tqsetSizePolicy( TQSizePolicy::SizeType, TQSizePolicy::SizeType, bool )
    \reimp
*/

/*! \reimp */
void TQSlider::tqsetSizePolicy( TQSizePolicy sp )
{
    // ## remove 4.0
    TQWidget::tqsetSizePolicy( sp );
}

/*! \reimp */
TQSizePolicy TQSlider::sizePolicy() const
{
    // ### 4.0 remove this reimplementation
    return TQWidget::tqsizePolicy();
}

/*!
    \property TQSlider::tickmarks
    \brief the tickmark settings for this slider

    The valid values are in \l{TQSlider::TickSetting}. The default is
    \c NoMarks.

    \sa tickInterval
*/

void TQSlider::setTickmarks( TickSetting s )
{
    ticks = s;
    initTicks();
    update();
}


/*!
    \property TQSlider::tickInterval
    \brief the interval between tickmarks

    This is a value interval, not a pixel interval. If it is 0, the
    slider will choose between lineStep() and pageStep(). The initial
    value of tickInterval is 0.

    \sa TQRangeControl::lineStep(), TQRangeControl::pageStep()
*/

void TQSlider::setTickInterval( int i )
{
    tickInt = TQMAX( 0, i );
    update();
}


/*!
    \reimp
*/
void TQSlider::styleChange( TQStyle& old )
{
    TQWidget::styleChange( old );
}

/*!
    \property TQSlider::minValue
    \brief the current minimum value of the slider

    When setting this property, the \l TQSlider::maxValue is adjusted,
    if necessary, to ensure that the range remains valid.

    \sa setRange()
*/
int TQSlider::minValue() const
{
    return TQRangeControl::minValue();
}

/*!
    \property TQSlider::maxValue
    \brief the current maximum value of the slider

    When setting this property, the \l TQSlider::minValue is adjusted,
    if necessary, to ensure that the range remains valid.

    \sa setRange()
*/
int TQSlider::maxValue() const
{
    return TQRangeControl::maxValue();
}

void TQSlider::setMinValue( int minVal )
{
    TQRangeControl::setMinValue( minVal );
}

void TQSlider::setMaxValue( int maxVal )
{
    TQRangeControl::setMaxValue( maxVal );
}

/*!
    \property TQSlider::lineStep
    \brief the current line step

    When setting lineStep, the virtual stepChange() function will be
    called if the new line step is different from the previous
    setting.

    \sa setSteps() TQRangeControl::pageStep() setRange()
*/
int TQSlider::lineStep() const
{
    return TQRangeControl::lineStep();
}

/*!
    \property TQSlider::pageStep
    \brief the current page step

    When setting pageStep, the virtual stepChange() function will be
    called if the new page step is different from the previous
    setting.

    \sa TQRangeControl::setSteps() setLineStep() setRange()
*/

int TQSlider::pageStep() const
{
    return TQRangeControl::pageStep();
}

void TQSlider::setLineStep( int i )
{
    setSteps( i, pageStep() );
}

void TQSlider::setPageStep( int i )
{
    setSteps( lineStep(), i );
}

/*!
    \property TQSlider::value
    \brief the current slider value

    \sa TQRangeControl::value() prevValue()
*/

int TQSlider::value() const
{
    return TQRangeControl::value();
}

#endif
