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.

529 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. bool juce_performDragDropFiles (const StringArray&, const bool copyFiles, bool& shouldStop);
  20. bool juce_performDragDropText (const String&, bool& shouldStop);
  21. //==============================================================================
  22. class DragAndDropContainer::DragImageComponent : public Component,
  23. private Timer
  24. {
  25. public:
  26. DragImageComponent (const Image& im,
  27. const var& desc,
  28. Component* const sourceComponent,
  29. Component* const mouseSource,
  30. DragAndDropContainer& ddc,
  31. Point<int> offset)
  32. : sourceDetails (desc, sourceComponent, Point<int>()),
  33. image (im), owner (ddc),
  34. mouseDragSource (mouseSource),
  35. imageOffset (offset)
  36. {
  37. updateSize();
  38. if (mouseDragSource == nullptr)
  39. mouseDragSource = sourceComponent;
  40. mouseDragSource->addMouseListener (this, false);
  41. startTimer (200);
  42. setInterceptsMouseClicks (false, false);
  43. setAlwaysOnTop (true);
  44. }
  45. ~DragImageComponent()
  46. {
  47. if (owner.dragImageComponent == this)
  48. owner.dragImageComponent.release();
  49. if (mouseDragSource != nullptr)
  50. {
  51. mouseDragSource->removeMouseListener (this);
  52. if (DragAndDropTarget* const current = getCurrentlyOver())
  53. if (current->isInterestedInDragSource (sourceDetails))
  54. current->itemDragExit (sourceDetails);
  55. }
  56. owner.dragOperationEnded (sourceDetails);
  57. }
  58. void paint (Graphics& g) override
  59. {
  60. if (isOpaque())
  61. g.fillAll (Colours::white);
  62. g.setOpacity (1.0f);
  63. g.drawImageAt (image, 0, 0);
  64. }
  65. void mouseUp (const MouseEvent& e) override
  66. {
  67. if (e.originalComponent != this)
  68. {
  69. if (mouseDragSource != nullptr)
  70. mouseDragSource->removeMouseListener (this);
  71. // (note: use a local copy of this in case the callback runs
  72. // a modal loop and deletes this object before the method completes)
  73. DragAndDropTarget::SourceDetails details (sourceDetails);
  74. DragAndDropTarget* finalTarget = nullptr;
  75. const bool wasVisible = isVisible();
  76. setVisible (false);
  77. Component* unused;
  78. finalTarget = findTarget (e.getScreenPosition(), details.localPosition, unused);
  79. if (wasVisible) // fade the component and remove it - it'll be deleted later by the timer callback
  80. dismissWithAnimation (finalTarget == nullptr);
  81. if (Component* parent = getParentComponent())
  82. parent->removeChildComponent (this);
  83. if (finalTarget != nullptr)
  84. {
  85. currentlyOverComp = nullptr;
  86. finalTarget->itemDropped (details);
  87. }
  88. // careful - this object could now be deleted..
  89. }
  90. }
  91. void mouseDrag (const MouseEvent& e) override
  92. {
  93. if (e.originalComponent != this)
  94. updateLocation (true, e.getScreenPosition());
  95. }
  96. void updateLocation (const bool canDoExternalDrag, Point<int> screenPos)
  97. {
  98. DragAndDropTarget::SourceDetails details (sourceDetails);
  99. setNewScreenPos (screenPos);
  100. Component* newTargetComp;
  101. DragAndDropTarget* const newTarget = findTarget (screenPos, details.localPosition, newTargetComp);
  102. setVisible (newTarget == nullptr || newTarget->shouldDrawDragImageWhenOver());
  103. if (newTargetComp != currentlyOverComp)
  104. {
  105. if (DragAndDropTarget* const lastTarget = getCurrentlyOver())
  106. if (details.sourceComponent != nullptr && lastTarget->isInterestedInDragSource (details))
  107. lastTarget->itemDragExit (details);
  108. currentlyOverComp = newTargetComp;
  109. if (newTarget != nullptr
  110. && newTarget->isInterestedInDragSource (details))
  111. newTarget->itemDragEnter (details);
  112. }
  113. sendDragMove (details);
  114. if (canDoExternalDrag)
  115. {
  116. const Time now (Time::getCurrentTime());
  117. if (getCurrentlyOver() != nullptr)
  118. lastTimeOverTarget = now;
  119. else if (now > lastTimeOverTarget + RelativeTime::milliseconds (700))
  120. checkForExternalDrag (details, screenPos);
  121. }
  122. forceMouseCursorUpdate();
  123. }
  124. void updateImage (const Image& newImage)
  125. {
  126. image = newImage;
  127. updateSize();
  128. repaint();
  129. }
  130. void timerCallback() override
  131. {
  132. forceMouseCursorUpdate();
  133. if (sourceDetails.sourceComponent == nullptr)
  134. {
  135. deleteSelf();
  136. }
  137. else if (! isMouseButtonDownAnywhere())
  138. {
  139. if (mouseDragSource != nullptr)
  140. mouseDragSource->removeMouseListener (this);
  141. deleteSelf();
  142. }
  143. }
  144. bool keyPressed (const KeyPress& key) override
  145. {
  146. if (key == KeyPress::escapeKey)
  147. {
  148. dismissWithAnimation (true);
  149. deleteSelf();
  150. return true;
  151. }
  152. return false;
  153. }
  154. bool canModalEventBeSentToComponent (const Component* targetComponent) override
  155. {
  156. return targetComponent == mouseDragSource;
  157. }
  158. // (overridden to avoid beeps when dragging)
  159. void inputAttemptWhenModal() override {}
  160. DragAndDropTarget::SourceDetails sourceDetails;
  161. private:
  162. Image image;
  163. DragAndDropContainer& owner;
  164. WeakReference<Component> mouseDragSource, currentlyOverComp;
  165. const Point<int> imageOffset;
  166. bool hasCheckedForExternalDrag = false;
  167. Time lastTimeOverTarget;
  168. void updateSize()
  169. {
  170. setSize (image.getWidth(), image.getHeight());
  171. }
  172. void forceMouseCursorUpdate()
  173. {
  174. Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
  175. }
  176. DragAndDropTarget* getCurrentlyOver() const noexcept
  177. {
  178. return dynamic_cast<DragAndDropTarget*> (currentlyOverComp.get());
  179. }
  180. static Component* findDesktopComponentBelow (Point<int> screenPos)
  181. {
  182. Desktop& desktop = Desktop::getInstance();
  183. for (int i = desktop.getNumComponents(); --i >= 0;)
  184. {
  185. Component* c = desktop.getComponent(i);
  186. if (Component* hit = c->getComponentAt (c->getLocalPoint (nullptr, screenPos)))
  187. return hit;
  188. }
  189. return nullptr;
  190. }
  191. DragAndDropTarget* findTarget (Point<int> screenPos, Point<int>& relativePos,
  192. Component*& resultComponent) const
  193. {
  194. Component* hit = getParentComponent();
  195. if (hit == nullptr)
  196. hit = findDesktopComponentBelow (screenPos);
  197. else
  198. hit = hit->getComponentAt (hit->getLocalPoint (nullptr, screenPos));
  199. // (note: use a local copy of this in case the callback runs
  200. // a modal loop and deletes this object before the method completes)
  201. const DragAndDropTarget::SourceDetails details (sourceDetails);
  202. while (hit != nullptr)
  203. {
  204. if (DragAndDropTarget* const ddt = dynamic_cast<DragAndDropTarget*> (hit))
  205. {
  206. if (ddt->isInterestedInDragSource (details))
  207. {
  208. relativePos = hit->getLocalPoint (nullptr, screenPos);
  209. resultComponent = hit;
  210. return ddt;
  211. }
  212. }
  213. hit = hit->getParentComponent();
  214. }
  215. resultComponent = nullptr;
  216. return nullptr;
  217. }
  218. void setNewScreenPos (Point<int> screenPos)
  219. {
  220. Point<int> newPos (screenPos - imageOffset);
  221. if (Component* p = getParentComponent())
  222. newPos = p->getLocalPoint (nullptr, newPos);
  223. setTopLeftPosition (newPos);
  224. }
  225. void sendDragMove (DragAndDropTarget::SourceDetails& details) const
  226. {
  227. if (DragAndDropTarget* const target = getCurrentlyOver())
  228. if (target->isInterestedInDragSource (details))
  229. target->itemDragMove (details);
  230. }
  231. void checkForExternalDrag (DragAndDropTarget::SourceDetails& details, Point<int> screenPos)
  232. {
  233. if (! hasCheckedForExternalDrag)
  234. {
  235. if (Desktop::getInstance().findComponentAt (screenPos) == nullptr)
  236. {
  237. hasCheckedForExternalDrag = true;
  238. if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown())
  239. {
  240. StringArray files;
  241. bool canMoveFiles = false;
  242. if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) && ! files.isEmpty())
  243. {
  244. MessageManager::callAsync ([=]() { DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); });
  245. deleteSelf();
  246. return;
  247. }
  248. String text;
  249. if (owner.shouldDropTextWhenDraggedExternally (details, text) && text.isNotEmpty())
  250. {
  251. MessageManager::callAsync ([=]() { DragAndDropContainer::performExternalDragDropOfText (text); });
  252. deleteSelf();
  253. return;
  254. }
  255. }
  256. }
  257. }
  258. }
  259. void deleteSelf()
  260. {
  261. delete this;
  262. }
  263. void dismissWithAnimation (const bool shouldSnapBack)
  264. {
  265. setVisible (true);
  266. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  267. if (shouldSnapBack && sourceDetails.sourceComponent != nullptr)
  268. {
  269. const Point<int> target (sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre()));
  270. const Point<int> ourCentre (localPointToGlobal (getLocalBounds().getCentre()));
  271. animator.animateComponent (this,
  272. getBounds() + (target - ourCentre),
  273. 0.0f, 120,
  274. true, 1.0, 1.0);
  275. }
  276. else
  277. {
  278. animator.fadeOut (this, 120);
  279. }
  280. }
  281. JUCE_DECLARE_NON_COPYABLE (DragImageComponent)
  282. };
  283. //==============================================================================
  284. DragAndDropContainer::DragAndDropContainer()
  285. {
  286. }
  287. DragAndDropContainer::~DragAndDropContainer()
  288. {
  289. dragImageComponent = nullptr;
  290. }
  291. void DragAndDropContainer::startDragging (const var& sourceDescription,
  292. Component* sourceComponent,
  293. Image dragImage,
  294. const bool allowDraggingToExternalWindows,
  295. const Point<int>* imageOffsetFromMouse)
  296. {
  297. if (dragImageComponent == nullptr)
  298. {
  299. MouseInputSource* const draggingSource = Desktop::getInstance().getDraggingMouseSource (0);
  300. if (draggingSource == nullptr || ! draggingSource->isDragging())
  301. {
  302. jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback!
  303. return;
  304. }
  305. const Point<int> lastMouseDown (draggingSource->getLastMouseDownPosition().roundToInt());
  306. Point<int> imageOffset;
  307. if (dragImage.isNull())
  308. {
  309. dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds())
  310. .convertedToFormat (Image::ARGB);
  311. dragImage.multiplyAllAlphas (0.6f);
  312. const int lo = 150;
  313. const int hi = 400;
  314. Point<int> relPos (sourceComponent->getLocalPoint (nullptr, lastMouseDown));
  315. Point<int> clipped (dragImage.getBounds().getConstrainedPoint (relPos));
  316. Random random;
  317. for (int y = dragImage.getHeight(); --y >= 0;)
  318. {
  319. const double dy = (y - clipped.getY()) * (y - clipped.getY());
  320. for (int x = dragImage.getWidth(); --x >= 0;)
  321. {
  322. const int dx = x - clipped.getX();
  323. const int distance = roundToInt (std::sqrt (dx * dx + dy));
  324. if (distance > lo)
  325. {
  326. const float alpha = (distance > hi) ? 0
  327. : (hi - distance) / (float) (hi - lo)
  328. + random.nextFloat() * 0.008f;
  329. dragImage.multiplyAlphaAt (x, y, alpha);
  330. }
  331. }
  332. }
  333. imageOffset = clipped;
  334. }
  335. else
  336. {
  337. if (imageOffsetFromMouse == nullptr)
  338. imageOffset = dragImage.getBounds().getCentre();
  339. else
  340. imageOffset = dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse);
  341. }
  342. dragImageComponent = new DragImageComponent (dragImage, sourceDescription, sourceComponent,
  343. draggingSource->getComponentUnderMouse(), *this, imageOffset);
  344. if (allowDraggingToExternalWindows)
  345. {
  346. if (! Desktop::canUseSemiTransparentWindows())
  347. dragImageComponent->setOpaque (true);
  348. dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
  349. | ComponentPeer::windowIsTemporary
  350. | ComponentPeer::windowIgnoresKeyPresses);
  351. }
  352. else
  353. {
  354. if (Component* const thisComp = dynamic_cast<Component*> (this))
  355. {
  356. thisComp->addChildComponent (dragImageComponent);
  357. }
  358. else
  359. {
  360. jassertfalse; // Your DragAndDropContainer needs to be a Component!
  361. return;
  362. }
  363. }
  364. static_cast<DragImageComponent*> (dragImageComponent.get())->updateLocation (false, lastMouseDown);
  365. dragImageComponent->enterModalState();
  366. #if JUCE_WINDOWS
  367. // Under heavy load, the layered window's paint callback can often be lost by the OS,
  368. // so forcing a repaint at least once makes sure that the window becomes visible..
  369. if (ComponentPeer* const peer = dragImageComponent->getPeer())
  370. peer->performAnyPendingRepaintsNow();
  371. #endif
  372. dragOperationStarted (dragImageComponent->sourceDetails);
  373. }
  374. }
  375. bool DragAndDropContainer::isDragAndDropActive() const
  376. {
  377. return dragImageComponent != nullptr;
  378. }
  379. var DragAndDropContainer::getCurrentDragDescription() const
  380. {
  381. return dragImageComponent != nullptr ? dragImageComponent->sourceDetails.description
  382. : var();
  383. }
  384. void DragAndDropContainer::setCurrentDragImage (const Image& newImage)
  385. {
  386. if (dragImageComponent != nullptr)
  387. dragImageComponent->updateImage (newImage);
  388. }
  389. DragAndDropContainer* DragAndDropContainer::findParentDragContainerFor (Component* c)
  390. {
  391. return c != nullptr ? c->findParentComponentOfClass<DragAndDropContainer>() : nullptr;
  392. }
  393. bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, StringArray&, bool&)
  394. {
  395. return false;
  396. }
  397. bool DragAndDropContainer::shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, String&)
  398. {
  399. return false;
  400. }
  401. void DragAndDropContainer::dragOperationStarted (const DragAndDropTarget::SourceDetails&) {}
  402. void DragAndDropContainer::dragOperationEnded (const DragAndDropTarget::SourceDetails&) {}
  403. //==============================================================================
  404. DragAndDropTarget::SourceDetails::SourceDetails (const var& desc, Component* comp, Point<int> pos) noexcept
  405. : description (desc),
  406. sourceComponent (comp),
  407. localPosition (pos)
  408. {
  409. }
  410. void DragAndDropTarget::itemDragEnter (const SourceDetails&) {}
  411. void DragAndDropTarget::itemDragMove (const SourceDetails&) {}
  412. void DragAndDropTarget::itemDragExit (const SourceDetails&) {}
  413. bool DragAndDropTarget::shouldDrawDragImageWhenOver() { return true; }
  414. //==============================================================================
  415. void FileDragAndDropTarget::fileDragEnter (const StringArray&, int, int) {}
  416. void FileDragAndDropTarget::fileDragMove (const StringArray&, int, int) {}
  417. void FileDragAndDropTarget::fileDragExit (const StringArray&) {}
  418. void TextDragAndDropTarget::textDragEnter (const String&, int, int) {}
  419. void TextDragAndDropTarget::textDragMove (const String&, int, int) {}
  420. void TextDragAndDropTarget::textDragExit (const String&) {}