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.

1599 lines
58KB

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