The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1440 lines
46KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_Slider.h"
  21. #include "../lookandfeel/juce_LookAndFeel.h"
  22. #include "../menus/juce_PopupMenu.h"
  23. #include "../juce_Desktop.h"
  24. #include "../special/juce_BubbleComponent.h"
  25. #include "../mouse/juce_MouseInputSource.h"
  26. #include "../../../text/juce_LocalisedStrings.h"
  27. //==============================================================================
  28. class SliderPopupDisplayComponent : public BubbleComponent
  29. {
  30. public:
  31. //==============================================================================
  32. SliderPopupDisplayComponent (Slider* const owner_)
  33. : owner (owner_),
  34. font (15.0f, Font::bold)
  35. {
  36. setAlwaysOnTop (true);
  37. }
  38. ~SliderPopupDisplayComponent()
  39. {
  40. }
  41. void paintContent (Graphics& g, int w, int h)
  42. {
  43. g.setFont (font);
  44. g.setColour (Colours::black);
  45. g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1);
  46. }
  47. void getContentSize (int& w, int& h)
  48. {
  49. w = font.getStringWidth (text) + 18;
  50. h = (int) (font.getHeight() * 1.6f);
  51. }
  52. void updatePosition (const String& newText)
  53. {
  54. if (text != newText)
  55. {
  56. text = newText;
  57. repaint();
  58. }
  59. BubbleComponent::setPosition (owner);
  60. }
  61. //==============================================================================
  62. juce_UseDebuggingNewOperator
  63. private:
  64. Slider* owner;
  65. Font font;
  66. String text;
  67. SliderPopupDisplayComponent (const SliderPopupDisplayComponent&);
  68. SliderPopupDisplayComponent& operator= (const SliderPopupDisplayComponent&);
  69. };
  70. //==============================================================================
  71. Slider::Slider (const String& name)
  72. : Component (name),
  73. lastCurrentValue (0),
  74. lastValueMin (0),
  75. lastValueMax (0),
  76. minimum (0),
  77. maximum (10),
  78. interval (0),
  79. skewFactor (1.0),
  80. velocityModeSensitivity (1.0),
  81. velocityModeOffset (0.0),
  82. velocityModeThreshold (1),
  83. rotaryStart (float_Pi * 1.2f),
  84. rotaryEnd (float_Pi * 2.8f),
  85. numDecimalPlaces (7),
  86. sliderRegionStart (0),
  87. sliderRegionSize (1),
  88. sliderBeingDragged (-1),
  89. pixelsForFullDragExtent (250),
  90. style (LinearHorizontal),
  91. textBoxPos (TextBoxLeft),
  92. textBoxWidth (80),
  93. textBoxHeight (20),
  94. incDecButtonMode (incDecButtonsNotDraggable),
  95. editableText (true),
  96. doubleClickToValue (false),
  97. isVelocityBased (false),
  98. userKeyOverridesVelocity (true),
  99. rotaryStop (true),
  100. incDecButtonsSideBySide (false),
  101. sendChangeOnlyOnRelease (false),
  102. popupDisplayEnabled (false),
  103. menuEnabled (false),
  104. menuShown (false),
  105. scrollWheelEnabled (true),
  106. snapsToMousePos (true),
  107. valueBox (0),
  108. incButton (0),
  109. decButton (0),
  110. popupDisplay (0),
  111. parentForPopupDisplay (0)
  112. {
  113. setWantsKeyboardFocus (false);
  114. setRepaintsOnMouseActivity (true);
  115. lookAndFeelChanged();
  116. updateText();
  117. currentValue.addListener (this);
  118. valueMin.addListener (this);
  119. valueMax.addListener (this);
  120. }
  121. Slider::~Slider()
  122. {
  123. currentValue.removeListener (this);
  124. valueMin.removeListener (this);
  125. valueMax.removeListener (this);
  126. popupDisplay = 0;
  127. deleteAllChildren();
  128. }
  129. //==============================================================================
  130. void Slider::handleAsyncUpdate()
  131. {
  132. cancelPendingUpdate();
  133. Component::BailOutChecker checker (this);
  134. listeners.callChecked (checker, &SliderListener::sliderValueChanged, this); // (can't use Slider::Listener due to idiotic VC2005 bug)
  135. }
  136. void Slider::sendDragStart()
  137. {
  138. startedDragging();
  139. Component::BailOutChecker checker (this);
  140. listeners.callChecked (checker, &SliderListener::sliderDragStarted, this);
  141. }
  142. void Slider::sendDragEnd()
  143. {
  144. stoppedDragging();
  145. sliderBeingDragged = -1;
  146. Component::BailOutChecker checker (this);
  147. listeners.callChecked (checker, &SliderListener::sliderDragEnded, this);
  148. }
  149. void Slider::addListener (Listener* const listener)
  150. {
  151. listeners.add (listener);
  152. }
  153. void Slider::removeListener (Listener* const listener)
  154. {
  155. listeners.remove (listener);
  156. }
  157. //==============================================================================
  158. void Slider::setSliderStyle (const SliderStyle newStyle)
  159. {
  160. if (style != newStyle)
  161. {
  162. style = newStyle;
  163. repaint();
  164. lookAndFeelChanged();
  165. }
  166. }
  167. void Slider::setRotaryParameters (const float startAngleRadians,
  168. const float endAngleRadians,
  169. const bool stopAtEnd)
  170. {
  171. // make sure the values are sensible..
  172. jassert (rotaryStart >= 0 && rotaryEnd >= 0);
  173. jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f);
  174. jassert (rotaryStart < rotaryEnd);
  175. rotaryStart = startAngleRadians;
  176. rotaryEnd = endAngleRadians;
  177. rotaryStop = stopAtEnd;
  178. }
  179. void Slider::setVelocityBasedMode (const bool velBased)
  180. {
  181. isVelocityBased = velBased;
  182. }
  183. void Slider::setVelocityModeParameters (const double sensitivity,
  184. const int threshold,
  185. const double offset,
  186. const bool userCanPressKeyToSwapMode)
  187. {
  188. jassert (threshold >= 0);
  189. jassert (sensitivity > 0);
  190. jassert (offset >= 0);
  191. velocityModeSensitivity = sensitivity;
  192. velocityModeOffset = offset;
  193. velocityModeThreshold = threshold;
  194. userKeyOverridesVelocity = userCanPressKeyToSwapMode;
  195. }
  196. void Slider::setSkewFactor (const double factor)
  197. {
  198. skewFactor = factor;
  199. }
  200. void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
  201. {
  202. if (maximum > minimum)
  203. skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum)
  204. / (maximum - minimum));
  205. }
  206. void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag)
  207. {
  208. jassert (distanceForFullScaleDrag > 0);
  209. pixelsForFullDragExtent = distanceForFullScaleDrag;
  210. }
  211. void Slider::setIncDecButtonsMode (const IncDecButtonMode mode)
  212. {
  213. if (incDecButtonMode != mode)
  214. {
  215. incDecButtonMode = mode;
  216. lookAndFeelChanged();
  217. }
  218. }
  219. void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition,
  220. const bool isReadOnly,
  221. const int textEntryBoxWidth,
  222. const int textEntryBoxHeight)
  223. {
  224. if (textBoxPos != newPosition
  225. || editableText != (! isReadOnly)
  226. || textBoxWidth != textEntryBoxWidth
  227. || textBoxHeight != textEntryBoxHeight)
  228. {
  229. textBoxPos = newPosition;
  230. editableText = ! isReadOnly;
  231. textBoxWidth = textEntryBoxWidth;
  232. textBoxHeight = textEntryBoxHeight;
  233. repaint();
  234. lookAndFeelChanged();
  235. }
  236. }
  237. void Slider::setTextBoxIsEditable (const bool shouldBeEditable)
  238. {
  239. editableText = shouldBeEditable;
  240. if (valueBox != 0)
  241. valueBox->setEditable (shouldBeEditable && isEnabled());
  242. }
  243. void Slider::showTextBox()
  244. {
  245. jassert (editableText); // this should probably be avoided in read-only sliders.
  246. if (valueBox != 0)
  247. valueBox->showEditor();
  248. }
  249. void Slider::hideTextBox (const bool discardCurrentEditorContents)
  250. {
  251. if (valueBox != 0)
  252. {
  253. valueBox->hideEditor (discardCurrentEditorContents);
  254. if (discardCurrentEditorContents)
  255. updateText();
  256. }
  257. }
  258. void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease)
  259. {
  260. sendChangeOnlyOnRelease = onlyNotifyOnRelease;
  261. }
  262. void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse)
  263. {
  264. snapsToMousePos = shouldSnapToMouse;
  265. }
  266. void Slider::setPopupDisplayEnabled (const bool enabled,
  267. Component* const parentComponentToUse)
  268. {
  269. popupDisplayEnabled = enabled;
  270. parentForPopupDisplay = parentComponentToUse;
  271. }
  272. //==============================================================================
  273. void Slider::colourChanged()
  274. {
  275. lookAndFeelChanged();
  276. }
  277. void Slider::lookAndFeelChanged()
  278. {
  279. const String previousTextBoxContent (valueBox != 0 ? valueBox->getText()
  280. : getTextFromValue (currentValue.getValue()));
  281. deleteAllChildren();
  282. valueBox = 0;
  283. LookAndFeel& lf = getLookAndFeel();
  284. if (textBoxPos != NoTextBox)
  285. {
  286. addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this));
  287. valueBox->setWantsKeyboardFocus (false);
  288. valueBox->setText (previousTextBoxContent, false);
  289. valueBox->setEditable (editableText && isEnabled());
  290. valueBox->addListener (this);
  291. if (style == LinearBar)
  292. valueBox->addMouseListener (this, false);
  293. valueBox->setTooltip (getTooltip());
  294. }
  295. if (style == IncDecButtons)
  296. {
  297. addAndMakeVisible (incButton = lf.createSliderButton (true));
  298. incButton->addButtonListener (this);
  299. addAndMakeVisible (decButton = lf.createSliderButton (false));
  300. decButton->addButtonListener (this);
  301. if (incDecButtonMode != incDecButtonsNotDraggable)
  302. {
  303. incButton->addMouseListener (this, false);
  304. decButton->addMouseListener (this, false);
  305. }
  306. else
  307. {
  308. incButton->setRepeatSpeed (300, 100, 20);
  309. incButton->addMouseListener (decButton, false);
  310. decButton->setRepeatSpeed (300, 100, 20);
  311. decButton->addMouseListener (incButton, false);
  312. }
  313. incButton->setTooltip (getTooltip());
  314. decButton->setTooltip (getTooltip());
  315. }
  316. setComponentEffect (lf.getSliderEffect());
  317. resized();
  318. repaint();
  319. }
  320. //==============================================================================
  321. void Slider::setRange (const double newMin,
  322. const double newMax,
  323. const double newInt)
  324. {
  325. if (minimum != newMin
  326. || maximum != newMax
  327. || interval != newInt)
  328. {
  329. minimum = newMin;
  330. maximum = newMax;
  331. interval = newInt;
  332. // figure out the number of DPs needed to display all values at this
  333. // interval setting.
  334. numDecimalPlaces = 7;
  335. if (newInt != 0)
  336. {
  337. int v = abs ((int) (newInt * 10000000));
  338. while ((v % 10) == 0)
  339. {
  340. --numDecimalPlaces;
  341. v /= 10;
  342. }
  343. }
  344. // keep the current values inside the new range..
  345. if (style != TwoValueHorizontal && style != TwoValueVertical)
  346. {
  347. setValue (getValue(), false, false);
  348. }
  349. else
  350. {
  351. setMinValue (getMinValue(), false, false);
  352. setMaxValue (getMaxValue(), false, false);
  353. }
  354. updateText();
  355. }
  356. }
  357. void Slider::triggerChangeMessage (const bool synchronous)
  358. {
  359. if (synchronous)
  360. handleAsyncUpdate();
  361. else
  362. triggerAsyncUpdate();
  363. valueChanged();
  364. }
  365. void Slider::valueChanged (Value& value)
  366. {
  367. if (value.refersToSameSourceAs (currentValue))
  368. {
  369. if (style != TwoValueHorizontal && style != TwoValueVertical)
  370. setValue (currentValue.getValue(), false, false);
  371. }
  372. else if (value.refersToSameSourceAs (valueMin))
  373. setMinValue (valueMin.getValue(), false, false, true);
  374. else if (value.refersToSameSourceAs (valueMax))
  375. setMaxValue (valueMax.getValue(), false, false, true);
  376. }
  377. double Slider::getValue() const
  378. {
  379. // for a two-value style slider, you should use the getMinValue() and getMaxValue()
  380. // methods to get the two values.
  381. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  382. return currentValue.getValue();
  383. }
  384. void Slider::setValue (double newValue,
  385. const bool sendUpdateMessage,
  386. const bool sendMessageSynchronously)
  387. {
  388. // for a two-value style slider, you should use the setMinValue() and setMaxValue()
  389. // methods to set the two values.
  390. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  391. newValue = constrainedValue (newValue);
  392. if (style == ThreeValueHorizontal || style == ThreeValueVertical)
  393. {
  394. jassert ((double) valueMin.getValue() <= (double) valueMax.getValue());
  395. newValue = jlimit ((double) valueMin.getValue(),
  396. (double) valueMax.getValue(),
  397. newValue);
  398. }
  399. if (newValue != lastCurrentValue)
  400. {
  401. if (valueBox != 0)
  402. valueBox->hideEditor (true);
  403. lastCurrentValue = newValue;
  404. currentValue = newValue;
  405. updateText();
  406. repaint();
  407. if (popupDisplay != 0)
  408. {
  409. static_cast <SliderPopupDisplayComponent*> (static_cast <Component*> (popupDisplay))
  410. ->updatePosition (getTextFromValue (newValue));
  411. popupDisplay->repaint();
  412. }
  413. if (sendUpdateMessage)
  414. triggerChangeMessage (sendMessageSynchronously);
  415. }
  416. }
  417. double Slider::getMinValue() const
  418. {
  419. // The minimum value only applies to sliders that are in two- or three-value mode.
  420. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  421. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  422. return valueMin.getValue();
  423. }
  424. double Slider::getMaxValue() const
  425. {
  426. // The maximum value only applies to sliders that are in two- or three-value mode.
  427. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  428. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  429. return valueMax.getValue();
  430. }
  431. void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues)
  432. {
  433. // The minimum value only applies to sliders that are in two- or three-value mode.
  434. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  435. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  436. newValue = constrainedValue (newValue);
  437. if (style == TwoValueHorizontal || style == TwoValueVertical)
  438. {
  439. if (allowNudgingOfOtherValues && newValue > (double) valueMax.getValue())
  440. setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously);
  441. newValue = jmin ((double) valueMax.getValue(), newValue);
  442. }
  443. else
  444. {
  445. if (allowNudgingOfOtherValues && newValue > lastCurrentValue)
  446. setValue (newValue, sendUpdateMessage, sendMessageSynchronously);
  447. newValue = jmin (lastCurrentValue, newValue);
  448. }
  449. if (lastValueMin != newValue)
  450. {
  451. lastValueMin = newValue;
  452. valueMin = newValue;
  453. repaint();
  454. if (popupDisplay != 0)
  455. {
  456. static_cast <SliderPopupDisplayComponent*> (static_cast <Component*> (popupDisplay))
  457. ->updatePosition (getTextFromValue (newValue));
  458. popupDisplay->repaint();
  459. }
  460. if (sendUpdateMessage)
  461. triggerChangeMessage (sendMessageSynchronously);
  462. }
  463. }
  464. void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues)
  465. {
  466. // The maximum value only applies to sliders that are in two- or three-value mode.
  467. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  468. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  469. newValue = constrainedValue (newValue);
  470. if (style == TwoValueHorizontal || style == TwoValueVertical)
  471. {
  472. if (allowNudgingOfOtherValues && newValue < (double) valueMin.getValue())
  473. setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously);
  474. newValue = jmax ((double) valueMin.getValue(), newValue);
  475. }
  476. else
  477. {
  478. if (allowNudgingOfOtherValues && newValue < lastCurrentValue)
  479. setValue (newValue, sendUpdateMessage, sendMessageSynchronously);
  480. newValue = jmax (lastCurrentValue, newValue);
  481. }
  482. if (lastValueMax != newValue)
  483. {
  484. lastValueMax = newValue;
  485. valueMax = newValue;
  486. repaint();
  487. if (popupDisplay != 0)
  488. {
  489. static_cast <SliderPopupDisplayComponent*> (static_cast <Component*> (popupDisplay))
  490. ->updatePosition (getTextFromValue (valueMax.getValue()));
  491. popupDisplay->repaint();
  492. }
  493. if (sendUpdateMessage)
  494. triggerChangeMessage (sendMessageSynchronously);
  495. }
  496. }
  497. void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled,
  498. const double valueToSetOnDoubleClick)
  499. {
  500. doubleClickToValue = isDoubleClickEnabled;
  501. doubleClickReturnValue = valueToSetOnDoubleClick;
  502. }
  503. double Slider::getDoubleClickReturnValue (bool& isEnabled_) const
  504. {
  505. isEnabled_ = doubleClickToValue;
  506. return doubleClickReturnValue;
  507. }
  508. void Slider::updateText()
  509. {
  510. if (valueBox != 0)
  511. valueBox->setText (getTextFromValue (currentValue.getValue()), false);
  512. }
  513. void Slider::setTextValueSuffix (const String& suffix)
  514. {
  515. if (textSuffix != suffix)
  516. {
  517. textSuffix = suffix;
  518. updateText();
  519. }
  520. }
  521. const String Slider::getTextValueSuffix() const
  522. {
  523. return textSuffix;
  524. }
  525. const String Slider::getTextFromValue (double v)
  526. {
  527. if (getNumDecimalPlacesToDisplay() > 0)
  528. return String (v, getNumDecimalPlacesToDisplay()) + getTextValueSuffix();
  529. else
  530. return String (roundToInt (v)) + getTextValueSuffix();
  531. }
  532. double Slider::getValueFromText (const String& text)
  533. {
  534. String t (text.trimStart());
  535. if (t.endsWith (textSuffix))
  536. t = t.substring (0, t.length() - textSuffix.length());
  537. while (t.startsWithChar ('+'))
  538. t = t.substring (1).trimStart();
  539. return t.initialSectionContainingOnly ("0123456789.,-")
  540. .getDoubleValue();
  541. }
  542. double Slider::proportionOfLengthToValue (double proportion)
  543. {
  544. if (skewFactor != 1.0 && proportion > 0.0)
  545. proportion = exp (log (proportion) / skewFactor);
  546. return minimum + (maximum - minimum) * proportion;
  547. }
  548. double Slider::valueToProportionOfLength (double value)
  549. {
  550. const double n = (value - minimum) / (maximum - minimum);
  551. return skewFactor == 1.0 ? n : pow (n, skewFactor);
  552. }
  553. double Slider::snapValue (double attemptedValue, const bool)
  554. {
  555. return attemptedValue;
  556. }
  557. //==============================================================================
  558. void Slider::startedDragging()
  559. {
  560. }
  561. void Slider::stoppedDragging()
  562. {
  563. }
  564. void Slider::valueChanged()
  565. {
  566. }
  567. //==============================================================================
  568. void Slider::enablementChanged()
  569. {
  570. repaint();
  571. }
  572. void Slider::setPopupMenuEnabled (const bool menuEnabled_)
  573. {
  574. menuEnabled = menuEnabled_;
  575. }
  576. void Slider::setScrollWheelEnabled (const bool enabled)
  577. {
  578. scrollWheelEnabled = enabled;
  579. }
  580. //==============================================================================
  581. void Slider::labelTextChanged (Label* label)
  582. {
  583. const double newValue = snapValue (getValueFromText (label->getText()), false);
  584. if (newValue != (double) currentValue.getValue())
  585. {
  586. sendDragStart();
  587. setValue (newValue, true, true);
  588. sendDragEnd();
  589. }
  590. updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this.
  591. }
  592. void Slider::buttonClicked (Button* button)
  593. {
  594. if (style == IncDecButtons)
  595. {
  596. sendDragStart();
  597. if (button == incButton)
  598. setValue (snapValue (getValue() + interval, false), true, true);
  599. else if (button == decButton)
  600. setValue (snapValue (getValue() - interval, false), true, true);
  601. sendDragEnd();
  602. }
  603. }
  604. //==============================================================================
  605. double Slider::constrainedValue (double value) const
  606. {
  607. if (interval > 0)
  608. value = minimum + interval * std::floor ((value - minimum) / interval + 0.5);
  609. if (value <= minimum || maximum <= minimum)
  610. value = minimum;
  611. else if (value >= maximum)
  612. value = maximum;
  613. return value;
  614. }
  615. float Slider::getLinearSliderPos (const double value)
  616. {
  617. double sliderPosProportional;
  618. if (maximum > minimum)
  619. {
  620. if (value < minimum)
  621. {
  622. sliderPosProportional = 0.0;
  623. }
  624. else if (value > maximum)
  625. {
  626. sliderPosProportional = 1.0;
  627. }
  628. else
  629. {
  630. sliderPosProportional = valueToProportionOfLength (value);
  631. jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0);
  632. }
  633. }
  634. else
  635. {
  636. sliderPosProportional = 0.5;
  637. }
  638. if (isVertical() || style == IncDecButtons)
  639. sliderPosProportional = 1.0 - sliderPosProportional;
  640. return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize);
  641. }
  642. bool Slider::isHorizontal() const
  643. {
  644. return style == LinearHorizontal
  645. || style == LinearBar
  646. || style == TwoValueHorizontal
  647. || style == ThreeValueHorizontal;
  648. }
  649. bool Slider::isVertical() const
  650. {
  651. return style == LinearVertical
  652. || style == TwoValueVertical
  653. || style == ThreeValueVertical;
  654. }
  655. bool Slider::incDecDragDirectionIsHorizontal() const
  656. {
  657. return incDecButtonMode == incDecButtonsDraggable_Horizontal
  658. || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide);
  659. }
  660. float Slider::getPositionOfValue (const double value)
  661. {
  662. if (isHorizontal() || isVertical())
  663. {
  664. return getLinearSliderPos (value);
  665. }
  666. else
  667. {
  668. jassertfalse; // not a valid call on a slider that doesn't work linearly!
  669. return 0.0f;
  670. }
  671. }
  672. //==============================================================================
  673. void Slider::paint (Graphics& g)
  674. {
  675. if (style != IncDecButtons)
  676. {
  677. if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
  678. {
  679. const float sliderPos = (float) valueToProportionOfLength (lastCurrentValue);
  680. jassert (sliderPos >= 0 && sliderPos <= 1.0f);
  681. getLookAndFeel().drawRotarySlider (g,
  682. sliderRect.getX(),
  683. sliderRect.getY(),
  684. sliderRect.getWidth(),
  685. sliderRect.getHeight(),
  686. sliderPos,
  687. rotaryStart, rotaryEnd,
  688. *this);
  689. }
  690. else
  691. {
  692. getLookAndFeel().drawLinearSlider (g,
  693. sliderRect.getX(),
  694. sliderRect.getY(),
  695. sliderRect.getWidth(),
  696. sliderRect.getHeight(),
  697. getLinearSliderPos (lastCurrentValue),
  698. getLinearSliderPos (lastValueMin),
  699. getLinearSliderPos (lastValueMax),
  700. style,
  701. *this);
  702. }
  703. if (style == LinearBar && valueBox == 0)
  704. {
  705. g.setColour (findColour (Slider::textBoxOutlineColourId));
  706. g.drawRect (0, 0, getWidth(), getHeight(), 1);
  707. }
  708. }
  709. }
  710. void Slider::resized()
  711. {
  712. int minXSpace = 0;
  713. int minYSpace = 0;
  714. if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
  715. minXSpace = 30;
  716. else
  717. minYSpace = 15;
  718. const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace));
  719. const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace));
  720. if (style == LinearBar)
  721. {
  722. if (valueBox != 0)
  723. valueBox->setBounds (getLocalBounds());
  724. }
  725. else
  726. {
  727. if (textBoxPos == NoTextBox)
  728. {
  729. sliderRect = getLocalBounds();
  730. }
  731. else if (textBoxPos == TextBoxLeft)
  732. {
  733. valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh);
  734. sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight());
  735. }
  736. else if (textBoxPos == TextBoxRight)
  737. {
  738. valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh);
  739. sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight());
  740. }
  741. else if (textBoxPos == TextBoxAbove)
  742. {
  743. valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh);
  744. sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh);
  745. }
  746. else if (textBoxPos == TextBoxBelow)
  747. {
  748. valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh);
  749. sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh);
  750. }
  751. }
  752. const int indent = getLookAndFeel().getSliderThumbRadius (*this);
  753. if (style == LinearBar)
  754. {
  755. const int barIndent = 1;
  756. sliderRegionStart = barIndent;
  757. sliderRegionSize = getWidth() - barIndent * 2;
  758. sliderRect.setBounds (sliderRegionStart, barIndent,
  759. sliderRegionSize, getHeight() - barIndent * 2);
  760. }
  761. else if (isHorizontal())
  762. {
  763. sliderRegionStart = sliderRect.getX() + indent;
  764. sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2);
  765. sliderRect.setBounds (sliderRegionStart, sliderRect.getY(),
  766. sliderRegionSize, sliderRect.getHeight());
  767. }
  768. else if (isVertical())
  769. {
  770. sliderRegionStart = sliderRect.getY() + indent;
  771. sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2);
  772. sliderRect.setBounds (sliderRect.getX(), sliderRegionStart,
  773. sliderRect.getWidth(), sliderRegionSize);
  774. }
  775. else
  776. {
  777. sliderRegionStart = 0;
  778. sliderRegionSize = 100;
  779. }
  780. if (style == IncDecButtons)
  781. {
  782. Rectangle<int> buttonRect (sliderRect);
  783. if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
  784. buttonRect.expand (-2, 0);
  785. else
  786. buttonRect.expand (0, -2);
  787. incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight();
  788. if (incDecButtonsSideBySide)
  789. {
  790. decButton->setBounds (buttonRect.getX(),
  791. buttonRect.getY(),
  792. buttonRect.getWidth() / 2,
  793. buttonRect.getHeight());
  794. decButton->setConnectedEdges (Button::ConnectedOnRight);
  795. incButton->setBounds (buttonRect.getCentreX(),
  796. buttonRect.getY(),
  797. buttonRect.getWidth() / 2,
  798. buttonRect.getHeight());
  799. incButton->setConnectedEdges (Button::ConnectedOnLeft);
  800. }
  801. else
  802. {
  803. incButton->setBounds (buttonRect.getX(),
  804. buttonRect.getY(),
  805. buttonRect.getWidth(),
  806. buttonRect.getHeight() / 2);
  807. incButton->setConnectedEdges (Button::ConnectedOnBottom);
  808. decButton->setBounds (buttonRect.getX(),
  809. buttonRect.getCentreY(),
  810. buttonRect.getWidth(),
  811. buttonRect.getHeight() / 2);
  812. decButton->setConnectedEdges (Button::ConnectedOnTop);
  813. }
  814. }
  815. }
  816. void Slider::focusOfChildComponentChanged (FocusChangeType)
  817. {
  818. repaint();
  819. }
  820. void Slider::mouseDown (const MouseEvent& e)
  821. {
  822. mouseWasHidden = false;
  823. incDecDragged = false;
  824. mouseXWhenLastDragged = e.x;
  825. mouseYWhenLastDragged = e.y;
  826. mouseDragStartX = e.getMouseDownX();
  827. mouseDragStartY = e.getMouseDownY();
  828. if (isEnabled())
  829. {
  830. if (e.mods.isPopupMenu() && menuEnabled)
  831. {
  832. menuShown = true;
  833. PopupMenu m;
  834. m.setLookAndFeel (&getLookAndFeel());
  835. m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased);
  836. m.addSeparator();
  837. if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
  838. {
  839. PopupMenu rotaryMenu;
  840. rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary);
  841. rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag);
  842. rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag);
  843. m.addSubMenu (TRANS ("rotary mode"), rotaryMenu);
  844. }
  845. const int r = m.show();
  846. if (r == 1)
  847. {
  848. setVelocityBasedMode (! isVelocityBased);
  849. }
  850. else if (r == 2)
  851. {
  852. setSliderStyle (Rotary);
  853. }
  854. else if (r == 3)
  855. {
  856. setSliderStyle (RotaryHorizontalDrag);
  857. }
  858. else if (r == 4)
  859. {
  860. setSliderStyle (RotaryVerticalDrag);
  861. }
  862. }
  863. else if (maximum > minimum)
  864. {
  865. menuShown = false;
  866. if (valueBox != 0)
  867. valueBox->hideEditor (true);
  868. sliderBeingDragged = 0;
  869. if (style == TwoValueHorizontal
  870. || style == TwoValueVertical
  871. || style == ThreeValueHorizontal
  872. || style == ThreeValueVertical)
  873. {
  874. const float mousePos = (float) (isVertical() ? e.y : e.x);
  875. const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos);
  876. const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos);
  877. const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 0.1f - mousePos);
  878. if (style == TwoValueHorizontal || style == TwoValueVertical)
  879. {
  880. if (maxPosDistance <= minPosDistance)
  881. sliderBeingDragged = 2;
  882. else
  883. sliderBeingDragged = 1;
  884. }
  885. else if (style == ThreeValueHorizontal || style == ThreeValueVertical)
  886. {
  887. if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance)
  888. sliderBeingDragged = 1;
  889. else if (normalPosDistance >= maxPosDistance)
  890. sliderBeingDragged = 2;
  891. }
  892. }
  893. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  894. lastAngle = rotaryStart + (rotaryEnd - rotaryStart)
  895. * valueToProportionOfLength (currentValue.getValue());
  896. valueWhenLastDragged = ((sliderBeingDragged == 2) ? valueMax
  897. : ((sliderBeingDragged == 1) ? valueMin
  898. : currentValue)).getValue();
  899. valueOnMouseDown = valueWhenLastDragged;
  900. if (popupDisplayEnabled)
  901. {
  902. SliderPopupDisplayComponent* const popup = new SliderPopupDisplayComponent (this);
  903. popupDisplay = popup;
  904. if (parentForPopupDisplay != 0)
  905. {
  906. parentForPopupDisplay->addChildComponent (popup);
  907. }
  908. else
  909. {
  910. popup->addToDesktop (0);
  911. }
  912. popup->setVisible (true);
  913. }
  914. sendDragStart();
  915. mouseDrag (e);
  916. }
  917. }
  918. }
  919. void Slider::mouseUp (const MouseEvent&)
  920. {
  921. if (isEnabled()
  922. && (! menuShown)
  923. && (maximum > minimum)
  924. && (style != IncDecButtons || incDecDragged))
  925. {
  926. restoreMouseIfHidden();
  927. if (sendChangeOnlyOnRelease && valueOnMouseDown != (double) currentValue.getValue())
  928. triggerChangeMessage (false);
  929. sendDragEnd();
  930. popupDisplay = 0;
  931. if (style == IncDecButtons)
  932. {
  933. incButton->setState (Button::buttonNormal);
  934. decButton->setState (Button::buttonNormal);
  935. }
  936. }
  937. }
  938. void Slider::restoreMouseIfHidden()
  939. {
  940. if (mouseWasHidden)
  941. {
  942. mouseWasHidden = false;
  943. for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;)
  944. Desktop::getInstance().getMouseSource(i)->enableUnboundedMouseMovement (false);
  945. const double pos = (sliderBeingDragged == 2) ? getMaxValue()
  946. : ((sliderBeingDragged == 1) ? getMinValue()
  947. : (double) currentValue.getValue());
  948. Point<int> mousePos;
  949. if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
  950. {
  951. mousePos = Desktop::getLastMouseDownPosition();
  952. if (style == RotaryHorizontalDrag)
  953. {
  954. const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown);
  955. mousePos += Point<int> (roundToInt (pixelsForFullDragExtent * posDiff), 0);
  956. }
  957. else
  958. {
  959. const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos);
  960. mousePos += Point<int> (0, roundToInt (pixelsForFullDragExtent * posDiff));
  961. }
  962. }
  963. else
  964. {
  965. const int pixelPos = (int) getLinearSliderPos (pos);
  966. mousePos = localPointToGlobal (Point<int> (isHorizontal() ? pixelPos : (getWidth() / 2),
  967. isVertical() ? pixelPos : (getHeight() / 2)));
  968. }
  969. Desktop::setMousePosition (mousePos);
  970. }
  971. }
  972. void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
  973. {
  974. if (isEnabled()
  975. && style != IncDecButtons
  976. && style != Rotary
  977. && isVelocityBased == modifiers.isAnyModifierKeyDown())
  978. {
  979. restoreMouseIfHidden();
  980. }
  981. }
  982. namespace SliderHelpers
  983. {
  984. double smallestAngleBetween (double a1, double a2) throw()
  985. {
  986. return jmin (std::abs (a1 - a2),
  987. std::abs (a1 + double_Pi * 2.0 - a2),
  988. std::abs (a2 + double_Pi * 2.0 - a1));
  989. }
  990. }
  991. void Slider::mouseDrag (const MouseEvent& e)
  992. {
  993. if (isEnabled()
  994. && (! menuShown)
  995. && (maximum > minimum))
  996. {
  997. if (style == Rotary)
  998. {
  999. int dx = e.x - sliderRect.getCentreX();
  1000. int dy = e.y - sliderRect.getCentreY();
  1001. if (dx * dx + dy * dy > 25)
  1002. {
  1003. double angle = std::atan2 ((double) dx, (double) -dy);
  1004. while (angle < 0.0)
  1005. angle += double_Pi * 2.0;
  1006. if (rotaryStop && ! e.mouseWasClicked())
  1007. {
  1008. if (std::abs (angle - lastAngle) > double_Pi)
  1009. {
  1010. if (angle >= lastAngle)
  1011. angle -= double_Pi * 2.0;
  1012. else
  1013. angle += double_Pi * 2.0;
  1014. }
  1015. if (angle >= lastAngle)
  1016. angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd));
  1017. else
  1018. angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd));
  1019. }
  1020. else
  1021. {
  1022. while (angle < rotaryStart)
  1023. angle += double_Pi * 2.0;
  1024. if (angle > rotaryEnd)
  1025. {
  1026. if (SliderHelpers::smallestAngleBetween (angle, rotaryStart)
  1027. <= SliderHelpers::smallestAngleBetween (angle, rotaryEnd))
  1028. angle = rotaryStart;
  1029. else
  1030. angle = rotaryEnd;
  1031. }
  1032. }
  1033. const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart);
  1034. valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion));
  1035. lastAngle = angle;
  1036. }
  1037. }
  1038. else
  1039. {
  1040. if (style == LinearBar && e.mouseWasClicked()
  1041. && valueBox != 0 && valueBox->isEditable())
  1042. return;
  1043. if (style == IncDecButtons && ! incDecDragged)
  1044. {
  1045. if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked())
  1046. return;
  1047. incDecDragged = true;
  1048. mouseDragStartX = e.x;
  1049. mouseDragStartY = e.y;
  1050. }
  1051. if ((isVelocityBased == (userKeyOverridesVelocity ? e.mods.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier | ModifierKeys::altModifier)
  1052. : false))
  1053. || ((maximum - minimum) / sliderRegionSize < interval))
  1054. {
  1055. const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y;
  1056. double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
  1057. if (style == RotaryHorizontalDrag
  1058. || style == RotaryVerticalDrag
  1059. || style == IncDecButtons
  1060. || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar)
  1061. && ! snapsToMousePos))
  1062. {
  1063. const int mouseDiff = (style == RotaryHorizontalDrag
  1064. || style == LinearHorizontal
  1065. || style == LinearBar
  1066. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
  1067. ? e.x - mouseDragStartX
  1068. : mouseDragStartY - e.y;
  1069. double newPos = valueToProportionOfLength (valueOnMouseDown)
  1070. + mouseDiff * (1.0 / pixelsForFullDragExtent);
  1071. valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos));
  1072. if (style == IncDecButtons)
  1073. {
  1074. incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown);
  1075. decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown);
  1076. }
  1077. }
  1078. else
  1079. {
  1080. if (isVertical())
  1081. scaledMousePos = 1.0 - scaledMousePos;
  1082. valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos));
  1083. }
  1084. }
  1085. else
  1086. {
  1087. const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag
  1088. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
  1089. ? e.x - mouseXWhenLastDragged
  1090. : e.y - mouseYWhenLastDragged;
  1091. const double maxSpeed = jmax (200, sliderRegionSize);
  1092. double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff));
  1093. if (speed != 0)
  1094. {
  1095. speed = 0.2 * velocityModeSensitivity
  1096. * (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset
  1097. + jmax (0.0, (double) (speed - velocityModeThreshold))
  1098. / maxSpeed))));
  1099. if (mouseDiff < 0)
  1100. speed = -speed;
  1101. if (isVertical() || style == RotaryVerticalDrag
  1102. || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal()))
  1103. speed = -speed;
  1104. const double currentPos = valueToProportionOfLength (valueWhenLastDragged);
  1105. valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed));
  1106. e.source.enableUnboundedMouseMovement (true, false);
  1107. mouseWasHidden = true;
  1108. }
  1109. }
  1110. }
  1111. valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
  1112. if (sliderBeingDragged == 0)
  1113. {
  1114. setValue (snapValue (valueWhenLastDragged, true),
  1115. ! sendChangeOnlyOnRelease, true);
  1116. }
  1117. else if (sliderBeingDragged == 1)
  1118. {
  1119. setMinValue (snapValue (valueWhenLastDragged, true),
  1120. ! sendChangeOnlyOnRelease, false, true);
  1121. if (e.mods.isShiftDown())
  1122. setMaxValue (getMinValue() + minMaxDiff, false, false, true);
  1123. else
  1124. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  1125. }
  1126. else
  1127. {
  1128. jassert (sliderBeingDragged == 2);
  1129. setMaxValue (snapValue (valueWhenLastDragged, true),
  1130. ! sendChangeOnlyOnRelease, false, true);
  1131. if (e.mods.isShiftDown())
  1132. setMinValue (getMaxValue() - minMaxDiff, false, false, true);
  1133. else
  1134. minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
  1135. }
  1136. mouseXWhenLastDragged = e.x;
  1137. mouseYWhenLastDragged = e.y;
  1138. }
  1139. }
  1140. void Slider::mouseDoubleClick (const MouseEvent&)
  1141. {
  1142. if (doubleClickToValue
  1143. && isEnabled()
  1144. && style != IncDecButtons
  1145. && minimum <= doubleClickReturnValue
  1146. && maximum >= doubleClickReturnValue)
  1147. {
  1148. sendDragStart();
  1149. setValue (doubleClickReturnValue, true, true);
  1150. sendDragEnd();
  1151. }
  1152. }
  1153. void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY)
  1154. {
  1155. if (scrollWheelEnabled && isEnabled()
  1156. && style != TwoValueHorizontal
  1157. && style != TwoValueVertical)
  1158. {
  1159. if (maximum > minimum && ! e.mods.isAnyMouseButtonDown())
  1160. {
  1161. if (valueBox != 0)
  1162. valueBox->hideEditor (false);
  1163. const double value = (double) currentValue.getValue();
  1164. const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f;
  1165. const double currentPos = valueToProportionOfLength (value);
  1166. const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta));
  1167. double delta = (newValue != value)
  1168. ? jmax (std::abs (newValue - value), interval) : 0;
  1169. if (value > newValue)
  1170. delta = -delta;
  1171. sendDragStart();
  1172. setValue (snapValue (value + delta, false), true, true);
  1173. sendDragEnd();
  1174. }
  1175. }
  1176. else
  1177. {
  1178. Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY);
  1179. }
  1180. }
  1181. void SliderListener::sliderDragStarted (Slider*) // (can't write Slider::Listener due to idiotic VC2005 bug)
  1182. {
  1183. }
  1184. void SliderListener::sliderDragEnded (Slider*)
  1185. {
  1186. }
  1187. END_JUCE_NAMESPACE