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.

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