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.

1609 lines
51KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../jucer_Headers.h"
  19. #include "jucer_PaintElementPath.h"
  20. #include "../properties/jucer_PositionPropertyBase.h"
  21. #include "jucer_PaintElementUndoableAction.h"
  22. #include "../jucer_UtilityFunctions.h"
  23. //==============================================================================
  24. class ChangePointAction : public PaintElementUndoableAction <PaintElementPath>
  25. {
  26. public:
  27. ChangePointAction (PathPoint* const point,
  28. const int pointIndex,
  29. const PathPoint& newValue_)
  30. : PaintElementUndoableAction <PaintElementPath> (point->owner),
  31. index (pointIndex),
  32. newValue (newValue_),
  33. oldValue (*point)
  34. {
  35. }
  36. ChangePointAction (PathPoint* const point,
  37. const PathPoint& newValue_)
  38. : PaintElementUndoableAction <PaintElementPath> (point->owner),
  39. index (point->owner->indexOfPoint (point)),
  40. newValue (newValue_),
  41. oldValue (*point)
  42. {
  43. }
  44. bool perform()
  45. {
  46. return changeTo (newValue);
  47. }
  48. bool undo()
  49. {
  50. return changeTo (oldValue);
  51. }
  52. private:
  53. const int index;
  54. PathPoint newValue, oldValue;
  55. PathPoint* getPoint() const
  56. {
  57. PathPoint* p = getElement()->getPoint (index);
  58. jassert (p != nullptr);
  59. return p;
  60. }
  61. bool changeTo (const PathPoint& value) const
  62. {
  63. showCorrectTab();
  64. PaintElementPath* const path = getElement();
  65. jassert (path != nullptr);
  66. PathPoint* const p = path->getPoint (index);
  67. jassert (p != nullptr);
  68. const bool typeChanged = (p->type != value.type);
  69. *p = value;
  70. p->owner = path;
  71. if (typeChanged)
  72. path->pointListChanged();
  73. path->changed();
  74. return true;
  75. }
  76. };
  77. //==============================================================================
  78. class PathWindingModeProperty : public ChoicePropertyComponent,
  79. public ChangeListener
  80. {
  81. public:
  82. PathWindingModeProperty (PaintElementPath* const owner_)
  83. : ChoicePropertyComponent ("winding rule"),
  84. owner (owner_)
  85. {
  86. choices.add ("Non-zero winding");
  87. choices.add ("Even/odd winding");
  88. owner->getDocument()->addChangeListener (this);
  89. }
  90. ~PathWindingModeProperty()
  91. {
  92. owner->getDocument()->removeChangeListener (this);
  93. }
  94. void setIndex (int newIndex) { owner->setNonZeroWinding (newIndex == 0, true); }
  95. int getIndex() const { return owner->isNonZeroWinding() ? 0 : 1; }
  96. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  97. private:
  98. PaintElementPath* const owner;
  99. };
  100. //==============================================================================
  101. PaintElementPath::PaintElementPath (PaintRoutine* owner)
  102. : ColouredElement (owner, "Path", true, true),
  103. nonZeroWinding (true)
  104. {
  105. }
  106. PaintElementPath::~PaintElementPath()
  107. {
  108. }
  109. static int randomPos (int size)
  110. {
  111. return size / 4 + Random::getSystemRandom().nextInt (size / 4) - size / 8;
  112. }
  113. void PaintElementPath::setInitialBounds (int w, int h)
  114. {
  115. String s;
  116. int x = randomPos (w);
  117. int y = randomPos (h);
  118. s << "s "
  119. << x << " " << y << " l "
  120. << (x + 30) << " " << (y + 50) << " l "
  121. << (x - 30) << " " << (y + 50) << " x";
  122. restorePathFromString (s);
  123. }
  124. //==============================================================================
  125. int PaintElementPath::getBorderSize() const
  126. {
  127. return isStrokePresent ? 1 + roundFloatToInt (strokeType.stroke.getStrokeThickness())
  128. : 0;
  129. }
  130. Rectangle<int> PaintElementPath::getCurrentBounds (const Rectangle<int>& parentArea) const
  131. {
  132. updateStoredPath (getDocument()->getComponentLayout(), parentArea);
  133. Rectangle<float> bounds (path.getBounds());
  134. const int borderSize = getBorderSize();
  135. return Rectangle<int> ((int) bounds.getX() - borderSize,
  136. (int) bounds.getY() - borderSize,
  137. (int) bounds.getWidth() + borderSize * 2,
  138. (int) bounds.getHeight() + borderSize * 2);
  139. }
  140. void PaintElementPath::setCurrentBounds (const Rectangle<int>& b,
  141. const Rectangle<int>& parentArea,
  142. const bool /*undoable*/)
  143. {
  144. Rectangle<int> newBounds (b);
  145. newBounds.setSize (jmax (1, newBounds.getWidth()),
  146. jmax (1, newBounds.getHeight()));
  147. const Rectangle<int> current (getCurrentBounds (parentArea));
  148. if (newBounds != current)
  149. {
  150. const int borderSize = getBorderSize();
  151. const int dx = newBounds.getX() - current.getX();
  152. const int dy = newBounds.getY() - current.getY();
  153. const double scaleStartX = current.getX() + borderSize;
  154. const double scaleStartY = current.getY() + borderSize;
  155. const double scaleX = (newBounds.getWidth() - borderSize * 2) / (double) (current.getWidth() - borderSize * 2);
  156. const double scaleY = (newBounds.getHeight() - borderSize * 2) / (double) (current.getHeight() - borderSize * 2);
  157. for (int i = 0; i < points.size(); ++i)
  158. {
  159. PathPoint* const destPoint = points.getUnchecked(i);
  160. PathPoint p (*destPoint);
  161. for (int j = p.getNumPoints(); --j >= 0;)
  162. rescalePoint (p.pos[j], dx, dy,
  163. scaleX, scaleY,
  164. scaleStartX, scaleStartY,
  165. parentArea);
  166. perform (new ChangePointAction (destPoint, i, p), "Move path");
  167. }
  168. }
  169. }
  170. void PaintElementPath::rescalePoint (RelativePositionedRectangle& pos, int dx, int dy,
  171. double scaleX, double scaleY,
  172. double scaleStartX, double scaleStartY,
  173. const Rectangle<int>& parentArea) const
  174. {
  175. double x, y, w, h;
  176. pos.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  177. x = (x - scaleStartX) * scaleX + scaleStartX + dx;
  178. y = (y - scaleStartY) * scaleY + scaleStartY + dy;
  179. pos.updateFrom (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  180. }
  181. //==============================================================================
  182. static void drawArrow (Graphics& g, const Point<float> p1, const Point<float> p2)
  183. {
  184. g.drawArrow (Line<float> (p1.x, p1.y, (p1.x + p2.x) * 0.5f, (p1.y + p2.y) * 0.5f), 1.0f, 8.0f, 10.0f);
  185. g.drawLine (p1.x + (p2.x - p1.x) * 0.49f, p1.y + (p2.y - p1.y) * 0.49f, p2.x, p2.y);
  186. }
  187. void PaintElementPath::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
  188. {
  189. updateStoredPath (layout, parentArea);
  190. path.setUsingNonZeroWinding (nonZeroWinding);
  191. fillType.setFillType (g, getDocument(), parentArea);
  192. g.fillPath (path);
  193. if (isStrokePresent)
  194. {
  195. strokeType.fill.setFillType (g, getDocument(), parentArea);
  196. g.strokePath (path, getStrokeType().stroke);
  197. }
  198. }
  199. void PaintElementPath::drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo)
  200. {
  201. ComponentLayout* layout = getDocument()->getComponentLayout();
  202. for (int i = 0; i < points.size(); ++i)
  203. {
  204. PathPoint* const p = points.getUnchecked (i);
  205. const int numPoints = p->getNumPoints();
  206. if (numPoints > 0)
  207. {
  208. if (owner->getSelectedPoints().isSelected (p))
  209. {
  210. g.setColour (Colours::red);
  211. Point<float> p1, p2;
  212. if (numPoints > 2)
  213. {
  214. p1 = p->pos[1].toXY (relativeTo, layout);
  215. p2 = p->pos[2].toXY (relativeTo, layout);
  216. drawArrow (g, p1, p2);
  217. }
  218. if (numPoints > 1)
  219. {
  220. p1 = p->pos[0].toXY (relativeTo, layout);
  221. p2 = p->pos[1].toXY (relativeTo, layout);
  222. drawArrow (g, p1, p2);
  223. }
  224. p2 = p->pos[0].toXY (relativeTo, layout);
  225. if (const PathPoint* const nextPoint = points [i - 1])
  226. {
  227. p1 = nextPoint->pos [nextPoint->getNumPoints() - 1].toXY (relativeTo, layout);
  228. drawArrow (g, p1, p2);
  229. }
  230. }
  231. }
  232. }
  233. }
  234. void PaintElementPath::resized()
  235. {
  236. ColouredElement::resized();
  237. }
  238. void PaintElementPath::parentSizeChanged()
  239. {
  240. repaint();
  241. }
  242. //==============================================================================
  243. void PaintElementPath::mouseDown (const MouseEvent& e)
  244. {
  245. if (e.mods.isPopupMenu() || ! owner->getSelectedElements().isSelected (this))
  246. mouseDownOnSegment = -1;
  247. else
  248. mouseDownOnSegment = findSegmentAtXY (getX() + e.x, getY() + e.y);
  249. if (points [mouseDownOnSegment] != nullptr)
  250. mouseDownSelectSegmentStatus = owner->getSelectedPoints().addToSelectionOnMouseDown (points [mouseDownOnSegment], e.mods);
  251. else
  252. ColouredElement::mouseDown (e);
  253. }
  254. void PaintElementPath::mouseDrag (const MouseEvent& e)
  255. {
  256. if (mouseDownOnSegment < 0)
  257. ColouredElement::mouseDrag (e);
  258. }
  259. void PaintElementPath::mouseUp (const MouseEvent& e)
  260. {
  261. if (points [mouseDownOnSegment] == 0)
  262. ColouredElement::mouseUp (e);
  263. else
  264. owner->getSelectedPoints().addToSelectionOnMouseUp (points [mouseDownOnSegment],
  265. e.mods, false, mouseDownSelectSegmentStatus);
  266. }
  267. //==============================================================================
  268. void PaintElementPath::changed()
  269. {
  270. ColouredElement::changed();
  271. lastPathBounds = Rectangle<int>();
  272. }
  273. void PaintElementPath::pointListChanged()
  274. {
  275. changed();
  276. siblingComponentsChanged();
  277. }
  278. //==============================================================================
  279. void PaintElementPath::getEditableProperties (Array <PropertyComponent*>& properties)
  280. {
  281. properties.add (new PathWindingModeProperty (this));
  282. getColourSpecificProperties (properties);
  283. }
  284. //==============================================================================
  285. static String positionToPairOfValues (const RelativePositionedRectangle& position,
  286. const ComponentLayout* layout)
  287. {
  288. String x, y, w, h;
  289. positionToCode (position, layout, x, y, w, h);
  290. return castToFloat (x) + ", " + castToFloat (y);
  291. }
  292. void PaintElementPath::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
  293. {
  294. if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
  295. return;
  296. const String pathVariable ("internalPath" + String (code.getUniqueSuffix()));
  297. const ComponentLayout* layout = code.document->getComponentLayout();
  298. code.privateMemberDeclarations
  299. << "Path " << pathVariable << ";\n";
  300. String r;
  301. bool somePointsAreRelative = false;
  302. if (! nonZeroWinding)
  303. r << pathVariable << ".setUsingNonZeroWinding (false);\n";
  304. for (int i = 0; i < points.size(); ++i)
  305. {
  306. const PathPoint* const p = points.getUnchecked(i);
  307. switch (p->type)
  308. {
  309. case Path::Iterator::startNewSubPath:
  310. r << pathVariable << ".startNewSubPath (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
  311. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  312. break;
  313. case Path::Iterator::lineTo:
  314. r << pathVariable << ".lineTo (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
  315. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  316. break;
  317. case Path::Iterator::quadraticTo:
  318. r << pathVariable << ".quadraticTo (" << positionToPairOfValues (p->pos[0], layout)
  319. << ", " << positionToPairOfValues (p->pos[1], layout) << ");\n";
  320. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  321. somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
  322. break;
  323. case Path::Iterator::cubicTo:
  324. r << pathVariable << ".cubicTo (" << positionToPairOfValues (p->pos[0], layout)
  325. << ", " << positionToPairOfValues (p->pos[1], layout)
  326. << ", " << positionToPairOfValues (p->pos[2], layout) << ");\n";
  327. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  328. somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
  329. somePointsAreRelative = somePointsAreRelative || ! p->pos[2].rect.isPositionAbsolute();
  330. break;
  331. case Path::Iterator::closePath:
  332. r << pathVariable << ".closeSubPath();\n";
  333. break;
  334. default:
  335. jassertfalse;
  336. break;
  337. }
  338. }
  339. r << '\n';
  340. if (somePointsAreRelative)
  341. code.getCallbackCode (String::empty, "void", "resized()", false)
  342. << pathVariable << ".clear();\n" << r;
  343. else
  344. code.constructorCode << r;
  345. if (! fillType.isInvisible())
  346. {
  347. fillType.fillInGeneratedCode (code, paintMethodCode);
  348. paintMethodCode << "g.fillPath (" << pathVariable << ");\n";
  349. }
  350. if (isStrokePresent && ! strokeType.isInvisible())
  351. {
  352. String s;
  353. strokeType.fill.fillInGeneratedCode (code, s);
  354. s << "g.strokePath (" << pathVariable << ", " << strokeType.getPathStrokeCode() << ");\n";
  355. paintMethodCode += s;
  356. }
  357. paintMethodCode += "\n";
  358. }
  359. //==============================================================================
  360. XmlElement* PaintElementPath::createXml() const
  361. {
  362. XmlElement* e = new XmlElement (getTagName());
  363. position.applyToXml (*e);
  364. addColourAttributes (e);
  365. e->setAttribute ("nonZeroWinding", nonZeroWinding);
  366. e->addTextElement (pathToString());
  367. return e;
  368. }
  369. bool PaintElementPath::loadFromXml (const XmlElement& xml)
  370. {
  371. if (xml.hasTagName (getTagName()))
  372. {
  373. position.restoreFromXml (xml, position);
  374. loadColourAttributes (xml);
  375. nonZeroWinding = xml.getBoolAttribute ("nonZeroWinding", true);
  376. restorePathFromString (xml.getAllSubText().trim());
  377. return true;
  378. }
  379. jassertfalse;
  380. return false;
  381. }
  382. //==============================================================================
  383. void PaintElementPath::createSiblingComponents()
  384. {
  385. ColouredElement::createSiblingComponents();
  386. for (int i = 0; i < points.size(); ++i)
  387. {
  388. switch (points.getUnchecked(i)->type)
  389. {
  390. case Path::Iterator::startNewSubPath:
  391. siblingComponents.add (new PathPointComponent (this, i, 0));
  392. break;
  393. case Path::Iterator::lineTo:
  394. siblingComponents.add (new PathPointComponent (this, i, 0));
  395. break;
  396. case Path::Iterator::quadraticTo:
  397. siblingComponents.add (new PathPointComponent (this, i, 0));
  398. siblingComponents.add (new PathPointComponent (this, i, 1));
  399. break;
  400. case Path::Iterator::cubicTo:
  401. siblingComponents.add (new PathPointComponent (this, i, 0));
  402. siblingComponents.add (new PathPointComponent (this, i, 1));
  403. siblingComponents.add (new PathPointComponent (this, i, 2));
  404. break;
  405. case Path::Iterator::closePath:
  406. break;
  407. default:
  408. jassertfalse; break;
  409. }
  410. }
  411. for (int i = 0; i < siblingComponents.size(); ++i)
  412. {
  413. getParentComponent()->addAndMakeVisible (siblingComponents.getUnchecked(i));
  414. siblingComponents.getUnchecked(i)->updatePosition();
  415. }
  416. }
  417. String PaintElementPath::pathToString() const
  418. {
  419. String s;
  420. for (int i = 0; i < points.size(); ++i)
  421. {
  422. const PathPoint* const p = points.getUnchecked(i);
  423. switch (p->type)
  424. {
  425. case Path::Iterator::startNewSubPath:
  426. s << "s " << p->pos[0].toString() << ' ';
  427. break;
  428. case Path::Iterator::lineTo:
  429. s << "l " << p->pos[0].toString() << ' ';
  430. break;
  431. case Path::Iterator::quadraticTo:
  432. s << "q " << p->pos[0].toString()
  433. << ' ' << p->pos[1].toString() << ' ';
  434. break;
  435. case Path::Iterator::cubicTo:
  436. s << "c " << p->pos[0].toString()
  437. << ' ' << p->pos[1].toString() << ' '
  438. << ' ' << p->pos[2].toString() << ' ';
  439. break;
  440. case Path::Iterator::closePath:
  441. s << "x ";
  442. break;
  443. default:
  444. jassertfalse; break;
  445. }
  446. }
  447. return s.trimEnd();
  448. }
  449. void PaintElementPath::restorePathFromString (const String& s)
  450. {
  451. points.clear();
  452. StringArray tokens;
  453. tokens.addTokens (s, false);
  454. tokens.trim();
  455. tokens.removeEmptyStrings();
  456. for (int i = 0; i < tokens.size(); ++i)
  457. {
  458. ScopedPointer<PathPoint> p (new PathPoint (this));
  459. if (tokens[i] == "s")
  460. {
  461. p->type = Path::Iterator::startNewSubPath;
  462. p->pos [0] = RelativePositionedRectangle();
  463. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  464. i += 2;
  465. }
  466. else if (tokens[i] == "l")
  467. {
  468. p->type = Path::Iterator::lineTo;
  469. p->pos [0] = RelativePositionedRectangle();
  470. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  471. i += 2;
  472. }
  473. else if (tokens[i] == "q")
  474. {
  475. p->type = Path::Iterator::quadraticTo;
  476. p->pos [0] = RelativePositionedRectangle();
  477. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  478. p->pos [1] = RelativePositionedRectangle();
  479. p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
  480. i += 4;
  481. }
  482. else if (tokens[i] == "c")
  483. {
  484. p->type = Path::Iterator::cubicTo;
  485. p->pos [0] = RelativePositionedRectangle();
  486. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  487. p->pos [1] = RelativePositionedRectangle();
  488. p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
  489. p->pos [2] = RelativePositionedRectangle();
  490. p->pos [2].rect = PositionedRectangle (tokens [i + 5] + " " + tokens [i + 6]);
  491. i += 6;
  492. }
  493. else if (tokens[i] == "x")
  494. {
  495. p->type = Path::Iterator::closePath;
  496. }
  497. else
  498. continue;
  499. points.add (p.release());
  500. }
  501. }
  502. void PaintElementPath::setToPath (const Path& newPath)
  503. {
  504. points.clear();
  505. Path::Iterator i (newPath);
  506. while (i.next())
  507. {
  508. ScopedPointer<PathPoint> p (new PathPoint (this));
  509. p->type = i.elementType;
  510. if (i.elementType == Path::Iterator::startNewSubPath)
  511. {
  512. p->pos [0].rect.setX (i.x1);
  513. p->pos [0].rect.setY (i.y1);
  514. }
  515. else if (i.elementType == Path::Iterator::lineTo)
  516. {
  517. p->pos [0].rect.setX (i.x1);
  518. p->pos [0].rect.setY (i.y1);
  519. }
  520. else if (i.elementType == Path::Iterator::quadraticTo)
  521. {
  522. p->pos [0].rect.setX (i.x1);
  523. p->pos [0].rect.setY (i.y1);
  524. p->pos [1].rect.setX (i.x2);
  525. p->pos [1].rect.setY (i.y2);
  526. }
  527. else if (i.elementType == Path::Iterator::cubicTo)
  528. {
  529. p->pos [0].rect.setX (i.x1);
  530. p->pos [0].rect.setY (i.y1);
  531. p->pos [1].rect.setX (i.x2);
  532. p->pos [1].rect.setY (i.y2);
  533. p->pos [2].rect.setX (i.x3);
  534. p->pos [2].rect.setY (i.y3);
  535. }
  536. else if (i.elementType == Path::Iterator::closePath)
  537. {
  538. }
  539. else
  540. {
  541. continue;
  542. }
  543. points.add (p.release());
  544. }
  545. }
  546. void PaintElementPath::updateStoredPath (const ComponentLayout* layout, const Rectangle<int>& relativeTo) const
  547. {
  548. if (lastPathBounds != relativeTo && ! relativeTo.isEmpty())
  549. {
  550. lastPathBounds = relativeTo;
  551. path.clear();
  552. for (int i = 0; i < points.size(); ++i)
  553. {
  554. const PathPoint* const p = points.getUnchecked(i);
  555. switch (p->type)
  556. {
  557. case Path::Iterator::startNewSubPath:
  558. path.startNewSubPath (p->pos[0].toXY (relativeTo, layout));
  559. break;
  560. case Path::Iterator::lineTo:
  561. path.lineTo (p->pos[0].toXY (relativeTo, layout));
  562. break;
  563. case Path::Iterator::quadraticTo:
  564. path.quadraticTo (p->pos[0].toXY (relativeTo, layout),
  565. p->pos[1].toXY (relativeTo, layout));
  566. break;
  567. case Path::Iterator::cubicTo:
  568. path.cubicTo (p->pos[0].toXY (relativeTo, layout),
  569. p->pos[1].toXY (relativeTo, layout),
  570. p->pos[2].toXY (relativeTo, layout));
  571. break;
  572. case Path::Iterator::closePath:
  573. path.closeSubPath();
  574. break;
  575. default:
  576. jassertfalse; break;
  577. }
  578. }
  579. }
  580. }
  581. //==============================================================================
  582. class ChangeWindingAction : public PaintElementUndoableAction <PaintElementPath>
  583. {
  584. public:
  585. ChangeWindingAction (PaintElementPath* const path, const bool newValue_)
  586. : PaintElementUndoableAction <PaintElementPath> (path),
  587. newValue (newValue_),
  588. oldValue (path->isNonZeroWinding())
  589. {
  590. }
  591. bool perform()
  592. {
  593. showCorrectTab();
  594. getElement()->setNonZeroWinding (newValue, false);
  595. return true;
  596. }
  597. bool undo()
  598. {
  599. showCorrectTab();
  600. getElement()->setNonZeroWinding (oldValue, false);
  601. return true;
  602. }
  603. private:
  604. bool newValue, oldValue;
  605. };
  606. void PaintElementPath::setNonZeroWinding (const bool nonZero, const bool undoable)
  607. {
  608. if (nonZero != nonZeroWinding)
  609. {
  610. if (undoable)
  611. {
  612. perform (new ChangeWindingAction (this, nonZero), "Change path winding rule");
  613. }
  614. else
  615. {
  616. nonZeroWinding = nonZero;
  617. changed();
  618. }
  619. }
  620. }
  621. bool PaintElementPath::isSubpathClosed (int index) const
  622. {
  623. for (int i = index + 1; i < points.size(); ++i)
  624. {
  625. if (points.getUnchecked (i)->type == Path::Iterator::closePath)
  626. return true;
  627. if (points.getUnchecked (i)->type == Path::Iterator::startNewSubPath)
  628. break;
  629. }
  630. return false;
  631. }
  632. //==============================================================================
  633. void PaintElementPath::setSubpathClosed (int index, const bool closed, const bool undoable)
  634. {
  635. if (closed != isSubpathClosed (index))
  636. {
  637. for (int i = index + 1; i < points.size(); ++i)
  638. {
  639. PathPoint* p = points.getUnchecked (i);
  640. if (p->type == Path::Iterator::closePath)
  641. {
  642. jassert (! closed);
  643. deletePoint (i, undoable);
  644. return;
  645. }
  646. if (p->type == Path::Iterator::startNewSubPath)
  647. {
  648. jassert (closed);
  649. PathPoint* pp = addPoint (i - 1, undoable);
  650. PathPoint p2 (*pp);
  651. p2.type = Path::Iterator::closePath;
  652. perform (new ChangePointAction (pp, p2), "Close subpath");
  653. return;
  654. }
  655. }
  656. jassert (closed);
  657. PathPoint* p = addPoint (points.size() - 1, undoable);
  658. PathPoint p2 (*p);
  659. p2.type = Path::Iterator::closePath;
  660. perform (new ChangePointAction (p, p2), "Close subpath");
  661. }
  662. }
  663. //==============================================================================
  664. class AddPointAction : public PaintElementUndoableAction <PaintElementPath>
  665. {
  666. public:
  667. AddPointAction (PaintElementPath* path, int pointIndexToAddItAfter_)
  668. : PaintElementUndoableAction <PaintElementPath> (path),
  669. indexAdded (-1),
  670. pointIndexToAddItAfter (pointIndexToAddItAfter_)
  671. {
  672. }
  673. bool perform()
  674. {
  675. showCorrectTab();
  676. PaintElementPath* const path = getElement();
  677. jassert (path != nullptr);
  678. PathPoint* const p = path->addPoint (pointIndexToAddItAfter, false);
  679. jassert (p != nullptr);
  680. indexAdded = path->indexOfPoint (p);
  681. jassert (indexAdded >= 0);
  682. return true;
  683. }
  684. bool undo()
  685. {
  686. showCorrectTab();
  687. PaintElementPath* const path = getElement();
  688. jassert (path != nullptr);
  689. path->deletePoint (indexAdded, false);
  690. return true;
  691. }
  692. int indexAdded;
  693. private:
  694. int pointIndexToAddItAfter;
  695. };
  696. PathPoint* PaintElementPath::addPoint (int pointIndexToAddItAfter, const bool undoable)
  697. {
  698. if (undoable)
  699. {
  700. AddPointAction* action = new AddPointAction (this, pointIndexToAddItAfter);
  701. perform (action, "Add path point");
  702. return points [action->indexAdded];
  703. }
  704. double x1 = 20.0, y1 = 20.0, x2, y2;
  705. ComponentLayout* layout = getDocument()->getComponentLayout();
  706. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  707. if (points [pointIndexToAddItAfter] != nullptr)
  708. points [pointIndexToAddItAfter]->pos [points [pointIndexToAddItAfter]->getNumPoints() - 1].getXY (x1, y1, area, layout);
  709. else if (points[0] != nullptr)
  710. points[0]->pos[0].getXY (x1, y1, area, layout);
  711. x2 = x1 + 50.0;
  712. y2 = y1 + 50.0;
  713. if (points [pointIndexToAddItAfter + 1] != nullptr)
  714. {
  715. if (points [pointIndexToAddItAfter + 1]->type == Path::Iterator::closePath
  716. || points [pointIndexToAddItAfter + 1]->type == Path::Iterator::startNewSubPath)
  717. {
  718. int i = pointIndexToAddItAfter;
  719. while (i > 0)
  720. if (points [--i]->type == Path::Iterator::startNewSubPath)
  721. break;
  722. if (i != pointIndexToAddItAfter)
  723. points [i]->pos[0].getXY (x2, y2, area, layout);
  724. }
  725. else
  726. {
  727. points [pointIndexToAddItAfter + 1]->pos[0].getXY (x2, y2, area, layout);
  728. }
  729. }
  730. else
  731. {
  732. int i = pointIndexToAddItAfter + 1;
  733. while (i > 0)
  734. if (points [--i]->type == Path::Iterator::startNewSubPath)
  735. break;
  736. points[i]->pos[0].getXY (x2, y2, area, layout);
  737. }
  738. PathPoint* const p = new PathPoint (this);
  739. p->type = Path::Iterator::lineTo;
  740. p->pos[0].rect.setX ((x1 + x2) * 0.5f);
  741. p->pos[0].rect.setY ((y1 + y2) * 0.5f);
  742. points.insert (pointIndexToAddItAfter + 1, p);
  743. pointListChanged();
  744. return p;
  745. }
  746. //==============================================================================
  747. class DeletePointAction : public PaintElementUndoableAction <PaintElementPath>
  748. {
  749. public:
  750. DeletePointAction (PaintElementPath* const path, const int indexToRemove_)
  751. : PaintElementUndoableAction <PaintElementPath> (path),
  752. indexToRemove (indexToRemove_),
  753. oldValue (*path->getPoint (indexToRemove))
  754. {
  755. }
  756. bool perform()
  757. {
  758. showCorrectTab();
  759. PaintElementPath* const path = getElement();
  760. jassert (path != nullptr);
  761. path->deletePoint (indexToRemove, false);
  762. return path != nullptr;
  763. }
  764. bool undo()
  765. {
  766. showCorrectTab();
  767. PaintElementPath* const path = getElement();
  768. jassert (path != nullptr);
  769. PathPoint* p = path->addPoint (indexToRemove - 1, false);
  770. *p = oldValue;
  771. return path != nullptr;
  772. }
  773. int indexToRemove;
  774. private:
  775. PathPoint oldValue;
  776. };
  777. void PaintElementPath::deletePoint (int pointIndex, const bool undoable)
  778. {
  779. if (undoable)
  780. {
  781. perform (new DeletePointAction (this, pointIndex), "Delete path point");
  782. }
  783. else
  784. {
  785. PathPoint* const p = points [pointIndex];
  786. if (p != nullptr && pointIndex > 0)
  787. {
  788. owner->getSelectedPoints().deselect (p);
  789. owner->getSelectedPoints().changed (true);
  790. points.remove (pointIndex);
  791. pointListChanged();
  792. }
  793. }
  794. }
  795. //==============================================================================
  796. bool PaintElementPath::getPoint (int index, int pointNumber, double& x, double& y, const Rectangle<int>& parentArea) const
  797. {
  798. const PathPoint* const p = points [index];
  799. if (p == nullptr)
  800. return false;
  801. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  802. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  803. p->pos [pointNumber].getXY (x, y, parentArea, getDocument()->getComponentLayout());
  804. return true;
  805. }
  806. int PaintElementPath::findSegmentAtXY (int x, int y) const
  807. {
  808. double x1, y1, x2, y2, x3, y3, lastX = 0.0, lastY = 0.0, subPathStartX = 0.0, subPathStartY = 0.0;
  809. ComponentLayout* const layout = getDocument()->getComponentLayout();
  810. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  811. int subpathStartIndex = 0;
  812. float thickness = 10.0f;
  813. if (isStrokePresent)
  814. thickness = jmax (thickness, strokeType.stroke.getStrokeThickness());
  815. for (int i = 0; i < points.size(); ++i)
  816. {
  817. Path segmentPath;
  818. PathPoint* const p = points.getUnchecked (i);
  819. switch (p->type)
  820. {
  821. case Path::Iterator::startNewSubPath:
  822. p->pos[0].getXY (lastX, lastY, area, layout);
  823. subPathStartX = lastX;
  824. subPathStartY = lastY;
  825. subpathStartIndex = i;
  826. break;
  827. case Path::Iterator::lineTo:
  828. p->pos[0].getXY (x1, y1, area, layout);
  829. segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) x1, (float) y1), thickness);
  830. if (segmentPath.contains ((float) x, (float) y))
  831. return i;
  832. lastX = x1;
  833. lastY = y1;
  834. break;
  835. case Path::Iterator::quadraticTo:
  836. p->pos[0].getXY (x1, y1, area, layout);
  837. p->pos[1].getXY (x2, y2, area, layout);
  838. segmentPath.startNewSubPath ((float) lastX, (float) lastY);
  839. segmentPath.quadraticTo ((float) x1, (float) y1, (float) x2, (float) y2);
  840. PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
  841. if (segmentPath.contains ((float) x, (float) y))
  842. return i;
  843. lastX = x2;
  844. lastY = y2;
  845. break;
  846. case Path::Iterator::cubicTo:
  847. p->pos[0].getXY (x1, y1, area, layout);
  848. p->pos[1].getXY (x2, y2, area, layout);
  849. p->pos[2].getXY (x3, y3, area, layout);
  850. segmentPath.startNewSubPath ((float) lastX, (float) lastY);
  851. segmentPath.cubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
  852. PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
  853. if (segmentPath.contains ((float) x, (float) y))
  854. return i;
  855. lastX = x3;
  856. lastY = y3;
  857. break;
  858. case Path::Iterator::closePath:
  859. segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) subPathStartX, (float) subPathStartY), thickness);
  860. if (segmentPath.contains ((float) x, (float) y))
  861. return subpathStartIndex;
  862. lastX = subPathStartX;
  863. lastY = subPathStartY;
  864. break;
  865. default:
  866. jassertfalse; break;
  867. }
  868. }
  869. return -1;
  870. }
  871. //==============================================================================
  872. void PaintElementPath::movePoint (int index, int pointNumber,
  873. double newX, double newY,
  874. const Rectangle<int>& parentArea,
  875. const bool undoable)
  876. {
  877. if (PathPoint* const p = points [index])
  878. {
  879. PathPoint newPoint (*p);
  880. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  881. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  882. RelativePositionedRectangle& pr = newPoint.pos [pointNumber];
  883. double x, y, w, h;
  884. pr.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  885. pr.updateFrom (newX, newY, w, h, parentArea, getDocument()->getComponentLayout());
  886. if (undoable)
  887. {
  888. perform (new ChangePointAction (p, index, newPoint), "Move path point");
  889. }
  890. else
  891. {
  892. *p = newPoint;
  893. changed();
  894. }
  895. }
  896. }
  897. RelativePositionedRectangle PaintElementPath::getPoint (int index, int pointNumber) const
  898. {
  899. if (PathPoint* const p = points [index])
  900. {
  901. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  902. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  903. return p->pos [pointNumber];
  904. }
  905. jassertfalse;
  906. return RelativePositionedRectangle();
  907. }
  908. void PaintElementPath::setPoint (int index, int pointNumber, const RelativePositionedRectangle& newPos, const bool undoable)
  909. {
  910. if (PathPoint* const p = points [index])
  911. {
  912. PathPoint newPoint (*p);
  913. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  914. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  915. if (newPoint.pos [pointNumber] != newPos)
  916. {
  917. newPoint.pos [pointNumber] = newPos;
  918. if (undoable)
  919. {
  920. perform (new ChangePointAction (p, index, newPoint), "Change path point position");
  921. }
  922. else
  923. {
  924. *p = newPoint;
  925. changed();
  926. }
  927. }
  928. }
  929. else
  930. {
  931. jassertfalse;
  932. }
  933. }
  934. //==============================================================================
  935. class PathPointTypeProperty : public ChoicePropertyComponent,
  936. public ChangeListener
  937. {
  938. public:
  939. PathPointTypeProperty (PaintElementPath* const owner_,
  940. const int index_)
  941. : ChoicePropertyComponent ("point type"),
  942. owner (owner_),
  943. index (index_)
  944. {
  945. choices.add ("Start of sub-path");
  946. choices.add ("Line");
  947. choices.add ("Quadratic");
  948. choices.add ("Cubic");
  949. owner->getDocument()->addChangeListener (this);
  950. }
  951. ~PathPointTypeProperty()
  952. {
  953. owner->getDocument()->removeChangeListener (this);
  954. }
  955. void setIndex (int newIndex)
  956. {
  957. Path::Iterator::PathElementType type = Path::Iterator::startNewSubPath;
  958. switch (newIndex)
  959. {
  960. case 0: type = Path::Iterator::startNewSubPath; break;
  961. case 1: type = Path::Iterator::lineTo; break;
  962. case 2: type = Path::Iterator::quadraticTo; break;
  963. case 3: type = Path::Iterator::cubicTo; break;
  964. default: jassertfalse; break;
  965. }
  966. const Rectangle<int> area (((PaintRoutineEditor*) owner->getParentComponent())->getComponentArea());
  967. owner->getPoint (index)->changePointType (type, area, true);
  968. }
  969. int getIndex() const
  970. {
  971. const PathPoint* const p = owner->getPoint (index);
  972. jassert (p != nullptr);
  973. switch (p->type)
  974. {
  975. case Path::Iterator::startNewSubPath: return 0;
  976. case Path::Iterator::lineTo: return 1;
  977. case Path::Iterator::quadraticTo: return 2;
  978. case Path::Iterator::cubicTo: return 3;
  979. case Path::Iterator::closePath: break;
  980. default: jassertfalse; break;
  981. }
  982. return 0;
  983. }
  984. void changeListenerCallback (ChangeBroadcaster*)
  985. {
  986. refresh();
  987. }
  988. private:
  989. PaintElementPath* const owner;
  990. const int index;
  991. };
  992. //==============================================================================
  993. class PathPointPositionProperty : public PositionPropertyBase
  994. {
  995. public:
  996. PathPointPositionProperty (PaintElementPath* const owner_,
  997. const int index_, const int pointNumber_,
  998. const String& name,
  999. ComponentPositionDimension dimension_)
  1000. : PositionPropertyBase (owner_, name, dimension_, false, false,
  1001. owner_->getDocument()->getComponentLayout()),
  1002. owner (owner_),
  1003. index (index_),
  1004. pointNumber (pointNumber_)
  1005. {
  1006. owner->getDocument()->addChangeListener (this);
  1007. }
  1008. ~PathPointPositionProperty()
  1009. {
  1010. owner->getDocument()->removeChangeListener (this);
  1011. }
  1012. void setPosition (const RelativePositionedRectangle& newPos)
  1013. {
  1014. owner->setPoint (index, pointNumber, newPos, true);
  1015. }
  1016. RelativePositionedRectangle getPosition() const
  1017. {
  1018. return owner->getPoint (index, pointNumber);
  1019. }
  1020. private:
  1021. PaintElementPath* const owner;
  1022. const int index, pointNumber;
  1023. };
  1024. //==============================================================================
  1025. class PathPointClosedProperty : public ChoicePropertyComponent,
  1026. private ChangeListener
  1027. {
  1028. public:
  1029. PathPointClosedProperty (PaintElementPath* const owner_, const int index_)
  1030. : ChoicePropertyComponent ("openness"),
  1031. owner (owner_),
  1032. index (index_)
  1033. {
  1034. owner->getDocument()->addChangeListener (this);
  1035. choices.add ("Subpath is closed");
  1036. choices.add ("Subpath is open-ended");
  1037. }
  1038. ~PathPointClosedProperty()
  1039. {
  1040. owner->getDocument()->removeChangeListener (this);
  1041. }
  1042. void changeListenerCallback (ChangeBroadcaster*)
  1043. {
  1044. refresh();
  1045. }
  1046. void setIndex (int newIndex)
  1047. {
  1048. owner->setSubpathClosed (index, newIndex == 0, true);
  1049. }
  1050. int getIndex() const
  1051. {
  1052. return owner->isSubpathClosed (index) ? 0 : 1;
  1053. }
  1054. private:
  1055. PaintElementPath* const owner;
  1056. const int index;
  1057. };
  1058. //==============================================================================
  1059. class AddNewPointProperty : public ButtonPropertyComponent
  1060. {
  1061. public:
  1062. AddNewPointProperty (PaintElementPath* const owner_, const int index_)
  1063. : ButtonPropertyComponent ("new point", false),
  1064. owner (owner_),
  1065. index (index_)
  1066. {
  1067. }
  1068. void buttonClicked()
  1069. {
  1070. owner->addPoint (index, true);
  1071. }
  1072. String getButtonText() const { return "Add new point"; }
  1073. private:
  1074. PaintElementPath* const owner;
  1075. const int index;
  1076. };
  1077. //==============================================================================
  1078. PathPoint::PathPoint (PaintElementPath* const owner_)
  1079. : owner (owner_)
  1080. {
  1081. }
  1082. PathPoint::PathPoint (const PathPoint& other)
  1083. : owner (other.owner),
  1084. type (other.type)
  1085. {
  1086. pos [0] = other.pos [0];
  1087. pos [1] = other.pos [1];
  1088. pos [2] = other.pos [2];
  1089. }
  1090. PathPoint& PathPoint::operator= (const PathPoint& other)
  1091. {
  1092. owner = other.owner;
  1093. type = other.type;
  1094. pos [0] = other.pos [0];
  1095. pos [1] = other.pos [1];
  1096. pos [2] = other.pos [2];
  1097. return *this;
  1098. }
  1099. PathPoint::~PathPoint()
  1100. {
  1101. }
  1102. int PathPoint::getNumPoints() const
  1103. {
  1104. if (type == Path::Iterator::cubicTo) return 3;
  1105. if (type == Path::Iterator::quadraticTo) return 2;
  1106. if (type == Path::Iterator::closePath) return 0;
  1107. return 1;
  1108. }
  1109. PathPoint PathPoint::withChangedPointType (const Path::Iterator::PathElementType newType,
  1110. const Rectangle<int>& parentArea) const
  1111. {
  1112. PathPoint p (*this);
  1113. if (newType != p.type)
  1114. {
  1115. int oldNumPoints = getNumPoints();
  1116. p.type = newType;
  1117. int numPoints = p.getNumPoints();
  1118. if (numPoints != oldNumPoints)
  1119. {
  1120. double lastX, lastY;
  1121. double x, y, w, h;
  1122. p.pos [numPoints - 1] = p.pos [oldNumPoints - 1];
  1123. p.pos [numPoints - 1].getRectangleDouble (x, y, w, h, parentArea, owner->getDocument()->getComponentLayout());
  1124. const int index = owner->points.indexOf (this);
  1125. PathPoint* lastPoint = owner->points [index - 1];
  1126. jassert (lastPoint != nullptr)
  1127. if (lastPoint != nullptr)
  1128. {
  1129. lastPoint->pos [lastPoint->getNumPoints() - 1]
  1130. .getRectangleDouble (lastX, lastY, w, h, parentArea, owner->getDocument()->getComponentLayout());
  1131. }
  1132. else
  1133. {
  1134. lastX = x;
  1135. lastY = y;
  1136. }
  1137. for (int i = 0; i < numPoints - 1; ++i)
  1138. {
  1139. p.pos[i] = p.pos [numPoints - 1];
  1140. p.pos[i].updateFrom (lastX + (x - lastX) * (i + 1) / numPoints,
  1141. lastY + (y - lastY) * (i + 1) / numPoints,
  1142. w, h,
  1143. parentArea,
  1144. owner->getDocument()->getComponentLayout());
  1145. }
  1146. }
  1147. }
  1148. return p;
  1149. }
  1150. void PathPoint::changePointType (const Path::Iterator::PathElementType newType,
  1151. const Rectangle<int>& parentArea, const bool undoable)
  1152. {
  1153. if (newType != type)
  1154. {
  1155. if (undoable)
  1156. {
  1157. owner->perform (new ChangePointAction (this, withChangedPointType (newType, parentArea)),
  1158. "Change path point type");
  1159. }
  1160. else
  1161. {
  1162. *this = withChangedPointType (newType, parentArea);
  1163. owner->pointListChanged();
  1164. }
  1165. }
  1166. }
  1167. void PathPoint::getEditableProperties (Array <PropertyComponent*>& properties)
  1168. {
  1169. const int index = owner->points.indexOf (this);
  1170. jassert (index >= 0);
  1171. switch (type)
  1172. {
  1173. case Path::Iterator::startNewSubPath:
  1174. properties.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
  1175. properties.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
  1176. properties.add (new PathPointClosedProperty (owner, index));
  1177. properties.add (new AddNewPointProperty (owner, index));
  1178. break;
  1179. case Path::Iterator::lineTo:
  1180. properties.add (new PathPointTypeProperty (owner, index));
  1181. properties.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
  1182. properties.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
  1183. properties.add (new AddNewPointProperty (owner, index));
  1184. break;
  1185. case Path::Iterator::quadraticTo:
  1186. properties.add (new PathPointTypeProperty (owner, index));
  1187. properties.add (new PathPointPositionProperty (owner, index, 0, "control pt x", PositionPropertyBase::componentX));
  1188. properties.add (new PathPointPositionProperty (owner, index, 0, "control pt y", PositionPropertyBase::componentY));
  1189. properties.add (new PathPointPositionProperty (owner, index, 1, "x", PositionPropertyBase::componentX));
  1190. properties.add (new PathPointPositionProperty (owner, index, 1, "y", PositionPropertyBase::componentY));
  1191. properties.add (new AddNewPointProperty (owner, index));
  1192. break;
  1193. case Path::Iterator::cubicTo:
  1194. properties.add (new PathPointTypeProperty (owner, index));
  1195. properties.add (new PathPointPositionProperty (owner, index, 0, "control pt1 x", PositionPropertyBase::componentX));
  1196. properties.add (new PathPointPositionProperty (owner, index, 0, "control pt1 y", PositionPropertyBase::componentY));
  1197. properties.add (new PathPointPositionProperty (owner, index, 1, "control pt2 x", PositionPropertyBase::componentX));
  1198. properties.add (new PathPointPositionProperty (owner, index, 1, "control pt2 y", PositionPropertyBase::componentY));
  1199. properties.add (new PathPointPositionProperty (owner, index, 2, "x", PositionPropertyBase::componentX));
  1200. properties.add (new PathPointPositionProperty (owner, index, 2, "y", PositionPropertyBase::componentY));
  1201. properties.add (new AddNewPointProperty (owner, index));
  1202. break;
  1203. case Path::Iterator::closePath:
  1204. break;
  1205. default:
  1206. jassertfalse;
  1207. break;
  1208. }
  1209. }
  1210. void PathPoint::deleteFromPath()
  1211. {
  1212. owner->deletePoint (owner->points.indexOf (this), true);
  1213. }
  1214. //==============================================================================
  1215. PathPointComponent::PathPointComponent (PaintElementPath* const path_,
  1216. const int index_,
  1217. const int pointNumber_)
  1218. : ElementSiblingComponent (path_),
  1219. path (path_),
  1220. routine (path_->getOwner()),
  1221. index (index_),
  1222. pointNumber (pointNumber_),
  1223. selected (false)
  1224. {
  1225. setSize (11, 11);
  1226. setRepaintsOnMouseActivity (true);
  1227. selected = routine->getSelectedPoints().isSelected (path_->points [index]);
  1228. routine->getSelectedPoints().addChangeListener (this);
  1229. }
  1230. PathPointComponent::~PathPointComponent()
  1231. {
  1232. routine->getSelectedPoints().removeChangeListener (this);
  1233. }
  1234. void PathPointComponent::updatePosition()
  1235. {
  1236. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  1237. jassert (getParentComponent() != nullptr);
  1238. double x, y;
  1239. path->getPoint (index, pointNumber, x, y, area);
  1240. setCentrePosition (roundToInt (x),
  1241. roundToInt (y));
  1242. }
  1243. void PathPointComponent::showPopupMenu()
  1244. {
  1245. }
  1246. void PathPointComponent::paint (Graphics& g)
  1247. {
  1248. if (isMouseOverOrDragging())
  1249. g.fillAll (Colours::red);
  1250. if (selected)
  1251. {
  1252. g.setColour (Colours::red);
  1253. g.drawRect (getLocalBounds());
  1254. }
  1255. g.setColour (Colours::white);
  1256. g.fillRect (getWidth() / 2 - 3, getHeight() / 2 - 3, 7, 7);
  1257. g.setColour (Colours::black);
  1258. if (pointNumber < path->getPoint (index)->getNumPoints() - 1)
  1259. g.drawRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
  1260. else
  1261. g.fillRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
  1262. }
  1263. void PathPointComponent::mouseDown (const MouseEvent& e)
  1264. {
  1265. dragging = false;
  1266. if (e.mods.isPopupMenu())
  1267. {
  1268. showPopupMenu();
  1269. return; // this may be deleted now..
  1270. }
  1271. dragX = getX() + getWidth() / 2;
  1272. dragY = getY() + getHeight() / 2;
  1273. mouseDownSelectStatus = routine->getSelectedPoints().addToSelectionOnMouseDown (path->points [index], e.mods);
  1274. owner->getDocument()->beginTransaction();
  1275. }
  1276. void PathPointComponent::mouseDrag (const MouseEvent& e)
  1277. {
  1278. if (! e.mods.isPopupMenu())
  1279. {
  1280. if (selected && ! dragging)
  1281. dragging = ! e.mouseWasClicked();
  1282. if (dragging)
  1283. {
  1284. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  1285. int x = dragX + e.getDistanceFromDragStartX() - area.getX();
  1286. int y = dragY + e.getDistanceFromDragStartY() - area.getY();
  1287. if (JucerDocument* const document = owner->getDocument())
  1288. {
  1289. x = document->snapPosition (x);
  1290. y = document->snapPosition (y);
  1291. }
  1292. owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  1293. path->movePoint (index, pointNumber, x + area.getX(), y + area.getY(), area, true);
  1294. }
  1295. }
  1296. }
  1297. void PathPointComponent::mouseUp (const MouseEvent& e)
  1298. {
  1299. routine->getSelectedPoints().addToSelectionOnMouseUp (path->points [index],
  1300. e.mods, dragging,
  1301. mouseDownSelectStatus);
  1302. }
  1303. void PathPointComponent::changeListenerCallback (ChangeBroadcaster* source)
  1304. {
  1305. ElementSiblingComponent::changeListenerCallback (source);
  1306. const bool nowSelected = routine->getSelectedPoints().isSelected (path->points [index]);
  1307. if (nowSelected != selected)
  1308. {
  1309. selected = nowSelected;
  1310. repaint();
  1311. if (Component* parent = getParentComponent())
  1312. parent->repaint();
  1313. }
  1314. }