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.

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