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.

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