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.

1610 lines
50KB

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