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.

427 lines
13KB

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