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.

490 lines
15KB

  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 "../../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. addChildComponent (border = new ResizableBorderComponent (this, this));
  44. border->setBorderThickness (BorderSize<int> (borderThickness));
  45. if (owner != nullptr)
  46. owner->getSelectedElements().addChangeListener (this);
  47. selfChangeListenerList.addChangeListener (this);
  48. siblingComponentsChanged();
  49. }
  50. PaintElement::~PaintElement()
  51. {
  52. siblingComponents.clear();
  53. if (owner != nullptr)
  54. {
  55. owner->getSelectedElements().deselect (this);
  56. owner->getSelectedElements().removeChangeListener (this);
  57. }
  58. }
  59. //==============================================================================
  60. void PaintElement::setInitialBounds (int parentWidth, int parentHeight)
  61. {
  62. RelativePositionedRectangle pr (getPosition());
  63. pr.rect.setX (parentWidth / 4 + Random::getSystemRandom().nextInt (parentWidth / 4) - parentWidth / 8);
  64. pr.rect.setY (parentHeight / 3 + Random::getSystemRandom().nextInt (parentHeight / 4) - parentHeight / 8);
  65. setPosition (pr, false);
  66. }
  67. //==============================================================================
  68. const RelativePositionedRectangle& PaintElement::getPosition() const
  69. {
  70. return position;
  71. }
  72. class PaintElementMoveAction : public PaintElementUndoableAction <PaintElement>
  73. {
  74. public:
  75. PaintElementMoveAction (PaintElement* const element, const RelativePositionedRectangle& newState_)
  76. : PaintElementUndoableAction <PaintElement> (element),
  77. newState (newState_),
  78. oldState (element->getPosition())
  79. {
  80. }
  81. bool perform()
  82. {
  83. showCorrectTab();
  84. getElement()->setPosition (newState, false);
  85. return true;
  86. }
  87. bool undo()
  88. {
  89. showCorrectTab();
  90. getElement()->setPosition (oldState, false);
  91. return true;
  92. }
  93. RelativePositionedRectangle newState, oldState;
  94. };
  95. void PaintElement::setPosition (const RelativePositionedRectangle& newPosition, const bool undoable)
  96. {
  97. if (position != newPosition)
  98. {
  99. if (undoable)
  100. {
  101. perform (new PaintElementMoveAction (this, newPosition),
  102. "Move " + getTypeName());
  103. }
  104. else
  105. {
  106. position = newPosition;
  107. if (owner != nullptr)
  108. owner->changed();
  109. }
  110. }
  111. }
  112. //==============================================================================
  113. Rectangle<int> PaintElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  114. {
  115. return position.getRectangle (parentArea, getDocument()->getComponentLayout());
  116. }
  117. void PaintElement::setCurrentBounds (const Rectangle<int>& newBounds,
  118. const Rectangle<int>& parentArea,
  119. const bool undoable)
  120. {
  121. RelativePositionedRectangle pr (position);
  122. pr.updateFrom (newBounds.getX() - parentArea.getX(),
  123. newBounds.getY() - parentArea.getY(),
  124. jmax (1, newBounds.getWidth()),
  125. jmax (1, newBounds.getHeight()),
  126. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  127. getDocument()->getComponentLayout());
  128. setPosition (pr, undoable);
  129. updateBounds (parentArea);
  130. }
  131. void PaintElement::updateBounds (const Rectangle<int>& parentArea)
  132. {
  133. if (! parentArea.isEmpty())
  134. {
  135. setBounds (getCurrentBounds (parentArea)
  136. .expanded (borderThickness,
  137. borderThickness));
  138. for (int i = siblingComponents.size(); --i >= 0;)
  139. siblingComponents.getUnchecked(i)->updatePosition();
  140. }
  141. }
  142. //==============================================================================
  143. class ElementPositionProperty : public PositionPropertyBase
  144. {
  145. public:
  146. ElementPositionProperty (PaintElement* e, const String& name,
  147. ComponentPositionDimension dimension_)
  148. : PositionPropertyBase (e, name, dimension_, true, false,
  149. e->getDocument()->getComponentLayout()),
  150. listener (e)
  151. {
  152. listener.setPropertyToRefresh (*this);
  153. }
  154. void setPosition (const RelativePositionedRectangle& newPos)
  155. {
  156. listener.owner->setPosition (newPos, true);
  157. }
  158. RelativePositionedRectangle getPosition() const
  159. {
  160. return listener.owner->getPosition();
  161. }
  162. ElementListener<PaintElement> listener;
  163. };
  164. //==============================================================================
  165. void PaintElement::getEditableProperties (Array <PropertyComponent*>& props)
  166. {
  167. props.add (new ElementPositionProperty (this, "x", PositionPropertyBase::componentX));
  168. props.add (new ElementPositionProperty (this, "y", PositionPropertyBase::componentY));
  169. props.add (new ElementPositionProperty (this, "width", PositionPropertyBase::componentWidth));
  170. props.add (new ElementPositionProperty (this, "height", PositionPropertyBase::componentHeight));
  171. }
  172. //==============================================================================
  173. JucerDocument* PaintElement::getDocument() const
  174. {
  175. return owner->getDocument();
  176. }
  177. void PaintElement::changed()
  178. {
  179. repaint();
  180. owner->changed();
  181. }
  182. bool PaintElement::perform (UndoableAction* action, const String& actionName)
  183. {
  184. return owner->perform (action, actionName);
  185. }
  186. void PaintElement::parentHierarchyChanged()
  187. {
  188. updateSiblingComps();
  189. }
  190. //==============================================================================
  191. void PaintElement::drawExtraEditorGraphics (Graphics&, const Rectangle<int>& /*relativeTo*/)
  192. {
  193. }
  194. void PaintElement::paint (Graphics& g)
  195. {
  196. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  197. {
  198. auto area = pe->getComponentArea();
  199. g.saveState();
  200. g.setOrigin (area.getPosition() - Component::getPosition());
  201. area.setPosition (0, 0);
  202. g.saveState();
  203. g.reduceClipRegion (0, 0, area.getWidth(), area.getHeight());
  204. draw (g, getDocument()->getComponentLayout(), area);
  205. g.restoreState();
  206. drawExtraEditorGraphics (g, area);
  207. g.restoreState();
  208. if (selected)
  209. {
  210. const BorderSize<int> borderSize (border->getBorderThickness());
  211. drawResizableBorder (g, getWidth(), getHeight(), borderSize,
  212. (isMouseOverOrDragging() || border->isMouseOverOrDragging()));
  213. }
  214. else if (isMouseOverOrDragging())
  215. {
  216. drawMouseOverCorners (g, getWidth(), getHeight());
  217. }
  218. }
  219. }
  220. void PaintElement::resized()
  221. {
  222. border->setBounds (getLocalBounds());
  223. }
  224. void PaintElement::mouseDown (const MouseEvent& e)
  225. {
  226. dragging = false;
  227. if (owner != nullptr)
  228. {
  229. owner->getSelectedPoints().deselectAll();
  230. mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
  231. }
  232. if (e.mods.isPopupMenu())
  233. {
  234. showPopupMenu();
  235. return; // this may be deleted now..
  236. }
  237. }
  238. void PaintElement::mouseDrag (const MouseEvent& e)
  239. {
  240. if (! e.mods.isPopupMenu())
  241. {
  242. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  243. {
  244. auto area = pe->getComponentArea();
  245. if (selected && ! dragging)
  246. {
  247. dragging = e.mouseWasDraggedSinceMouseDown();
  248. if (dragging)
  249. owner->startDragging (area);
  250. }
  251. if (dragging)
  252. owner->dragSelectedComps (e.getDistanceFromDragStartX(),
  253. e.getDistanceFromDragStartY(),
  254. area);
  255. }
  256. }
  257. }
  258. void PaintElement::mouseUp (const MouseEvent& e)
  259. {
  260. if (owner != nullptr)
  261. {
  262. if (dragging)
  263. owner->endDragging();
  264. if (owner != nullptr)
  265. owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
  266. }
  267. }
  268. void PaintElement::resizeStart()
  269. {
  270. if (getHeight() > 0)
  271. originalAspectRatio = getWidth() / (double) getHeight();
  272. else
  273. originalAspectRatio = 1.0;
  274. }
  275. void PaintElement::resizeEnd()
  276. {
  277. }
  278. void PaintElement::checkBounds (Rectangle<int>& b,
  279. const Rectangle<int>& previousBounds,
  280. const Rectangle<int>& limits,
  281. const bool isStretchingTop,
  282. const bool isStretchingLeft,
  283. const bool isStretchingBottom,
  284. const bool isStretchingRight)
  285. {
  286. if (ModifierKeys::getCurrentModifiers().isShiftDown())
  287. setFixedAspectRatio (originalAspectRatio);
  288. else
  289. setFixedAspectRatio (0.0);
  290. ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
  291. if (auto* document = getDocument())
  292. {
  293. if (document->isSnapActive (true))
  294. {
  295. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  296. {
  297. auto area = pe->getComponentArea();
  298. int x = b.getX();
  299. int y = b.getY();
  300. int w = b.getWidth();
  301. int h = b.getHeight();
  302. x += borderThickness - area.getX();
  303. y += borderThickness - area.getY();
  304. w -= borderThickness * 2;
  305. h -= borderThickness * 2;
  306. int right = x + w;
  307. int bottom = y + h;
  308. if (isStretchingRight)
  309. right = document->snapPosition (right);
  310. if (isStretchingBottom)
  311. bottom = document->snapPosition (bottom);
  312. if (isStretchingLeft)
  313. x = document->snapPosition (x);
  314. if (isStretchingTop)
  315. y = document->snapPosition (y);
  316. w = (right - x) + borderThickness * 2;
  317. h = (bottom - y) + borderThickness * 2;
  318. x -= borderThickness - area.getX();
  319. y -= borderThickness - area.getY();
  320. b = { x, y, w, h };
  321. }
  322. }
  323. }
  324. }
  325. void PaintElement::applyBoundsToComponent (Component*, const Rectangle<int>& newBounds)
  326. {
  327. if (getBounds() != newBounds)
  328. {
  329. getDocument()->getUndoManager().undoCurrentTransactionOnly();
  330. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  331. setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
  332. pe->getComponentArea(), true);
  333. }
  334. }
  335. Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
  336. {
  337. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  338. return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
  339. return {};
  340. }
  341. void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
  342. {
  343. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  344. position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
  345. }
  346. void PaintElement::changeListenerCallback (ChangeBroadcaster*)
  347. {
  348. const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
  349. if (selected != nowSelected)
  350. {
  351. selected = nowSelected;
  352. border->setVisible (nowSelected);
  353. repaint();
  354. selectionChanged (nowSelected);
  355. }
  356. updateSiblingComps();
  357. }
  358. void PaintElement::selectionChanged (const bool /*isSelected*/)
  359. {
  360. }
  361. void PaintElement::createSiblingComponents()
  362. {
  363. }
  364. void PaintElement::siblingComponentsChanged()
  365. {
  366. siblingComponents.clear();
  367. selfChangeListenerList.sendChangeMessage();
  368. }
  369. void PaintElement::updateSiblingComps()
  370. {
  371. if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
  372. {
  373. if (siblingComponents.size() == 0)
  374. createSiblingComponents();
  375. for (int i = siblingComponents.size(); --i >= 0;)
  376. siblingComponents.getUnchecked(i)->updatePosition();
  377. }
  378. else
  379. {
  380. siblingComponents.clear();
  381. }
  382. }
  383. void PaintElement::showPopupMenu()
  384. {
  385. auto* commandManager = &ProjucerApplication::getCommandManager();
  386. PopupMenu m;
  387. m.addCommandItem (commandManager, JucerCommandIDs::toFront);
  388. m.addCommandItem (commandManager, JucerCommandIDs::toBack);
  389. m.addSeparator();
  390. m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  391. m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  392. m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  393. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  394. m.show();
  395. }