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.

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