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.

598 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. static uint32 lastUniquePeerID = 1;
  18. //==============================================================================
  19. ComponentPeer::ComponentPeer (Component& comp, const int flags)
  20. : component (comp),
  21. styleFlags (flags),
  22. constrainer (nullptr),
  23. lastDragAndDropCompUnderMouse (nullptr),
  24. uniqueID (lastUniquePeerID += 2), // increment by 2 so that this can never hit 0
  25. isWindowMinimised (false)
  26. {
  27. Desktop::getInstance().peers.add (this);
  28. }
  29. ComponentPeer::~ComponentPeer()
  30. {
  31. Desktop& desktop = Desktop::getInstance();
  32. desktop.peers.removeFirstMatchingValue (this);
  33. desktop.triggerFocusCallback();
  34. }
  35. //==============================================================================
  36. int ComponentPeer::getNumPeers() noexcept
  37. {
  38. return Desktop::getInstance().peers.size();
  39. }
  40. ComponentPeer* ComponentPeer::getPeer (const int index) noexcept
  41. {
  42. return Desktop::getInstance().peers [index];
  43. }
  44. ComponentPeer* ComponentPeer::getPeerFor (const Component* const component) noexcept
  45. {
  46. const Array<ComponentPeer*>& peers = Desktop::getInstance().peers;
  47. for (int i = peers.size(); --i >= 0;)
  48. {
  49. ComponentPeer* const peer = peers.getUnchecked(i);
  50. if (&(peer->getComponent()) == component)
  51. return peer;
  52. }
  53. return nullptr;
  54. }
  55. bool ComponentPeer::isValidPeer (const ComponentPeer* const peer) noexcept
  56. {
  57. return Desktop::getInstance().peers.contains (const_cast<ComponentPeer*> (peer));
  58. }
  59. void ComponentPeer::updateBounds()
  60. {
  61. setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, component.getBoundsInParent()), false);
  62. }
  63. bool ComponentPeer::isKioskMode() const
  64. {
  65. return Desktop::getInstance().getKioskModeComponent() == &component;
  66. }
  67. //==============================================================================
  68. void ComponentPeer::handleMouseEvent (MouseInputSource::InputSourceType type, Point<float> pos, ModifierKeys newMods,
  69. float newPressure, float newOrientation, int64 time, PenDetails pen, int touchIndex)
  70. {
  71. if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
  72. MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods, newPressure, newOrientation, pen);
  73. }
  74. void ComponentPeer::handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> pos, int64 time, const MouseWheelDetails& wheel, int touchIndex)
  75. {
  76. if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
  77. MouseInputSource (*mouse).handleWheel (*this, pos, time, wheel);
  78. }
  79. void ComponentPeer::handleMagnifyGesture (MouseInputSource::InputSourceType type, Point<float> pos, int64 time, float scaleFactor, int touchIndex)
  80. {
  81. if (auto* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (type, touchIndex))
  82. MouseInputSource (*mouse).handleMagnifyGesture (*this, pos, time, scaleFactor);
  83. }
  84. //==============================================================================
  85. void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
  86. {
  87. ModifierKeys::updateCurrentModifiers();
  88. Graphics g (contextToPaintTo);
  89. if (component.isTransformed())
  90. g.addTransform (component.getTransform());
  91. auto peerBounds = getBounds();
  92. if (peerBounds.getWidth() != component.getWidth() || peerBounds.getHeight() != component.getHeight())
  93. // Tweak the scaling so that the component's integer size exactly aligns with the peer's scaled size
  94. g.addTransform (AffineTransform::scale (peerBounds.getWidth() / (float) component.getWidth(),
  95. peerBounds.getHeight() / (float) component.getHeight()));
  96. #if JUCE_ENABLE_REPAINT_DEBUGGING
  97. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  98. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  99. #endif
  100. {
  101. g.saveState();
  102. }
  103. #endif
  104. JUCE_TRY
  105. {
  106. component.paintEntireComponent (g, true);
  107. }
  108. JUCE_CATCH_EXCEPTION
  109. #if JUCE_ENABLE_REPAINT_DEBUGGING
  110. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  111. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  112. #endif
  113. {
  114. // enabling this code will fill all areas that get repainted with a colour overlay, to show
  115. // clearly when things are being repainted.
  116. g.restoreState();
  117. static Random rng;
  118. g.fillAll (Colour ((uint8) rng.nextInt (255),
  119. (uint8) rng.nextInt (255),
  120. (uint8) rng.nextInt (255),
  121. (uint8) 0x50));
  122. }
  123. #endif
  124. /** If this fails, it's probably be because your CPU floating-point precision mode has
  125. been set to low.. This setting is sometimes changed by things like Direct3D, and can
  126. mess up a lot of the calculations that the library needs to do.
  127. */
  128. jassert (roundToInt (10.1f) == 10);
  129. }
  130. Component* ComponentPeer::getTargetForKeyPress()
  131. {
  132. Component* c = Component::getCurrentlyFocusedComponent();
  133. if (c == nullptr)
  134. c = &component;
  135. if (c->isCurrentlyBlockedByAnotherModalComponent())
  136. if (Component* const currentModalComp = Component::getCurrentlyModalComponent())
  137. c = currentModalComp;
  138. return c;
  139. }
  140. bool ComponentPeer::handleKeyPress (const int keyCode, const juce_wchar textCharacter)
  141. {
  142. ModifierKeys::updateCurrentModifiers();
  143. return handleKeyPress (KeyPress (keyCode,
  144. ModifierKeys::getCurrentModifiers().withoutMouseButtons(),
  145. textCharacter));
  146. }
  147. bool ComponentPeer::handleKeyPress (const KeyPress& keyInfo)
  148. {
  149. bool keyWasUsed = false;
  150. for (Component* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent())
  151. {
  152. const WeakReference<Component> deletionChecker (target);
  153. if (const Array<KeyListener*>* const keyListeners = target->keyListeners)
  154. {
  155. for (int i = keyListeners->size(); --i >= 0;)
  156. {
  157. keyWasUsed = keyListeners->getUnchecked(i)->keyPressed (keyInfo, target);
  158. if (keyWasUsed || deletionChecker == nullptr)
  159. return keyWasUsed;
  160. i = jmin (i, keyListeners->size());
  161. }
  162. }
  163. keyWasUsed = target->keyPressed (keyInfo);
  164. if (keyWasUsed || deletionChecker == nullptr)
  165. break;
  166. if (Component* const currentlyFocused = Component::getCurrentlyFocusedComponent())
  167. {
  168. const bool isTab = (keyInfo == KeyPress::tabKey);
  169. const bool isShiftTab = (keyInfo == KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier, 0));
  170. if (isTab || isShiftTab)
  171. {
  172. currentlyFocused->moveKeyboardFocusToSibling (isTab);
  173. keyWasUsed = (currentlyFocused != Component::getCurrentlyFocusedComponent());
  174. if (keyWasUsed || deletionChecker == nullptr)
  175. break;
  176. }
  177. }
  178. }
  179. return keyWasUsed;
  180. }
  181. bool ComponentPeer::handleKeyUpOrDown (const bool isKeyDown)
  182. {
  183. ModifierKeys::updateCurrentModifiers();
  184. bool keyWasUsed = false;
  185. for (Component* target = getTargetForKeyPress(); target != nullptr; target = target->getParentComponent())
  186. {
  187. const WeakReference<Component> deletionChecker (target);
  188. keyWasUsed = target->keyStateChanged (isKeyDown);
  189. if (keyWasUsed || deletionChecker == nullptr)
  190. break;
  191. if (const Array<KeyListener*>* const keyListeners = target->keyListeners)
  192. {
  193. for (int i = keyListeners->size(); --i >= 0;)
  194. {
  195. keyWasUsed = keyListeners->getUnchecked(i)->keyStateChanged (isKeyDown, target);
  196. if (keyWasUsed || deletionChecker == nullptr)
  197. return keyWasUsed;
  198. i = jmin (i, keyListeners->size());
  199. }
  200. }
  201. }
  202. return keyWasUsed;
  203. }
  204. void ComponentPeer::handleModifierKeysChange()
  205. {
  206. ModifierKeys::updateCurrentModifiers();
  207. Component* target = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  208. if (target == nullptr)
  209. target = Component::getCurrentlyFocusedComponent();
  210. if (target == nullptr)
  211. target = &component;
  212. if (target != nullptr)
  213. target->internalModifierKeysChanged();
  214. }
  215. TextInputTarget* ComponentPeer::findCurrentTextInputTarget()
  216. {
  217. Component* const c = Component::getCurrentlyFocusedComponent();
  218. if (c == &component || component.isParentOf (c))
  219. if (TextInputTarget* const ti = dynamic_cast<TextInputTarget*> (c))
  220. if (ti->isTextInputActive())
  221. return ti;
  222. return nullptr;
  223. }
  224. void ComponentPeer::dismissPendingTextInput() {}
  225. //==============================================================================
  226. void ComponentPeer::handleBroughtToFront()
  227. {
  228. ModifierKeys::updateCurrentModifiers();
  229. component.internalBroughtToFront();
  230. }
  231. void ComponentPeer::setConstrainer (ComponentBoundsConstrainer* const newConstrainer) noexcept
  232. {
  233. constrainer = newConstrainer;
  234. }
  235. void ComponentPeer::handleMovedOrResized()
  236. {
  237. ModifierKeys::updateCurrentModifiers();
  238. const bool nowMinimised = isMinimised();
  239. if (component.flags.hasHeavyweightPeerFlag && ! nowMinimised)
  240. {
  241. const WeakReference<Component> deletionChecker (&component);
  242. Rectangle<int> newBounds (Component::ComponentHelpers::rawPeerPositionToLocal (component, getBounds()));
  243. Rectangle<int> oldBounds (component.getBounds());
  244. const bool wasMoved = (oldBounds.getPosition() != newBounds.getPosition());
  245. const bool wasResized = (oldBounds.getWidth() != newBounds.getWidth() || oldBounds.getHeight() != newBounds.getHeight());
  246. if (wasMoved || wasResized)
  247. {
  248. component.boundsRelativeToParent = newBounds;
  249. if (wasResized)
  250. component.repaint();
  251. component.sendMovedResizedMessages (wasMoved, wasResized);
  252. if (deletionChecker == nullptr)
  253. return;
  254. }
  255. }
  256. if (isWindowMinimised != nowMinimised)
  257. {
  258. isWindowMinimised = nowMinimised;
  259. component.minimisationStateChanged (nowMinimised);
  260. component.sendVisibilityChangeMessage();
  261. }
  262. if (! isFullScreen())
  263. lastNonFullscreenBounds = component.getBounds();
  264. }
  265. void ComponentPeer::handleFocusGain()
  266. {
  267. ModifierKeys::updateCurrentModifiers();
  268. if (component.isParentOf (lastFocusedComponent))
  269. {
  270. Component::currentlyFocusedComponent = lastFocusedComponent;
  271. Desktop::getInstance().triggerFocusCallback();
  272. lastFocusedComponent->internalFocusGain (Component::focusChangedDirectly);
  273. }
  274. else
  275. {
  276. if (! component.isCurrentlyBlockedByAnotherModalComponent())
  277. component.grabKeyboardFocus();
  278. else
  279. ModalComponentManager::getInstance()->bringModalComponentsToFront();
  280. }
  281. }
  282. void ComponentPeer::handleFocusLoss()
  283. {
  284. ModifierKeys::updateCurrentModifiers();
  285. if (component.hasKeyboardFocus (true))
  286. {
  287. lastFocusedComponent = Component::currentlyFocusedComponent;
  288. if (lastFocusedComponent != nullptr)
  289. {
  290. Component::currentlyFocusedComponent = nullptr;
  291. Desktop::getInstance().triggerFocusCallback();
  292. lastFocusedComponent->internalFocusLoss (Component::focusChangedByMouseClick);
  293. }
  294. }
  295. }
  296. Component* ComponentPeer::getLastFocusedSubcomponent() const noexcept
  297. {
  298. return (component.isParentOf (lastFocusedComponent) && lastFocusedComponent->isShowing())
  299. ? static_cast<Component*> (lastFocusedComponent)
  300. : &component;
  301. }
  302. void ComponentPeer::handleScreenSizeChange()
  303. {
  304. ModifierKeys::updateCurrentModifiers();
  305. component.parentSizeChanged();
  306. handleMovedOrResized();
  307. }
  308. void ComponentPeer::setNonFullScreenBounds (const Rectangle<int>& newBounds) noexcept
  309. {
  310. lastNonFullscreenBounds = newBounds;
  311. }
  312. const Rectangle<int>& ComponentPeer::getNonFullScreenBounds() const noexcept
  313. {
  314. return lastNonFullscreenBounds;
  315. }
  316. Point<int> ComponentPeer::localToGlobal (Point<int> p) { return localToGlobal (p.toFloat()).roundToInt(); }
  317. Point<int> ComponentPeer::globalToLocal (Point<int> p) { return globalToLocal (p.toFloat()).roundToInt(); }
  318. Rectangle<int> ComponentPeer::localToGlobal (const Rectangle<int>& relativePosition)
  319. {
  320. return relativePosition.withPosition (localToGlobal (relativePosition.getPosition()));
  321. }
  322. Rectangle<int> ComponentPeer::globalToLocal (const Rectangle<int>& screenPosition)
  323. {
  324. return screenPosition.withPosition (globalToLocal (screenPosition.getPosition()));
  325. }
  326. Rectangle<int> ComponentPeer::getAreaCoveredBy (Component& subComponent) const
  327. {
  328. return ScalingHelpers::scaledScreenPosToUnscaled
  329. (component, component.getLocalArea (&subComponent, subComponent.getLocalBounds()));
  330. }
  331. //==============================================================================
  332. namespace DragHelpers
  333. {
  334. static bool isFileDrag (const ComponentPeer::DragInfo& info)
  335. {
  336. return info.files.size() > 0;
  337. }
  338. static bool isSuitableTarget (const ComponentPeer::DragInfo& info, Component* target)
  339. {
  340. return isFileDrag (info) ? dynamic_cast<FileDragAndDropTarget*> (target) != nullptr
  341. : dynamic_cast<TextDragAndDropTarget*> (target) != nullptr;
  342. }
  343. static bool isInterested (const ComponentPeer::DragInfo& info, Component* target)
  344. {
  345. return isFileDrag (info) ? dynamic_cast<FileDragAndDropTarget*> (target)->isInterestedInFileDrag (info.files)
  346. : dynamic_cast<TextDragAndDropTarget*> (target)->isInterestedInTextDrag (info.text);
  347. }
  348. static Component* findDragAndDropTarget (Component* c, const ComponentPeer::DragInfo& info, Component* const lastOne)
  349. {
  350. for (; c != nullptr; c = c->getParentComponent())
  351. if (isSuitableTarget (info, c) && (c == lastOne || isInterested (info, c)))
  352. return c;
  353. return nullptr;
  354. }
  355. // We'll use an async message to deliver the drop, because if the target decides
  356. // to run a modal loop, it can gum-up the operating system..
  357. class AsyncDropMessage : public CallbackMessage
  358. {
  359. public:
  360. AsyncDropMessage (Component* c, const ComponentPeer::DragInfo& d) : target (c), info (d) {}
  361. void messageCallback() override
  362. {
  363. if (Component* const c = target.get())
  364. {
  365. if (isFileDrag (info))
  366. dynamic_cast<FileDragAndDropTarget*> (c)->filesDropped (info.files, info.position.x, info.position.y);
  367. else
  368. dynamic_cast<TextDragAndDropTarget*> (c)->textDropped (info.text, info.position.x, info.position.y);
  369. }
  370. }
  371. private:
  372. WeakReference<Component> target;
  373. const ComponentPeer::DragInfo info;
  374. JUCE_DECLARE_NON_COPYABLE (AsyncDropMessage)
  375. };
  376. }
  377. bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info)
  378. {
  379. ModifierKeys::updateCurrentModifiers();
  380. Component* const compUnderMouse = component.getComponentAt (info.position);
  381. Component* const lastTarget = dragAndDropTargetComponent;
  382. Component* newTarget = nullptr;
  383. if (compUnderMouse != lastDragAndDropCompUnderMouse)
  384. {
  385. lastDragAndDropCompUnderMouse = compUnderMouse;
  386. newTarget = DragHelpers::findDragAndDropTarget (compUnderMouse, info, lastTarget);
  387. if (newTarget != lastTarget)
  388. {
  389. if (lastTarget != nullptr)
  390. {
  391. if (DragHelpers::isFileDrag (info))
  392. dynamic_cast<FileDragAndDropTarget*> (lastTarget)->fileDragExit (info.files);
  393. else
  394. dynamic_cast<TextDragAndDropTarget*> (lastTarget)->textDragExit (info.text);
  395. }
  396. dragAndDropTargetComponent = nullptr;
  397. if (DragHelpers::isSuitableTarget (info, newTarget))
  398. {
  399. dragAndDropTargetComponent = newTarget;
  400. const Point<int> pos (newTarget->getLocalPoint (&component, info.position));
  401. if (DragHelpers::isFileDrag (info))
  402. dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragEnter (info.files, pos.x, pos.y);
  403. else
  404. dynamic_cast<TextDragAndDropTarget*> (newTarget)->textDragEnter (info.text, pos.x, pos.y);
  405. }
  406. }
  407. }
  408. else
  409. {
  410. newTarget = lastTarget;
  411. }
  412. if (! DragHelpers::isSuitableTarget (info, newTarget))
  413. return false;
  414. const Point<int> pos (newTarget->getLocalPoint (&component, info.position));
  415. if (DragHelpers::isFileDrag (info))
  416. dynamic_cast<FileDragAndDropTarget*> (newTarget)->fileDragMove (info.files, pos.x, pos.y);
  417. else
  418. dynamic_cast<TextDragAndDropTarget*> (newTarget)->textDragMove (info.text, pos.x, pos.y);
  419. return true;
  420. }
  421. bool ComponentPeer::handleDragExit (const ComponentPeer::DragInfo& info)
  422. {
  423. DragInfo info2 (info);
  424. info2.position.setXY (-1, -1);
  425. const bool used = handleDragMove (info2);
  426. jassert (dragAndDropTargetComponent == nullptr);
  427. lastDragAndDropCompUnderMouse = nullptr;
  428. return used;
  429. }
  430. bool ComponentPeer::handleDragDrop (const ComponentPeer::DragInfo& info)
  431. {
  432. handleDragMove (info);
  433. if (Component* const targetComp = dragAndDropTargetComponent)
  434. {
  435. dragAndDropTargetComponent = nullptr;
  436. lastDragAndDropCompUnderMouse = nullptr;
  437. if (DragHelpers::isSuitableTarget (info, targetComp))
  438. {
  439. if (targetComp->isCurrentlyBlockedByAnotherModalComponent())
  440. {
  441. targetComp->internalModalInputAttempt();
  442. if (targetComp->isCurrentlyBlockedByAnotherModalComponent())
  443. return true;
  444. }
  445. ComponentPeer::DragInfo info2 (info);
  446. info2.position = targetComp->getLocalPoint (&component, info.position);
  447. (new DragHelpers::AsyncDropMessage (targetComp, info2))->post();
  448. return true;
  449. }
  450. }
  451. return false;
  452. }
  453. //==============================================================================
  454. void ComponentPeer::handleUserClosingWindow()
  455. {
  456. ModifierKeys::updateCurrentModifiers();
  457. component.userTriedToCloseWindow();
  458. }
  459. bool ComponentPeer::setDocumentEditedStatus (bool)
  460. {
  461. return false;
  462. }
  463. void ComponentPeer::setRepresentedFile (const File&)
  464. {
  465. }
  466. //==============================================================================
  467. int ComponentPeer::getCurrentRenderingEngine() const { return 0; }
  468. void ComponentPeer::setCurrentRenderingEngine (int index) { jassert (index == 0); ignoreUnused (index); }