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.

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