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.

736 lines
19KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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_Button.h"
  21. #include "../juce_ComponentDeletionWatcher.h"
  22. #include "../keyboard/juce_KeyPressMappingSet.h"
  23. #include "../../../text/juce_LocalisedStrings.h"
  24. //==============================================================================
  25. Button::Button (const String& name)
  26. : Component (name),
  27. keySource (0),
  28. text (name),
  29. buttonPressTime (0),
  30. lastTimeCallbackTime (0),
  31. commandManagerToUse (0),
  32. autoRepeatDelay (-1),
  33. autoRepeatSpeed (0),
  34. autoRepeatMinimumDelay (-1),
  35. radioGroupId (0),
  36. commandID (0),
  37. connectedEdgeFlags (0),
  38. buttonState (buttonNormal),
  39. lastToggleState (false),
  40. clickTogglesState (false),
  41. needsToRelease (false),
  42. needsRepainting (false),
  43. isKeyDown (false),
  44. triggerOnMouseDown (false),
  45. generateTooltip (false)
  46. {
  47. setWantsKeyboardFocus (true);
  48. isOn.addListener (this);
  49. }
  50. Button::~Button()
  51. {
  52. isOn.removeListener (this);
  53. if (commandManagerToUse != 0)
  54. commandManagerToUse->removeListener (this);
  55. repeatTimer = 0;
  56. clearShortcuts();
  57. }
  58. //==============================================================================
  59. void Button::setButtonText (const String& newText) throw()
  60. {
  61. if (text != newText)
  62. {
  63. text = newText;
  64. repaint();
  65. }
  66. }
  67. void Button::setTooltip (const String& newTooltip)
  68. {
  69. SettableTooltipClient::setTooltip (newTooltip);
  70. generateTooltip = false;
  71. }
  72. const String Button::getTooltip()
  73. {
  74. if (generateTooltip && commandManagerToUse != 0 && commandID != 0)
  75. {
  76. String tt (commandManagerToUse->getDescriptionOfCommand (commandID));
  77. Array <KeyPress> keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID));
  78. for (int i = 0; i < keyPresses.size(); ++i)
  79. {
  80. const String key (keyPresses.getReference(i).getTextDescription());
  81. tt << " [";
  82. if (key.length() == 1)
  83. tt << TRANS("shortcut") << ": '" << key << "']";
  84. else
  85. tt << key << ']';
  86. }
  87. return tt;
  88. }
  89. return SettableTooltipClient::getTooltip();
  90. }
  91. void Button::setConnectedEdges (const int connectedEdgeFlags_) throw()
  92. {
  93. if (connectedEdgeFlags != connectedEdgeFlags_)
  94. {
  95. connectedEdgeFlags = connectedEdgeFlags_;
  96. repaint();
  97. }
  98. }
  99. //==============================================================================
  100. void Button::setToggleState (const bool shouldBeOn,
  101. const bool sendChangeNotification)
  102. {
  103. if (shouldBeOn != lastToggleState)
  104. {
  105. const ComponentDeletionWatcher deletionWatcher (this);
  106. isOn = shouldBeOn;
  107. lastToggleState = shouldBeOn;
  108. repaint();
  109. if (sendChangeNotification)
  110. sendClickMessage (ModifierKeys());
  111. if ((! deletionWatcher.hasBeenDeleted()) && getToggleState())
  112. turnOffOtherButtonsInGroup (sendChangeNotification);
  113. }
  114. }
  115. void Button::setClickingTogglesState (const bool shouldToggle) throw()
  116. {
  117. clickTogglesState = shouldToggle;
  118. // if you've got clickTogglesState turned on, you shouldn't also connect the button
  119. // up to be a command invoker. Instead, your command handler must flip the state of whatever
  120. // it is that this button represents, and the button will update its state to reflect this
  121. // in the applicationCommandListChanged() method.
  122. jassert (commandManagerToUse == 0 || ! clickTogglesState);
  123. }
  124. bool Button::getClickingTogglesState() const throw()
  125. {
  126. return clickTogglesState;
  127. }
  128. void Button::valueChanged (Value& value)
  129. {
  130. if (value.refersToSameSourceAs (isOn))
  131. setToggleState (isOn.getValue(), true);
  132. }
  133. void Button::setRadioGroupId (const int newGroupId)
  134. {
  135. if (radioGroupId != newGroupId)
  136. {
  137. radioGroupId = newGroupId;
  138. if (getToggleState())
  139. turnOffOtherButtonsInGroup (true);
  140. }
  141. }
  142. void Button::turnOffOtherButtonsInGroup (const bool sendChangeNotification)
  143. {
  144. Component* const p = getParentComponent();
  145. if (p != 0 && radioGroupId != 0)
  146. {
  147. const ComponentDeletionWatcher deletionWatcher (this);
  148. for (int i = p->getNumChildComponents(); --i >= 0;)
  149. {
  150. Component* const c = p->getChildComponent (i);
  151. if (c != this)
  152. {
  153. Button* const b = dynamic_cast <Button*> (c);
  154. if (b != 0 && b->getRadioGroupId() == radioGroupId)
  155. {
  156. b->setToggleState (false, sendChangeNotification);
  157. if (deletionWatcher.hasBeenDeleted())
  158. return;
  159. }
  160. }
  161. }
  162. }
  163. }
  164. //==============================================================================
  165. void Button::enablementChanged()
  166. {
  167. updateState (0);
  168. repaint();
  169. }
  170. Button::ButtonState Button::updateState (const MouseEvent* const e) throw()
  171. {
  172. ButtonState state = buttonNormal;
  173. if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent())
  174. {
  175. int mx, my;
  176. if (e == 0)
  177. {
  178. getMouseXYRelative (mx, my);
  179. }
  180. else
  181. {
  182. const MouseEvent e2 (e->getEventRelativeTo (this));
  183. mx = e2.x;
  184. my = e2.y;
  185. }
  186. const bool over = reallyContains (mx, my, true);
  187. const bool down = isMouseButtonDown();
  188. if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown)
  189. state = buttonDown;
  190. else if (over)
  191. state = buttonOver;
  192. }
  193. setState (state);
  194. return state;
  195. }
  196. void Button::setState (const ButtonState newState)
  197. {
  198. if (buttonState != newState)
  199. {
  200. buttonState = newState;
  201. repaint();
  202. if (buttonState == buttonDown)
  203. {
  204. buttonPressTime = Time::getApproximateMillisecondCounter();
  205. lastTimeCallbackTime = buttonPressTime;
  206. }
  207. sendStateMessage();
  208. }
  209. }
  210. bool Button::isDown() const throw()
  211. {
  212. return buttonState == buttonDown;
  213. }
  214. bool Button::isOver() const throw()
  215. {
  216. return buttonState != buttonNormal;
  217. }
  218. void Button::buttonStateChanged()
  219. {
  220. }
  221. uint32 Button::getMillisecondsSinceButtonDown() const throw()
  222. {
  223. const uint32 now = Time::getApproximateMillisecondCounter();
  224. return now > buttonPressTime ? now - buttonPressTime : 0;
  225. }
  226. void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) throw()
  227. {
  228. triggerOnMouseDown = isTriggeredOnMouseDown;
  229. }
  230. //==============================================================================
  231. void Button::clicked()
  232. {
  233. }
  234. void Button::clicked (const ModifierKeys& /*modifiers*/)
  235. {
  236. clicked();
  237. }
  238. static const int clickMessageId = 0x2f3f4f99;
  239. void Button::triggerClick()
  240. {
  241. postCommandMessage (clickMessageId);
  242. }
  243. void Button::internalClickCallback (const ModifierKeys& modifiers)
  244. {
  245. if (clickTogglesState)
  246. setToggleState ((radioGroupId != 0) || ! getToggleState(), false);
  247. sendClickMessage (modifiers);
  248. }
  249. void Button::flashButtonState() throw()
  250. {
  251. if (isEnabled())
  252. {
  253. needsToRelease = true;
  254. setState (buttonDown);
  255. getRepeatTimer().startTimer (100);
  256. }
  257. }
  258. void Button::handleCommandMessage (int commandId)
  259. {
  260. if (commandId == clickMessageId)
  261. {
  262. if (isEnabled())
  263. {
  264. flashButtonState();
  265. internalClickCallback (ModifierKeys::getCurrentModifiers());
  266. }
  267. }
  268. else
  269. {
  270. Component::handleCommandMessage (commandId);
  271. }
  272. }
  273. //==============================================================================
  274. void Button::addButtonListener (ButtonListener* const newListener) throw()
  275. {
  276. jassert (newListener != 0);
  277. jassert (! buttonListeners.contains (newListener)); // trying to add a listener to the list twice!
  278. if (newListener != 0)
  279. buttonListeners.add (newListener);
  280. }
  281. void Button::removeButtonListener (ButtonListener* const listener) throw()
  282. {
  283. jassert (buttonListeners.contains (listener)); // trying to remove a listener that isn't on the list!
  284. buttonListeners.removeValue (listener);
  285. }
  286. void Button::sendClickMessage (const ModifierKeys& modifiers)
  287. {
  288. const ComponentDeletionWatcher cdw (this);
  289. if (commandManagerToUse != 0 && commandID != 0)
  290. {
  291. ApplicationCommandTarget::InvocationInfo info (commandID);
  292. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton;
  293. info.originatingComponent = this;
  294. commandManagerToUse->invoke (info, true);
  295. }
  296. clicked (modifiers);
  297. if (! cdw.hasBeenDeleted())
  298. {
  299. for (int i = buttonListeners.size(); --i >= 0;)
  300. {
  301. ButtonListener* const bl = (ButtonListener*) buttonListeners[i];
  302. if (bl != 0)
  303. {
  304. bl->buttonClicked (this);
  305. if (cdw.hasBeenDeleted())
  306. return;
  307. }
  308. }
  309. }
  310. }
  311. void Button::sendStateMessage()
  312. {
  313. const ComponentDeletionWatcher cdw (this);
  314. buttonStateChanged();
  315. if (cdw.hasBeenDeleted())
  316. return;
  317. for (int i = buttonListeners.size(); --i >= 0;)
  318. {
  319. ButtonListener* const bl = (ButtonListener*) buttonListeners[i];
  320. if (bl != 0)
  321. {
  322. bl->buttonStateChanged (this);
  323. if (cdw.hasBeenDeleted())
  324. return;
  325. }
  326. }
  327. }
  328. //==============================================================================
  329. void Button::paint (Graphics& g)
  330. {
  331. if (needsToRelease && isEnabled())
  332. {
  333. needsToRelease = false;
  334. needsRepainting = true;
  335. }
  336. paintButton (g, isOver(), isDown());
  337. }
  338. //==============================================================================
  339. void Button::mouseEnter (const MouseEvent& e)
  340. {
  341. updateState (&e);
  342. }
  343. void Button::mouseExit (const MouseEvent& e)
  344. {
  345. updateState (&e);
  346. }
  347. void Button::mouseDown (const MouseEvent& e)
  348. {
  349. updateState (&e);
  350. if (isDown())
  351. {
  352. if (autoRepeatDelay >= 0)
  353. getRepeatTimer().startTimer (autoRepeatDelay);
  354. if (triggerOnMouseDown)
  355. internalClickCallback (e.mods);
  356. }
  357. }
  358. void Button::mouseUp (const MouseEvent& e)
  359. {
  360. const bool wasDown = isDown();
  361. updateState (&e);
  362. if (wasDown && isOver() && ! triggerOnMouseDown)
  363. internalClickCallback (e.mods);
  364. }
  365. void Button::mouseDrag (const MouseEvent& e)
  366. {
  367. const ButtonState oldState = buttonState;
  368. updateState (&e);
  369. if (autoRepeatDelay >= 0 && buttonState != oldState && isDown())
  370. getRepeatTimer().startTimer (autoRepeatSpeed);
  371. }
  372. void Button::focusGained (FocusChangeType)
  373. {
  374. updateState (0);
  375. repaint();
  376. }
  377. void Button::focusLost (FocusChangeType)
  378. {
  379. updateState (0);
  380. repaint();
  381. }
  382. //==============================================================================
  383. void Button::setVisible (bool shouldBeVisible)
  384. {
  385. if (shouldBeVisible != isVisible())
  386. {
  387. Component::setVisible (shouldBeVisible);
  388. if (! shouldBeVisible)
  389. needsToRelease = false;
  390. updateState (0);
  391. }
  392. else
  393. {
  394. Component::setVisible (shouldBeVisible);
  395. }
  396. }
  397. void Button::parentHierarchyChanged()
  398. {
  399. Component* const newKeySource = (shortcuts.size() == 0) ? 0 : getTopLevelComponent();
  400. if (newKeySource != keySource)
  401. {
  402. if (keySource->isValidComponent())
  403. keySource->removeKeyListener (this);
  404. keySource = newKeySource;
  405. if (keySource->isValidComponent())
  406. keySource->addKeyListener (this);
  407. }
  408. }
  409. //==============================================================================
  410. void Button::setCommandToTrigger (ApplicationCommandManager* const commandManagerToUse_,
  411. const int commandID_,
  412. const bool generateTooltip_)
  413. {
  414. commandID = commandID_;
  415. generateTooltip = generateTooltip_;
  416. if (commandManagerToUse != commandManagerToUse_)
  417. {
  418. if (commandManagerToUse != 0)
  419. commandManagerToUse->removeListener (this);
  420. commandManagerToUse = commandManagerToUse_;
  421. if (commandManagerToUse != 0)
  422. commandManagerToUse->addListener (this);
  423. // if you've got clickTogglesState turned on, you shouldn't also connect the button
  424. // up to be a command invoker. Instead, your command handler must flip the state of whatever
  425. // it is that this button represents, and the button will update its state to reflect this
  426. // in the applicationCommandListChanged() method.
  427. jassert (commandManagerToUse == 0 || ! clickTogglesState);
  428. }
  429. if (commandManagerToUse != 0)
  430. applicationCommandListChanged();
  431. else
  432. setEnabled (true);
  433. }
  434. void Button::applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info)
  435. {
  436. if (info.commandID == commandID
  437. && (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0)
  438. {
  439. flashButtonState();
  440. }
  441. }
  442. void Button::applicationCommandListChanged()
  443. {
  444. if (commandManagerToUse != 0)
  445. {
  446. ApplicationCommandInfo info (0);
  447. ApplicationCommandTarget* const target = commandManagerToUse->getTargetForCommand (commandID, info);
  448. setEnabled (target != 0 && (info.flags & ApplicationCommandInfo::isDisabled) == 0);
  449. if (target != 0)
  450. setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, false);
  451. }
  452. }
  453. //==============================================================================
  454. void Button::addShortcut (const KeyPress& key)
  455. {
  456. if (key.isValid())
  457. {
  458. jassert (! isRegisteredForShortcut (key)); // already registered!
  459. shortcuts.add (key);
  460. parentHierarchyChanged();
  461. }
  462. }
  463. void Button::clearShortcuts()
  464. {
  465. shortcuts.clear();
  466. parentHierarchyChanged();
  467. }
  468. bool Button::isShortcutPressed() const throw()
  469. {
  470. if (! isCurrentlyBlockedByAnotherModalComponent())
  471. {
  472. for (int i = shortcuts.size(); --i >= 0;)
  473. if (shortcuts.getReference(i).isCurrentlyDown())
  474. return true;
  475. }
  476. return false;
  477. }
  478. bool Button::isRegisteredForShortcut (const KeyPress& key) const throw()
  479. {
  480. for (int i = shortcuts.size(); --i >= 0;)
  481. if (key == shortcuts.getReference(i))
  482. return true;
  483. return false;
  484. }
  485. bool Button::keyStateChanged (const bool, Component*)
  486. {
  487. if (! isEnabled())
  488. return false;
  489. const bool wasDown = isKeyDown;
  490. isKeyDown = isShortcutPressed();
  491. if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown))
  492. getRepeatTimer().startTimer (autoRepeatDelay);
  493. updateState (0);
  494. if (isEnabled() && wasDown && ! isKeyDown)
  495. {
  496. internalClickCallback (ModifierKeys::getCurrentModifiers());
  497. // (return immediately - this button may now have been deleted)
  498. return true;
  499. }
  500. return wasDown || isKeyDown;
  501. }
  502. bool Button::keyPressed (const KeyPress&, Component*)
  503. {
  504. // returning true will avoid forwarding events for keys that we're using as shortcuts
  505. return isShortcutPressed();
  506. }
  507. bool Button::keyPressed (const KeyPress& key)
  508. {
  509. if (isEnabled() && key.isKeyCode (KeyPress::returnKey))
  510. {
  511. triggerClick();
  512. return true;
  513. }
  514. return false;
  515. }
  516. //==============================================================================
  517. void Button::setRepeatSpeed (const int initialDelayMillisecs,
  518. const int repeatMillisecs,
  519. const int minimumDelayInMillisecs) throw()
  520. {
  521. autoRepeatDelay = initialDelayMillisecs;
  522. autoRepeatSpeed = repeatMillisecs;
  523. autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs);
  524. }
  525. void Button::repeatTimerCallback() throw()
  526. {
  527. if (needsRepainting)
  528. {
  529. getRepeatTimer().stopTimer();
  530. updateState (0);
  531. needsRepainting = false;
  532. }
  533. else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState (0) == buttonDown)))
  534. {
  535. int repeatSpeed = autoRepeatSpeed;
  536. if (autoRepeatMinimumDelay >= 0)
  537. {
  538. double timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0);
  539. timeHeldDown *= timeHeldDown;
  540. repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed));
  541. }
  542. repeatSpeed = jmax (1, repeatSpeed);
  543. getRepeatTimer().startTimer (repeatSpeed);
  544. const uint32 now = Time::getApproximateMillisecondCounter();
  545. const int numTimesToCallback
  546. = (now > lastTimeCallbackTime) ? jmax (1, (now - lastTimeCallbackTime) / repeatSpeed) : 1;
  547. lastTimeCallbackTime = now;
  548. const ComponentDeletionWatcher cdw (this);
  549. for (int i = numTimesToCallback; --i >= 0;)
  550. {
  551. internalClickCallback (ModifierKeys::getCurrentModifiers());
  552. if (cdw.hasBeenDeleted() || ! isDown())
  553. return;
  554. }
  555. }
  556. else if (! needsToRelease)
  557. {
  558. getRepeatTimer().stopTimer();
  559. }
  560. }
  561. class InternalButtonRepeatTimer : public Timer
  562. {
  563. public:
  564. InternalButtonRepeatTimer (Button& owner_) throw()
  565. : owner (owner_)
  566. {
  567. }
  568. ~InternalButtonRepeatTimer()
  569. {
  570. }
  571. void timerCallback()
  572. {
  573. owner.repeatTimerCallback();
  574. }
  575. private:
  576. Button& owner;
  577. InternalButtonRepeatTimer (const InternalButtonRepeatTimer&);
  578. const InternalButtonRepeatTimer& operator= (const InternalButtonRepeatTimer&);
  579. };
  580. Timer& Button::getRepeatTimer() throw()
  581. {
  582. if (repeatTimer == 0)
  583. repeatTimer = new InternalButtonRepeatTimer (*this);
  584. return *repeatTimer;
  585. }
  586. END_JUCE_NAMESPACE