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.

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