/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. 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. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../../core/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_ScrollBar.h" #include "../lookandfeel/juce_LookAndFeel.h" #include "../buttons/juce_Button.h" //============================================================================== class ScrollbarButton : public Button { public: int direction; ScrollbarButton (const int direction_, ScrollBar& owner_) throw() : Button (String::empty), direction (direction_), owner (owner_) { setWantsKeyboardFocus (false); } ~ScrollbarButton() { } void paintButton (Graphics& g, bool isMouseOver, bool isMouseDown) { getLookAndFeel() .drawScrollbarButton (g, owner, getWidth(), getHeight(), direction, owner.isVertical(), isMouseOver, isMouseDown); } void clicked() { owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1); } juce_UseDebuggingNewOperator private: ScrollBar& owner; ScrollbarButton (const ScrollbarButton&); ScrollbarButton& operator= (const ScrollbarButton&); }; //============================================================================== ScrollBar::ScrollBar (const bool vertical_, const bool buttonsAreVisible) : minimum (0.0), maximum (1.0), rangeStart (0.0), rangeSize (0.1), singleStepSize (0.1), thumbAreaStart (0), thumbAreaSize (0), thumbStart (0), thumbSize (0), initialDelayInMillisecs (100), repeatDelayInMillisecs (50), minimumDelayInMillisecs (10), vertical (vertical_), isDraggingThumb (false), alwaysVisible (false), upButton (0), downButton (0) { setButtonVisibility (buttonsAreVisible); setRepaintsOnMouseActivity (true); setFocusContainer (true); } ScrollBar::~ScrollBar() { deleteAllChildren(); } //============================================================================== void ScrollBar::setRangeLimits (const double newMinimum, const double newMaximum) throw() { minimum = newMinimum; maximum = newMaximum; jassert (maximum >= minimum); // these can't be the wrong way round! setCurrentRangeStart (rangeStart); updateThumbPosition(); } void ScrollBar::setCurrentRange (double newStart, double newSize) throw() { newSize = jlimit (0.0, maximum - minimum, newSize); newStart = jlimit (minimum, maximum - newSize, newStart); if (rangeStart != newStart || rangeSize != newSize) { rangeStart = newStart; rangeSize = newSize; updateThumbPosition(); triggerAsyncUpdate(); } } void ScrollBar::setCurrentRangeStart (double newStart) throw() { setCurrentRange (newStart, rangeSize); } void ScrollBar::setSingleStepSize (const double newSingleStepSize) throw() { singleStepSize = newSingleStepSize; } void ScrollBar::moveScrollbarInSteps (const int howManySteps) throw() { setCurrentRangeStart (rangeStart + howManySteps * singleStepSize); } void ScrollBar::moveScrollbarInPages (const int howManyPages) throw() { setCurrentRangeStart (rangeStart + howManyPages * rangeSize); } void ScrollBar::scrollToTop() throw() { setCurrentRangeStart (minimum); } void ScrollBar::scrollToBottom() throw() { setCurrentRangeStart (maximum - rangeSize); } void ScrollBar::setButtonRepeatSpeed (const int initialDelayInMillisecs_, const int repeatDelayInMillisecs_, const int minimumDelayInMillisecs_) throw() { initialDelayInMillisecs = initialDelayInMillisecs_; repeatDelayInMillisecs = repeatDelayInMillisecs_; minimumDelayInMillisecs = minimumDelayInMillisecs_; if (upButton != 0) { upButton->setRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); downButton->setRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); } } //============================================================================== void ScrollBar::addListener (ScrollBarListener* const listener) throw() { listeners.add (listener); } void ScrollBar::removeListener (ScrollBarListener* const listener) throw() { listeners.remove (listener); } void ScrollBar::handleAsyncUpdate() { listeners.call (&ScrollBarListener::scrollBarMoved, this, rangeStart); } //============================================================================== void ScrollBar::updateThumbPosition() throw() { int newThumbSize = roundToInt ((maximum > minimum) ? (rangeSize * thumbAreaSize) / (maximum - minimum) : thumbAreaSize); if (newThumbSize < getLookAndFeel().getMinimumScrollbarThumbSize (*this)) newThumbSize = jmin (getLookAndFeel().getMinimumScrollbarThumbSize (*this), thumbAreaSize - 1); if (newThumbSize > thumbAreaSize) newThumbSize = thumbAreaSize; int newThumbStart = thumbAreaStart; if (maximum - minimum > rangeSize) newThumbStart += roundToInt (((rangeStart - minimum) * (thumbAreaSize - newThumbSize)) / ((maximum - minimum) - rangeSize)); setVisible (alwaysVisible || (maximum - minimum > rangeSize && rangeSize > 0.0)); if (thumbStart != newThumbStart || thumbSize != newThumbSize) { const int repaintStart = jmin (thumbStart, newThumbStart) - 4; const int repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart; if (vertical) repaint (0, repaintStart, getWidth(), repaintSize); else repaint (repaintStart, 0, repaintSize, getHeight()); thumbStart = newThumbStart; thumbSize = newThumbSize; } } void ScrollBar::setOrientation (const bool shouldBeVertical) throw() { if (vertical != shouldBeVertical) { vertical = shouldBeVertical; if (upButton != 0) { ((ScrollbarButton*) upButton)->direction = (vertical) ? 0 : 3; ((ScrollbarButton*) downButton)->direction = (vertical) ? 2 : 1; } updateThumbPosition(); } } void ScrollBar::setButtonVisibility (const bool buttonsAreVisible) { deleteAndZero (upButton); deleteAndZero (downButton); if (buttonsAreVisible) { addAndMakeVisible (upButton = new ScrollbarButton ((vertical) ? 0 : 3, *this)); addAndMakeVisible (downButton = new ScrollbarButton ((vertical) ? 2 : 1, *this)); setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs); } updateThumbPosition(); } void ScrollBar::setAutoHide (const bool shouldHideWhenFullRange) { alwaysVisible = ! shouldHideWhenFullRange; updateThumbPosition(); } //============================================================================== void ScrollBar::paint (Graphics& g) { if (thumbAreaSize > 0) { LookAndFeel& lf = getLookAndFeel(); const int thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this)) ? thumbSize : 0; if (vertical) { lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize, vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown()); } else { lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(), vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown()); } } } void ScrollBar::lookAndFeelChanged() { setComponentEffect (getLookAndFeel().getScrollbarEffect()); } void ScrollBar::resized() { const int length = ((vertical) ? getHeight() : getWidth()); const int buttonSize = (upButton != 0) ? jmin (getLookAndFeel().getScrollbarButtonSize (*this), (length >> 1)) : 0; if (length < 32 + getLookAndFeel().getMinimumScrollbarThumbSize (*this)) { thumbAreaStart = length >> 1; thumbAreaSize = 0; } else { thumbAreaStart = buttonSize; thumbAreaSize = length - (buttonSize << 1); } if (upButton != 0) { if (vertical) { upButton->setBounds (0, 0, getWidth(), buttonSize); downButton->setBounds (0, thumbAreaStart + thumbAreaSize, getWidth(), buttonSize); } else { upButton->setBounds (0, 0, buttonSize, getHeight()); downButton->setBounds (thumbAreaStart + thumbAreaSize, 0, buttonSize, getHeight()); } } updateThumbPosition(); } void ScrollBar::mouseDown (const MouseEvent& e) { isDraggingThumb = false; lastMousePos = vertical ? e.y : e.x; dragStartMousePos = lastMousePos; dragStartRange = rangeStart; if (dragStartMousePos < thumbStart) { moveScrollbarInPages (-1); startTimer (400); } else if (dragStartMousePos >= thumbStart + thumbSize) { moveScrollbarInPages (1); startTimer (400); } else { isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this)) && (thumbAreaSize > thumbSize); } } void ScrollBar::mouseDrag (const MouseEvent& e) { if (isDraggingThumb) { const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos; setCurrentRangeStart (dragStartRange + deltaPixels * ((maximum - minimum) - rangeSize) / (thumbAreaSize - thumbSize)); } else { lastMousePos = (vertical) ? e.y : e.x; } } void ScrollBar::mouseUp (const MouseEvent&) { isDraggingThumb = false; stopTimer(); repaint(); } void ScrollBar::mouseWheelMove (const MouseEvent&, float wheelIncrementX, float wheelIncrementY) { float increment = vertical ? wheelIncrementY : wheelIncrementX; if (increment < 0) increment = jmin (increment * 10.0f, -1.0f); else if (increment > 0) increment = jmax (increment * 10.0f, 1.0f); setCurrentRangeStart (rangeStart - singleStepSize * increment); } void ScrollBar::timerCallback() { if (isMouseButtonDown()) { startTimer (40); if (lastMousePos < thumbStart) setCurrentRangeStart (rangeStart - rangeSize); else if (lastMousePos > thumbStart + thumbSize) setCurrentRangeStart (rangeStart + rangeSize); } else { stopTimer(); } } bool ScrollBar::keyPressed (const KeyPress& key) { if (! isVisible()) return false; if (key.isKeyCode (KeyPress::upKey) || key.isKeyCode (KeyPress::leftKey)) moveScrollbarInSteps (-1); else if (key.isKeyCode (KeyPress::downKey) || key.isKeyCode (KeyPress::rightKey)) moveScrollbarInSteps (1); else if (key.isKeyCode (KeyPress::pageUpKey)) moveScrollbarInPages (-1); else if (key.isKeyCode (KeyPress::pageDownKey)) moveScrollbarInPages (1); else if (key.isKeyCode (KeyPress::homeKey)) scrollToTop(); else if (key.isKeyCode (KeyPress::endKey)) scrollToBottom(); else return false; return true; } END_JUCE_NAMESPACE