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.

1113 lines
36KB

  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 "../Application/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. std::unique_ptr<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. std::unique_ptr<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.reset (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. std::unique_ptr<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. auto c = components.getUnchecked(i);
  214. if (selected.isSelected (c))
  215. {
  216. if (auto type = ComponentTypeHandler::getHandlerFor (*c))
  217. {
  218. auto e = type->createXmlFor (c, this);
  219. clip.addChildElement (e);
  220. }
  221. }
  222. }
  223. SystemClipboard::copyTextToClipboard (clip.toString());
  224. }
  225. void ComponentLayout::paste()
  226. {
  227. if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), 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::alignTop()
  272. {
  273. if (selected.getNumSelected() > 1)
  274. {
  275. auto* main = selected.getSelectedItem (0);
  276. auto yPos = main->getY();
  277. for (auto* other : selected)
  278. {
  279. if (other != main)
  280. setComponentBoundsAndProperties (other,
  281. other->getBounds().withPosition (other->getX(), yPos),
  282. main, true);
  283. }
  284. }
  285. }
  286. void ComponentLayout::alignRight()
  287. {
  288. if (selected.getNumSelected() > 1)
  289. {
  290. auto* main = selected.getSelectedItem (0);
  291. auto rightPos = main->getRight();
  292. for (auto* other : selected)
  293. {
  294. if (other != main)
  295. setComponentBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(), other->getY()), main, true);
  296. }
  297. }
  298. }
  299. void ComponentLayout::alignBottom()
  300. {
  301. if (selected.getNumSelected() > 1)
  302. {
  303. auto* main = selected.getSelectedItem (0);
  304. auto bottomPos = main->getBottom();
  305. for (auto* other : selected)
  306. {
  307. if (other != main)
  308. setComponentBoundsAndProperties (other, other->getBounds().withPosition (other->getX(), bottomPos - other->getHeight()), main, true);
  309. }
  310. }
  311. }
  312. void ComponentLayout::alignLeft()
  313. {
  314. if (selected.getNumSelected() > 1)
  315. {
  316. auto* main = selected.getSelectedItem (0);
  317. auto xPos = main->getX();
  318. for (auto* other : selected)
  319. {
  320. if (other != main)
  321. setComponentBoundsAndProperties (other, other->getBounds().withPosition (xPos, other->getY()), main, true);
  322. }
  323. }
  324. }
  325. void ComponentLayout::bringLostItemsBackOnScreen (int width, int height)
  326. {
  327. for (int i = components.size(); --i >= 0;)
  328. {
  329. Component* const c = components[i];
  330. if (! c->getBounds().intersects (Rectangle<int> (0, 0, width, height)))
  331. {
  332. c->setTopLeftPosition (width / 2, height / 2);
  333. updateStoredComponentPosition (c, false);
  334. }
  335. }
  336. }
  337. Component* ComponentLayout::addNewComponent (ComponentTypeHandler* const type, int x, int y)
  338. {
  339. std::unique_ptr<Component> c (type->createNewComponent (getDocument()));
  340. jassert (c != nullptr);
  341. if (c != nullptr)
  342. {
  343. c->setSize (type->getDefaultWidth(), type->getDefaultHeight());
  344. c->setCentrePosition (x, y);
  345. updateStoredComponentPosition (c.get(), false);
  346. c->getProperties().set ("id", nextCompUID++);
  347. std::unique_ptr<XmlElement> xml (type->createXmlFor (c.get(), this));
  348. c.reset (addComponentFromXml (*xml, true));
  349. String memberName (CodeHelpers::makeValidIdentifier (type->getClassName (c.get()), true, true, false));
  350. setComponentMemberVariableName (c.get(), memberName);
  351. selected.selectOnly (c.get());
  352. }
  353. return c.release();
  354. }
  355. Component* ComponentLayout::addComponentFromXml (const XmlElement& xml, const bool undoable)
  356. {
  357. if (undoable)
  358. {
  359. AddCompAction* const action = new AddCompAction (new XmlElement (xml), *this);
  360. perform (action, "Add new components");
  361. return components [action->indexAdded];
  362. }
  363. if (ComponentTypeHandler* const type
  364. = ComponentTypeHandler::getHandlerForXmlTag (xml.getTagName()))
  365. {
  366. std::unique_ptr<Component> newComp (type->createNewComponent (getDocument()));
  367. if (type->restoreFromXml (xml, newComp.get(), this))
  368. {
  369. // ensure that the new comp's name is unique
  370. setComponentMemberVariableName (newComp.get(), getComponentMemberVariableName (newComp.get()));
  371. // check for duped IDs..
  372. while (findComponentWithId (ComponentTypeHandler::getComponentId (newComp.get())) != nullptr)
  373. ComponentTypeHandler::setComponentId (newComp.get(), Random::getSystemRandom().nextInt64());
  374. components.add (newComp.get());
  375. changed();
  376. return newComp.release();
  377. }
  378. }
  379. return nullptr;
  380. }
  381. Component* ComponentLayout::findComponentWithId (const int64 componentId) const
  382. {
  383. for (int i = 0; i < components.size(); ++i)
  384. if (ComponentTypeHandler::getComponentId (components.getUnchecked(i)) == componentId)
  385. return components.getUnchecked(i);
  386. return nullptr;
  387. }
  388. //==============================================================================
  389. static const char* const dimensionSuffixes[] = { "X", "Y", "W", "H" };
  390. Component* ComponentLayout::getComponentRelativePosTarget (Component* comp, int whichDimension) const
  391. {
  392. jassert (comp != nullptr);
  393. if (PaintElement* const pe = dynamic_cast<PaintElement*> (comp))
  394. {
  395. int64 compId;
  396. if (whichDimension == 0) compId = pe->getPosition().relativeToX;
  397. else if (whichDimension == 1) compId = pe->getPosition().relativeToY;
  398. else if (whichDimension == 2) compId = pe->getPosition().relativeToW;
  399. else compId = pe->getPosition().relativeToH;
  400. return findComponentWithId (compId);
  401. }
  402. return findComponentWithId (comp->getProperties() [String ("relativeTo") + dimensionSuffixes [whichDimension]]
  403. .toString().getHexValue64());
  404. }
  405. void ComponentLayout::setComponentRelativeTarget (Component* comp, int whichDimension, Component* compToBeRelativeTo)
  406. {
  407. PaintElement* const pe = dynamic_cast<PaintElement*> (comp);
  408. jassert (comp != nullptr);
  409. jassert (pe != nullptr || components.contains (comp));
  410. jassert (compToBeRelativeTo == nullptr || components.contains (compToBeRelativeTo));
  411. jassert (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp));
  412. if (compToBeRelativeTo != getComponentRelativePosTarget (comp, whichDimension)
  413. && (compToBeRelativeTo == nullptr || ! dependsOnComponentForRelativePos (compToBeRelativeTo, comp)))
  414. {
  415. const int64 compId = ComponentTypeHandler::getComponentId (compToBeRelativeTo);
  416. Rectangle<int> oldBounds (comp->getBounds());
  417. RelativePositionedRectangle pos;
  418. if (pe != nullptr)
  419. {
  420. oldBounds = pe->getCurrentBounds (dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea());
  421. pos = pe->getPosition();
  422. }
  423. else
  424. {
  425. pos = ComponentTypeHandler::getComponentPosition (comp);
  426. }
  427. if (whichDimension == 0) pos.relativeToX = compId;
  428. else if (whichDimension == 1) pos.relativeToY = compId;
  429. else if (whichDimension == 2) pos.relativeToW = compId;
  430. else if (whichDimension == 3) pos.relativeToH = compId;
  431. if (pe != nullptr)
  432. {
  433. pe->setPosition (pos, true);
  434. pe->setCurrentBounds (oldBounds, dynamic_cast<PaintRoutineEditor*> (pe->getParentComponent())->getComponentArea(), true);
  435. }
  436. else
  437. {
  438. setComponentPosition (comp, pos, true);
  439. comp->setBounds (oldBounds);
  440. updateStoredComponentPosition (comp, false);
  441. }
  442. changed();
  443. }
  444. }
  445. bool ComponentLayout::dependsOnComponentForRelativePos (Component* comp, Component* possibleDependee) const
  446. {
  447. for (int i = 0; i < 4; ++i)
  448. if (Component* const c = getComponentRelativePosTarget (comp, i))
  449. if (c == possibleDependee || dependsOnComponentForRelativePos (c, possibleDependee))
  450. return true;
  451. return false;
  452. }
  453. bool ComponentLayout::isComponentPositionRelative (Component* comp) const
  454. {
  455. for (int i = 0; i < getNumComponents(); ++i)
  456. if (dependsOnComponentForRelativePos (comp, getComponent (i)))
  457. return true;
  458. return false;
  459. }
  460. const int menuIdBase = 0x63240000;
  461. PopupMenu ComponentLayout::getRelativeTargetMenu (Component* comp, int whichDimension) const
  462. {
  463. PopupMenu m;
  464. auto current = getComponentRelativePosTarget (comp, whichDimension);
  465. m.addItem (menuIdBase, "Relative to parent component", true, current == nullptr);
  466. m.addSeparator();
  467. for (int i = 0; i < components.size(); ++i)
  468. {
  469. Component* const c = components.getUnchecked(i);
  470. if (c != comp)
  471. m.addItem (menuIdBase + i + 1,
  472. "Relative to " + getComponentMemberVariableName (c)
  473. + " (class: " + ComponentTypeHandler::getHandlerFor (*c)->getClassName (c) + ")",
  474. ! dependsOnComponentForRelativePos (c, comp),
  475. current == c);
  476. }
  477. return m;
  478. }
  479. void ComponentLayout::processRelativeTargetMenuResult (Component* comp, int whichDimension, int menuResultID)
  480. {
  481. if (menuResultID != 0)
  482. {
  483. Component* const newTarget = components [menuResultID - menuIdBase - 1];
  484. setComponentRelativeTarget (comp, whichDimension, newTarget);
  485. }
  486. }
  487. //==============================================================================
  488. class ChangeCompPositionAction : public ComponentUndoableAction <Component>
  489. {
  490. public:
  491. ChangeCompPositionAction (Component* const comp, ComponentLayout& l,
  492. const RelativePositionedRectangle& newPos_)
  493. : ComponentUndoableAction <Component> (comp, l),
  494. newPos (newPos_), oldPos (ComponentTypeHandler::getComponentPosition (comp))
  495. {
  496. }
  497. bool perform()
  498. {
  499. showCorrectTab();
  500. layout.setComponentPosition (getComponent(), newPos, false);
  501. return true;
  502. }
  503. bool undo()
  504. {
  505. showCorrectTab();
  506. layout.setComponentPosition (getComponent(), oldPos, false);
  507. return true;
  508. }
  509. private:
  510. RelativePositionedRectangle newPos, oldPos;
  511. };
  512. class ChangeCompBoundsAndPropertiesAction : public ComponentUndoableAction<Component>
  513. {
  514. public:
  515. ChangeCompBoundsAndPropertiesAction (Component* const comp, ComponentLayout& l,
  516. const Rectangle<int>& bounds, const NamedValueSet& props)
  517. : ComponentUndoableAction <Component> (comp, l),
  518. newBounds (bounds),
  519. oldBounds (comp->getBounds()),
  520. newProps (props),
  521. oldProps(comp->getProperties())
  522. {
  523. }
  524. bool perform()
  525. {
  526. showCorrectTab();
  527. getComponent()->setBounds (newBounds);
  528. getComponent()->getProperties() = newProps;
  529. layout.updateStoredComponentPosition (getComponent(), false);
  530. return true;
  531. }
  532. bool undo()
  533. {
  534. showCorrectTab();
  535. getComponent()->setBounds (oldBounds);
  536. getComponent()->getProperties() = oldProps;
  537. layout.updateStoredComponentPosition (getComponent(), false);
  538. return true;
  539. }
  540. private:
  541. Rectangle<int> newBounds, oldBounds;
  542. NamedValueSet newProps, oldProps;
  543. };
  544. void ComponentLayout::setComponentPosition (Component* comp,
  545. const RelativePositionedRectangle& newPos,
  546. const bool undoable)
  547. {
  548. if (ComponentTypeHandler::getComponentPosition (comp) != newPos)
  549. {
  550. if (undoable)
  551. {
  552. perform (new ChangeCompPositionAction (comp, *this, newPos), "Move components");
  553. }
  554. else
  555. {
  556. ComponentTypeHandler::setComponentPosition (comp, newPos, this);
  557. changed();
  558. }
  559. }
  560. }
  561. void ComponentLayout::setComponentBoundsAndProperties (Component* componentToPosition, const Rectangle<int>& newBounds,
  562. Component* referenceComponent, const bool undoable)
  563. {
  564. auto props = NamedValueSet (componentToPosition->getProperties());
  565. auto rect = ComponentTypeHandler::getComponentPosition (componentToPosition).rect;
  566. auto referenceComponentPosition = ComponentTypeHandler::getComponentPosition (referenceComponent);
  567. auto referenceComponentRect = referenceComponentPosition.rect;
  568. rect.setModes (referenceComponentRect.getAnchorPointX(), referenceComponentRect.getPositionModeX(),
  569. referenceComponentRect.getAnchorPointY(), referenceComponentRect.getPositionModeY(),
  570. referenceComponentRect.getWidthMode(), referenceComponentRect.getHeightMode(),
  571. componentToPosition->getBounds());
  572. props.set ("pos", rect.toString());
  573. props.set ("relativeToX", String::toHexString (referenceComponentPosition.relativeToX));
  574. props.set ("relativeToY", String::toHexString (referenceComponentPosition.relativeToY));
  575. props.set ("relativeToW", String::toHexString (referenceComponentPosition.relativeToW));
  576. props.set ("relativeToH", String::toHexString (referenceComponentPosition.relativeToH));
  577. if (componentToPosition->getBounds() != newBounds || componentToPosition->getProperties() != props)
  578. {
  579. if (undoable)
  580. {
  581. perform (new ChangeCompBoundsAndPropertiesAction (componentToPosition, *this, newBounds, props), "Change component bounds");
  582. }
  583. else
  584. {
  585. componentToPosition->setBounds (newBounds);
  586. componentToPosition->getProperties() = props;
  587. updateStoredComponentPosition (componentToPosition, false);
  588. changed();
  589. }
  590. }
  591. }
  592. void ComponentLayout::updateStoredComponentPosition (Component* comp, const bool undoable)
  593. {
  594. RelativePositionedRectangle newPos (ComponentTypeHandler::getComponentPosition (comp));
  595. newPos.updateFromComponent (*comp, this);
  596. setComponentPosition (comp, newPos, undoable);
  597. }
  598. //==============================================================================
  599. void ComponentLayout::startDragging()
  600. {
  601. for (int i = 0; i < components.size(); ++i)
  602. {
  603. Component* const c = components[i];
  604. c->getProperties().set ("xDragStart", c->getX());
  605. c->getProperties().set ("yDragStart", c->getY());
  606. }
  607. jassert (document != nullptr);
  608. document->beginTransaction();
  609. }
  610. void ComponentLayout::dragSelectedComps (int dx, int dy, const bool allowSnap)
  611. {
  612. if (allowSnap && document != nullptr && selected.getNumSelected() > 1)
  613. {
  614. dx = document->snapPosition (dx);
  615. dy = document->snapPosition (dy);
  616. }
  617. for (int i = 0; i < selected.getNumSelected(); ++i)
  618. {
  619. Component* const c = selected.getSelectedItem (i);
  620. const int startX = c->getProperties() ["xDragStart"];
  621. const int startY = c->getProperties() ["yDragStart"];
  622. if (allowSnap && document != nullptr && selected.getNumSelected() == 1)
  623. {
  624. c->setTopLeftPosition (document->snapPosition (startX + dx),
  625. document->snapPosition (startY + dy));
  626. }
  627. else
  628. {
  629. c->setTopLeftPosition (startX + dx,
  630. startY + dy);
  631. }
  632. updateStoredComponentPosition (c, false);
  633. }
  634. }
  635. void ComponentLayout::endDragging()
  636. {
  637. // after the drag, roll back all the comps to their start position, then
  638. // back to their finish positions using an undoable command.
  639. document->beginTransaction();
  640. for (int i = 0; i < selected.getNumSelected(); ++i)
  641. {
  642. Component* const c = selected.getSelectedItem (i);
  643. const int newX = c->getX();
  644. const int newY = c->getY();
  645. const int startX = c->getProperties() ["xDragStart"];
  646. const int startY = c->getProperties() ["yDragStart"];
  647. c->setTopLeftPosition (startX, startY);
  648. updateStoredComponentPosition (c, false);
  649. c->setTopLeftPosition (newX, newY);
  650. updateStoredComponentPosition (c, true);
  651. }
  652. document->beginTransaction();
  653. }
  654. void ComponentLayout::moveSelectedComps (int dx, int dy, bool snap)
  655. {
  656. startDragging();
  657. dragSelectedComps (dx, dy, snap);
  658. endDragging();
  659. }
  660. void ComponentLayout::stretchSelectedComps (int dw, int dh, bool allowSnap)
  661. {
  662. int neww, newh;
  663. if (document != nullptr && selected.getNumSelected() == 1)
  664. {
  665. Component* const c = selected.getSelectedItem (0);
  666. if (allowSnap)
  667. {
  668. int bot = c->getBottom() + dh;
  669. int right = c->getRight() + dw;
  670. bot = (dh != 0) ? document->snapPosition (bot) : bot;
  671. right = (dw != 0) ? document->snapPosition (right) : right;
  672. newh = bot - c->getY();
  673. neww = right - c->getX();
  674. }
  675. else
  676. {
  677. newh = c->getHeight() + dh;
  678. neww = c->getWidth() + dw;
  679. }
  680. c->setSize (neww, newh);
  681. updateStoredComponentPosition (c, true);
  682. }
  683. else
  684. {
  685. for (int i = 0; i < selected.getNumSelected(); ++i)
  686. {
  687. Component* const c = selected.getSelectedItem (i);
  688. neww = c->getWidth() + dw;
  689. newh = c->getHeight() + dh;
  690. c->setSize (neww, newh);
  691. updateStoredComponentPosition (c, true);
  692. }
  693. }
  694. }
  695. //==============================================================================
  696. void ComponentLayout::fillInGeneratedCode (GeneratedCode& code) const
  697. {
  698. for (int i = 0; i < components.size(); ++i)
  699. if (Component* const comp = components.getUnchecked(i))
  700. if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
  701. type->fillInGeneratedCode (comp, code);
  702. }
  703. //==============================================================================
  704. String ComponentLayout::getComponentMemberVariableName (Component* comp) const
  705. {
  706. if (comp == nullptr)
  707. return {};
  708. String name (comp->getProperties() ["memberName"].toString());
  709. if (name.isEmpty())
  710. name = getUnusedMemberName (CodeHelpers::makeValidIdentifier (comp->getName(), true, true, false), comp);
  711. return name;
  712. }
  713. void ComponentLayout::setComponentMemberVariableName (Component* comp, const String& newName)
  714. {
  715. jassert (comp != nullptr);
  716. const String oldName (getComponentMemberVariableName (comp));
  717. comp->getProperties().set ("memberName", String());
  718. const String n (getUnusedMemberName (CodeHelpers::makeValidIdentifier (newName, false, true, false), comp));
  719. comp->getProperties().set ("memberName", n);
  720. if (n != oldName)
  721. changed();
  722. }
  723. String ComponentLayout::getUnusedMemberName (String nameRoot, Component* comp) const
  724. {
  725. String n (nameRoot);
  726. while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
  727. nameRoot = nameRoot.dropLastCharacters (1);
  728. int suffix = 2;
  729. for (;;)
  730. {
  731. bool alreadyUsed = false;
  732. for (int i = 0; i < components.size(); ++i)
  733. {
  734. if (components[i] != comp
  735. && components[i]->getProperties() ["memberName"] == n)
  736. {
  737. alreadyUsed = true;
  738. break;
  739. }
  740. }
  741. if (! alreadyUsed)
  742. break;
  743. n = nameRoot + String (suffix++);
  744. }
  745. return n;
  746. }
  747. //==============================================================================
  748. String ComponentLayout::getComponentVirtualClassName (Component* comp) const
  749. {
  750. if (comp == nullptr)
  751. return {};
  752. return comp->getProperties() ["virtualName"];
  753. }
  754. void ComponentLayout::setComponentVirtualClassName (Component* comp, const String& newName)
  755. {
  756. jassert (comp != nullptr);
  757. const String name (CodeHelpers::makeValidIdentifier (newName, false, false, true));
  758. if (name != getComponentVirtualClassName (comp))
  759. {
  760. comp->getProperties().set ("virtualName", name);
  761. changed();
  762. }
  763. }
  764. //==============================================================================
  765. void ComponentLayout::addToXml (XmlElement& xml) const
  766. {
  767. for (int i = 0; i < components.size(); ++i)
  768. if (ComponentTypeHandler* h = ComponentTypeHandler::getHandlerFor (*components [i]))
  769. xml.addChildElement (h->createXmlFor (components [i], this));
  770. }
  771. static String bracketIfNeeded (const String& s)
  772. {
  773. return s.containsAnyOf ("+-*/%") ? "(" + s + ")" : s;
  774. }
  775. //==============================================================================
  776. void positionToCode (const RelativePositionedRectangle& position,
  777. const ComponentLayout* layout,
  778. String& x, String& y, String& w, String& h)
  779. {
  780. // these are the code sections for the positions of the relative comps
  781. String xrx, xry, xrw, xrh;
  782. if (Component* const relCompX = layout != nullptr ? layout->findComponentWithId (position.relativeToX) : nullptr)
  783. positionToCode (ComponentTypeHandler::getComponentPosition (relCompX), layout, xrx, xry, xrw, xrh);
  784. String yrx, yry, yrw, yrh;
  785. if (Component* const relCompY = layout != nullptr ? layout->findComponentWithId (position.relativeToY) : nullptr)
  786. positionToCode (ComponentTypeHandler::getComponentPosition (relCompY), layout, yrx, yry, yrw, yrh);
  787. String wrx, wry, wrw, wrh;
  788. if (Component* const relCompW = (layout != nullptr && position.rect.getWidthMode() != PositionedRectangle::absoluteSize)
  789. ? layout->findComponentWithId (position.relativeToW) : nullptr)
  790. positionToCode (ComponentTypeHandler::getComponentPosition (relCompW), layout, wrx, wry, wrw, wrh);
  791. String hrx, hry, hrw, hrh;
  792. if (Component* const relCompH = (layout != nullptr && position.rect.getHeightMode() != PositionedRectangle::absoluteSize)
  793. ? layout->findComponentWithId (position.relativeToH) : nullptr)
  794. positionToCode (ComponentTypeHandler::getComponentPosition (relCompH), layout, hrx, hry, hrw, hrh);
  795. // width
  796. if (position.rect.getWidthMode() == PositionedRectangle::proportionalSize)
  797. {
  798. if (wrw.isNotEmpty())
  799. w << "roundToInt (" << bracketIfNeeded (wrw) << " * " << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  800. else
  801. w << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getWidth(), 4) << ")";
  802. }
  803. else if (position.rect.getWidthMode() == PositionedRectangle::parentSizeMinusAbsolute)
  804. {
  805. if (wrw.isNotEmpty())
  806. w << bracketIfNeeded (wrw) << " - " << roundToInt (position.rect.getWidth());
  807. else
  808. w << "getWidth() - " << roundToInt (position.rect.getWidth());
  809. }
  810. else
  811. {
  812. if (wrw.isNotEmpty())
  813. w << bracketIfNeeded (wrw) << " + ";
  814. w << roundToInt (position.rect.getWidth());
  815. }
  816. // height
  817. if (position.rect.getHeightMode() == PositionedRectangle::proportionalSize)
  818. {
  819. if (hrh.isNotEmpty())
  820. h << "roundToInt (" << bracketIfNeeded (hrh) << " * " << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  821. else
  822. h << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getHeight(), 4) << ")";
  823. }
  824. else if (position.rect.getHeightMode() == PositionedRectangle::parentSizeMinusAbsolute)
  825. {
  826. if (hrh.isNotEmpty())
  827. h << bracketIfNeeded (hrh) << " - " << roundToInt (position.rect.getHeight());
  828. else
  829. h << "getHeight() - " << roundToInt (position.rect.getHeight());
  830. }
  831. else
  832. {
  833. if (hrh.isNotEmpty())
  834. h << bracketIfNeeded (hrh) << " + ";
  835. h << roundToInt (position.rect.getHeight());
  836. }
  837. // x-pos
  838. if (position.rect.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  839. {
  840. if (xrx.isNotEmpty() && xrw.isNotEmpty())
  841. x << bracketIfNeeded (xrx) << " + roundToInt (" << bracketIfNeeded (xrw) << " * " << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  842. else
  843. x << "proportionOfWidth (" << CodeHelpers::floatLiteral (position.rect.getX(), 4) << ")";
  844. }
  845. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentTopLeft)
  846. {
  847. if (xrx.isNotEmpty())
  848. x << bracketIfNeeded (xrx) << " + ";
  849. x << roundToInt (position.rect.getX());
  850. }
  851. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentBottomRight)
  852. {
  853. if (xrx.isNotEmpty())
  854. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw);
  855. else
  856. x << "getWidth()";
  857. const int d = roundToInt (position.rect.getX());
  858. if (d != 0)
  859. x << " - " << d;
  860. }
  861. else if (position.rect.getPositionModeX() == PositionedRectangle::absoluteFromParentCentre)
  862. {
  863. if (xrx.isNotEmpty())
  864. x << bracketIfNeeded (xrx) << " + " << bracketIfNeeded (xrw) << " / 2";
  865. else
  866. x << "(getWidth() / 2)";
  867. const int d = roundToInt (position.rect.getX());
  868. if (d != 0)
  869. x << " + " << d;
  870. }
  871. if (w != "0")
  872. {
  873. if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtRightOrBottom)
  874. x << " - " << bracketIfNeeded (w);
  875. else if (position.rect.getAnchorPointX() == PositionedRectangle::anchorAtCentre)
  876. x << " - (" << bracketIfNeeded (w) << " / 2)";
  877. }
  878. // y-pos
  879. if (position.rect.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  880. {
  881. if (yry.isNotEmpty() && yrh.isNotEmpty())
  882. y << bracketIfNeeded (yry) << " + roundToInt (" << bracketIfNeeded (yrh) << " * " << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  883. else
  884. y << "proportionOfHeight (" << CodeHelpers::floatLiteral (position.rect.getY(), 4) << ")";
  885. }
  886. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentTopLeft)
  887. {
  888. if (yry.isNotEmpty())
  889. y << bracketIfNeeded (yry) << " + ";
  890. y << roundToInt (position.rect.getY());
  891. }
  892. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentBottomRight)
  893. {
  894. if (yry.isNotEmpty())
  895. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh);
  896. else
  897. y << "getHeight()";
  898. const int d = roundToInt (position.rect.getY());
  899. if (d != 0)
  900. y << " - " << d;
  901. }
  902. else if (position.rect.getPositionModeY() == PositionedRectangle::absoluteFromParentCentre)
  903. {
  904. if (yry.isNotEmpty())
  905. y << bracketIfNeeded (yry) << " + " << bracketIfNeeded (yrh) << " / 2";
  906. else
  907. y << "(getHeight() / 2)";
  908. const int d = roundToInt (position.rect.getY());
  909. if (d != 0)
  910. y << " + " << d;
  911. }
  912. if (h != "0")
  913. {
  914. if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtRightOrBottom)
  915. y << " - " << bracketIfNeeded (h);
  916. else if (position.rect.getAnchorPointY() == PositionedRectangle::anchorAtCentre)
  917. y << " - (" << bracketIfNeeded (h) << " / 2)";
  918. }
  919. }