Audio plugin host https://kx.studio/carla
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.

juce_DragAndDropContainer.cpp 17KB

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