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.

1105 lines
36KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "../Application/jucer_Headers.h"
  14. #include "jucer_JucerDocument.h"
  15. #include "jucer_ObjectTypes.h"
  16. #include "UI/jucer_JucerDocumentEditor.h"
  17. #include "Components/jucer_ComponentUndoableAction.h"
  18. //==============================================================================
  19. ComponentLayout::ComponentLayout()
  20. : document (nullptr),
  21. nextCompUID (1)
  22. {
  23. }
  24. ComponentLayout::~ComponentLayout()
  25. {
  26. components.clear();
  27. }
  28. //==============================================================================
  29. void ComponentLayout::changed()
  30. {
  31. if (document != nullptr)
  32. document->changed();
  33. }
  34. void ComponentLayout::perform (UndoableAction* action, const String& actionName)
  35. {
  36. jassert (document != nullptr);
  37. if (document != nullptr)
  38. {
  39. document->getUndoManager().perform (action, actionName);
  40. }
  41. else
  42. {
  43. std::unique_ptr<UndoableAction> deleter (action);
  44. action->perform();
  45. }
  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_)
  60. : indexAdded (-1),
  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. indexAdded = layout.indexOfComponent (newComp);
  71. jassert (indexAdded >= 0);
  72. return indexAdded >= 0;
  73. }
  74. bool undo()
  75. {
  76. showCorrectTab();
  77. layout.removeComponent (layout.getComponent (indexAdded), false);
  78. return true;
  79. }
  80. int getSizeInUnits() { return 10; }
  81. int indexAdded;
  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 (new 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 (new 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 (new 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. forEachXmlChildElement (*doc, e)
  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. AddCompAction* const action = new AddCompAction (new XmlElement (xml), *this);
  354. perform (action, "Add new components");
  355. return components [action->indexAdded];
  356. }
  357. if (ComponentTypeHandler* const type
  358. = ComponentTypeHandler::getHandlerForXmlTag (xml.getTagName()))
  359. {
  360. std::unique_ptr<Component> newComp (type->createNewComponent (getDocument()));
  361. if (type->restoreFromXml (xml, newComp.get(), this))
  362. {
  363. // ensure that the new comp's name is unique
  364. setComponentMemberVariableName (newComp.get(), getComponentMemberVariableName (newComp.get()));
  365. // check for duped IDs..
  366. while (findComponentWithId (ComponentTypeHandler::getComponentId (newComp.get())) != nullptr)
  367. ComponentTypeHandler::setComponentId (newComp.get(), Random::getSystemRandom().nextInt64());
  368. components.add (newComp.get());
  369. changed();
  370. return newComp.release();
  371. }
  372. }
  373. return nullptr;
  374. }
  375. Component* ComponentLayout::findComponentWithId (const int64 componentId) const
  376. {
  377. for (int i = 0; i < components.size(); ++i)
  378. if (ComponentTypeHandler::getComponentId (components.getUnchecked(i)) == componentId)
  379. return components.getUnchecked(i);
  380. return nullptr;
  381. }
  382. //==============================================================================
  383. static const char* const dimensionSuffixes[] = { "X", "Y", "W", "H" };
  384. Component* ComponentLayout::getComponentRelativePosTarget (Component* comp, int whichDimension) const
  385. {
  386. jassert (comp != nullptr);
  387. if (PaintElement* const pe = dynamic_cast<PaintElement*> (comp))
  388. {
  389. int64 compId;
  390. if (whichDimension == 0) compId = pe->getPosition().relativeToX;
  391. else if (whichDimension == 1) compId = pe->getPosition().relativeToY;
  392. else if (whichDimension == 2) compId = pe->getPosition().relativeToW;
  393. else compId = pe->getPosition().relativeToH;
  394. return findComponentWithId (compId);
  395. }
  396. return findComponentWithId (comp->getProperties() [String ("relativeTo") + dimensionSuffixes [whichDimension]]
  397. .toString().getHexValue64());
  398. }
  399. void ComponentLayout::setComponentRelativeTarget (Component* comp, int whichDimension, Component* compToBeRelativeTo)
  400. {
  401. PaintElement* const pe = dynamic_cast<PaintElement*> (comp);
  402. jassert (comp != nullptr);
  403. jassert (pe != nullptr || components.contains (comp));
  404. jassert (compToBeRelativeTo == nullptr || components.contains (compToBeRelativeTo));
  405. jassert (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp));
  406. if (compToBeRelativeTo != getComponentRelativePosTarget (comp, whichDimension)
  407. && (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp)))
  408. {
  409. const int64 compId = ComponentTypeHandler::getComponentId (compToBeRelativeTo);
  410. Rectangle<int> oldBounds (comp->getBounds());
  411. RelativePositionedRectangle pos;
  412. if (pe != nullptr)
  413. {
  414. oldBounds = pe->getCurrentBounds (dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea());
  415. pos = pe->getPosition();
  416. }
  417. else
  418. {
  419. pos = ComponentTypeHandler::getComponentPosition (comp);
  420. }
  421. if (whichDimension == 0) pos.relativeToX = compId;
  422. else if (whichDimension == 1) pos.relativeToY = compId;
  423. else if (whichDimension == 2) pos.relativeToW = compId;
  424. else if (whichDimension == 3) pos.relativeToH = compId;
  425. if (pe != nullptr)
  426. {
  427. pe->setPosition (pos, true);
  428. pe->setCurrentBounds (oldBounds, dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea(), true);
  429. }
  430. else
  431. {
  432. setComponentPosition (comp, pos, true);
  433. comp->setBounds (oldBounds);
  434. updateStoredComponentPosition (comp, false);
  435. }
  436. changed();
  437. }
  438. }
  439. bool ComponentLayout::dependsOnComponentForRelativePos (Component* comp, Component* possibleDependee) const
  440. {
  441. for (int i = 0; i < 4; ++i)
  442. if (Component* const c = getComponentRelativePosTarget (comp, i))
  443. if (c == possibleDependee || dependsOnComponentForRelativePos (c, possibleDependee))
  444. return true;
  445. return false;
  446. }
  447. bool ComponentLayout::isComponentPositionRelative (Component* comp) const
  448. {
  449. for (int i = 0; i < getNumComponents(); ++i)
  450. if (dependsOnComponentForRelativePos (comp, getComponent (i)))
  451. return true;
  452. return false;
  453. }
  454. const int menuIdBase = 0x63240000;
  455. PopupMenu ComponentLayout::getRelativeTargetMenu (Component* comp, int whichDimension) const
  456. {
  457. PopupMenu m;
  458. auto current = getComponentRelativePosTarget (comp, whichDimension);
  459. m.addItem (menuIdBase, "Relative to parent component", true, current == nullptr);
  460. m.addSeparator();
  461. for (int i = 0; i < components.size(); ++i)
  462. {
  463. Component* const c = components.getUnchecked(i);
  464. if (c != comp)
  465. m.addItem (menuIdBase + i + 1,
  466. "Relative to " + getComponentMemberVariableName (c)
  467. + " (class: " + ComponentTypeHandler::getHandlerFor (*c)->getClassName (c) + ")",
  468. ! dependsOnComponentForRelativePos (c, comp),
  469. current == c);
  470. }
  471. return m;
  472. }
  473. void ComponentLayout::processRelativeTargetMenuResult (Component* comp, int whichDimension, int menuResultID)
  474. {
  475. if (menuResultID != 0)
  476. {
  477. Component* const newTarget = components [menuResultID - menuIdBase - 1];
  478. setComponentRelativeTarget (comp, whichDimension, newTarget);
  479. }
  480. }
  481. //==============================================================================
  482. class ChangeCompPositionAction : public ComponentUndoableAction <Component>
  483. {
  484. public:
  485. ChangeCompPositionAction (Component* const comp, ComponentLayout& l,
  486. const RelativePositionedRectangle& newPos_)
  487. : ComponentUndoableAction <Component> (comp, l),
  488. newPos (newPos_), oldPos (ComponentTypeHandler::getComponentPosition (comp))
  489. {
  490. }
  491. bool perform()
  492. {
  493. showCorrectTab();
  494. layout.setComponentPosition (getComponent(), newPos, false);
  495. return true;
  496. }
  497. bool undo()
  498. {
  499. showCorrectTab();
  500. layout.setComponentPosition (getComponent(), oldPos, false);
  501. return true;
  502. }
  503. private:
  504. RelativePositionedRectangle newPos, oldPos;
  505. };
  506. class ChangeCompBoundsAndPropertiesAction : public ComponentUndoableAction<Component>
  507. {
  508. public:
  509. ChangeCompBoundsAndPropertiesAction (Component* const comp, ComponentLayout& l,
  510. const Rectangle<int>& bounds, const NamedValueSet& props)
  511. : ComponentUndoableAction <Component> (comp, l),
  512. newBounds (bounds),
  513. oldBounds (comp->getBounds()),
  514. newProps (props),
  515. oldProps(comp->getProperties())
  516. {
  517. }
  518. bool perform()
  519. {
  520. showCorrectTab();
  521. getComponent()->setBounds (newBounds);
  522. getComponent()->getProperties() = newProps;
  523. layout.updateStoredComponentPosition (getComponent(), false);
  524. return true;
  525. }
  526. bool undo()
  527. {
  528. showCorrectTab();
  529. getComponent()->setBounds (oldBounds);
  530. getComponent()->getProperties() = oldProps;
  531. layout.updateStoredComponentPosition (getComponent(), false);
  532. return true;
  533. }
  534. private:
  535. Rectangle<int> newBounds, oldBounds;
  536. NamedValueSet newProps, oldProps;
  537. };
  538. void ComponentLayout::setComponentPosition (Component* comp,
  539. const RelativePositionedRectangle& newPos,
  540. const bool undoable)
  541. {
  542. if (ComponentTypeHandler::getComponentPosition (comp) != newPos)
  543. {
  544. if (undoable)
  545. {
  546. perform (new ChangeCompPositionAction (comp, *this, newPos), "Move components");
  547. }
  548. else
  549. {
  550. ComponentTypeHandler::setComponentPosition (comp, newPos, this);
  551. changed();
  552. }
  553. }
  554. }
  555. void ComponentLayout::setComponentBoundsAndProperties (Component* componentToPosition, const Rectangle<int>& newBounds,
  556. Component* referenceComponent, const bool undoable)
  557. {
  558. auto props = NamedValueSet (componentToPosition->getProperties());
  559. auto rect = ComponentTypeHandler::getComponentPosition (componentToPosition).rect;
  560. auto referenceComponentPosition = ComponentTypeHandler::getComponentPosition (referenceComponent);
  561. auto referenceComponentRect = referenceComponentPosition.rect;
  562. rect.setModes (referenceComponentRect.getAnchorPointX(), referenceComponentRect.getPositionModeX(),
  563. referenceComponentRect.getAnchorPointY(), referenceComponentRect.getPositionModeY(),
  564. referenceComponentRect.getWidthMode(), referenceComponentRect.getHeightMode(),
  565. componentToPosition->getBounds());
  566. props.set ("pos", rect.toString());
  567. props.set ("relativeToX", String::toHexString (referenceComponentPosition.relativeToX));
  568. props.set ("relativeToY", String::toHexString (referenceComponentPosition.relativeToY));
  569. props.set ("relativeToW", String::toHexString (referenceComponentPosition.relativeToW));
  570. props.set ("relativeToH", String::toHexString (referenceComponentPosition.relativeToH));
  571. if (componentToPosition->getBounds() != newBounds || componentToPosition->getProperties() != props)
  572. {
  573. if (undoable)
  574. {
  575. perform (new ChangeCompBoundsAndPropertiesAction (componentToPosition, *this, newBounds, props), "Change component bounds");
  576. }
  577. else
  578. {
  579. componentToPosition->setBounds (newBounds);
  580. componentToPosition->getProperties() = props;
  581. updateStoredComponentPosition (componentToPosition, false);
  582. changed();
  583. }
  584. }
  585. }
  586. void ComponentLayout::updateStoredComponentPosition (Component* comp, const bool undoable)
  587. {
  588. RelativePositionedRectangle newPos (ComponentTypeHandler::getComponentPosition (comp));
  589. newPos.updateFromComponent (*comp, this);
  590. setComponentPosition (comp, newPos, undoable);
  591. }
  592. //==============================================================================
  593. void ComponentLayout::startDragging()
  594. {
  595. for (int i = 0; i < components.size(); ++i)
  596. {
  597. Component* const c = components[i];
  598. c->getProperties().set ("xDragStart", c->getX());
  599. c->getProperties().set ("yDragStart", c->getY());
  600. }
  601. jassert (document != nullptr);
  602. document->beginTransaction();
  603. }
  604. void ComponentLayout::dragSelectedComps (int dx, int dy, const bool allowSnap)
  605. {
  606. if (allowSnap && document != nullptr && selected.getNumSelected() > 1)
  607. {
  608. dx = document->snapPosition (dx);
  609. dy = document->snapPosition (dy);
  610. }
  611. for (int i = 0; i < selected.getNumSelected(); ++i)
  612. {
  613. Component* const c = selected.getSelectedItem (i);
  614. const int startX = c->getProperties() ["xDragStart"];
  615. const int startY = c->getProperties() ["yDragStart"];
  616. if (allowSnap && document != nullptr && selected.getNumSelected() == 1)
  617. {
  618. c->setTopLeftPosition (document->snapPosition (startX + dx),
  619. document->snapPosition (startY + dy));
  620. }
  621. else
  622. {
  623. c->setTopLeftPosition (startX + dx,
  624. startY + dy);
  625. }
  626. updateStoredComponentPosition (c, false);
  627. }
  628. }
  629. void ComponentLayout::endDragging()
  630. {
  631. // after the drag, roll back all the comps to their start position, then
  632. // back to their finish positions using an undoable command.
  633. document->beginTransaction();
  634. for (int i = 0; i < selected.getNumSelected(); ++i)
  635. {
  636. Component* const c = selected.getSelectedItem (i);
  637. const int newX = c->getX();
  638. const int newY = c->getY();
  639. const int startX = c->getProperties() ["xDragStart"];
  640. const int startY = c->getProperties() ["yDragStart"];
  641. c->setTopLeftPosition (startX, startY);
  642. updateStoredComponentPosition (c, false);
  643. c->setTopLeftPosition (newX, newY);
  644. updateStoredComponentPosition (c, true);
  645. }
  646. document->beginTransaction();
  647. }
  648. void ComponentLayout::moveSelectedComps (int dx, int dy, bool snap)
  649. {
  650. startDragging();
  651. dragSelectedComps (dx, dy, snap);
  652. endDragging();
  653. }
  654. void ComponentLayout::stretchSelectedComps (int dw, int dh, bool allowSnap)
  655. {
  656. int neww, newh;
  657. if (document != nullptr && selected.getNumSelected() == 1)
  658. {
  659. Component* const c = selected.getSelectedItem (0);
  660. if (allowSnap)
  661. {
  662. int bot = c->getBottom() + dh;
  663. int right = c->getRight() + dw;
  664. bot = (dh != 0) ? document->snapPosition (bot) : bot;
  665. right = (dw != 0) ? document->snapPosition (right) : right;
  666. newh = bot - c->getY();
  667. neww = right - c->getX();
  668. }
  669. else
  670. {
  671. newh = c->getHeight() + dh;
  672. neww = c->getWidth() + dw;
  673. }
  674. c->setSize (neww, newh);
  675. updateStoredComponentPosition (c, true);
  676. }
  677. else
  678. {
  679. for (int i = 0; i < selected.getNumSelected(); ++i)
  680. {
  681. Component* const c = selected.getSelectedItem (i);
  682. neww = c->getWidth() + dw;
  683. newh = c->getHeight() + dh;
  684. c->setSize (neww, newh);
  685. updateStoredComponentPosition (c, true);
  686. }
  687. }
  688. }
  689. //==============================================================================
  690. void ComponentLayout::fillInGeneratedCode (GeneratedCode& code) const
  691. {
  692. for (int i = 0; i < components.size(); ++i)
  693. if (Component* const comp = components.getUnchecked(i))
  694. if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
  695. type->fillInGeneratedCode (comp, code);
  696. }
  697. //==============================================================================
  698. String ComponentLayout::getComponentMemberVariableName (Component* comp) const
  699. {
  700. if (comp == nullptr)
  701. return {};
  702. String name (comp->getProperties() ["memberName"].toString());
  703. if (name.isEmpty())
  704. name = getUnusedMemberName (build_tools::makeValidIdentifier (comp->getName(), true, true, false), comp);
  705. return name;
  706. }
  707. void ComponentLayout::setComponentMemberVariableName (Component* comp, const String& newName)
  708. {
  709. jassert (comp != nullptr);
  710. const String oldName (getComponentMemberVariableName (comp));
  711. comp->getProperties().set ("memberName", String());
  712. const String n (getUnusedMemberName (build_tools::makeValidIdentifier (newName, false, true, false), comp));
  713. comp->getProperties().set ("memberName", n);
  714. if (n != oldName)
  715. changed();
  716. }
  717. String ComponentLayout::getUnusedMemberName (String nameRoot, Component* comp) const
  718. {
  719. String n (nameRoot);
  720. while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
  721. nameRoot = nameRoot.dropLastCharacters (1);
  722. int suffix = 2;
  723. for (;;)
  724. {
  725. bool alreadyUsed = false;
  726. for (int i = 0; i < components.size(); ++i)
  727. {
  728. if (components[i] != comp
  729. && components[i]->getProperties() ["memberName"] == n)
  730. {
  731. alreadyUsed = true;
  732. break;
  733. }
  734. }
  735. if (! alreadyUsed)
  736. break;
  737. n = nameRoot + String (suffix++);
  738. }
  739. return n;
  740. }
  741. //==============================================================================
  742. String ComponentLayout::getComponentVirtualClassName (Component* comp) const
  743. {
  744. if (comp == nullptr)
  745. return {};
  746. return comp->getProperties() ["virtualName"];
  747. }
  748. void ComponentLayout::setComponentVirtualClassName (Component* comp, const String& newName)
  749. {
  750. jassert (comp != nullptr);
  751. const String name (build_tools::makeValidIdentifier (newName, false, false, true));
  752. if (name != getComponentVirtualClassName (comp))
  753. {
  754. comp->getProperties().set ("virtualName", name);
  755. changed();
  756. }
  757. }
  758. //==============================================================================
  759. void ComponentLayout::addToXml (XmlElement& xml) const
  760. {
  761. for (int i = 0; i < components.size(); ++i)
  762. if (ComponentTypeHandler* h = ComponentTypeHandler::getHandlerFor (*components [i]))
  763. xml.addChildElement (h->createXmlFor (components [i], this));
  764. }
  765. static String bracketIfNeeded (const String& s)
  766. {
  767. return s.containsAnyOf ("+-*/%") ? "(" + s + ")" : s;
  768. }
  769. //==============================================================================
  770. void positionToCode (const RelativePositionedRectangle& position,
  771. const ComponentLayout* layout,
  772. String& x, String& y, String& w, String& h)
  773. {
  774. // these are the code sections for the positions of the relative comps
  775. String xrx, xry, xrw, xrh;
  776. if (Component* const relCompX = layout != nullptr ? layout->findComponentWithId (position.relativeToX) : nullptr)
  777. positionToCode (ComponentTypeHandler::getComponentPosition (relCompX), layout, xrx, xry, xrw, xrh);
  778. String yrx, yry, yrw, yrh;
  779. if (Component* const relCompY = layout != nullptr ? layout->findComponentWithId (position.relativeToY) : nullptr)
  780. positionToCode (ComponentTypeHandler::getComponentPosition (relCompY), layout, yrx, yry, yrw, yrh);
  781. String wrx, wry, wrw, wrh;
  782. if (Component* const relCompW = (layout != nullptr && position.rect.getWidthMode() != PositionedRectangle::absoluteSize)
  783. ? layout->findComponentWithId (position.relativeToW) : nullptr)
  784. positionToCode (ComponentTypeHandler::getComponentPosition (relCompW), layout, wrx, wry, wrw, wrh);
  785. String hrx, hry, hrw, hrh;
  786. if (Component* const relCompH = (layout != nullptr && position.rect.getHeightMode() != PositionedRectangle::absoluteSize)
  787. ? layout->findComponentWithId (position.relativeToH) : nullptr)
  788. positionToCode (ComponentTypeHandler::getComponentPosition (relCompH), layout, hrx, hry, hrw, hrh);
  789. // width
  790. if (position.rect.getWidthMode() == PositionedRectangle::proportionalSize)
  791. {
  792. if (wrw.isNotEmpty())
  793. w << "juce::roundToInt (" << bracketIfNeeded (wrw) << " * " << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  794. else
  795. w << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  796. }
  797. else if (position.rect.getWidthMode() == PositionedRectangle::parentSizeMinusAbsolute)
  798. {
  799. if (wrw.isNotEmpty())
  800. w << bracketIfNeeded (wrw) << " - " << roundToInt (position.rect.getWidth());
  801. else
  802. w << "getWidth() - " << roundToInt (position.rect.getWidth());
  803. }
  804. else
  805. {
  806. if (wrw.isNotEmpty())
  807. w << bracketIfNeeded (wrw) << " + ";
  808. w << roundToInt (position.rect.getWidth());
  809. }
  810. // height
  811. if (position.rect.getHeightMode() == PositionedRectangle::proportionalSize)
  812. {
  813. if (hrh.isNotEmpty())
  814. h << "juce::roundToInt (" << bracketIfNeeded (hrh) << " * " << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  815. else
  816. h << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  817. }
  818. else if (position.rect.getHeightMode() == PositionedRectangle::parentSizeMinusAbsolute)
  819. {
  820. if (hrh.isNotEmpty())
  821. h << bracketIfNeeded (hrh) << " - " << roundToInt (position.rect.getHeight());
  822. else
  823. h << "getHeight() - " << roundToInt (position.rect.getHeight());
  824. }
  825. else
  826. {
  827. if (hrh.isNotEmpty())
  828. h << bracketIfNeeded (hrh) << " + ";
  829. h << roundToInt (position.rect.getHeight());
  830. }
  831. // x-pos
  832. if (position.rect.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  833. {
  834. if (xrx.isNotEmpty() && xrw.isNotEmpty())
  835. x << bracketIfNeeded (xrx) << " + juce::roundToInt (" << bracketIfNeeded (xrw) << " * " << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  836. else
  837. x << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  838. }
  839. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentTopLeft)
  840. {
  841. if (xrx.isNotEmpty())
  842. x << bracketIfNeeded (xrx) << " + ";
  843. x << roundToInt (position.rect.getX());
  844. }
  845. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentBottomRight)
  846. {
  847. if (xrx.isNotEmpty())
  848. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw);
  849. else
  850. x << "getWidth()";
  851. const int d = roundToInt (position.rect.getX());
  852. if (d != 0)
  853. x << " - " << d;
  854. }
  855. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentCentre)
  856. {
  857. if (xrx.isNotEmpty())
  858. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw) << " / 2";
  859. else
  860. x << "(getWidth() / 2)";
  861. const int d = roundToInt (position.rect.getX());
  862. if (d != 0)
  863. x << " + " << d;
  864. }
  865. if (w != "0")
  866. {
  867. if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtRightOrBottom)
  868. x << " - " << bracketIfNeeded (w);
  869. else if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtCentre)
  870. x << " - (" << bracketIfNeeded (w) << " / 2)";
  871. }
  872. // y-pos
  873. if (position.rect.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  874. {
  875. if (yry.isNotEmpty() && yrh.isNotEmpty())
  876. y << bracketIfNeeded (yry) << " + juce::roundToInt (" << bracketIfNeeded (yrh) << " * " << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  877. else
  878. y << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  879. }
  880. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentTopLeft)
  881. {
  882. if (yry.isNotEmpty())
  883. y << bracketIfNeeded (yry) << " + ";
  884. y << roundToInt (position.rect.getY());
  885. }
  886. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentBottomRight)
  887. {
  888. if (yry.isNotEmpty())
  889. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh);
  890. else
  891. y << "getHeight()";
  892. const int d = roundToInt (position.rect.getY());
  893. if (d != 0)
  894. y << " - " << d;
  895. }
  896. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentCentre)
  897. {
  898. if (yry.isNotEmpty())
  899. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh) << " / 2";
  900. else
  901. y << "(getHeight() / 2)";
  902. const int d = roundToInt (position.rect.getY());
  903. if (d != 0)
  904. y << " + " << d;
  905. }
  906. if (h != "0")
  907. {
  908. if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtRightOrBottom)
  909. y << " - " << bracketIfNeeded (h);
  910. else if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtCentre)
  911. y << " - (" << bracketIfNeeded (h) << " / 2)";
  912. }
  913. }