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.

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