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.

431 lines
13KB

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