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.

995 lines
31KB

  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_ColouredElement.h"
  20. #include "jucer_GradientPointComponent.h"
  21. #include "../properties/jucer_PositionPropertyBase.h"
  22. #include "../properties/jucer_ColourPropertyComponent.h"
  23. #include "jucer_PaintElementUndoableAction.h"
  24. #include "jucer_PaintElementPath.h"
  25. #include "jucer_ImageResourceProperty.h"
  26. //==============================================================================
  27. class ElementFillModeProperty : public ChoicePropertyComponent,
  28. private ChangeListener
  29. {
  30. public:
  31. ElementFillModeProperty (ColouredElement* const owner_, const bool isForStroke_)
  32. : ChoicePropertyComponent ("fill mode"),
  33. owner (owner_),
  34. isForStroke (isForStroke_)
  35. {
  36. choices.add ("Solid Colour");
  37. choices.add ("Linear Gradient");
  38. choices.add ("Radial Gradient");
  39. choices.add ("Image Brush");
  40. owner->getDocument()->addChangeListener (this);
  41. }
  42. ~ElementFillModeProperty()
  43. {
  44. owner->getDocument()->removeChangeListener (this);
  45. }
  46. void setIndex (int newIndex)
  47. {
  48. JucerFillType fill (isForStroke ? owner->getStrokeType().fill
  49. : owner->getFillType());
  50. switch (newIndex)
  51. {
  52. case 0: fill.mode = JucerFillType::solidColour; break;
  53. case 1: fill.mode = JucerFillType::linearGradient; break;
  54. case 2: fill.mode = JucerFillType::radialGradient; break;
  55. case 3: fill.mode = JucerFillType::imageBrush; break;
  56. default: jassertfalse; break;
  57. }
  58. if (! isForStroke)
  59. owner->setFillType (fill, true);
  60. else
  61. owner->setStrokeFill (fill, true);
  62. }
  63. int getIndex() const
  64. {
  65. switch (isForStroke ? owner->getStrokeType().fill.mode
  66. : owner->getFillType().mode)
  67. {
  68. case JucerFillType::solidColour: return 0;
  69. case JucerFillType::linearGradient: return 1;
  70. case JucerFillType::radialGradient: return 2;
  71. case JucerFillType::imageBrush: return 3;
  72. default: jassertfalse; break;
  73. }
  74. return 0;
  75. }
  76. void changeListenerCallback (ChangeBroadcaster*)
  77. {
  78. refresh();
  79. }
  80. private:
  81. ColouredElement* const owner;
  82. const bool isForStroke;
  83. };
  84. //==============================================================================
  85. class ElementFillColourProperty : public JucerColourPropertyComponent,
  86. private ChangeListener
  87. {
  88. public:
  89. enum ColourType
  90. {
  91. solidColour,
  92. gradientColour1,
  93. gradientColour2
  94. };
  95. ElementFillColourProperty (const String& name,
  96. ColouredElement* const owner_,
  97. const ColourType type_,
  98. const bool isForStroke_)
  99. : JucerColourPropertyComponent (name, false),
  100. owner (owner_),
  101. type (type_),
  102. isForStroke (isForStroke_)
  103. {
  104. owner->getDocument()->addChangeListener (this);
  105. }
  106. ~ElementFillColourProperty()
  107. {
  108. owner->getDocument()->removeChangeListener (this);
  109. }
  110. void setColour (const Colour& newColour)
  111. {
  112. owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  113. JucerFillType fill (isForStroke ? owner->getStrokeType().fill
  114. : owner->getFillType());
  115. switch (type)
  116. {
  117. case solidColour: fill.colour = newColour; break;
  118. case gradientColour1: fill.gradCol1 = newColour; break;
  119. case gradientColour2: fill.gradCol2 = newColour; break;
  120. default: jassertfalse; break;
  121. }
  122. if (! isForStroke)
  123. owner->setFillType (fill, true);
  124. else
  125. owner->setStrokeFill (fill, true);
  126. }
  127. Colour getColour() const
  128. {
  129. const JucerFillType fill (isForStroke ? owner->getStrokeType().fill
  130. : owner->getFillType());
  131. switch (type)
  132. {
  133. case solidColour: return fill.colour; break;
  134. case gradientColour1: return fill.gradCol1; break;
  135. case gradientColour2: return fill.gradCol2; break;
  136. default: jassertfalse; break;
  137. }
  138. return Colours::black;
  139. }
  140. void resetToDefault()
  141. {
  142. jassertfalse; // option shouldn't be visible
  143. }
  144. void changeListenerCallback (ChangeBroadcaster*)
  145. {
  146. refresh();
  147. }
  148. private:
  149. ColouredElement* const owner;
  150. const ColourType type;
  151. const bool isForStroke;
  152. };
  153. //==============================================================================
  154. class ElementFillPositionProperty : public PositionPropertyBase
  155. {
  156. public:
  157. ElementFillPositionProperty (ColouredElement* const owner_,
  158. const String& name,
  159. ComponentPositionDimension dimension_,
  160. const bool isStart_,
  161. const bool isForStroke_)
  162. : PositionPropertyBase (owner_, name, dimension_, false, false,
  163. owner_->getDocument()->getComponentLayout()),
  164. owner (owner_),
  165. isStart (isStart_),
  166. isForStroke (isForStroke_)
  167. {
  168. owner->getDocument()->addChangeListener (this);
  169. }
  170. ~ElementFillPositionProperty()
  171. {
  172. owner->getDocument()->removeChangeListener (this);
  173. }
  174. void setPosition (const RelativePositionedRectangle& newPos)
  175. {
  176. JucerFillType fill (isForStroke ? owner->getStrokeType().fill
  177. : owner->getFillType());
  178. if (isStart)
  179. fill.gradPos1 = newPos;
  180. else
  181. fill.gradPos2 = newPos;
  182. if (! isForStroke)
  183. owner->setFillType (fill, true);
  184. else
  185. owner->setStrokeFill (fill, true);
  186. }
  187. RelativePositionedRectangle getPosition() const
  188. {
  189. const JucerFillType fill (isForStroke ? owner->getStrokeType().fill
  190. : owner->getFillType());
  191. return isStart ? fill.gradPos1
  192. : fill.gradPos2;
  193. }
  194. private:
  195. ColouredElement* const owner;
  196. const bool isStart, isForStroke;
  197. };
  198. //==============================================================================
  199. class EnableStrokeProperty : public BooleanPropertyComponent,
  200. public ChangeListener
  201. {
  202. public:
  203. EnableStrokeProperty (ColouredElement* const owner_)
  204. : BooleanPropertyComponent ("outline", "Outline enabled", "No outline"),
  205. owner (owner_)
  206. {
  207. owner->getDocument()->addChangeListener (this);
  208. }
  209. ~EnableStrokeProperty()
  210. {
  211. owner->getDocument()->removeChangeListener (this);
  212. }
  213. //==============================================================================
  214. void setState (bool newState) { owner->enableStroke (newState, true); }
  215. bool getState() const { return owner->isStrokeEnabled(); }
  216. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  217. private:
  218. ColouredElement* const owner;
  219. };
  220. //==============================================================================
  221. class StrokeThicknessProperty : public SliderPropertyComponent,
  222. public ChangeListener
  223. {
  224. public:
  225. StrokeThicknessProperty (ColouredElement* const owner_)
  226. : SliderPropertyComponent ("outline thickness", 0.1, 200.0, 0.1, 0.3),
  227. owner (owner_)
  228. {
  229. owner->getDocument()->addChangeListener (this);
  230. }
  231. ~StrokeThicknessProperty()
  232. {
  233. owner->getDocument()->removeChangeListener (this);
  234. }
  235. void setValue (double newValue)
  236. {
  237. owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  238. owner->setStrokeType (PathStrokeType ((float) newValue,
  239. owner->getStrokeType().stroke.getJointStyle(),
  240. owner->getStrokeType().stroke.getEndStyle()),
  241. true);
  242. }
  243. double getValue() const { return owner->getStrokeType().stroke.getStrokeThickness(); }
  244. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  245. private:
  246. ColouredElement* const owner;
  247. };
  248. //==============================================================================
  249. class StrokeJointProperty : public ChoicePropertyComponent,
  250. public ChangeListener
  251. {
  252. public:
  253. StrokeJointProperty (ColouredElement* const owner_)
  254. : ChoicePropertyComponent ("joint style"),
  255. owner (owner_)
  256. {
  257. choices.add ("mitered");
  258. choices.add ("curved");
  259. choices.add ("beveled");
  260. owner->getDocument()->addChangeListener (this);
  261. }
  262. ~StrokeJointProperty()
  263. {
  264. owner->getDocument()->removeChangeListener (this);
  265. }
  266. void setIndex (int newIndex)
  267. {
  268. const PathStrokeType::JointStyle joints[] = { PathStrokeType::mitered,
  269. PathStrokeType::curved,
  270. PathStrokeType::beveled };
  271. jassert (newIndex >= 0 && newIndex < 3);
  272. owner->setStrokeType (PathStrokeType (owner->getStrokeType().stroke.getStrokeThickness(),
  273. joints [newIndex],
  274. owner->getStrokeType().stroke.getEndStyle()),
  275. true);
  276. }
  277. int getIndex() const
  278. {
  279. switch (owner->getStrokeType().stroke.getJointStyle())
  280. {
  281. case PathStrokeType::mitered: return 0;
  282. case PathStrokeType::curved: return 1;
  283. case PathStrokeType::beveled: return 2;
  284. default: jassertfalse; break;
  285. }
  286. return 0;
  287. }
  288. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  289. private:
  290. ColouredElement* const owner;
  291. };
  292. //==============================================================================
  293. class StrokeEndCapProperty : public ChoicePropertyComponent,
  294. public ChangeListener
  295. {
  296. public:
  297. StrokeEndCapProperty (ColouredElement* const owner_)
  298. : ChoicePropertyComponent ("end-cap style"),
  299. owner (owner_)
  300. {
  301. choices.add ("butt");
  302. choices.add ("square");
  303. choices.add ("round");
  304. owner->getDocument()->addChangeListener (this);
  305. }
  306. ~StrokeEndCapProperty()
  307. {
  308. owner->getDocument()->removeChangeListener (this);
  309. }
  310. void setIndex (int newIndex)
  311. {
  312. const PathStrokeType::EndCapStyle ends[] = { PathStrokeType::butt,
  313. PathStrokeType::square,
  314. PathStrokeType::rounded };
  315. jassert (newIndex >= 0 && newIndex < 3);
  316. owner->setStrokeType (PathStrokeType (owner->getStrokeType().stroke.getStrokeThickness(),
  317. owner->getStrokeType().stroke.getJointStyle(),
  318. ends [newIndex]),
  319. true);
  320. }
  321. int getIndex() const
  322. {
  323. switch (owner->getStrokeType().stroke.getEndStyle())
  324. {
  325. case PathStrokeType::butt: return 0;
  326. case PathStrokeType::square: return 1;
  327. case PathStrokeType::rounded: return 2;
  328. default: jassertfalse; break;
  329. }
  330. return 0;
  331. }
  332. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  333. private:
  334. ColouredElement* const owner;
  335. };
  336. //==============================================================================
  337. class ImageBrushResourceProperty : public ImageResourceProperty <ColouredElement>
  338. {
  339. public:
  340. ImageBrushResourceProperty (ColouredElement* const e, const bool isForStroke_)
  341. : ImageResourceProperty <ColouredElement> (e, isForStroke_ ? "stroke image"
  342. : "fill image"),
  343. isForStroke (isForStroke_)
  344. {
  345. }
  346. //==============================================================================
  347. void setResource (const String& newName)
  348. {
  349. if (isForStroke)
  350. {
  351. JucerFillType type (element->getStrokeType().fill);
  352. type.imageResourceName = newName;
  353. element->setStrokeFill (type, true);
  354. }
  355. else
  356. {
  357. JucerFillType type (element->getFillType());
  358. type.imageResourceName = newName;
  359. element->setFillType (type, true);
  360. }
  361. }
  362. String getResource() const
  363. {
  364. if (isForStroke)
  365. return element->getStrokeType().fill.imageResourceName;
  366. return element->getFillType().imageResourceName;
  367. }
  368. private:
  369. bool isForStroke;
  370. };
  371. //==============================================================================
  372. class ImageBrushPositionProperty : public PositionPropertyBase
  373. {
  374. public:
  375. ImageBrushPositionProperty (ColouredElement* const owner_,
  376. const String& name,
  377. ComponentPositionDimension dimension_,
  378. const bool isForStroke_)
  379. : PositionPropertyBase (owner_, name, dimension_, false, false,
  380. owner_->getDocument()->getComponentLayout()),
  381. owner (owner_),
  382. isForStroke (isForStroke_)
  383. {
  384. owner->getDocument()->addChangeListener (this);
  385. }
  386. ~ImageBrushPositionProperty()
  387. {
  388. owner->getDocument()->removeChangeListener (this);
  389. }
  390. void setPosition (const RelativePositionedRectangle& newPos)
  391. {
  392. if (isForStroke)
  393. {
  394. JucerFillType type (owner->getStrokeType().fill);
  395. type.imageAnchor = newPos;
  396. owner->setStrokeFill (type, true);
  397. }
  398. else
  399. {
  400. JucerFillType type (owner->getFillType());
  401. type.imageAnchor = newPos;
  402. owner->setFillType (type, true);
  403. }
  404. }
  405. RelativePositionedRectangle getPosition() const
  406. {
  407. if (isForStroke)
  408. return owner->getStrokeType().fill.imageAnchor;
  409. return owner->getFillType().imageAnchor;
  410. }
  411. private:
  412. ColouredElement* const owner;
  413. const bool isForStroke;
  414. };
  415. //==============================================================================
  416. class ImageBrushOpacityProperty : public SliderPropertyComponent,
  417. private ChangeListener
  418. {
  419. public:
  420. ImageBrushOpacityProperty (ColouredElement* const e, const bool isForStroke_)
  421. : SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
  422. element (e),
  423. isForStroke (isForStroke_)
  424. {
  425. element->getDocument()->addChangeListener (this);
  426. }
  427. ~ImageBrushOpacityProperty()
  428. {
  429. element->getDocument()->removeChangeListener (this);
  430. }
  431. void setValue (double newValue)
  432. {
  433. element->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  434. if (isForStroke)
  435. {
  436. JucerFillType type (element->getStrokeType().fill);
  437. type.imageOpacity = newValue;
  438. element->setStrokeFill (type, true);
  439. }
  440. else
  441. {
  442. JucerFillType type (element->getFillType());
  443. type.imageOpacity = newValue;
  444. element->setFillType (type, true);
  445. }
  446. }
  447. double getValue() const
  448. {
  449. if (isForStroke)
  450. return element->getStrokeType().fill.imageOpacity;
  451. else
  452. return element->getFillType().imageOpacity;
  453. }
  454. void changeListenerCallback (ChangeBroadcaster*)
  455. {
  456. refresh();
  457. }
  458. private:
  459. ColouredElement* const element;
  460. bool isForStroke;
  461. };
  462. //==============================================================================
  463. ColouredElement::ColouredElement (PaintRoutine* owner_,
  464. const String& name,
  465. const bool showOutline_,
  466. const bool showJointAndEnd_)
  467. : PaintElement (owner_, name),
  468. isStrokePresent (false),
  469. showOutline (showOutline_),
  470. showJointAndEnd (showJointAndEnd_)
  471. {
  472. }
  473. ColouredElement::~ColouredElement()
  474. {
  475. }
  476. //==============================================================================
  477. void ColouredElement::getEditableProperties (Array <PropertyComponent*>& properties)
  478. {
  479. PaintElement::getEditableProperties (properties);
  480. getColourSpecificProperties (properties);
  481. }
  482. void ColouredElement::getColourSpecificProperties (Array <PropertyComponent*>& properties)
  483. {
  484. properties.add (new ElementFillModeProperty (this, false));
  485. switch (getFillType().mode)
  486. {
  487. case JucerFillType::solidColour:
  488. properties.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, false));
  489. break;
  490. case JucerFillType::linearGradient:
  491. case JucerFillType::radialGradient:
  492. properties.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, false));
  493. properties.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, false));
  494. properties.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, false));
  495. properties.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, false));
  496. properties.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, false));
  497. properties.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, false));
  498. break;
  499. case JucerFillType::imageBrush:
  500. properties.add (new ImageBrushResourceProperty (this, false));
  501. properties.add (new ImageBrushPositionProperty (this, "anchor x", PositionPropertyBase::componentX, false));
  502. properties.add (new ImageBrushPositionProperty (this, "anchor y", PositionPropertyBase::componentY, false));
  503. properties.add (new ImageBrushOpacityProperty (this, false));
  504. break;
  505. default:
  506. jassertfalse;
  507. break;
  508. }
  509. if (showOutline)
  510. {
  511. properties.add (new EnableStrokeProperty (this));
  512. if (isStrokePresent)
  513. {
  514. properties.add (new StrokeThicknessProperty (this));
  515. if (showJointAndEnd)
  516. {
  517. properties.add (new StrokeJointProperty (this));
  518. properties.add (new StrokeEndCapProperty (this));
  519. }
  520. properties.add (new ElementFillModeProperty (this, true));
  521. switch (getStrokeType().fill.mode)
  522. {
  523. case JucerFillType::solidColour:
  524. properties.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, true));
  525. break;
  526. case JucerFillType::linearGradient:
  527. case JucerFillType::radialGradient:
  528. properties.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, true));
  529. properties.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, true));
  530. properties.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, true));
  531. properties.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, true));
  532. properties.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, true));
  533. properties.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, true));
  534. break;
  535. case JucerFillType::imageBrush:
  536. properties.add (new ImageBrushResourceProperty (this, true));
  537. properties.add (new ImageBrushPositionProperty (this, "stroke anchor x", PositionPropertyBase::componentX, true));
  538. properties.add (new ImageBrushPositionProperty (this, "stroke anchor y", PositionPropertyBase::componentY, true));
  539. properties.add (new ImageBrushOpacityProperty (this, true));
  540. break;
  541. default:
  542. jassertfalse;
  543. break;
  544. }
  545. }
  546. }
  547. }
  548. //==============================================================================
  549. const JucerFillType& ColouredElement::getFillType() noexcept
  550. {
  551. return fillType;
  552. }
  553. class FillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  554. {
  555. public:
  556. FillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  557. : PaintElementUndoableAction <ColouredElement> (element),
  558. newState (newState_)
  559. {
  560. oldState = element->getFillType();
  561. }
  562. bool perform()
  563. {
  564. showCorrectTab();
  565. getElement()->setFillType (newState, false);
  566. return true;
  567. }
  568. bool undo()
  569. {
  570. showCorrectTab();
  571. getElement()->setFillType (oldState, false);
  572. return true;
  573. }
  574. private:
  575. JucerFillType newState, oldState;
  576. };
  577. void ColouredElement::setFillType (const JucerFillType& newType, const bool undoable)
  578. {
  579. if (fillType != newType)
  580. {
  581. if (undoable)
  582. {
  583. perform (new FillTypeChangeAction (this, newType),
  584. "Change fill type");
  585. }
  586. else
  587. {
  588. repaint();
  589. if (fillType.mode != newType.mode)
  590. {
  591. owner->getSelectedElements().changed();
  592. siblingComponentsChanged();
  593. }
  594. fillType = newType;
  595. changed();
  596. }
  597. }
  598. }
  599. //==============================================================================
  600. bool ColouredElement::isStrokeEnabled() const noexcept
  601. {
  602. return isStrokePresent && showOutline;
  603. }
  604. class StrokeEnableChangeAction : public PaintElementUndoableAction <ColouredElement>
  605. {
  606. public:
  607. StrokeEnableChangeAction (ColouredElement* const element, const bool newState_)
  608. : PaintElementUndoableAction <ColouredElement> (element),
  609. newState (newState_)
  610. {
  611. oldState = element->isStrokeEnabled();
  612. }
  613. bool perform()
  614. {
  615. showCorrectTab();
  616. getElement()->enableStroke (newState, false);
  617. return true;
  618. }
  619. bool undo()
  620. {
  621. showCorrectTab();
  622. getElement()->enableStroke (oldState, false);
  623. return true;
  624. }
  625. private:
  626. bool newState, oldState;
  627. };
  628. void ColouredElement::enableStroke (bool enable, const bool undoable)
  629. {
  630. enable = enable && showOutline;
  631. if (isStrokePresent != enable)
  632. {
  633. if (undoable)
  634. {
  635. perform (new StrokeEnableChangeAction (this, enable),
  636. "Change stroke mode");
  637. }
  638. else
  639. {
  640. repaint();
  641. isStrokePresent = enable;
  642. siblingComponentsChanged();
  643. owner->changed();
  644. owner->getSelectedElements().changed();
  645. }
  646. }
  647. }
  648. //==============================================================================
  649. const StrokeType& ColouredElement::getStrokeType() noexcept
  650. {
  651. return strokeType;
  652. }
  653. class StrokeTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  654. {
  655. public:
  656. StrokeTypeChangeAction (ColouredElement* const element, const PathStrokeType& newState_)
  657. : PaintElementUndoableAction <ColouredElement> (element),
  658. newState (newState_),
  659. oldState (element->getStrokeType().stroke)
  660. {
  661. }
  662. bool perform()
  663. {
  664. showCorrectTab();
  665. getElement()->setStrokeType (newState, false);
  666. return true;
  667. }
  668. bool undo()
  669. {
  670. showCorrectTab();
  671. getElement()->setStrokeType (oldState, false);
  672. return true;
  673. }
  674. private:
  675. PathStrokeType newState, oldState;
  676. };
  677. void ColouredElement::setStrokeType (const PathStrokeType& newType, const bool undoable)
  678. {
  679. if (strokeType.stroke != newType)
  680. {
  681. if (undoable)
  682. {
  683. perform (new StrokeTypeChangeAction (this, newType),
  684. "Change stroke type");
  685. }
  686. else
  687. {
  688. repaint();
  689. strokeType.stroke = newType;
  690. changed();
  691. }
  692. }
  693. }
  694. class StrokeFillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  695. {
  696. public:
  697. StrokeFillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  698. : PaintElementUndoableAction <ColouredElement> (element),
  699. newState (newState_)
  700. {
  701. oldState = element->getStrokeType().fill;
  702. }
  703. bool perform()
  704. {
  705. showCorrectTab();
  706. getElement()->setStrokeFill (newState, false);
  707. return true;
  708. }
  709. bool undo()
  710. {
  711. showCorrectTab();
  712. getElement()->setStrokeFill (oldState, false);
  713. return true;
  714. }
  715. private:
  716. JucerFillType newState, oldState;
  717. };
  718. void ColouredElement::setStrokeFill (const JucerFillType& newType, const bool undoable)
  719. {
  720. if (strokeType.fill != newType)
  721. {
  722. if (undoable)
  723. {
  724. perform (new StrokeFillTypeChangeAction (this, newType),
  725. "Change stroke fill type");
  726. }
  727. else
  728. {
  729. repaint();
  730. if (strokeType.fill.mode != newType.mode)
  731. {
  732. siblingComponentsChanged();
  733. owner->getSelectedElements().changed();
  734. }
  735. strokeType.fill = newType;
  736. changed();
  737. }
  738. }
  739. }
  740. //==============================================================================
  741. void ColouredElement::createSiblingComponents()
  742. {
  743. {
  744. GradientPointComponent* g1 = new GradientPointComponent (this, false, true);
  745. siblingComponents.add (g1);
  746. GradientPointComponent* g2 = new GradientPointComponent (this, false, false);
  747. siblingComponents.add (g2);
  748. getParentComponent()->addAndMakeVisible (g1);
  749. getParentComponent()->addAndMakeVisible (g2);
  750. g1->updatePosition();
  751. g2->updatePosition();
  752. }
  753. if (isStrokePresent && showOutline)
  754. {
  755. GradientPointComponent* g1 = new GradientPointComponent (this, true, true);
  756. siblingComponents.add (g1);
  757. GradientPointComponent* g2 = new GradientPointComponent (this, true, false);
  758. siblingComponents.add (g2);
  759. getParentComponent()->addAndMakeVisible (g1);
  760. getParentComponent()->addAndMakeVisible (g2);
  761. g1->updatePosition();
  762. g2->updatePosition();
  763. }
  764. }
  765. Rectangle<int> ColouredElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  766. {
  767. int border = 0;
  768. if (isStrokePresent)
  769. border = (int) strokeType.stroke.getStrokeThickness() / 2 + 1;
  770. return position.getRectangle (parentArea, getDocument()->getComponentLayout())
  771. .expanded (border, border);
  772. }
  773. void ColouredElement::setCurrentBounds (const Rectangle<int>& newBounds,
  774. const Rectangle<int>& parentArea,
  775. const bool undoable)
  776. {
  777. Rectangle<int> r (newBounds);
  778. if (isStrokePresent)
  779. {
  780. const int border = (int) strokeType.stroke.getStrokeThickness() / 2 + 1;
  781. r = r.expanded (-border, -border);
  782. r.setSize (jmax (1, r.getWidth()), jmax (1, r.getHeight()));
  783. }
  784. RelativePositionedRectangle pr (position);
  785. pr.updateFrom (r.getX() - parentArea.getX(),
  786. r.getY() - parentArea.getY(),
  787. r.getWidth(), r.getHeight(),
  788. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  789. getDocument()->getComponentLayout());
  790. setPosition (pr, undoable);
  791. updateBounds (parentArea);
  792. }
  793. //==============================================================================
  794. void ColouredElement::addColourAttributes (XmlElement* const e) const
  795. {
  796. e->setAttribute ("fill", fillType.toString());
  797. e->setAttribute ("hasStroke", isStrokePresent);
  798. if (isStrokePresent && showOutline)
  799. {
  800. e->setAttribute ("stroke", strokeType.toString());
  801. e->setAttribute ("strokeColour", strokeType.fill.toString());
  802. }
  803. }
  804. bool ColouredElement::loadColourAttributes (const XmlElement& xml)
  805. {
  806. fillType.restoreFromString (xml.getStringAttribute ("fill", String::empty));
  807. isStrokePresent = showOutline && xml.getBoolAttribute ("hasStroke", false);
  808. strokeType.restoreFromString (xml.getStringAttribute ("stroke", String::empty));
  809. strokeType.fill.restoreFromString (xml.getStringAttribute ("strokeColour", String::empty));
  810. return true;
  811. }
  812. //==============================================================================
  813. void ColouredElement::convertToNewPathElement (const Path& path)
  814. {
  815. if (! path.isEmpty())
  816. {
  817. PaintElementPath newElement (getOwner());
  818. newElement.setToPath (path);
  819. newElement.setFillType (fillType, false);
  820. newElement.enableStroke (isStrokeEnabled(), false);
  821. newElement.setStrokeType (getStrokeType().stroke, false);
  822. newElement.setStrokeFill (getStrokeType().fill, false);
  823. ScopedPointer<XmlElement> xml (newElement.createXml());
  824. PaintElement* e = getOwner()->addElementFromXml (*xml, getOwner()->indexOfElement (this), true);
  825. getOwner()->getSelectedElements().selectOnly (e);
  826. getOwner()->removeElement (this, true);
  827. }
  828. }