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.

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