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.

521 lines
21KB

  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 MouseInputSourceInternal : private AsyncUpdater
  19. {
  20. public:
  21. //==============================================================================
  22. MouseInputSourceInternal (MouseInputSource& source_, const int index_, const bool isMouseDevice_)
  23. : index (index_), isMouseDevice (isMouseDevice_), source (source_), lastPeer (nullptr),
  24. isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), currentCursorHandle (nullptr),
  25. mouseEventCounter (0), mouseMovedSignificantlySincePressed (false)
  26. {
  27. }
  28. //==============================================================================
  29. bool isDragging() const noexcept
  30. {
  31. return buttonState.isAnyMouseButtonDown();
  32. }
  33. Component* getComponentUnderMouse() const
  34. {
  35. return static_cast <Component*> (componentUnderMouse);
  36. }
  37. ModifierKeys getCurrentModifiers() const
  38. {
  39. return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags());
  40. }
  41. ComponentPeer* getPeer()
  42. {
  43. if (! ComponentPeer::isValidPeer (lastPeer))
  44. lastPeer = nullptr;
  45. return lastPeer;
  46. }
  47. Component* findComponentAt (const Point<int>& screenPos)
  48. {
  49. if (ComponentPeer* const peer = getPeer())
  50. {
  51. Component& comp = peer->getComponent();
  52. const Point<int> relativePos (comp.getLocalPoint (nullptr, screenPos));
  53. // (the contains() call is needed to test for overlapping desktop windows)
  54. if (comp.contains (relativePos))
  55. return comp.getComponentAt (relativePos);
  56. }
  57. return nullptr;
  58. }
  59. Point<int> getScreenPosition() const
  60. {
  61. // This needs to return the live position if possible, but it mustn't update the lastScreenPos
  62. // value, because that can cause continuity problems.
  63. return unboundedMouseOffset + (isMouseDevice ? MouseInputSource::getCurrentMousePosition()
  64. : lastScreenPos);
  65. }
  66. //==============================================================================
  67. void sendMouseEnter (Component* const comp, const Point<int>& screenPos, const Time& time)
  68. {
  69. //DBG ("Mouse " + String (source.getIndex()) + " enter: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  70. comp->internalMouseEnter (source, comp->getLocalPoint (nullptr, screenPos), time);
  71. }
  72. void sendMouseExit (Component* const comp, const Point<int>& screenPos, const Time& time)
  73. {
  74. //DBG ("Mouse " + String (source.getIndex()) + " exit: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  75. comp->internalMouseExit (source, comp->getLocalPoint (nullptr, screenPos), time);
  76. }
  77. void sendMouseMove (Component* const comp, const Point<int>& screenPos, const Time& time)
  78. {
  79. //DBG ("Mouse " + String (source.getIndex()) + " move: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  80. comp->internalMouseMove (source, comp->getLocalPoint (nullptr, screenPos), time);
  81. }
  82. void sendMouseDown (Component* const comp, const Point<int>& screenPos, const Time& time)
  83. {
  84. //DBG ("Mouse " + String (source.getIndex()) + " down: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  85. comp->internalMouseDown (source, comp->getLocalPoint (nullptr, screenPos), time);
  86. }
  87. void sendMouseDrag (Component* const comp, const Point<int>& screenPos, const Time& time)
  88. {
  89. //DBG ("Mouse " + String (source.getIndex()) + " drag: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  90. comp->internalMouseDrag (source, comp->getLocalPoint (nullptr, screenPos), time);
  91. }
  92. void sendMouseUp (Component* const comp, const Point<int>& screenPos, const Time& time, const ModifierKeys& oldMods)
  93. {
  94. //DBG ("Mouse " + String (source.getIndex()) + " up: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  95. comp->internalMouseUp (source, comp->getLocalPoint (nullptr, screenPos), time, oldMods);
  96. }
  97. void sendMouseWheel (Component* const comp, const Point<int>& screenPos, const Time& time, const MouseWheelDetails& wheel)
  98. {
  99. //DBG ("Mouse " + String (source.getIndex()) + " wheel: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
  100. comp->internalMouseWheel (source, comp->getLocalPoint (nullptr, screenPos), time, wheel);
  101. }
  102. //==============================================================================
  103. // (returns true if the button change caused a modal event loop)
  104. bool setButtons (const Point<int>& screenPos, const Time& time, const ModifierKeys& newButtonState)
  105. {
  106. if (buttonState == newButtonState)
  107. return false;
  108. // (avoid sending a spurious mouse-drag when we receive a mouse-up)
  109. if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown()))
  110. setScreenPos (screenPos, time, false);
  111. // (ignore secondary clicks when there's already a button down)
  112. if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown())
  113. {
  114. buttonState = newButtonState;
  115. return false;
  116. }
  117. const int lastCounter = mouseEventCounter;
  118. if (buttonState.isAnyMouseButtonDown())
  119. {
  120. if (Component* const current = getComponentUnderMouse())
  121. {
  122. const ModifierKeys oldMods (getCurrentModifiers());
  123. buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop
  124. sendMouseUp (current, screenPos + unboundedMouseOffset, time, oldMods);
  125. }
  126. enableUnboundedMouseMovement (false, false);
  127. }
  128. buttonState = newButtonState;
  129. if (buttonState.isAnyMouseButtonDown())
  130. {
  131. Desktop::getInstance().incrementMouseClickCounter();
  132. if (Component* const current = getComponentUnderMouse())
  133. {
  134. registerMouseDown (screenPos, time, *current, buttonState);
  135. sendMouseDown (current, screenPos, time);
  136. }
  137. }
  138. return lastCounter != mouseEventCounter;
  139. }
  140. void setComponentUnderMouse (Component* const newComponent, const Point<int>& screenPos, const Time& time)
  141. {
  142. Component* current = getComponentUnderMouse();
  143. if (newComponent != current)
  144. {
  145. WeakReference<Component> safeNewComp (newComponent);
  146. const ModifierKeys originalButtonState (buttonState);
  147. if (current != nullptr)
  148. {
  149. WeakReference<Component> safeOldComp (current);
  150. setButtons (screenPos, time, ModifierKeys());
  151. if (safeOldComp != nullptr)
  152. {
  153. componentUnderMouse = safeNewComp;
  154. sendMouseExit (safeOldComp, screenPos, time);
  155. }
  156. buttonState = originalButtonState;
  157. }
  158. current = componentUnderMouse = safeNewComp;
  159. if (current != nullptr)
  160. sendMouseEnter (current, screenPos, time);
  161. revealCursor (false);
  162. setButtons (screenPos, time, originalButtonState);
  163. }
  164. }
  165. void setPeer (ComponentPeer* const newPeer, const Point<int>& screenPos, const Time& time)
  166. {
  167. ModifierKeys::updateCurrentModifiers();
  168. if (newPeer != lastPeer)
  169. {
  170. setComponentUnderMouse (nullptr, screenPos, time);
  171. lastPeer = newPeer;
  172. setComponentUnderMouse (findComponentAt (screenPos), screenPos, time);
  173. }
  174. }
  175. void setScreenPos (const Point<int>& newScreenPos, const Time& time, const bool forceUpdate)
  176. {
  177. if (! isDragging())
  178. setComponentUnderMouse (findComponentAt (newScreenPos), newScreenPos, time);
  179. if (newScreenPos != lastScreenPos || forceUpdate)
  180. {
  181. cancelPendingUpdate();
  182. lastScreenPos = newScreenPos;
  183. if (Component* const current = getComponentUnderMouse())
  184. {
  185. if (isDragging())
  186. {
  187. registerMouseDrag (newScreenPos);
  188. sendMouseDrag (current, newScreenPos + unboundedMouseOffset, time);
  189. if (isUnboundedMouseModeOn)
  190. handleUnboundedDrag (current);
  191. }
  192. else
  193. {
  194. sendMouseMove (current, newScreenPos, time);
  195. }
  196. }
  197. revealCursor (false);
  198. }
  199. }
  200. //==============================================================================
  201. void handleEvent (ComponentPeer* const newPeer, const Point<int>& positionWithinPeer, const Time& time, const ModifierKeys& newMods)
  202. {
  203. jassert (newPeer != nullptr);
  204. lastTime = time;
  205. ++mouseEventCounter;
  206. const Point<int> screenPos (newPeer->localToGlobal (positionWithinPeer));
  207. if (isDragging() && newMods.isAnyMouseButtonDown())
  208. {
  209. setScreenPos (screenPos, time, false);
  210. }
  211. else
  212. {
  213. setPeer (newPeer, screenPos, time);
  214. if (ComponentPeer* peer = getPeer())
  215. {
  216. if (setButtons (screenPos, time, newMods))
  217. return; // some modal events have been dispatched, so the current event is now out-of-date
  218. peer = getPeer();
  219. if (peer != nullptr)
  220. setScreenPos (screenPos, time, false);
  221. }
  222. }
  223. }
  224. void handleWheel (ComponentPeer* const peer, const Point<int>& positionWithinPeer,
  225. const Time& time, const MouseWheelDetails& wheel)
  226. {
  227. jassert (peer != nullptr);
  228. lastTime = time;
  229. ++mouseEventCounter;
  230. Desktop::getInstance().incrementMouseWheelCounter();
  231. const Point<int> screenPos (peer->localToGlobal (positionWithinPeer));
  232. setPeer (peer, screenPos, time);
  233. setScreenPos (screenPos, time, false);
  234. triggerFakeMove();
  235. if (! isDragging())
  236. {
  237. if (Component* current = getComponentUnderMouse())
  238. sendMouseWheel (current, screenPos, time, wheel);
  239. }
  240. }
  241. //==============================================================================
  242. const Time& getLastMouseDownTime() const noexcept { return mouseDowns[0].time; }
  243. const Point<int>& getLastMouseDownPosition() const noexcept { return mouseDowns[0].position; }
  244. int getNumberOfMultipleClicks() const noexcept
  245. {
  246. int numClicks = 0;
  247. if (mouseDowns[0].time != Time())
  248. {
  249. if (! mouseMovedSignificantlySincePressed)
  250. ++numClicks;
  251. for (int i = 1; i < numElementsInArray (mouseDowns); ++i)
  252. {
  253. if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2)))
  254. ++numClicks;
  255. else
  256. break;
  257. }
  258. }
  259. return numClicks;
  260. }
  261. bool hasMouseMovedSignificantlySincePressed() const noexcept
  262. {
  263. return mouseMovedSignificantlySincePressed
  264. || lastTime > mouseDowns[0].time + RelativeTime::milliseconds (300);
  265. }
  266. //==============================================================================
  267. void triggerFakeMove()
  268. {
  269. triggerAsyncUpdate();
  270. }
  271. void handleAsyncUpdate()
  272. {
  273. setScreenPos (lastScreenPos, jmax (lastTime, Time::getCurrentTime()), true);
  274. }
  275. //==============================================================================
  276. void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen)
  277. {
  278. enable = enable && isDragging();
  279. isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen;
  280. if (enable != isUnboundedMouseModeOn)
  281. {
  282. if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin()))
  283. {
  284. // when released, return the mouse to within the component's bounds
  285. if (Component* current = getComponentUnderMouse())
  286. Desktop::setMousePosition (current->getScreenBounds()
  287. .getConstrainedPoint (lastScreenPos));
  288. }
  289. isUnboundedMouseModeOn = enable;
  290. unboundedMouseOffset = Point<int>();
  291. revealCursor (true);
  292. }
  293. }
  294. void handleUnboundedDrag (Component* current)
  295. {
  296. const Rectangle<int> screenArea (current->getParentMonitorArea().expanded (-2, -2));
  297. if (! screenArea.contains (lastScreenPos))
  298. {
  299. const Point<int> componentCentre (current->getScreenBounds().getCentre());
  300. unboundedMouseOffset += (lastScreenPos - componentCentre);
  301. Desktop::setMousePosition (componentCentre);
  302. }
  303. else if (isCursorVisibleUntilOffscreen
  304. && (! unboundedMouseOffset.isOrigin())
  305. && screenArea.contains (lastScreenPos + unboundedMouseOffset))
  306. {
  307. Desktop::setMousePosition (lastScreenPos + unboundedMouseOffset);
  308. unboundedMouseOffset = Point<int>();
  309. }
  310. }
  311. //==============================================================================
  312. void showMouseCursor (MouseCursor cursor, bool forcedUpdate)
  313. {
  314. if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen))
  315. {
  316. cursor = MouseCursor::NoCursor;
  317. forcedUpdate = true;
  318. }
  319. if (forcedUpdate || cursor.getHandle() != currentCursorHandle)
  320. {
  321. currentCursorHandle = cursor.getHandle();
  322. cursor.showInWindow (getPeer());
  323. }
  324. }
  325. void hideCursor()
  326. {
  327. showMouseCursor (MouseCursor::NoCursor, true);
  328. }
  329. void revealCursor (bool forcedUpdate)
  330. {
  331. MouseCursor mc (MouseCursor::NormalCursor);
  332. if (Component* current = getComponentUnderMouse())
  333. mc = current->getLookAndFeel().getMouseCursorFor (*current);
  334. showMouseCursor (mc, forcedUpdate);
  335. }
  336. //==============================================================================
  337. const int index;
  338. const bool isMouseDevice;
  339. Point<int> lastScreenPos;
  340. ModifierKeys buttonState;
  341. private:
  342. MouseInputSource& source;
  343. WeakReference<Component> componentUnderMouse;
  344. ComponentPeer* lastPeer;
  345. Point<int> unboundedMouseOffset;
  346. bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen;
  347. void* currentCursorHandle;
  348. int mouseEventCounter;
  349. struct RecentMouseDown
  350. {
  351. RecentMouseDown() noexcept : peerID (0) {}
  352. Point<int> position;
  353. Time time;
  354. ModifierKeys buttons;
  355. uint32 peerID;
  356. bool canBePartOfMultipleClickWith (const RecentMouseDown& other, const int maxTimeBetweenMs) const
  357. {
  358. return time - other.time < RelativeTime::milliseconds (maxTimeBetweenMs)
  359. && abs (position.x - other.position.x) < 8
  360. && abs (position.y - other.position.y) < 8
  361. && buttons == other.buttons
  362. && peerID == other.peerID;
  363. }
  364. };
  365. RecentMouseDown mouseDowns[4];
  366. Time lastTime;
  367. bool mouseMovedSignificantlySincePressed;
  368. void registerMouseDown (const Point<int>& screenPos, const Time& time,
  369. Component& component, const ModifierKeys& modifiers) noexcept
  370. {
  371. for (int i = numElementsInArray (mouseDowns); --i > 0;)
  372. mouseDowns[i] = mouseDowns[i - 1];
  373. mouseDowns[0].position = screenPos;
  374. mouseDowns[0].time = time;
  375. mouseDowns[0].buttons = modifiers.withOnlyMouseButtons();
  376. if (ComponentPeer* const peer = component.getPeer())
  377. mouseDowns[0].peerID = peer->getUniqueID();
  378. else
  379. mouseDowns[0].peerID = 0;
  380. mouseMovedSignificantlySincePressed = false;
  381. }
  382. void registerMouseDrag (const Point<int>& screenPos) noexcept
  383. {
  384. mouseMovedSignificantlySincePressed = mouseMovedSignificantlySincePressed
  385. || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4;
  386. }
  387. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInputSourceInternal)
  388. };
  389. //==============================================================================
  390. MouseInputSource::MouseInputSource (const int index, const bool isMouseDevice)
  391. {
  392. pimpl = new MouseInputSourceInternal (*this, index, isMouseDevice);
  393. }
  394. MouseInputSource::~MouseInputSource() {}
  395. bool MouseInputSource::isMouse() const { return pimpl->isMouseDevice; }
  396. bool MouseInputSource::isTouch() const { return ! isMouse(); }
  397. bool MouseInputSource::canHover() const { return isMouse(); }
  398. bool MouseInputSource::hasMouseWheel() const { return isMouse(); }
  399. int MouseInputSource::getIndex() const { return pimpl->index; }
  400. bool MouseInputSource::isDragging() const { return pimpl->isDragging(); }
  401. Point<int> MouseInputSource::getScreenPosition() const { return pimpl->getScreenPosition(); }
  402. ModifierKeys MouseInputSource::getCurrentModifiers() const { return pimpl->getCurrentModifiers(); }
  403. Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); }
  404. void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); }
  405. int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); }
  406. Time MouseInputSource::getLastMouseDownTime() const noexcept { return pimpl->getLastMouseDownTime(); }
  407. Point<int> MouseInputSource::getLastMouseDownPosition() const noexcept { return pimpl->getLastMouseDownPosition(); }
  408. bool MouseInputSource::hasMouseMovedSignificantlySincePressed() const noexcept { return pimpl->hasMouseMovedSignificantlySincePressed(); }
  409. bool MouseInputSource::canDoUnboundedMovement() const noexcept { return isMouse(); }
  410. void MouseInputSource::enableUnboundedMouseMovement (bool isEnabled, bool keepCursorVisibleUntilOffscreen) { pimpl->enableUnboundedMouseMovement (isEnabled, keepCursorVisibleUntilOffscreen); }
  411. bool MouseInputSource::hasMouseCursor() const noexcept { return isMouse(); }
  412. void MouseInputSource::showMouseCursor (const MouseCursor& cursor) { pimpl->showMouseCursor (cursor, false); }
  413. void MouseInputSource::hideCursor() { pimpl->hideCursor(); }
  414. void MouseInputSource::revealCursor() { pimpl->revealCursor (false); }
  415. void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); }
  416. void MouseInputSource::handleEvent (ComponentPeer* peer, const Point<int>& positionWithinPeer,
  417. const int64 time, const ModifierKeys& mods)
  418. {
  419. pimpl->handleEvent (peer, positionWithinPeer, Time (time), mods.withOnlyMouseButtons());
  420. }
  421. void MouseInputSource::handleWheel (ComponentPeer* const peer, const Point<int>& positionWithinPeer,
  422. const int64 time, const MouseWheelDetails& wheel)
  423. {
  424. pimpl->handleWheel (peer, positionWithinPeer, Time (time), wheel);
  425. }