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.

451 lines
14KB

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