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.

1106 lines
36KB

  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 "jucer_JucerDocument.h"
  20. #include "jucer_ObjectTypes.h"
  21. #include "UI/jucer_JucerDocumentEditor.h"
  22. #include "Components/jucer_ComponentUndoableAction.h"
  23. //==============================================================================
  24. ComponentLayout::ComponentLayout()
  25. : document (nullptr),
  26. nextCompUID (1)
  27. {
  28. }
  29. ComponentLayout::~ComponentLayout()
  30. {
  31. components.clear();
  32. }
  33. //==============================================================================
  34. void ComponentLayout::changed()
  35. {
  36. if (document != nullptr)
  37. document->changed();
  38. }
  39. void ComponentLayout::perform (std::unique_ptr<UndoableAction> action, const String& actionName)
  40. {
  41. jassert (document != nullptr);
  42. if (document != nullptr)
  43. document->getUndoManager().perform (action.release(), actionName);
  44. else
  45. action->perform();
  46. }
  47. //==============================================================================
  48. void ComponentLayout::clearComponents()
  49. {
  50. selected.deselectAll();
  51. selected.dispatchPendingMessages();
  52. components.clear();
  53. changed();
  54. }
  55. //==============================================================================
  56. class AddCompAction : public UndoableAction
  57. {
  58. public:
  59. AddCompAction (XmlElement* const xml_, ComponentLayout& layout_, int& index)
  60. : indexRef (index),
  61. xml (xml_),
  62. layout (layout_)
  63. {
  64. }
  65. bool perform()
  66. {
  67. showCorrectTab();
  68. Component* const newComp = layout.addComponentFromXml (*xml, false);
  69. jassert (newComp != nullptr);
  70. indexRef = layout.indexOfComponent (newComp);
  71. jassert (indexRef >= 0);
  72. return indexRef >= 0;
  73. }
  74. bool undo()
  75. {
  76. showCorrectTab();
  77. layout.removeComponent (layout.getComponent (indexRef), false);
  78. return true;
  79. }
  80. int getSizeInUnits() { return 10; }
  81. int& indexRef;
  82. private:
  83. std::unique_ptr<XmlElement> xml;
  84. ComponentLayout& layout;
  85. static void showCorrectTab()
  86. {
  87. if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder())
  88. ed->showLayout();
  89. }
  90. AddCompAction (const AddCompAction&);
  91. AddCompAction& operator= (const AddCompAction&);
  92. };
  93. //==============================================================================
  94. class DeleteCompAction : public ComponentUndoableAction <Component>
  95. {
  96. public:
  97. DeleteCompAction (Component* const comp, ComponentLayout& l)
  98. : ComponentUndoableAction <Component> (comp, l),
  99. oldIndex (-1)
  100. {
  101. if (ComponentTypeHandler* const h = ComponentTypeHandler::getHandlerFor (*comp))
  102. xml.reset (h->createXmlFor (comp, &layout));
  103. else
  104. jassertfalse;
  105. oldIndex = l.indexOfComponent (comp);
  106. }
  107. bool perform()
  108. {
  109. showCorrectTab();
  110. layout.removeComponent (getComponent(), false);
  111. return true;
  112. }
  113. bool undo()
  114. {
  115. Component* c = layout.addComponentFromXml (*xml, false);
  116. jassert (c != nullptr);
  117. layout.moveComponentZOrder (layout.indexOfComponent (c), oldIndex);
  118. showCorrectTab();
  119. return c != nullptr;
  120. }
  121. private:
  122. std::unique_ptr<XmlElement> xml;
  123. int oldIndex;
  124. };
  125. void ComponentLayout::removeComponent (Component* comp, const bool undoable)
  126. {
  127. if (comp != nullptr && components.contains (comp))
  128. {
  129. if (undoable)
  130. {
  131. perform (std::make_unique<DeleteCompAction> (comp, *this), "Delete components");
  132. }
  133. else
  134. {
  135. selected.deselect (comp);
  136. selected.changed (true); // synchronous message to get rid of any property components
  137. components.removeObject (comp);
  138. changed();
  139. }
  140. }
  141. }
  142. //==============================================================================
  143. class FrontBackCompAction : public ComponentUndoableAction <Component>
  144. {
  145. public:
  146. FrontBackCompAction (Component* const comp, ComponentLayout& l, int newIndex_)
  147. : ComponentUndoableAction <Component> (comp, l),
  148. newIndex (newIndex_)
  149. {
  150. oldIndex = l.indexOfComponent (comp);
  151. }
  152. bool perform()
  153. {
  154. showCorrectTab();
  155. Component* comp = layout.getComponent (oldIndex);
  156. layout.moveComponentZOrder (oldIndex, newIndex);
  157. newIndex = layout.indexOfComponent (comp);
  158. return true;
  159. }
  160. bool undo()
  161. {
  162. showCorrectTab();
  163. layout.moveComponentZOrder (newIndex, oldIndex);
  164. return true;
  165. }
  166. private:
  167. int newIndex, oldIndex;
  168. };
  169. void ComponentLayout::moveComponentZOrder (int oldIndex, int newIndex)
  170. {
  171. jassert (components [oldIndex] != nullptr);
  172. if (oldIndex != newIndex && components [oldIndex] != nullptr)
  173. {
  174. components.move (oldIndex, newIndex);
  175. changed();
  176. }
  177. }
  178. void ComponentLayout::componentToFront (Component* comp, const bool undoable)
  179. {
  180. if (comp != nullptr && components.contains (comp))
  181. {
  182. if (undoable)
  183. perform (std::make_unique<FrontBackCompAction> (comp, *this, -1), "Move components to front");
  184. else
  185. moveComponentZOrder (components.indexOf (comp), -1);
  186. }
  187. }
  188. void ComponentLayout::componentToBack (Component* comp, const bool undoable)
  189. {
  190. if (comp != nullptr && components.contains (comp))
  191. {
  192. if (undoable)
  193. perform (std::make_unique<FrontBackCompAction> (comp, *this, 0), "Move components to back");
  194. else
  195. moveComponentZOrder (components.indexOf (comp), 0);
  196. }
  197. }
  198. //==============================================================================
  199. const char* const ComponentLayout::clipboardXmlTag = "COMPONENTS";
  200. void ComponentLayout::copySelectedToClipboard()
  201. {
  202. if (selected.getNumSelected() == 0)
  203. return;
  204. XmlElement clip (clipboardXmlTag);
  205. for (int i = 0; i < components.size(); ++i)
  206. {
  207. auto c = components.getUnchecked (i);
  208. if (selected.isSelected (c))
  209. {
  210. if (auto type = ComponentTypeHandler::getHandlerFor (*c))
  211. {
  212. auto e = type->createXmlFor (c, this);
  213. clip.addChildElement (e);
  214. }
  215. }
  216. }
  217. SystemClipboard::copyTextToClipboard (clip.toString());
  218. }
  219. void ComponentLayout::paste()
  220. {
  221. if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), clipboardXmlTag))
  222. {
  223. selected.deselectAll();
  224. for (auto* e : doc->getChildIterator())
  225. if (Component* newComp = addComponentFromXml (*e, true))
  226. selected.addToSelection (newComp);
  227. startDragging();
  228. dragSelectedComps (Random::getSystemRandom().nextInt (40),
  229. Random::getSystemRandom().nextInt (40));
  230. endDragging();
  231. }
  232. }
  233. void ComponentLayout::deleteSelected()
  234. {
  235. const SelectedItemSet <Component*> temp (selected);
  236. selected.deselectAll();
  237. selected.changed (true); // synchronous message to get rid of any property components
  238. if (temp.getNumSelected() > 0)
  239. {
  240. for (int i = temp.getNumSelected(); --i >= 0;)
  241. removeComponent (temp.getSelectedItem (i), true);
  242. changed();
  243. if (document != nullptr)
  244. document->dispatchPendingMessages(); // forces the change to propagate before a paint() callback can happen,
  245. // in case there are components floating around that are now dangling pointers
  246. }
  247. }
  248. void ComponentLayout::selectAll()
  249. {
  250. for (int i = 0; i < components.size(); ++i)
  251. selected.addToSelection (components.getUnchecked (i));
  252. }
  253. void ComponentLayout::selectedToFront()
  254. {
  255. const SelectedItemSet <Component*> temp (selected);
  256. for (int i = temp.getNumSelected(); --i >= 0;)
  257. componentToFront (temp.getSelectedItem (i), true);
  258. }
  259. void ComponentLayout::selectedToBack()
  260. {
  261. const SelectedItemSet <Component*> temp (selected);
  262. for (int i = 0; i < temp.getNumSelected(); ++i)
  263. componentToBack (temp.getSelectedItem (i), true);
  264. }
  265. void ComponentLayout::alignTop()
  266. {
  267. if (selected.getNumSelected() > 1)
  268. {
  269. auto* main = selected.getSelectedItem (0);
  270. auto yPos = main->getY();
  271. for (auto* other : selected)
  272. {
  273. if (other != main)
  274. setComponentBoundsAndProperties (other,
  275. other->getBounds().withPosition (other->getX(), yPos),
  276. main, true);
  277. }
  278. }
  279. }
  280. void ComponentLayout::alignRight()
  281. {
  282. if (selected.getNumSelected() > 1)
  283. {
  284. auto* main = selected.getSelectedItem (0);
  285. auto rightPos = main->getRight();
  286. for (auto* other : selected)
  287. {
  288. if (other != main)
  289. setComponentBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(), other->getY()), main, true);
  290. }
  291. }
  292. }
  293. void ComponentLayout::alignBottom()
  294. {
  295. if (selected.getNumSelected() > 1)
  296. {
  297. auto* main = selected.getSelectedItem (0);
  298. auto bottomPos = main->getBottom();
  299. for (auto* other : selected)
  300. {
  301. if (other != main)
  302. setComponentBoundsAndProperties (other, other->getBounds().withPosition (other->getX(), bottomPos - other->getHeight()), main, true);
  303. }
  304. }
  305. }
  306. void ComponentLayout::alignLeft()
  307. {
  308. if (selected.getNumSelected() > 1)
  309. {
  310. auto* main = selected.getSelectedItem (0);
  311. auto xPos = main->getX();
  312. for (auto* other : selected)
  313. {
  314. if (other != main)
  315. setComponentBoundsAndProperties (other, other->getBounds().withPosition (xPos, other->getY()), main, true);
  316. }
  317. }
  318. }
  319. void ComponentLayout::bringLostItemsBackOnScreen (int width, int height)
  320. {
  321. for (int i = components.size(); --i >= 0;)
  322. {
  323. Component* const c = components[i];
  324. if (! c->getBounds().intersects (Rectangle<int> (0, 0, width, height)))
  325. {
  326. c->setTopLeftPosition (width / 2, height / 2);
  327. updateStoredComponentPosition (c, false);
  328. }
  329. }
  330. }
  331. Component* ComponentLayout::addNewComponent (ComponentTypeHandler* const type, int x, int y)
  332. {
  333. std::unique_ptr<Component> c (type->createNewComponent (getDocument()));
  334. jassert (c != nullptr);
  335. if (c != nullptr)
  336. {
  337. c->setSize (type->getDefaultWidth(), type->getDefaultHeight());
  338. c->setCentrePosition (x, y);
  339. updateStoredComponentPosition (c.get(), false);
  340. c->getProperties().set ("id", nextCompUID++);
  341. std::unique_ptr<XmlElement> xml (type->createXmlFor (c.get(), this));
  342. c.reset (addComponentFromXml (*xml, true));
  343. String memberName (build_tools::makeValidIdentifier (type->getClassName (c.get()), true, true, false));
  344. setComponentMemberVariableName (c.get(), memberName);
  345. selected.selectOnly (c.get());
  346. }
  347. return c.release();
  348. }
  349. Component* ComponentLayout::addComponentFromXml (const XmlElement& xml, const bool undoable)
  350. {
  351. if (undoable)
  352. {
  353. perform (std::make_unique<AddCompAction> (new XmlElement (xml), *this, addComponentIndexAdded), "Add new components");
  354. return components [addComponentIndexAdded];
  355. }
  356. if (ComponentTypeHandler* const type
  357. = ComponentTypeHandler::getHandlerForXmlTag (xml.getTagName()))
  358. {
  359. std::unique_ptr<Component> newComp (type->createNewComponent (getDocument()));
  360. if (type->restoreFromXml (xml, newComp.get(), this))
  361. {
  362. // ensure that the new comp's name is unique
  363. setComponentMemberVariableName (newComp.get(), getComponentMemberVariableName (newComp.get()));
  364. // check for duped IDs..
  365. while (findComponentWithId (ComponentTypeHandler::getComponentId (newComp.get())) != nullptr)
  366. ComponentTypeHandler::setComponentId (newComp.get(), Random::getSystemRandom().nextInt64());
  367. components.add (newComp.get());
  368. changed();
  369. return newComp.release();
  370. }
  371. }
  372. return nullptr;
  373. }
  374. Component* ComponentLayout::findComponentWithId (const int64 componentId) const
  375. {
  376. for (int i = 0; i < components.size(); ++i)
  377. if (ComponentTypeHandler::getComponentId (components.getUnchecked (i)) == componentId)
  378. return components.getUnchecked (i);
  379. return nullptr;
  380. }
  381. //==============================================================================
  382. static const char* const dimensionSuffixes[] = { "X", "Y", "W", "H" };
  383. Component* ComponentLayout::getComponentRelativePosTarget (Component* comp, int whichDimension) const
  384. {
  385. jassert (comp != nullptr);
  386. if (PaintElement* const pe = dynamic_cast<PaintElement*> (comp))
  387. {
  388. int64 compId;
  389. if (whichDimension == 0) compId = pe->getPosition().relativeToX;
  390. else if (whichDimension == 1) compId = pe->getPosition().relativeToY;
  391. else if (whichDimension == 2) compId = pe->getPosition().relativeToW;
  392. else compId = pe->getPosition().relativeToH;
  393. return findComponentWithId (compId);
  394. }
  395. return findComponentWithId (comp->getProperties() [String ("relativeTo") + dimensionSuffixes [whichDimension]]
  396. .toString().getHexValue64());
  397. }
  398. void ComponentLayout::setComponentRelativeTarget (Component* comp, int whichDimension, Component* compToBeRelativeTo)
  399. {
  400. PaintElement* const pe = dynamic_cast<PaintElement*> (comp);
  401. jassert (comp != nullptr);
  402. jassert (pe != nullptr || components.contains (comp));
  403. jassert (compToBeRelativeTo == nullptr || components.contains (compToBeRelativeTo));
  404. jassert (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp));
  405. if (compToBeRelativeTo != getComponentRelativePosTarget (comp, whichDimension)
  406. && (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp)))
  407. {
  408. const int64 compId = ComponentTypeHandler::getComponentId (compToBeRelativeTo);
  409. Rectangle<int> oldBounds (comp->getBounds());
  410. RelativePositionedRectangle pos;
  411. if (pe != nullptr)
  412. {
  413. oldBounds = pe->getCurrentBounds (dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea());
  414. pos = pe->getPosition();
  415. }
  416. else
  417. {
  418. pos = ComponentTypeHandler::getComponentPosition (comp);
  419. }
  420. if (whichDimension == 0) pos.relativeToX = compId;
  421. else if (whichDimension == 1) pos.relativeToY = compId;
  422. else if (whichDimension == 2) pos.relativeToW = compId;
  423. else if (whichDimension == 3) pos.relativeToH = compId;
  424. if (pe != nullptr)
  425. {
  426. pe->setPosition (pos, true);
  427. pe->setCurrentBounds (oldBounds, dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea(), true);
  428. }
  429. else
  430. {
  431. setComponentPosition (comp, pos, true);
  432. comp->setBounds (oldBounds);
  433. updateStoredComponentPosition (comp, false);
  434. }
  435. changed();
  436. }
  437. }
  438. bool ComponentLayout::dependsOnComponentForRelativePos (Component* comp, Component* possibleDependee) const
  439. {
  440. for (int i = 0; i < 4; ++i)
  441. if (Component* const c = getComponentRelativePosTarget (comp, i))
  442. if (c == possibleDependee || dependsOnComponentForRelativePos (c, possibleDependee))
  443. return true;
  444. return false;
  445. }
  446. bool ComponentLayout::isComponentPositionRelative (Component* comp) const
  447. {
  448. for (int i = 0; i < getNumComponents(); ++i)
  449. if (dependsOnComponentForRelativePos (comp, getComponent (i)))
  450. return true;
  451. return false;
  452. }
  453. const int menuIdBase = 0x63240000;
  454. PopupMenu ComponentLayout::getRelativeTargetMenu (Component* comp, int whichDimension) const
  455. {
  456. PopupMenu m;
  457. auto current = getComponentRelativePosTarget (comp, whichDimension);
  458. m.addItem (menuIdBase, "Relative to parent component", true, current == nullptr);
  459. m.addSeparator();
  460. for (int i = 0; i < components.size(); ++i)
  461. {
  462. auto* const c = components.getUnchecked (i);
  463. if (c != nullptr && c != comp)
  464. m.addItem (menuIdBase + i + 1,
  465. "Relative to " + getComponentMemberVariableName (c)
  466. + " (class: " + ComponentTypeHandler::getHandlerFor (*c)->getClassName (c) + ")",
  467. ! dependsOnComponentForRelativePos (c, comp),
  468. current == c);
  469. }
  470. return m;
  471. }
  472. void ComponentLayout::processRelativeTargetMenuResult (Component* comp, int whichDimension, int menuResultID)
  473. {
  474. if (menuResultID != 0)
  475. {
  476. Component* const newTarget = components [menuResultID - menuIdBase - 1];
  477. setComponentRelativeTarget (comp, whichDimension, newTarget);
  478. }
  479. }
  480. //==============================================================================
  481. class ChangeCompPositionAction : public ComponentUndoableAction <Component>
  482. {
  483. public:
  484. ChangeCompPositionAction (Component* const comp, ComponentLayout& l,
  485. const RelativePositionedRectangle& newPos_)
  486. : ComponentUndoableAction <Component> (comp, l),
  487. newPos (newPos_), oldPos (ComponentTypeHandler::getComponentPosition (comp))
  488. {
  489. }
  490. bool perform()
  491. {
  492. showCorrectTab();
  493. layout.setComponentPosition (getComponent(), newPos, false);
  494. return true;
  495. }
  496. bool undo()
  497. {
  498. showCorrectTab();
  499. layout.setComponentPosition (getComponent(), oldPos, false);
  500. return true;
  501. }
  502. private:
  503. RelativePositionedRectangle newPos, oldPos;
  504. };
  505. class ChangeCompBoundsAndPropertiesAction : public ComponentUndoableAction<Component>
  506. {
  507. public:
  508. ChangeCompBoundsAndPropertiesAction (Component* const comp, ComponentLayout& l,
  509. const Rectangle<int>& bounds, const NamedValueSet& props)
  510. : ComponentUndoableAction <Component> (comp, l),
  511. newBounds (bounds),
  512. oldBounds (comp->getBounds()),
  513. newProps (props),
  514. oldProps (comp->getProperties())
  515. {
  516. }
  517. bool perform()
  518. {
  519. showCorrectTab();
  520. getComponent()->setBounds (newBounds);
  521. getComponent()->getProperties() = newProps;
  522. layout.updateStoredComponentPosition (getComponent(), false);
  523. return true;
  524. }
  525. bool undo()
  526. {
  527. showCorrectTab();
  528. getComponent()->setBounds (oldBounds);
  529. getComponent()->getProperties() = oldProps;
  530. layout.updateStoredComponentPosition (getComponent(), false);
  531. return true;
  532. }
  533. private:
  534. Rectangle<int> newBounds, oldBounds;
  535. NamedValueSet newProps, oldProps;
  536. };
  537. void ComponentLayout::setComponentPosition (Component* comp,
  538. const RelativePositionedRectangle& newPos,
  539. const bool undoable)
  540. {
  541. if (ComponentTypeHandler::getComponentPosition (comp) != newPos)
  542. {
  543. if (undoable)
  544. {
  545. perform (std::make_unique<ChangeCompPositionAction> (comp, *this, newPos), "Move components");
  546. }
  547. else
  548. {
  549. ComponentTypeHandler::setComponentPosition (comp, newPos, this);
  550. changed();
  551. }
  552. }
  553. }
  554. void ComponentLayout::setComponentBoundsAndProperties (Component* componentToPosition, const Rectangle<int>& newBounds,
  555. Component* referenceComponent, const bool undoable)
  556. {
  557. auto props = NamedValueSet (componentToPosition->getProperties());
  558. auto rect = ComponentTypeHandler::getComponentPosition (componentToPosition).rect;
  559. auto referenceComponentPosition = ComponentTypeHandler::getComponentPosition (referenceComponent);
  560. auto referenceComponentRect = referenceComponentPosition.rect;
  561. rect.setModes (referenceComponentRect.getAnchorPointX(), referenceComponentRect.getPositionModeX(),
  562. referenceComponentRect.getAnchorPointY(), referenceComponentRect.getPositionModeY(),
  563. referenceComponentRect.getWidthMode(), referenceComponentRect.getHeightMode(),
  564. componentToPosition->getBounds());
  565. props.set ("pos", rect.toString());
  566. props.set ("relativeToX", String::toHexString (referenceComponentPosition.relativeToX));
  567. props.set ("relativeToY", String::toHexString (referenceComponentPosition.relativeToY));
  568. props.set ("relativeToW", String::toHexString (referenceComponentPosition.relativeToW));
  569. props.set ("relativeToH", String::toHexString (referenceComponentPosition.relativeToH));
  570. if (componentToPosition->getBounds() != newBounds || componentToPosition->getProperties() != props)
  571. {
  572. if (undoable)
  573. {
  574. perform (std::make_unique<ChangeCompBoundsAndPropertiesAction> (componentToPosition, *this, newBounds, props), "Change component bounds");
  575. }
  576. else
  577. {
  578. componentToPosition->setBounds (newBounds);
  579. componentToPosition->getProperties() = props;
  580. updateStoredComponentPosition (componentToPosition, false);
  581. changed();
  582. }
  583. }
  584. }
  585. void ComponentLayout::updateStoredComponentPosition (Component* comp, const bool undoable)
  586. {
  587. RelativePositionedRectangle newPos (ComponentTypeHandler::getComponentPosition (comp));
  588. newPos.updateFromComponent (*comp, this);
  589. setComponentPosition (comp, newPos, undoable);
  590. }
  591. //==============================================================================
  592. void ComponentLayout::startDragging()
  593. {
  594. for (int i = 0; i < components.size(); ++i)
  595. {
  596. Component* const c = components[i];
  597. c->getProperties().set ("xDragStart", c->getX());
  598. c->getProperties().set ("yDragStart", c->getY());
  599. }
  600. jassert (document != nullptr);
  601. document->beginTransaction();
  602. }
  603. void ComponentLayout::dragSelectedComps (int dx, int dy, const bool allowSnap)
  604. {
  605. if (allowSnap && document != nullptr && selected.getNumSelected() > 1)
  606. {
  607. dx = document->snapPosition (dx);
  608. dy = document->snapPosition (dy);
  609. }
  610. for (int i = 0; i < selected.getNumSelected(); ++i)
  611. {
  612. Component* const c = selected.getSelectedItem (i);
  613. const int startX = c->getProperties() ["xDragStart"];
  614. const int startY = c->getProperties() ["yDragStart"];
  615. if (allowSnap && document != nullptr && selected.getNumSelected() == 1)
  616. {
  617. c->setTopLeftPosition (document->snapPosition (startX + dx),
  618. document->snapPosition (startY + dy));
  619. }
  620. else
  621. {
  622. c->setTopLeftPosition (startX + dx,
  623. startY + dy);
  624. }
  625. updateStoredComponentPosition (c, false);
  626. }
  627. }
  628. void ComponentLayout::endDragging()
  629. {
  630. // after the drag, roll back all the comps to their start position, then
  631. // back to their finish positions using an undoable command.
  632. document->beginTransaction();
  633. for (int i = 0; i < selected.getNumSelected(); ++i)
  634. {
  635. Component* const c = selected.getSelectedItem (i);
  636. const int newX = c->getX();
  637. const int newY = c->getY();
  638. const int startX = c->getProperties() ["xDragStart"];
  639. const int startY = c->getProperties() ["yDragStart"];
  640. c->setTopLeftPosition (startX, startY);
  641. updateStoredComponentPosition (c, false);
  642. c->setTopLeftPosition (newX, newY);
  643. updateStoredComponentPosition (c, true);
  644. }
  645. document->beginTransaction();
  646. }
  647. void ComponentLayout::moveSelectedComps (int dx, int dy, bool snap)
  648. {
  649. startDragging();
  650. dragSelectedComps (dx, dy, snap);
  651. endDragging();
  652. }
  653. void ComponentLayout::stretchSelectedComps (int dw, int dh, bool allowSnap)
  654. {
  655. int neww, newh;
  656. if (document != nullptr && selected.getNumSelected() == 1)
  657. {
  658. Component* const c = selected.getSelectedItem (0);
  659. if (allowSnap)
  660. {
  661. int bot = c->getBottom() + dh;
  662. int right = c->getRight() + dw;
  663. bot = (dh != 0) ? document->snapPosition (bot) : bot;
  664. right = (dw != 0) ? document->snapPosition (right) : right;
  665. newh = bot - c->getY();
  666. neww = right - c->getX();
  667. }
  668. else
  669. {
  670. newh = c->getHeight() + dh;
  671. neww = c->getWidth() + dw;
  672. }
  673. c->setSize (neww, newh);
  674. updateStoredComponentPosition (c, true);
  675. }
  676. else
  677. {
  678. for (int i = 0; i < selected.getNumSelected(); ++i)
  679. {
  680. Component* const c = selected.getSelectedItem (i);
  681. neww = c->getWidth() + dw;
  682. newh = c->getHeight() + dh;
  683. c->setSize (neww, newh);
  684. updateStoredComponentPosition (c, true);
  685. }
  686. }
  687. }
  688. //==============================================================================
  689. void ComponentLayout::fillInGeneratedCode (GeneratedCode& code) const
  690. {
  691. for (int i = 0; i < components.size(); ++i)
  692. if (Component* const comp = components.getUnchecked (i))
  693. if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
  694. type->fillInGeneratedCode (comp, code);
  695. }
  696. //==============================================================================
  697. String ComponentLayout::getComponentMemberVariableName (Component* comp) const
  698. {
  699. if (comp == nullptr)
  700. return {};
  701. String name (comp->getProperties() ["memberName"].toString());
  702. if (name.isEmpty())
  703. name = getUnusedMemberName (build_tools::makeValidIdentifier (comp->getName(), true, true, false), comp);
  704. return name;
  705. }
  706. void ComponentLayout::setComponentMemberVariableName (Component* comp, const String& newName)
  707. {
  708. jassert (comp != nullptr);
  709. const String oldName (getComponentMemberVariableName (comp));
  710. comp->getProperties().set ("memberName", String());
  711. const String n (getUnusedMemberName (build_tools::makeValidIdentifier (newName, false, true, false), comp));
  712. comp->getProperties().set ("memberName", n);
  713. if (n != oldName)
  714. changed();
  715. }
  716. String ComponentLayout::getUnusedMemberName (String nameRoot, Component* comp) const
  717. {
  718. String n (nameRoot);
  719. while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
  720. nameRoot = nameRoot.dropLastCharacters (1);
  721. int suffix = 2;
  722. for (;;)
  723. {
  724. bool alreadyUsed = false;
  725. for (int i = 0; i < components.size(); ++i)
  726. {
  727. if (components[i] != comp
  728. && components[i]->getProperties() ["memberName"] == n)
  729. {
  730. alreadyUsed = true;
  731. break;
  732. }
  733. }
  734. if (! alreadyUsed)
  735. break;
  736. n = nameRoot + String (suffix++);
  737. }
  738. return n;
  739. }
  740. //==============================================================================
  741. String ComponentLayout::getComponentVirtualClassName (Component* comp) const
  742. {
  743. if (comp == nullptr)
  744. return {};
  745. return comp->getProperties() ["virtualName"];
  746. }
  747. void ComponentLayout::setComponentVirtualClassName (Component* comp, const String& newName)
  748. {
  749. jassert (comp != nullptr);
  750. const String name (build_tools::makeValidIdentifier (newName, false, false, true));
  751. if (name != getComponentVirtualClassName (comp))
  752. {
  753. comp->getProperties().set ("virtualName", name);
  754. changed();
  755. }
  756. }
  757. //==============================================================================
  758. void ComponentLayout::addToXml (XmlElement& xml) const
  759. {
  760. for (int i = 0; i < components.size(); ++i)
  761. if (ComponentTypeHandler* h = ComponentTypeHandler::getHandlerFor (*components [i]))
  762. xml.addChildElement (h->createXmlFor (components [i], this));
  763. }
  764. static String bracketIfNeeded (const String& s)
  765. {
  766. return s.containsAnyOf ("+-*/%") ? "(" + s + ")" : s;
  767. }
  768. //==============================================================================
  769. void positionToCode (const RelativePositionedRectangle& position,
  770. const ComponentLayout* layout,
  771. String& x, String& y, String& w, String& h)
  772. {
  773. // these are the code sections for the positions of the relative comps
  774. String xrx, xry, xrw, xrh;
  775. if (Component* const relCompX = layout != nullptr ? layout->findComponentWithId (position.relativeToX) : nullptr)
  776. positionToCode (ComponentTypeHandler::getComponentPosition (relCompX), layout, xrx, xry, xrw, xrh);
  777. String yrx, yry, yrw, yrh;
  778. if (Component* const relCompY = layout != nullptr ? layout->findComponentWithId (position.relativeToY) : nullptr)
  779. positionToCode (ComponentTypeHandler::getComponentPosition (relCompY), layout, yrx, yry, yrw, yrh);
  780. String wrx, wry, wrw, wrh;
  781. if (Component* const relCompW = (layout != nullptr && position.rect.getWidthMode() != PositionedRectangle::absoluteSize)
  782. ? layout->findComponentWithId (position.relativeToW) : nullptr)
  783. positionToCode (ComponentTypeHandler::getComponentPosition (relCompW), layout, wrx, wry, wrw, wrh);
  784. String hrx, hry, hrw, hrh;
  785. if (Component* const relCompH = (layout != nullptr && position.rect.getHeightMode() != PositionedRectangle::absoluteSize)
  786. ? layout->findComponentWithId (position.relativeToH) : nullptr)
  787. positionToCode (ComponentTypeHandler::getComponentPosition (relCompH), layout, hrx, hry, hrw, hrh);
  788. // width
  789. if (position.rect.getWidthMode() == PositionedRectangle::proportionalSize)
  790. {
  791. if (wrw.isNotEmpty())
  792. w << "juce::roundToInt (" << bracketIfNeeded (wrw) << " * " << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  793. else
  794. w << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  795. }
  796. else if (position.rect.getWidthMode() == PositionedRectangle::parentSizeMinusAbsolute)
  797. {
  798. if (wrw.isNotEmpty())
  799. w << bracketIfNeeded (wrw) << " - " << roundToInt (position.rect.getWidth());
  800. else
  801. w << "getWidth() - " << roundToInt (position.rect.getWidth());
  802. }
  803. else
  804. {
  805. if (wrw.isNotEmpty())
  806. w << bracketIfNeeded (wrw) << " + ";
  807. w << roundToInt (position.rect.getWidth());
  808. }
  809. // height
  810. if (position.rect.getHeightMode() == PositionedRectangle::proportionalSize)
  811. {
  812. if (hrh.isNotEmpty())
  813. h << "juce::roundToInt (" << bracketIfNeeded (hrh) << " * " << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  814. else
  815. h << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  816. }
  817. else if (position.rect.getHeightMode() == PositionedRectangle::parentSizeMinusAbsolute)
  818. {
  819. if (hrh.isNotEmpty())
  820. h << bracketIfNeeded (hrh) << " - " << roundToInt (position.rect.getHeight());
  821. else
  822. h << "getHeight() - " << roundToInt (position.rect.getHeight());
  823. }
  824. else
  825. {
  826. if (hrh.isNotEmpty())
  827. h << bracketIfNeeded (hrh) << " + ";
  828. h << roundToInt (position.rect.getHeight());
  829. }
  830. // x-pos
  831. if (position.rect.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  832. {
  833. if (xrx.isNotEmpty() && xrw.isNotEmpty())
  834. x << bracketIfNeeded (xrx) << " + juce::roundToInt (" << bracketIfNeeded (xrw) << " * " << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  835. else
  836. x << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  837. }
  838. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentTopLeft)
  839. {
  840. if (xrx.isNotEmpty())
  841. x << bracketIfNeeded (xrx) << " + ";
  842. x << roundToInt (position.rect.getX());
  843. }
  844. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentBottomRight)
  845. {
  846. if (xrx.isNotEmpty())
  847. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw);
  848. else
  849. x << "getWidth()";
  850. const int d = roundToInt (position.rect.getX());
  851. if (d != 0)
  852. x << " - " << d;
  853. }
  854. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentCentre)
  855. {
  856. if (xrx.isNotEmpty())
  857. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw) << " / 2";
  858. else
  859. x << "(getWidth() / 2)";
  860. const int d = roundToInt (position.rect.getX());
  861. if (d != 0)
  862. x << " + " << d;
  863. }
  864. if (w != "0")
  865. {
  866. if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtRightOrBottom)
  867. x << " - " << bracketIfNeeded (w);
  868. else if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtCentre)
  869. x << " - (" << bracketIfNeeded (w) << " / 2)";
  870. }
  871. // y-pos
  872. if (position.rect.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  873. {
  874. if (yry.isNotEmpty() && yrh.isNotEmpty())
  875. y << bracketIfNeeded (yry) << " + juce::roundToInt (" << bracketIfNeeded (yrh) << " * " << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  876. else
  877. y << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  878. }
  879. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentTopLeft)
  880. {
  881. if (yry.isNotEmpty())
  882. y << bracketIfNeeded (yry) << " + ";
  883. y << roundToInt (position.rect.getY());
  884. }
  885. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentBottomRight)
  886. {
  887. if (yry.isNotEmpty())
  888. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh);
  889. else
  890. y << "getHeight()";
  891. const int d = roundToInt (position.rect.getY());
  892. if (d != 0)
  893. y << " - " << d;
  894. }
  895. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentCentre)
  896. {
  897. if (yry.isNotEmpty())
  898. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh) << " / 2";
  899. else
  900. y << "(getHeight() / 2)";
  901. const int d = roundToInt (position.rect.getY());
  902. if (d != 0)
  903. y << " + " << d;
  904. }
  905. if (h != "0")
  906. {
  907. if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtRightOrBottom)
  908. y << " - " << bracketIfNeeded (h);
  909. else if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtCentre)
  910. y << " - (" << bracketIfNeeded (h) << " / 2)";
  911. }
  912. }