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.

439 lines
16KB

  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. bool juce_performDragDropFiles (const StringArray&, const bool copyFiles, bool& shouldStop);
  19. bool juce_performDragDropText (const String&, bool& shouldStop);
  20. //==============================================================================
  21. class DragImageComponent : public Component,
  22. public Timer
  23. {
  24. public:
  25. DragImageComponent (const Image& im,
  26. const var& desc,
  27. Component* const sourceComponent,
  28. Component* const mouseDragSource_,
  29. DragAndDropContainer& owner_,
  30. const Point<int>& imageOffset_)
  31. : sourceDetails (desc, sourceComponent, Point<int>()),
  32. image (im),
  33. owner (owner_),
  34. mouseDragSource (mouseDragSource_),
  35. imageOffset (imageOffset_),
  36. hasCheckedForExternalDrag (false),
  37. isDoingExternalDrag (false)
  38. {
  39. setSize (im.getWidth(), im.getHeight());
  40. if (mouseDragSource == nullptr)
  41. mouseDragSource = sourceComponent;
  42. mouseDragSource->addMouseListener (this, false);
  43. startTimer (200);
  44. setInterceptsMouseClicks (false, false);
  45. setAlwaysOnTop (true);
  46. }
  47. ~DragImageComponent()
  48. {
  49. if (owner.dragImageComponent == this)
  50. owner.dragImageComponent.release();
  51. if (mouseDragSource != nullptr)
  52. {
  53. mouseDragSource->removeMouseListener (this);
  54. DragAndDropTarget* const current = getCurrentlyOver();
  55. if (current != nullptr && current->isInterestedInDragSource (sourceDetails))
  56. current->itemDragExit (sourceDetails);
  57. }
  58. }
  59. void paint (Graphics& g)
  60. {
  61. if (isOpaque())
  62. g.fillAll (Colours::white);
  63. g.setOpacity (1.0f);
  64. g.drawImageAt (image, 0, 0);
  65. }
  66. void mouseUp (const MouseEvent& e)
  67. {
  68. if (e.originalComponent != this)
  69. {
  70. if (mouseDragSource != nullptr)
  71. mouseDragSource->removeMouseListener (this);
  72. // (note: use a local copy of this in case the callback runs
  73. // a modal loop and deletes this object before the method completes)
  74. DragAndDropTarget::SourceDetails details (sourceDetails);
  75. DragAndDropTarget* finalTarget = nullptr;
  76. if (! isDoingExternalDrag)
  77. {
  78. const bool wasVisible = isVisible();
  79. setVisible (false);
  80. finalTarget = findTarget (e.getScreenPosition(), details.localPosition);
  81. if (wasVisible) // fade the component and remove it - it'll be deleted later by the timer callback
  82. dismissWithAnimation (finalTarget == nullptr);
  83. }
  84. if (getParentComponent() != nullptr)
  85. getParentComponent()->removeChildComponent (this);
  86. if (finalTarget != nullptr)
  87. {
  88. currentlyOverComp = nullptr;
  89. finalTarget->itemDropped (details);
  90. }
  91. // careful - this object could now be deleted..
  92. }
  93. }
  94. void mouseDrag (const MouseEvent& e)
  95. {
  96. if (e.originalComponent != this)
  97. updateLocation (true, e.getScreenPosition());
  98. }
  99. void updateLocation (const bool canDoExternalDrag, const Point<int>& screenPos)
  100. {
  101. DragAndDropTarget::SourceDetails details (sourceDetails);
  102. setNewScreenPos (screenPos);
  103. DragAndDropTarget* const newTarget = findTarget (screenPos, details.localPosition);
  104. Component* newTargetComp = dynamic_cast <Component*> (newTarget);
  105. setVisible (newTarget == nullptr || newTarget->shouldDrawDragImageWhenOver());
  106. if (newTargetComp != currentlyOverComp)
  107. {
  108. DragAndDropTarget* const lastTarget = getCurrentlyOver();
  109. if (lastTarget != nullptr && details.sourceComponent != nullptr
  110. && lastTarget->isInterestedInDragSource (details))
  111. lastTarget->itemDragExit (details);
  112. currentlyOverComp = newTargetComp;
  113. if (newTarget != nullptr
  114. && newTarget->isInterestedInDragSource (details))
  115. newTarget->itemDragEnter (details);
  116. }
  117. sendDragMove (details);
  118. if (canDoExternalDrag && getCurrentlyOver() == nullptr)
  119. checkForExternalDrag (details, screenPos);
  120. }
  121. void timerCallback()
  122. {
  123. if (sourceDetails.sourceComponent == nullptr)
  124. {
  125. delete this;
  126. }
  127. else if (! isMouseButtonDownAnywhere())
  128. {
  129. if (mouseDragSource != nullptr)
  130. mouseDragSource->removeMouseListener (this);
  131. delete this;
  132. }
  133. }
  134. private:
  135. DragAndDropTarget::SourceDetails sourceDetails;
  136. Image image;
  137. DragAndDropContainer& owner;
  138. WeakReference<Component> mouseDragSource, currentlyOverComp;
  139. const Point<int> imageOffset;
  140. bool hasCheckedForExternalDrag, isDoingExternalDrag;
  141. DragAndDropTarget* getCurrentlyOver() const noexcept
  142. {
  143. return dynamic_cast <DragAndDropTarget*> (currentlyOverComp.get());
  144. }
  145. DragAndDropTarget* findTarget (const Point<int>& screenPos, Point<int>& relativePos) const
  146. {
  147. Component* hit = getParentComponent();
  148. if (hit == nullptr)
  149. hit = Desktop::getInstance().findComponentAt (screenPos);
  150. else
  151. hit = hit->getComponentAt (hit->getLocalPoint (nullptr, screenPos));
  152. // (note: use a local copy of this in case the callback runs
  153. // a modal loop and deletes this object before the method completes)
  154. const DragAndDropTarget::SourceDetails details (sourceDetails);
  155. while (hit != nullptr)
  156. {
  157. DragAndDropTarget* const ddt = dynamic_cast <DragAndDropTarget*> (hit);
  158. if (ddt != nullptr && ddt->isInterestedInDragSource (details))
  159. {
  160. relativePos = hit->getLocalPoint (nullptr, screenPos);
  161. return ddt;
  162. }
  163. hit = hit->getParentComponent();
  164. }
  165. return nullptr;
  166. }
  167. void setNewScreenPos (const Point<int>& screenPos)
  168. {
  169. Point<int> newPos (screenPos - imageOffset);
  170. if (getParentComponent() != nullptr)
  171. newPos = getParentComponent()->getLocalPoint (nullptr, newPos);
  172. setTopLeftPosition (newPos);
  173. }
  174. void sendDragMove (DragAndDropTarget::SourceDetails& details) const
  175. {
  176. DragAndDropTarget* const target = getCurrentlyOver();
  177. if (target != nullptr && target->isInterestedInDragSource (details))
  178. target->itemDragMove (details);
  179. }
  180. void checkForExternalDrag (DragAndDropTarget::SourceDetails& details, const Point<int>& screenPos)
  181. {
  182. if (! hasCheckedForExternalDrag)
  183. {
  184. if (Desktop::getInstance().findComponentAt (screenPos) == nullptr)
  185. {
  186. hasCheckedForExternalDrag = true;
  187. StringArray files;
  188. bool canMoveFiles = false;
  189. if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles)
  190. && files.size() > 0)
  191. {
  192. WeakReference<Component> thisWeakRef (this);
  193. setVisible (false);
  194. isDoingExternalDrag = true;
  195. if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown())
  196. DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles);
  197. delete thisWeakRef.get();
  198. return;
  199. }
  200. }
  201. }
  202. }
  203. void dismissWithAnimation (const bool shouldSnapBack)
  204. {
  205. setVisible (true);
  206. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  207. if (shouldSnapBack && sourceDetails.sourceComponent != nullptr)
  208. {
  209. const Point<int> target (sourceDetails.sourceComponent->localPointToGlobal (sourceDetails.sourceComponent->getLocalBounds().getCentre()));
  210. const Point<int> ourCentre (localPointToGlobal (getLocalBounds().getCentre()));
  211. animator.animateComponent (this,
  212. getBounds() + (target - ourCentre),
  213. 0.0f, 120,
  214. true, 1.0, 1.0);
  215. }
  216. else
  217. {
  218. animator.fadeOut (this, 120);
  219. }
  220. }
  221. JUCE_DECLARE_NON_COPYABLE (DragImageComponent);
  222. };
  223. //==============================================================================
  224. DragAndDropContainer::DragAndDropContainer()
  225. {
  226. }
  227. DragAndDropContainer::~DragAndDropContainer()
  228. {
  229. dragImageComponent = nullptr;
  230. }
  231. void DragAndDropContainer::startDragging (const var& sourceDescription,
  232. Component* sourceComponent,
  233. const Image& dragImage_,
  234. const bool allowDraggingToExternalWindows,
  235. const Point<int>* imageOffsetFromMouse)
  236. {
  237. Image dragImage (dragImage_);
  238. if (dragImageComponent == nullptr)
  239. {
  240. Component* const thisComp = dynamic_cast <Component*> (this);
  241. if (thisComp == nullptr)
  242. {
  243. jassertfalse; // Your DragAndDropContainer needs to be a Component!
  244. return;
  245. }
  246. MouseInputSource* draggingSource = Desktop::getInstance().getDraggingMouseSource (0);
  247. if (draggingSource == nullptr || ! draggingSource->isDragging())
  248. {
  249. jassertfalse; // You must call startDragging() from within a mouseDown or mouseDrag callback!
  250. return;
  251. }
  252. const Point<int> lastMouseDown (Desktop::getLastMouseDownPosition());
  253. Point<int> imageOffset;
  254. if (dragImage.isNull())
  255. {
  256. dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds())
  257. .convertedToFormat (Image::ARGB);
  258. dragImage.multiplyAllAlphas (0.6f);
  259. const int lo = 150;
  260. const int hi = 400;
  261. Point<int> relPos (sourceComponent->getLocalPoint (nullptr, lastMouseDown));
  262. Point<int> clipped (dragImage.getBounds().getConstrainedPoint (relPos));
  263. Random random;
  264. for (int y = dragImage.getHeight(); --y >= 0;)
  265. {
  266. const double dy = (y - clipped.getY()) * (y - clipped.getY());
  267. for (int x = dragImage.getWidth(); --x >= 0;)
  268. {
  269. const int dx = x - clipped.getX();
  270. const int distance = roundToInt (std::sqrt (dx * dx + dy));
  271. if (distance > lo)
  272. {
  273. const float alpha = (distance > hi) ? 0
  274. : (hi - distance) / (float) (hi - lo)
  275. + random.nextFloat() * 0.008f;
  276. dragImage.multiplyAlphaAt (x, y, alpha);
  277. }
  278. }
  279. }
  280. imageOffset = clipped;
  281. }
  282. else
  283. {
  284. if (imageOffsetFromMouse == nullptr)
  285. imageOffset = dragImage.getBounds().getCentre();
  286. else
  287. imageOffset = dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse);
  288. }
  289. dragImageComponent = new DragImageComponent (dragImage, sourceDescription, sourceComponent,
  290. draggingSource->getComponentUnderMouse(), *this, imageOffset);
  291. currentDragDesc = sourceDescription;
  292. if (allowDraggingToExternalWindows)
  293. {
  294. if (! Desktop::canUseSemiTransparentWindows())
  295. dragImageComponent->setOpaque (true);
  296. dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks
  297. | ComponentPeer::windowIsTemporary
  298. | ComponentPeer::windowIgnoresKeyPresses);
  299. }
  300. else
  301. thisComp->addChildComponent (dragImageComponent);
  302. static_cast <DragImageComponent*> (dragImageComponent.get())->updateLocation (false, lastMouseDown);
  303. dragImageComponent->setVisible (true);
  304. #if JUCE_WINDOWS
  305. // Under heavy load, the layered window's paint callback can often be lost by the OS,
  306. // so forcing a repaint at least once makes sure that the window becomes visible..
  307. ComponentPeer* const peer = dragImageComponent->getPeer();
  308. if (peer != nullptr)
  309. peer->performAnyPendingRepaintsNow();
  310. #endif
  311. }
  312. }
  313. bool DragAndDropContainer::isDragAndDropActive() const
  314. {
  315. return dragImageComponent != nullptr;
  316. }
  317. String DragAndDropContainer::getCurrentDragDescription() const
  318. {
  319. return dragImageComponent != nullptr ? currentDragDesc
  320. : String::empty;
  321. }
  322. DragAndDropContainer* DragAndDropContainer::findParentDragContainerFor (Component* c)
  323. {
  324. return c == nullptr ? nullptr : c->findParentComponentOfClass ((DragAndDropContainer*) nullptr);
  325. }
  326. bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, StringArray&, bool&)
  327. {
  328. return false;
  329. }
  330. //==============================================================================
  331. DragAndDropTarget::SourceDetails::SourceDetails (const var& description_, Component* sourceComponent_, const Point<int>& localPosition_) noexcept
  332. : description (description_),
  333. sourceComponent (sourceComponent_),
  334. localPosition (localPosition_)
  335. {
  336. }
  337. void DragAndDropTarget::itemDragEnter (const SourceDetails&) {}
  338. void DragAndDropTarget::itemDragMove (const SourceDetails&) {}
  339. void DragAndDropTarget::itemDragExit (const SourceDetails&) {}
  340. bool DragAndDropTarget::shouldDrawDragImageWhenOver() { return true; }
  341. //==============================================================================
  342. void FileDragAndDropTarget::fileDragEnter (const StringArray&, int, int) {}
  343. void FileDragAndDropTarget::fileDragMove (const StringArray&, int, int) {}
  344. void FileDragAndDropTarget::fileDragExit (const StringArray&) {}