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.

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