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.

1306 lines
40KB

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