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.

973 lines
31KB

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