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.

975 lines
31KB

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