/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified 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.

   JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../../../../juce_core/basics/juce_StandardHeader.h"


BEGIN_JUCE_NAMESPACE

#include "juce_Slider.h"
#include "../lookandfeel/juce_LookAndFeel.h"
#include "../menus/juce_PopupMenu.h"
#include "../juce_Desktop.h"
#include "../special/juce_BubbleComponent.h"
#include "../../../../juce_core/text/juce_LocalisedStrings.h"


//==============================================================================
class SliderPopupDisplayComponent  : public BubbleComponent
{
public:
    //==============================================================================
    SliderPopupDisplayComponent (Slider* const owner_)
        : owner (owner_),
          font (15.0f, Font::bold)
    {
        setAlwaysOnTop (true);
    }

    ~SliderPopupDisplayComponent()
    {
    }

    void paintContent (Graphics& g, int w, int h)
    {
        const String text (owner->getTextFromValue (owner->getValue()));

        g.setFont (font);
        g.setColour (Colours::black);

        g.drawFittedText (owner->getTextFromValue (owner->getValue()),
                          0, 0, w, h,
                          Justification::centred, 1);
    }

    void getContentSize (int& w, int& h)
    {
        w = font.getStringWidth (owner->getTextFromValue (owner->getValue())) + 18;
        h = (int) (font.getHeight() * 1.6f);
    }

    void updatePosition()
    {
        BubbleComponent::setPosition (owner);
    }

    //==============================================================================
    juce_UseDebuggingNewOperator

private:
    Slider* owner;
    Font font;

    SliderPopupDisplayComponent (const SliderPopupDisplayComponent&);
    const SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&);
};

//==============================================================================
Slider::Slider (const String& name)
  : Component (name),
    currentValue (0.0),
    minimum (0),
    maximum (10),
    interval (0),
    skewFactor (1.0),
    rotaryStart (float_Pi * 1.2f),
    rotaryEnd (float_Pi * 2.8f),
    numDecimalPlaces (7),
    sliderRegionStart (0),
    sliderRegionSize (1),
    style (LinearHorizontal),
    textBoxPos (TextBoxLeft),
    textBoxWidth (80),
    textBoxHeight (20),
    editableText (true),
    doubleClickToValue (false),
    isVelocityBased (false),
    rotaryStop (true),
    sendChangeOnlyOnRelease (false),
    popupDisplayEnabled (false),
    menuEnabled (false),
    menuShown (false),
    valueBox (0),
    incButton (0),
    decButton (0),
    popupDisplay (0),
    parentForPopupDisplay (0)
{
    setWantsKeyboardFocus (false);
    setRepaintsOnMouseActivity (true);

    lookAndFeelChanged();
    updateText();
}

Slider::~Slider()
{
    deleteAndZero (popupDisplay);
    deleteAllChildren();
}

//==============================================================================
void Slider::setSliderStyle (const SliderStyle newStyle)
{
    if (style != newStyle)
    {
        style = newStyle;
        lookAndFeelChanged();
    }
}

void Slider::setRotaryParameters (const float startAngleRadians,
                                  const float endAngleRadians,
                                  const bool stopAtEnd)
{
    // make sure the values are sensible..
    jassert (rotaryStart >= 0 && rotaryEnd >= 0);
    jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f);
    jassert (rotaryStart < rotaryEnd);

    rotaryStart = startAngleRadians;
    rotaryEnd = endAngleRadians;
    rotaryStop = stopAtEnd;
}

void Slider::setVelocityBasedMode (const bool velBased) throw()
{
    isVelocityBased = velBased;
}

void Slider::setSkewFactor (const double factor) throw()
{
    skewFactor = factor;
}

void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition,
                              const bool isReadOnly,
                              const int textEntryBoxWidth,
                              const int textEntryBoxHeight)
{
    textBoxPos = newPosition;
    editableText = ! isReadOnly;
    textBoxWidth = textEntryBoxWidth;
    textBoxHeight = textEntryBoxHeight;

    lookAndFeelChanged();
}

void Slider::setTextBoxIsEditable (const bool shouldBeEditable) throw()
{
    editableText = shouldBeEditable;

    if (valueBox != 0)
        valueBox->setEditable (shouldBeEditable && isEnabled());
}

void Slider::hideTextBox (const bool discardCurrentEditorContents)
{
    if (valueBox != 0)
    {
        valueBox->hideEditor (discardCurrentEditorContents);

        if (discardCurrentEditorContents)
            textChange (true, false);
    }
}

void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease)
{
    sendChangeOnlyOnRelease = onlyNotifyOnRelease;
}

void Slider::setPopupDisplayEnabled (const bool enabled,
                                     Component* const parentComponentToUse)
{
    popupDisplayEnabled = enabled;
    parentForPopupDisplay = parentComponentToUse;
}

//==============================================================================
void Slider::lookAndFeelChanged()
{
    const String previousTextBoxContent (valueBox != 0 ? valueBox->getText()
                                                       : String::empty);

    deleteAllChildren();
    valueBox = 0;

    LookAndFeel& lf = getLookAndFeel();

    if (textBoxPos != NoTextBox)
    {
        addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this));

        valueBox->setWantsKeyboardFocus (false);
        valueBox->setText (previousTextBoxContent, false);

        valueBox->setEditable (editableText && isEnabled());
        valueBox->addTextEditorListener (this);

        if (style == LinearBar)
            valueBox->addMouseListener (this, false);
    }

    if (style == IncDecButtons)
    {
        addAndMakeVisible (incButton = lf.createSliderButton (true));
        incButton->addButtonListener (this);
        incButton->setRepeatSpeed (300, 100, 20);

        addAndMakeVisible (decButton = lf.createSliderButton (false));
        decButton->addButtonListener (this);
        decButton->setRepeatSpeed (300, 100, 20);
    }

    setComponentEffect (lf.getSliderEffect());

    resized();
}

//==============================================================================
void Slider::setRange (const double newMin,
                       const double newMax,
                       const double newInt)
{
    if (minimum != newMin
        || maximum != newMax
        || interval != newInt)
    {
        minimum = newMin;
        maximum = newMax;
        interval = newInt;

        // figure out the number of DPs needed to display all values at this
        // interval setting.
        numDecimalPlaces = 7;

        if (newInt != 0)
        {
            int v = abs ((int) (newInt * 10000000));

            while ((v % 10) == 0)
            {
                --numDecimalPlaces;
                v /= 10;
            }
        }

        setValue (currentValue, false, false);
        updateText();
    }
}

void Slider::setValue (double newValue,
                       const bool sendUpdateMessage,
                       const bool sendMessageSynchronously)
{
    if (interval > 0)
        newValue = minimum + interval * floor ((newValue - minimum) / interval + 0.5);

    if (newValue <= minimum || maximum <= minimum)
        newValue = minimum;
    else if (newValue >= maximum)
        newValue = maximum;

    if (currentValue != newValue)
    {
        if (valueBox != 0)
            valueBox->hideEditor (true);

        currentValue = newValue;
        updateText();
        repaint();

        if (popupDisplay != 0)
        {
            ((SliderPopupDisplayComponent*) popupDisplay)->updatePosition();
            popupDisplay->repaint();
        }

        if (sendUpdateMessage)
        {
            if (sendMessageSynchronously)
                sendSynchronousChangeMessage (this);
            else
                sendChangeMessage (this);

            valueChanged (currentValue);
        }
    }
}

void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled,
                                        const double valueToSetOnDoubleClick)
{
    doubleClickToValue = isDoubleClickEnabled;
    doubleClickReturnValue = valueToSetOnDoubleClick;
}

double Slider::getDoubleClickReturnValue (bool& isEnabled_) const
{
    isEnabled_ = doubleClickToValue;
    return doubleClickReturnValue;
}

void Slider::updateText()
{
    if (valueBox != 0)
        valueBox->setText (getTextFromValue (currentValue), false);
}

void Slider::setTextValueSuffix (const String& suffix)
{
    if (textSuffix != suffix)
    {
        textSuffix = suffix;
        updateText();
    }
}

const String Slider::getTextFromValue (double v)
{
    if (numDecimalPlaces > 0)
        return String (v, numDecimalPlaces) + textSuffix;
    else
        return String (roundDoubleToInt (v)) + textSuffix;
}

double Slider::getValueFromText (const String& text)
{
    String t (text.trimStart());

    if (t.endsWith (textSuffix))
        t = t.substring (0, t.length() - textSuffix.length());

    while (t.startsWithChar (T('+')))
        t = t.substring (1).trimStart();

    return t.initialSectionContainingOnly (T("0123456789.-"))
            .getDoubleValue();
}

double Slider::proportionOfLengthToValue (double proportion)
{
    if (skewFactor != 1.0 && proportion > 0.0)
        proportion = exp (log (proportion) / skewFactor);

    return minimum + (maximum - minimum) * proportion;
}

double Slider::valueToProportionOfLength (double value)
{
    const double n = (value - minimum) / (maximum - minimum);

    return skewFactor == 1.0 ? n : pow (n, skewFactor);
}

double Slider::snapValue (double attemptedValue, const bool)
{
    return attemptedValue;
}

void Slider::startedDragging()
{
}

void Slider::stoppedDragging()
{
}

void Slider::valueChanged (double)
{
}

//==============================================================================
void Slider::enablementChanged()
{
    if (valueBox != 0)
        valueBox->setEditable (isEnabled());

    if (incButton != 0)
        incButton->setEnabled (isEnabled());

    if (decButton != 0)
        decButton->setEnabled (isEnabled());

    repaint();
}

void Slider::setPopupMenuEnabled (const bool menuEnabled_)
{
    menuEnabled = menuEnabled_;
}

//==============================================================================
void Slider::textChange (const bool retPressed,
                         const bool escPressed)
{
    if (valueBox != 0)
    {
        if (retPressed || ! hasKeyboardFocus (true))
        {
            startedDragging();
            setValue (snapValue (getValueFromText (valueBox->getText()), false), true, true);
            stoppedDragging();
        }

        if ((retPressed || escPressed) && hasKeyboardFocus (true))
        {
            grabKeyboardFocus();
            updateText();
        }
    }
}

void Slider::textEditorTextChanged (TextEditor&)
{
    textChange (false, false);
}

void Slider::textEditorReturnKeyPressed (TextEditor&)
{
    textChange (true, false);
}

void Slider::textEditorEscapeKeyPressed (TextEditor&)
{
    textChange (false, true);
}

void Slider::textEditorFocusLost (TextEditor&)
{
    textChange (false, true);
}

void Slider::buttonClicked (Button* button)
{
    if (style == IncDecButtons)
    {
        startedDragging();

        if (button == incButton)
            setValue (snapValue (getValue() + interval, false), true, true);
        else if (button == decButton)
            setValue (snapValue (getValue() - interval, false), true, true);

        stoppedDragging();
    }
}

//==============================================================================
float Slider::getLinearSliderPos()
{
    double sliderPosProportional;

    if (maximum > minimum)
    {
        if (currentValue <= minimum)
        {
            sliderPosProportional = 0.0;
        }
        else if (currentValue >= maximum)
        {
            sliderPosProportional = 1.0;
        }
        else
        {
            sliderPosProportional = valueToProportionOfLength (currentValue);
            jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0);
        }
    }
    else
    {
        sliderPosProportional = 0.5;
    }

    if (style == LinearVertical)
        sliderPosProportional = 1.0 - sliderPosProportional;

    return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize);
}

void Slider::paint (Graphics& g)
{
    if (style != IncDecButtons)
    {
        if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
        {
            const float sliderPos = (float) valueToProportionOfLength (currentValue);
            jassert (sliderPos >= 0 && sliderPos <= 1.0f);

            getLookAndFeel().drawRotarySlider (g,
                                               sliderRect.getX(),
                                               sliderRect.getY(),
                                               sliderRect.getWidth(),
                                               sliderRect.getHeight(),
                                               sliderPos,
                                               rotaryStart, rotaryEnd,
                                               *this);
        }
        else
        {
            getLookAndFeel().drawLinearSlider (g,
                                               sliderRect.getX(),
                                               sliderRect.getY(),
                                               sliderRect.getWidth(),
                                               sliderRect.getHeight(),
                                               getLinearSliderPos(),
                                               style,
                                               *this);
        }

        if (style == LinearBar && valueBox == 0)
        {
            g.setColour (getLookAndFeel().sliderTextBoxOutline);
            g.drawRect (0, 0, getWidth(), getHeight(), 1);
        }
    }
}

void Slider::resized()
{
    int minXSpace = 0;
    int minYSpace = 0;

    if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
        minXSpace = 30;
    else
        minYSpace = 30;

    const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace));
    const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace));

    if (style == LinearBar)
    {
        if (valueBox != 0)
            valueBox->setBounds (0, 0, getWidth(), getHeight());
    }
    else
    {
        if (textBoxPos == NoTextBox)
        {
            sliderRect.setBounds (0, 0, getWidth(), getHeight());
        }
        else if (textBoxPos == TextBoxLeft)
        {
            valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh);
            sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight());
        }
        else if (textBoxPos == TextBoxRight)
        {
            valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh);
            sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight());
        }
        else if (textBoxPos == TextBoxAbove)
        {
            valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh);
            sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh);
        }
        else if (textBoxPos == TextBoxBelow)
        {
            valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh);
            sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh);
        }
    }

    const int indent = getLookAndFeel().getSliderThumbRadius (*this) + 2;

    if (style == LinearHorizontal)
    {
        sliderRegionStart = sliderRect.getX() + indent;
        sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2);

        sliderRect.setBounds (sliderRegionStart, sliderRect.getY(),
                              sliderRegionSize, sliderRect.getHeight());
    }
    else if (style == LinearVertical)
    {
        sliderRegionStart = sliderRect.getY() + indent;
        sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2);

        sliderRect.setBounds (sliderRect.getX(), sliderRegionStart,
                              sliderRect.getWidth(), sliderRegionSize);
    }
    else if (style == LinearBar)
    {
        const int barIndent = 1;
        sliderRegionStart = barIndent;
        sliderRegionSize = getWidth() - barIndent * 2;

        sliderRect.setBounds (sliderRegionStart, barIndent,
                              sliderRegionSize, getHeight() - barIndent * 2);
    }
    else
    {
        sliderRegionStart = 0;
        sliderRegionSize = 100;
    }

    if (style == IncDecButtons)
    {
        Rectangle buttonRect (sliderRect);

        if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
            buttonRect.expand (-2, 0);
        else
            buttonRect.expand (0, -2);

        if (buttonRect.getWidth() > buttonRect.getHeight())
        {
            decButton->setBounds (buttonRect.getX(),
                                  buttonRect.getY(),
                                  buttonRect.getWidth() / 2,
                                  buttonRect.getHeight());

            decButton->setConnectedEdges (Button::ConnectedOnRight);

            incButton->setBounds (buttonRect.getCentreX(),
                                  buttonRect.getY(),
                                  buttonRect.getWidth() / 2,
                                  buttonRect.getHeight());

            incButton->setConnectedEdges (Button::ConnectedOnLeft);
        }
        else
        {
            incButton->setBounds (buttonRect.getX(),
                                  buttonRect.getY(),
                                  buttonRect.getWidth(),
                                  buttonRect.getHeight() / 2);

            incButton->setConnectedEdges (Button::ConnectedOnBottom);

            decButton->setBounds (buttonRect.getX(),
                                  buttonRect.getCentreY(),
                                  buttonRect.getWidth(),
                                  buttonRect.getHeight() / 2);

            decButton->setConnectedEdges (Button::ConnectedOnTop);
        }
    }
}

void Slider::focusOfChildComponentChanged (FocusChangeType)
{
    repaint();
}

void Slider::mouseDown (const MouseEvent& e)
{
    mouseWasHidden = false;

    if (isEnabled())
    {
        if (e.mods.isPopupMenu() && menuEnabled)
        {
            menuShown = true;

            PopupMenu m;
            m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased);
            m.addSeparator();

            if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
            {
                PopupMenu rotaryMenu;
                rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary);
                rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag);
                rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag);

                m.addSubMenu (TRANS ("rotary mode"), rotaryMenu);
            }

            const int r = m.show();

            if (r == 1)
            {
                setVelocityBasedMode (! isVelocityBased);
            }
            else if (r == 2)
            {
                setSliderStyle (Rotary);
            }
            else if (r == 3)
            {
                setSliderStyle (RotaryHorizontalDrag);
            }
            else if (r == 4)
            {
                setSliderStyle (RotaryVerticalDrag);
            }
        }
        else if ((maximum > minimum) && style != IncDecButtons)
        {
            menuShown = false;

            if (valueBox != 0)
                valueBox->hideEditor (true);

            mouseXWhenLastDragged = e.x;
            mouseYWhenLastDragged = e.y;
            lastAngle = rotaryStart + (rotaryEnd - rotaryStart)
                                        * valueToProportionOfLength (currentValue);

            valueWhenLastDragged = currentValue;
            valueOnMouseDown = currentValue;

            if (popupDisplayEnabled)
            {
                SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this);
                popupDisplay = popup;

                if (parentForPopupDisplay != 0)
                {
                    parentForPopupDisplay->addChildComponent (popup);
                }
                else
                {
                    popup->addToDesktop (0);
                }

                popup->updatePosition();
                popup->setVisible (true);
            }

            startedDragging();

            mouseDrag (e);
        }
    }
}

void Slider::mouseUp (const MouseEvent&)
{
    if (isEnabled()
         && (! menuShown)
         && (maximum > minimum)
         && style != IncDecButtons)
    {
        if (sendChangeOnlyOnRelease && valueOnMouseDown != currentValue)
            sendChangeMessage (this);

        stoppedDragging();
        restoreMouseIfHidden();

        deleteAndZero (popupDisplay);
    }
}

void Slider::restoreMouseIfHidden()
{
    if (mouseWasHidden)
    {
        mouseWasHidden = false;

        Component* c = Component::getComponentUnderMouse();

        if (c == 0)
            c = this;

        c->enableUnboundedMouseMovement (false);

        int x = getScreenX() + getWidth() / 2;
        int y = getScreenY() + getHeight() / 2;

        if (style == LinearHorizontal || style == LinearBar)
            x = getScreenX() + (int) getLinearSliderPos();
        else if (style == LinearVertical)
            y = getScreenY() + (int) getLinearSliderPos();

        Desktop::setMousePosition (x, y);
    }
}

void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
{
    if (isEnabled()
         && style != IncDecButtons
         && style != Rotary
         && isVelocityBased == modifiers.isAnyModifierKeyDown())
    {
        restoreMouseIfHidden();
    }
}

static double smallestAngleBetween (double a1, double a2)
{
    return jmin (fabs (a1 - a2),
                 fabs (a1 + double_Pi * 2.0 - a2),
                 fabs (a2 + double_Pi * 2.0 - a1));
}

void Slider::mouseDrag (const MouseEvent& e)
{
    if (isEnabled()
         && (! menuShown)
         && (maximum > minimum)
         && style != IncDecButtons)
    {
        if (style == Rotary)
        {
            int dx = e.x - sliderRect.getCentreX();
            int dy = e.y - sliderRect.getCentreY();

            if (dx * dx + dy * dy > 25)
            {
                double angle = atan2 ((double) dx, (double) -dy);
                while (angle < 0.0)
                    angle += double_Pi * 2.0;

                if (rotaryStop && ! e.mouseWasClicked())
                {
                    if (fabs (angle - lastAngle) > double_Pi * 1.5)
                    {
                        if (angle >= lastAngle)
                            angle -= double_Pi * 2.0;
                        else
                            angle += double_Pi * 2.0;
                    }

                    if (angle >= lastAngle)
                        angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd));
                    else
                        angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd));
                }
                else
                {
                    while (angle < rotaryStart)
                        angle += double_Pi * 2.0;

                    if (angle > rotaryEnd)
                    {
                        if (smallestAngleBetween (angle, rotaryStart) <= smallestAngleBetween (angle, rotaryEnd))
                            angle = rotaryStart;
                        else
                            angle = rotaryEnd;
                    }
                }

                const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart);

                valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion));

                lastAngle = angle;
            }
        }
        else
        {
            if (style == LinearBar && e.mouseWasClicked())
                return;

            if (isVelocityBased == e.mods.isAnyModifierKeyDown()
                || ((maximum - minimum) / sliderRegionSize < interval))
            {
                int mousePos = (style == LinearHorizontal || style == LinearBar || style == RotaryHorizontalDrag)
                                    ? e.x : e.y;

                double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;

                if (style == LinearHorizontal || style == LinearVertical || style == LinearBar)
                {
                    if (style == LinearVertical)
                        scaledMousePos = 1.0 - scaledMousePos;

                    valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos));
                }
                else
                {
                    const int mouseDiff = (style == RotaryHorizontalDrag)
                                            ? e.getDistanceFromDragStartX()
                                            : -e.getDistanceFromDragStartY();

                    // how many pixels you need to drag the mouse in rotarylineardrag mode to go full-scale
                    const double fullLengthForLinearDrag = 250.0;

                    double newPos = valueToProportionOfLength (valueOnMouseDown)
                                       + mouseDiff * (1.0 / fullLengthForLinearDrag);

                    valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos));
                }
            }
            else
            {
                int mouseDiff = (style == LinearHorizontal || style == LinearBar || style == RotaryHorizontalDrag)
                                    ? e.x - mouseXWhenLastDragged
                                    : e.y - mouseYWhenLastDragged;

                const double maxSpeed = jmax (200, sliderRegionSize);
                double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff));

                if (speed != 0)
                {
                    speed = 0.2 * (1.0 + sin (double_Pi * (1.5 + jmax (0.0, speed - 1.0) / maxSpeed)));

                    if (mouseDiff < 0)
                        speed = -speed;

                    if (style == LinearVertical || style == RotaryVerticalDrag)
                        speed = -speed;

                    const double currentPos = valueToProportionOfLength (valueWhenLastDragged);

                    valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed));

                    e.component->enableUnboundedMouseMovement (true, false);
                    mouseWasHidden = true;
                }
            }
        }

        valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);

        setValue (snapValue (valueWhenLastDragged, true),
                  ! sendChangeOnlyOnRelease, false);

        mouseXWhenLastDragged = e.x;
        mouseYWhenLastDragged = e.y;
    }
}

void Slider::mouseDoubleClick (const MouseEvent&)
{
    if (doubleClickToValue
         && isEnabled()
         && style != IncDecButtons
         && minimum <= doubleClickReturnValue
         && maximum >= doubleClickReturnValue)
    {
        startedDragging();
        setValue (doubleClickReturnValue, true, true);
        stoppedDragging();
    }
}

void Slider::mouseWheelMove (const MouseEvent&, float increment)
{
    if (isEnabled()
         && (maximum > minimum)
         && ! isMouseButtonDownAnywhere())
    {
        if (valueBox != 0)
            valueBox->hideEditor (false);

        const double proportionDelta = increment * 0.15f;
        const double currentPos = valueToProportionOfLength (currentValue);
        const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta));

        double delta = jmax (fabs (newValue - currentValue), interval);

        if (increment < 0)
            delta = -delta;

        startedDragging();

        setValue (snapValue (currentValue + delta, false), true, true);

        stoppedDragging();
    }
}


END_JUCE_NAMESPACE
