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.

691 lines
22KB

  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 "../../Application/jucer_Application.h"
  21. #include "../jucer_PaintRoutine.h"
  22. #include "../jucer_UtilityFunctions.h"
  23. #include "../UI/jucer_JucerCommandIDs.h"
  24. #include "../UI/jucer_PaintRoutineEditor.h"
  25. #include "../Properties/jucer_PositionPropertyBase.h"
  26. #include "jucer_ElementSiblingComponent.h"
  27. #include "jucer_PaintElementUndoableAction.h"
  28. //==============================================================================
  29. PaintElement::PaintElement (PaintRoutine* owner_,
  30. const String& typeName_)
  31. : borderThickness (4),
  32. owner (owner_),
  33. typeName (typeName_),
  34. selected (false),
  35. dragging (false),
  36. originalAspectRatio (1.0)
  37. {
  38. setRepaintsOnMouseActivity (true);
  39. position.rect.setWidth (100);
  40. position.rect.setHeight (100);
  41. setMinimumOnscreenAmounts (0, 0, 0, 0);
  42. setSizeLimits (borderThickness * 2 + 1, borderThickness * 2 + 1, 8192, 8192);
  43. border.reset (new ResizableBorderComponent (this, this));
  44. addChildComponent (border.get());
  45. border->setBorderThickness (BorderSize<int> (borderThickness));
  46. if (owner != nullptr)
  47. owner->getSelectedElements().addChangeListener (this);
  48. selfChangeListenerList.addChangeListener (this);
  49. siblingComponentsChanged();
  50. }
  51. PaintElement::~PaintElement()
  52. {
  53. siblingComponents.clear();
  54. if (owner != nullptr)
  55. {
  56. owner->getSelectedElements().deselect (this);
  57. owner->getSelectedElements().removeChangeListener (this);
  58. }
  59. }
  60. //==============================================================================
  61. void PaintElement::setInitialBounds (int parentWidth, int parentHeight)
  62. {
  63. RelativePositionedRectangle pr (getPosition());
  64. pr.rect.setX (parentWidth / 4 + Random::getSystemRandom().nextInt (parentWidth / 4) - parentWidth / 8);
  65. pr.rect.setY (parentHeight / 3 + Random::getSystemRandom().nextInt (parentHeight / 4) - parentHeight / 8);
  66. setPosition (pr, false);
  67. }
  68. //==============================================================================
  69. const RelativePositionedRectangle& PaintElement::getPosition() const
  70. {
  71. return position;
  72. }
  73. class PaintElementMoveAction : public PaintElementUndoableAction <PaintElement>
  74. {
  75. public:
  76. PaintElementMoveAction (PaintElement* const element, const RelativePositionedRectangle& newState_)
  77. : PaintElementUndoableAction <PaintElement> (element),
  78. newState (newState_),
  79. oldState (element->getPosition())
  80. {
  81. }
  82. bool perform()
  83. {
  84. showCorrectTab();
  85. getElement()->setPosition (newState, false);
  86. return true;
  87. }
  88. bool undo()
  89. {
  90. showCorrectTab();
  91. getElement()->setPosition (oldState, false);
  92. return true;
  93. }
  94. RelativePositionedRectangle newState, oldState;
  95. };
  96. class ChangePaintElementBoundsAction : public PaintElementUndoableAction <PaintElement>
  97. {
  98. public:
  99. ChangePaintElementBoundsAction (PaintElement* const element, const Rectangle<int>& bounds)
  100. : PaintElementUndoableAction <PaintElement> (element),
  101. newBounds (bounds),
  102. oldBounds (element->getBounds())
  103. {
  104. }
  105. bool perform()
  106. {
  107. showCorrectTab();
  108. getElement()->setBounds (newBounds);
  109. return true;
  110. }
  111. bool undo()
  112. {
  113. showCorrectTab();
  114. getElement()->setBounds (oldBounds);
  115. return true;
  116. }
  117. private:
  118. Rectangle<int> newBounds, oldBounds;
  119. };
  120. class ChangePaintElementBoundsAndPropertiesAction : public PaintElementUndoableAction <PaintElement>
  121. {
  122. public:
  123. ChangePaintElementBoundsAndPropertiesAction (PaintElement* const element, const Rectangle<int>& bounds,
  124. const NamedValueSet& props)
  125. : PaintElementUndoableAction <PaintElement> (element),
  126. newBounds (bounds),
  127. oldBounds (element->getBounds()),
  128. newProps (props),
  129. oldProps (element->getProperties())
  130. {
  131. }
  132. bool perform()
  133. {
  134. showCorrectTab();
  135. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
  136. getElement()->setCurrentBounds (newBounds, pe->getComponentArea(), false);
  137. getElement()->getProperties() = newProps;
  138. return true;
  139. }
  140. bool undo()
  141. {
  142. showCorrectTab();
  143. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
  144. getElement()->setCurrentBounds (oldBounds, pe->getComponentArea(), false);
  145. getElement()->getProperties() = oldProps;
  146. return true;
  147. }
  148. private:
  149. Rectangle<int> newBounds, oldBounds;
  150. NamedValueSet newProps, oldProps;
  151. };
  152. void PaintElement::setPosition (const RelativePositionedRectangle& newPosition, const bool undoable)
  153. {
  154. if (position != newPosition)
  155. {
  156. if (undoable)
  157. {
  158. perform (new PaintElementMoveAction (this, newPosition),
  159. "Move " + getTypeName());
  160. }
  161. else
  162. {
  163. position = newPosition;
  164. if (owner != nullptr)
  165. owner->changed();
  166. }
  167. }
  168. }
  169. void PaintElement::setPaintElementBounds (const Rectangle<int>& newBounds, const bool undoable)
  170. {
  171. if (getBounds() != newBounds)
  172. {
  173. if (undoable)
  174. {
  175. perform (new ChangePaintElementBoundsAction (this, newBounds), "Change paint element bounds");
  176. }
  177. else
  178. {
  179. setBounds (newBounds);
  180. changed();
  181. }
  182. }
  183. }
  184. void PaintElement::setPaintElementBoundsAndProperties (PaintElement* elementToPosition, const Rectangle<int>& newBounds,
  185. PaintElement* referenceElement, const bool undoable)
  186. {
  187. auto props = NamedValueSet (elementToPosition->getProperties());
  188. auto rect = elementToPosition->getPosition().rect;
  189. auto referenceElementPosition = referenceElement->getPosition();
  190. auto referenceElementRect = referenceElementPosition.rect;
  191. rect.setModes (referenceElementRect.getAnchorPointX(), referenceElementRect.getPositionModeX(),
  192. referenceElementRect.getAnchorPointY(), referenceElementRect.getPositionModeY(),
  193. referenceElementRect.getWidthMode(), referenceElementRect.getHeightMode(),
  194. elementToPosition->getBounds());
  195. props.set ("pos", rect.toString());
  196. props.set ("relativeToX", String::toHexString (referenceElementPosition.relativeToX));
  197. props.set ("relativeToY", String::toHexString (referenceElementPosition.relativeToY));
  198. props.set ("relativeToW", String::toHexString (referenceElementPosition.relativeToW));
  199. props.set ("relativeToH", String::toHexString (referenceElementPosition.relativeToH));
  200. if (elementToPosition->getBounds() != newBounds || elementToPosition->getProperties() != props)
  201. {
  202. if (undoable)
  203. {
  204. perform (new ChangePaintElementBoundsAndPropertiesAction (elementToPosition, newBounds, props),
  205. "Change paint element bounds");
  206. }
  207. else
  208. {
  209. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (elementToPosition->getParentComponent()))
  210. elementToPosition->setCurrentBounds (newBounds, pe->getComponentArea(), false);
  211. elementToPosition->getProperties() = props;
  212. owner->changed();
  213. }
  214. }
  215. }
  216. //==============================================================================
  217. Rectangle<int> PaintElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  218. {
  219. return position.getRectangle (parentArea, getDocument()->getComponentLayout());
  220. }
  221. void PaintElement::setCurrentBounds (const Rectangle<int>& newBounds,
  222. const Rectangle<int>& parentArea,
  223. const bool undoable)
  224. {
  225. RelativePositionedRectangle pr (position);
  226. pr.updateFrom (newBounds.getX() - parentArea.getX(),
  227. newBounds.getY() - parentArea.getY(),
  228. jmax (1, newBounds.getWidth()),
  229. jmax (1, newBounds.getHeight()),
  230. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  231. getDocument()->getComponentLayout());
  232. setPosition (pr, undoable);
  233. updateBounds (parentArea);
  234. }
  235. void PaintElement::updateBounds (const Rectangle<int>& parentArea)
  236. {
  237. if (! parentArea.isEmpty())
  238. {
  239. setBounds (getCurrentBounds (parentArea)
  240. .expanded (borderThickness,
  241. borderThickness));
  242. for (int i = siblingComponents.size(); --i >= 0;)
  243. siblingComponents.getUnchecked(i)->updatePosition();
  244. }
  245. }
  246. //==============================================================================
  247. class ElementPositionProperty : public PositionPropertyBase
  248. {
  249. public:
  250. ElementPositionProperty (PaintElement* e, const String& name,
  251. ComponentPositionDimension dimension_)
  252. : PositionPropertyBase (e, name, dimension_, true, false,
  253. e->getDocument()->getComponentLayout()),
  254. listener (e),
  255. element (e)
  256. {
  257. listener.setPropertyToRefresh (*this);
  258. }
  259. void setPosition (const RelativePositionedRectangle& newPos)
  260. {
  261. if (element->getOwner()->getSelectedElements().getNumSelected() > 1)
  262. positionOtherSelectedElements (getPosition(), newPos);
  263. listener.owner->setPosition (newPos, true);
  264. }
  265. RelativePositionedRectangle getPosition() const
  266. {
  267. return listener.owner->getPosition();
  268. }
  269. private:
  270. ElementListener<PaintElement> listener;
  271. PaintElement* element;
  272. void positionOtherSelectedElements (const RelativePositionedRectangle& oldPos, const RelativePositionedRectangle& newPos)
  273. {
  274. for (auto* s : element->getOwner()->getSelectedElements())
  275. {
  276. if (s != element)
  277. {
  278. auto currentPos = s->getPosition();
  279. auto diff = 0.0;
  280. if (dimension == ComponentPositionDimension::componentX)
  281. {
  282. diff = newPos.rect.getX() - oldPos.rect.getX();
  283. currentPos.rect.setX (currentPos.rect.getX() + diff);
  284. }
  285. else if (dimension == ComponentPositionDimension::componentY)
  286. {
  287. diff = newPos.rect.getY() - oldPos.rect.getY();
  288. currentPos.rect.setY (currentPos.rect.getY() + diff);
  289. }
  290. else if (dimension == ComponentPositionDimension::componentWidth)
  291. {
  292. diff = newPos.rect.getWidth() - oldPos.rect.getWidth();
  293. currentPos.rect.setWidth (currentPos.rect.getWidth() + diff);
  294. }
  295. else if (dimension == ComponentPositionDimension::componentHeight)
  296. {
  297. diff = newPos.rect.getHeight() - oldPos.rect.getHeight();
  298. currentPos.rect.setHeight (currentPos.rect.getHeight() + diff);
  299. }
  300. s->setPosition (currentPos, true);
  301. }
  302. }
  303. }
  304. };
  305. //==============================================================================
  306. void PaintElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
  307. {
  308. ignoreUnused (multipleSelected);
  309. props.add (new ElementPositionProperty (this, "x", PositionPropertyBase::componentX));
  310. props.add (new ElementPositionProperty (this, "y", PositionPropertyBase::componentY));
  311. props.add (new ElementPositionProperty (this, "width", PositionPropertyBase::componentWidth));
  312. props.add (new ElementPositionProperty (this, "height", PositionPropertyBase::componentHeight));
  313. }
  314. //==============================================================================
  315. JucerDocument* PaintElement::getDocument() const
  316. {
  317. return owner->getDocument();
  318. }
  319. void PaintElement::changed()
  320. {
  321. repaint();
  322. owner->changed();
  323. }
  324. bool PaintElement::perform (UndoableAction* action, const String& actionName)
  325. {
  326. return owner->perform (action, actionName);
  327. }
  328. void PaintElement::parentHierarchyChanged()
  329. {
  330. updateSiblingComps();
  331. }
  332. //==============================================================================
  333. void PaintElement::drawExtraEditorGraphics (Graphics&, const Rectangle<int>& /*relativeTo*/)
  334. {
  335. }
  336. void PaintElement::paint (Graphics& g)
  337. {
  338. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  339. {
  340. auto area = pe->getComponentArea();
  341. g.saveState();
  342. g.setOrigin (area.getPosition() - Component::getPosition());
  343. area.setPosition (0, 0);
  344. g.saveState();
  345. g.reduceClipRegion (0, 0, area.getWidth(), area.getHeight());
  346. draw (g, getDocument()->getComponentLayout(), area);
  347. g.restoreState();
  348. drawExtraEditorGraphics (g, area);
  349. g.restoreState();
  350. if (selected)
  351. {
  352. const BorderSize<int> borderSize (border->getBorderThickness());
  353. auto baseColour = findColour (defaultHighlightColourId);
  354. drawResizableBorder (g, getWidth(), getHeight(), borderSize,
  355. (isMouseOverOrDragging() || border->isMouseOverOrDragging()),
  356. baseColour.withAlpha (owner->getSelectedElements().getSelectedItem (0) == this ? 1.0f : 0.3f));
  357. }
  358. else if (isMouseOverOrDragging())
  359. {
  360. drawMouseOverCorners (g, getWidth(), getHeight());
  361. }
  362. }
  363. }
  364. void PaintElement::resized()
  365. {
  366. border->setBounds (getLocalBounds());
  367. }
  368. void PaintElement::mouseDown (const MouseEvent& e)
  369. {
  370. dragging = false;
  371. if (owner != nullptr)
  372. {
  373. owner->getSelectedPoints().deselectAll();
  374. mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
  375. }
  376. if (e.mods.isPopupMenu())
  377. {
  378. showPopupMenu();
  379. return; // this may be deleted now..
  380. }
  381. }
  382. void PaintElement::mouseDrag (const MouseEvent& e)
  383. {
  384. if (! e.mods.isPopupMenu())
  385. {
  386. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  387. {
  388. auto area = pe->getComponentArea();
  389. if (selected && ! dragging)
  390. {
  391. dragging = e.mouseWasDraggedSinceMouseDown();
  392. if (dragging)
  393. owner->startDragging (area);
  394. }
  395. if (dragging)
  396. owner->dragSelectedComps (e.getDistanceFromDragStartX(),
  397. e.getDistanceFromDragStartY(),
  398. area);
  399. }
  400. }
  401. }
  402. void PaintElement::mouseUp (const MouseEvent& e)
  403. {
  404. if (owner != nullptr)
  405. {
  406. if (dragging)
  407. owner->endDragging();
  408. if (owner != nullptr)
  409. owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
  410. }
  411. }
  412. void PaintElement::resizeStart()
  413. {
  414. if (getHeight() > 0)
  415. originalAspectRatio = getWidth() / (double) getHeight();
  416. else
  417. originalAspectRatio = 1.0;
  418. }
  419. void PaintElement::resizeEnd()
  420. {
  421. }
  422. void PaintElement::checkBounds (Rectangle<int>& b,
  423. const Rectangle<int>& previousBounds,
  424. const Rectangle<int>& limits,
  425. const bool isStretchingTop,
  426. const bool isStretchingLeft,
  427. const bool isStretchingBottom,
  428. const bool isStretchingRight)
  429. {
  430. if (ModifierKeys::currentModifiers.isShiftDown())
  431. setFixedAspectRatio (originalAspectRatio);
  432. else
  433. setFixedAspectRatio (0.0);
  434. ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
  435. if (auto* document = getDocument())
  436. {
  437. if (document->isSnapActive (true))
  438. {
  439. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  440. {
  441. auto area = pe->getComponentArea();
  442. int x = b.getX();
  443. int y = b.getY();
  444. int w = b.getWidth();
  445. int h = b.getHeight();
  446. x += borderThickness - area.getX();
  447. y += borderThickness - area.getY();
  448. w -= borderThickness * 2;
  449. h -= borderThickness * 2;
  450. int right = x + w;
  451. int bottom = y + h;
  452. if (isStretchingRight)
  453. right = document->snapPosition (right);
  454. if (isStretchingBottom)
  455. bottom = document->snapPosition (bottom);
  456. if (isStretchingLeft)
  457. x = document->snapPosition (x);
  458. if (isStretchingTop)
  459. y = document->snapPosition (y);
  460. w = (right - x) + borderThickness * 2;
  461. h = (bottom - y) + borderThickness * 2;
  462. x -= borderThickness - area.getX();
  463. y -= borderThickness - area.getY();
  464. b = { x, y, w, h };
  465. }
  466. }
  467. }
  468. }
  469. void PaintElement::applyBoundsToComponent (Component&, Rectangle<int> newBounds)
  470. {
  471. if (getBounds() != newBounds)
  472. {
  473. getDocument()->getUndoManager().undoCurrentTransactionOnly();
  474. auto dX = newBounds.getX() - getX();
  475. auto dY = newBounds.getY() - getY();
  476. auto dW = newBounds.getWidth() - getWidth();
  477. auto dH = newBounds.getHeight() - getHeight();
  478. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  479. setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
  480. pe->getComponentArea(), true);
  481. if (owner->getSelectedElements().getNumSelected() > 1)
  482. {
  483. for (auto selectedElement : owner->getSelectedElements())
  484. {
  485. if (selectedElement != this)
  486. {
  487. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (selectedElement->getParentComponent()))
  488. {
  489. Rectangle<int> r { selectedElement->getX() + dX, selectedElement->getY() + dY,
  490. selectedElement->getWidth() + dW, selectedElement->getHeight() + dH };
  491. selectedElement->setCurrentBounds (r.expanded (-borderThickness, -borderThickness),
  492. pe->getComponentArea(), true);
  493. }
  494. }
  495. }
  496. }
  497. }
  498. }
  499. Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
  500. {
  501. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  502. return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
  503. return {};
  504. }
  505. void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
  506. {
  507. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  508. position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
  509. }
  510. void PaintElement::changeListenerCallback (ChangeBroadcaster*)
  511. {
  512. const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
  513. if (selected != nowSelected)
  514. {
  515. selected = nowSelected;
  516. border->setVisible (nowSelected);
  517. repaint();
  518. selectionChanged (nowSelected);
  519. }
  520. updateSiblingComps();
  521. }
  522. void PaintElement::selectionChanged (const bool /*isSelected*/)
  523. {
  524. }
  525. void PaintElement::createSiblingComponents()
  526. {
  527. }
  528. void PaintElement::siblingComponentsChanged()
  529. {
  530. siblingComponents.clear();
  531. selfChangeListenerList.sendChangeMessage();
  532. }
  533. void PaintElement::updateSiblingComps()
  534. {
  535. if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
  536. {
  537. if (siblingComponents.size() == 0)
  538. createSiblingComponents();
  539. for (int i = siblingComponents.size(); --i >= 0;)
  540. siblingComponents.getUnchecked(i)->updatePosition();
  541. }
  542. else
  543. {
  544. siblingComponents.clear();
  545. }
  546. }
  547. void PaintElement::showPopupMenu()
  548. {
  549. auto* commandManager = &ProjucerApplication::getCommandManager();
  550. PopupMenu m;
  551. m.addCommandItem (commandManager, JucerCommandIDs::toFront);
  552. m.addCommandItem (commandManager, JucerCommandIDs::toBack);
  553. m.addSeparator();
  554. if (owner->getSelectedElements().getNumSelected() > 1)
  555. {
  556. m.addCommandItem (commandManager, JucerCommandIDs::alignTop);
  557. m.addCommandItem (commandManager, JucerCommandIDs::alignRight);
  558. m.addCommandItem (commandManager, JucerCommandIDs::alignBottom);
  559. m.addCommandItem (commandManager, JucerCommandIDs::alignLeft);
  560. m.addSeparator();
  561. }
  562. m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  563. m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  564. m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  565. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  566. m.show();
  567. }