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.

429 lines
13KB

  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. #include "../../jucer_Headers.h"
  20. #include "../../Application/jucer_Application.h"
  21. #include "jucer_ComponentLayoutEditor.h"
  22. #include "../ui/jucer_JucerCommandIDs.h"
  23. #include "../jucer_ObjectTypes.h"
  24. #include "../components/jucer_JucerComponentHandler.h"
  25. //==============================================================================
  26. class SubComponentHolderComp : public Component
  27. {
  28. public:
  29. SubComponentHolderComp (JucerDocument& doc,
  30. SnapGridPainter& g)
  31. : document (doc), grid (g),
  32. dontFillBackground (false)
  33. {
  34. setInterceptsMouseClicks (false, false);
  35. setWantsKeyboardFocus (false);
  36. setFocusContainer (true);
  37. }
  38. void paint (Graphics& g) override
  39. {
  40. if (! dontFillBackground)
  41. {
  42. if (PaintRoutine* const background = document.getPaintRoutine (0))
  43. {
  44. background->fillWithBackground (g, false);
  45. background->drawElements (g, getLocalBounds());
  46. grid.draw (g, background);
  47. }
  48. else
  49. grid.draw (g, nullptr);
  50. }
  51. }
  52. void resized() override
  53. {
  54. if (! getBounds().isEmpty())
  55. {
  56. int numTimesToTry = 10;
  57. while (--numTimesToTry >= 0)
  58. {
  59. bool anyCompsMoved = false;
  60. for (int i = 0; i < getNumChildComponents(); ++i)
  61. {
  62. Component* comp = getChildComponent (i);
  63. if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
  64. {
  65. const Rectangle<int> newBounds (type->getComponentPosition (comp)
  66. .getRectangle (getLocalBounds(),
  67. document.getComponentLayout()));
  68. anyCompsMoved = anyCompsMoved || (comp->getBounds() != newBounds);
  69. comp->setBounds (newBounds);
  70. }
  71. }
  72. // repeat this loop until they've all stopped shuffling (might require a few
  73. // loops for all the relative positioned comps to settle down)
  74. if (! anyCompsMoved)
  75. break;
  76. }
  77. }
  78. }
  79. void moved() override
  80. {
  81. ((ComponentLayoutEditor*) getParentComponent())->updateOverlayPositions();
  82. }
  83. JucerDocument& document;
  84. SnapGridPainter& grid;
  85. bool dontFillBackground;
  86. };
  87. //==============================================================================
  88. ComponentLayoutEditor::ComponentLayoutEditor (JucerDocument& doc, ComponentLayout& cl)
  89. : document (doc), layout (cl), firstResize (true)
  90. {
  91. setWantsKeyboardFocus (true);
  92. addAndMakeVisible (subCompHolder = new SubComponentHolderComp (document, grid));
  93. refreshAllComponents();
  94. setSize (document.getInitialWidth(),
  95. document.getInitialHeight());
  96. }
  97. ComponentLayoutEditor::~ComponentLayoutEditor()
  98. {
  99. document.removeChangeListener (this);
  100. removeChildComponent (&lassoComp);
  101. deleteAllChildren();
  102. }
  103. //==============================================================================
  104. void ComponentLayoutEditor::visibilityChanged()
  105. {
  106. document.beginTransaction();
  107. if (isVisible())
  108. {
  109. refreshAllComponents();
  110. document.addChangeListener (this);
  111. }
  112. else
  113. {
  114. document.removeChangeListener (this);
  115. }
  116. }
  117. void ComponentLayoutEditor::changeListenerCallback (ChangeBroadcaster*)
  118. {
  119. refreshAllComponents();
  120. }
  121. void ComponentLayoutEditor::paint (Graphics&)
  122. {
  123. }
  124. void ComponentLayoutEditor::resized()
  125. {
  126. if (firstResize && getWidth() > 0 && getHeight() > 0)
  127. {
  128. firstResize = false;
  129. refreshAllComponents();
  130. }
  131. subCompHolder->setBounds (getComponentArea());
  132. updateOverlayPositions();
  133. }
  134. Rectangle<int> ComponentLayoutEditor::getComponentArea() const
  135. {
  136. const int editorEdgeGap = 4;
  137. if (document.isFixedSize())
  138. return Rectangle<int> ((getWidth() - document.getInitialWidth()) / 2,
  139. (getHeight() - document.getInitialHeight()) / 2,
  140. document.getInitialWidth(),
  141. document.getInitialHeight());
  142. return Rectangle<int> (editorEdgeGap, editorEdgeGap,
  143. getWidth() - editorEdgeGap * 2,
  144. getHeight() - editorEdgeGap * 2);
  145. }
  146. Image ComponentLayoutEditor::createComponentLayerSnapshot() const
  147. {
  148. ((SubComponentHolderComp*) subCompHolder)->dontFillBackground = true;
  149. Image im = subCompHolder->createComponentSnapshot (Rectangle<int> (0, 0, subCompHolder->getWidth(), subCompHolder->getHeight()));
  150. ((SubComponentHolderComp*) subCompHolder)->dontFillBackground = false;
  151. return im;
  152. }
  153. void ComponentLayoutEditor::updateOverlayPositions()
  154. {
  155. for (int i = getNumChildComponents(); --i >= 0;)
  156. if (ComponentOverlayComponent* const overlay = dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)))
  157. overlay->updateBoundsToMatchTarget();
  158. }
  159. void ComponentLayoutEditor::refreshAllComponents()
  160. {
  161. for (int i = getNumChildComponents(); --i >= 0;)
  162. {
  163. ScopedPointer<ComponentOverlayComponent> overlay (dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)));
  164. if (overlay != nullptr && layout.containsComponent (overlay->target))
  165. overlay.release();
  166. }
  167. for (int i = subCompHolder->getNumChildComponents(); --i >= 0;)
  168. {
  169. Component* const comp = subCompHolder->getChildComponent (i);
  170. if (! layout.containsComponent (comp))
  171. subCompHolder->removeChildComponent (comp);
  172. }
  173. Component* lastComp = nullptr;
  174. Component* lastOverlay = nullptr;
  175. for (int i = layout.getNumComponents(); --i >= 0;)
  176. {
  177. Component* const c = layout.getComponent (i);
  178. jassert (c != nullptr);
  179. ComponentOverlayComponent* overlay = getOverlayCompFor (c);
  180. bool isNewOverlay = false;
  181. if (overlay == 0)
  182. {
  183. ComponentTypeHandler* const handler = ComponentTypeHandler::getHandlerFor (*c);
  184. jassert (handler != nullptr);
  185. overlay = handler->createOverlayComponent (c, layout);
  186. addAndMakeVisible (overlay);
  187. isNewOverlay = true;
  188. }
  189. if (lastOverlay != nullptr)
  190. overlay->toBehind (lastOverlay);
  191. else
  192. overlay->toFront (false);
  193. lastOverlay = overlay;
  194. subCompHolder->addAndMakeVisible (c);
  195. if (lastComp != nullptr)
  196. c->toBehind (lastComp);
  197. else
  198. c->toFront (false);
  199. lastComp = c;
  200. c->setWantsKeyboardFocus (false);
  201. c->setFocusContainer (true);
  202. if (isNewOverlay)
  203. overlay->updateBoundsToMatchTarget();
  204. }
  205. if (grid.updateFromDesign (document))
  206. subCompHolder->repaint();
  207. subCompHolder->setBounds (getComponentArea());
  208. subCompHolder->resized();
  209. }
  210. void ComponentLayoutEditor::mouseDown (const MouseEvent& e)
  211. {
  212. if (e.mods.isPopupMenu())
  213. {
  214. ApplicationCommandManager* commandManager = &ProjucerApplication::getCommandManager();
  215. PopupMenu m;
  216. m.addCommandItem (commandManager, JucerCommandIDs::editCompLayout);
  217. m.addCommandItem (commandManager, JucerCommandIDs::editCompGraphics);
  218. m.addSeparator();
  219. for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
  220. m.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i);
  221. m.show();
  222. }
  223. else
  224. {
  225. addChildComponent (lassoComp);
  226. lassoComp.beginLasso (e, this);
  227. }
  228. }
  229. void ComponentLayoutEditor::mouseDrag (const MouseEvent& e)
  230. {
  231. lassoComp.toFront (false);
  232. lassoComp.dragLasso (e);
  233. }
  234. void ComponentLayoutEditor::mouseUp (const MouseEvent& e)
  235. {
  236. lassoComp.endLasso();
  237. removeChildComponent (&lassoComp);
  238. if (! (e.mouseWasDraggedSinceMouseDown() || e.mods.isAnyModifierKeyDown()))
  239. layout.getSelectedSet().deselectAll();
  240. }
  241. static void moveOrStretch (ComponentLayout& layout, int x, int y, bool snap, bool stretch)
  242. {
  243. if (stretch)
  244. layout.stretchSelectedComps (x, y, snap);
  245. else
  246. layout.moveSelectedComps (x, y, snap);
  247. }
  248. bool ComponentLayoutEditor::keyPressed (const KeyPress& key)
  249. {
  250. const bool snap = key.getModifiers().isAltDown();
  251. const bool stretch = key.getModifiers().isShiftDown();
  252. const int amount = snap ? document.getSnappingGridSize() + 1
  253. : 1;
  254. if (key.isKeyCode (KeyPress::rightKey))
  255. {
  256. moveOrStretch (layout, amount, 0, snap, stretch);
  257. }
  258. else if (key.isKeyCode (KeyPress::downKey))
  259. {
  260. moveOrStretch (layout, 0,amount, snap, stretch);
  261. }
  262. else if (key.isKeyCode (KeyPress::leftKey))
  263. {
  264. moveOrStretch (layout, -amount, 0, snap, stretch);
  265. }
  266. else if (key.isKeyCode (KeyPress::upKey))
  267. {
  268. moveOrStretch (layout, 0, -amount, snap, stretch);
  269. }
  270. else
  271. {
  272. return false;
  273. }
  274. return true;
  275. }
  276. bool ComponentLayoutEditor::isInterestedInFileDrag (const StringArray& filenames)
  277. {
  278. const File f (filenames [0]);
  279. return f.hasFileExtension (".cpp");
  280. }
  281. void ComponentLayoutEditor::filesDropped (const StringArray& filenames, int x, int y)
  282. {
  283. const File f (filenames [0]);
  284. if (JucerDocument::isValidJucerCppFile (f))
  285. {
  286. JucerComponentHandler jucerDocHandler;
  287. layout.getDocument()->beginTransaction();
  288. if (TestComponent* newOne = dynamic_cast<TestComponent*> (layout.addNewComponent (&jucerDocHandler,
  289. x - subCompHolder->getX(),
  290. y - subCompHolder->getY())))
  291. {
  292. JucerComponentHandler::setJucerComponentFile (*layout.getDocument(), newOne,
  293. f.getRelativePathFrom (document.getCppFile().getParentDirectory()));
  294. layout.getSelectedSet().selectOnly (newOne);
  295. }
  296. layout.getDocument()->beginTransaction();
  297. }
  298. }
  299. bool ComponentLayoutEditor::isInterestedInDragSource (const SourceDetails& dragSourceDetails)
  300. {
  301. if (dragSourceDetails.description != projectItemDragType)
  302. return false;
  303. OwnedArray<Project::Item> selectedNodes;
  304. ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  305. return selectedNodes.size() > 0;
  306. }
  307. void ComponentLayoutEditor::itemDropped (const SourceDetails& dragSourceDetails)
  308. {
  309. OwnedArray <Project::Item> selectedNodes;
  310. ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
  311. StringArray filenames;
  312. for (int i = 0; i < selectedNodes.size(); ++i)
  313. if (selectedNodes.getUnchecked(i)->getFile().hasFileExtension (".cpp"))
  314. filenames.add (selectedNodes.getUnchecked(i)->getFile().getFullPathName());
  315. filesDropped (filenames, dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y);
  316. }
  317. ComponentOverlayComponent* ComponentLayoutEditor::getOverlayCompFor (Component* compToFind) const
  318. {
  319. for (int i = getNumChildComponents(); --i >= 0;)
  320. {
  321. if (ComponentOverlayComponent* const overlay = dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)))
  322. if (overlay->target == compToFind)
  323. return overlay;
  324. }
  325. return nullptr;
  326. }
  327. void ComponentLayoutEditor::findLassoItemsInArea (Array <Component*>& results, const Rectangle<int>& area)
  328. {
  329. const Rectangle<int> lasso (area - subCompHolder->getPosition());
  330. for (int i = 0; i < subCompHolder->getNumChildComponents(); ++i)
  331. {
  332. Component* c = subCompHolder->getChildComponent (i);
  333. if (c->getBounds().intersects (lasso))
  334. results.add (c);
  335. }
  336. }
  337. SelectedItemSet <Component*>& ComponentLayoutEditor::getLassoSelection()
  338. {
  339. return layout.getSelectedSet();
  340. }