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.

488 lines
15KB

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