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.

491 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. findColour (defaultHighlightColourId));
  214. }
  215. else if (isMouseOverOrDragging())
  216. {
  217. drawMouseOverCorners (g, getWidth(), getHeight());
  218. }
  219. }
  220. }
  221. void PaintElement::resized()
  222. {
  223. border->setBounds (getLocalBounds());
  224. }
  225. void PaintElement::mouseDown (const MouseEvent& e)
  226. {
  227. dragging = false;
  228. if (owner != nullptr)
  229. {
  230. owner->getSelectedPoints().deselectAll();
  231. mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
  232. }
  233. if (e.mods.isPopupMenu())
  234. {
  235. showPopupMenu();
  236. return; // this may be deleted now..
  237. }
  238. }
  239. void PaintElement::mouseDrag (const MouseEvent& e)
  240. {
  241. if (! e.mods.isPopupMenu())
  242. {
  243. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  244. {
  245. auto area = pe->getComponentArea();
  246. if (selected && ! dragging)
  247. {
  248. dragging = e.mouseWasDraggedSinceMouseDown();
  249. if (dragging)
  250. owner->startDragging (area);
  251. }
  252. if (dragging)
  253. owner->dragSelectedComps (e.getDistanceFromDragStartX(),
  254. e.getDistanceFromDragStartY(),
  255. area);
  256. }
  257. }
  258. }
  259. void PaintElement::mouseUp (const MouseEvent& e)
  260. {
  261. if (owner != nullptr)
  262. {
  263. if (dragging)
  264. owner->endDragging();
  265. if (owner != nullptr)
  266. owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
  267. }
  268. }
  269. void PaintElement::resizeStart()
  270. {
  271. if (getHeight() > 0)
  272. originalAspectRatio = getWidth() / (double) getHeight();
  273. else
  274. originalAspectRatio = 1.0;
  275. }
  276. void PaintElement::resizeEnd()
  277. {
  278. }
  279. void PaintElement::checkBounds (Rectangle<int>& b,
  280. const Rectangle<int>& previousBounds,
  281. const Rectangle<int>& limits,
  282. const bool isStretchingTop,
  283. const bool isStretchingLeft,
  284. const bool isStretchingBottom,
  285. const bool isStretchingRight)
  286. {
  287. if (ModifierKeys::getCurrentModifiers().isShiftDown())
  288. setFixedAspectRatio (originalAspectRatio);
  289. else
  290. setFixedAspectRatio (0.0);
  291. ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
  292. if (auto* document = getDocument())
  293. {
  294. if (document->isSnapActive (true))
  295. {
  296. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  297. {
  298. auto area = pe->getComponentArea();
  299. int x = b.getX();
  300. int y = b.getY();
  301. int w = b.getWidth();
  302. int h = b.getHeight();
  303. x += borderThickness - area.getX();
  304. y += borderThickness - area.getY();
  305. w -= borderThickness * 2;
  306. h -= borderThickness * 2;
  307. int right = x + w;
  308. int bottom = y + h;
  309. if (isStretchingRight)
  310. right = document->snapPosition (right);
  311. if (isStretchingBottom)
  312. bottom = document->snapPosition (bottom);
  313. if (isStretchingLeft)
  314. x = document->snapPosition (x);
  315. if (isStretchingTop)
  316. y = document->snapPosition (y);
  317. w = (right - x) + borderThickness * 2;
  318. h = (bottom - y) + borderThickness * 2;
  319. x -= borderThickness - area.getX();
  320. y -= borderThickness - area.getY();
  321. b = { x, y, w, h };
  322. }
  323. }
  324. }
  325. }
  326. void PaintElement::applyBoundsToComponent (Component&, Rectangle<int> newBounds)
  327. {
  328. if (getBounds() != newBounds)
  329. {
  330. getDocument()->getUndoManager().undoCurrentTransactionOnly();
  331. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  332. setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
  333. pe->getComponentArea(), true);
  334. }
  335. }
  336. Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
  337. {
  338. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  339. return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
  340. return {};
  341. }
  342. void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
  343. {
  344. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  345. position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
  346. }
  347. void PaintElement::changeListenerCallback (ChangeBroadcaster*)
  348. {
  349. const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
  350. if (selected != nowSelected)
  351. {
  352. selected = nowSelected;
  353. border->setVisible (nowSelected);
  354. repaint();
  355. selectionChanged (nowSelected);
  356. }
  357. updateSiblingComps();
  358. }
  359. void PaintElement::selectionChanged (const bool /*isSelected*/)
  360. {
  361. }
  362. void PaintElement::createSiblingComponents()
  363. {
  364. }
  365. void PaintElement::siblingComponentsChanged()
  366. {
  367. siblingComponents.clear();
  368. selfChangeListenerList.sendChangeMessage();
  369. }
  370. void PaintElement::updateSiblingComps()
  371. {
  372. if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
  373. {
  374. if (siblingComponents.size() == 0)
  375. createSiblingComponents();
  376. for (int i = siblingComponents.size(); --i >= 0;)
  377. siblingComponents.getUnchecked(i)->updatePosition();
  378. }
  379. else
  380. {
  381. siblingComponents.clear();
  382. }
  383. }
  384. void PaintElement::showPopupMenu()
  385. {
  386. auto* commandManager = &ProjucerApplication::getCommandManager();
  387. PopupMenu m;
  388. m.addCommandItem (commandManager, JucerCommandIDs::toFront);
  389. m.addCommandItem (commandManager, JucerCommandIDs::toBack);
  390. m.addSeparator();
  391. m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  392. m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  393. m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  394. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  395. m.show();
  396. }