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.

637 lines
19KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../Application/jucer_Headers.h"
  19. #include "jucer_PaintRoutine.h"
  20. #include "jucer_JucerDocument.h"
  21. #include "jucer_ObjectTypes.h"
  22. #include "PaintElements/jucer_PaintElementUndoableAction.h"
  23. #include "PaintElements/jucer_PaintElementPath.h"
  24. #include "PaintElements/jucer_PaintElementImage.h"
  25. #include "PaintElements/jucer_PaintElementGroup.h"
  26. #include "UI/jucer_JucerDocumentEditor.h"
  27. #include "../Application/jucer_Application.h"
  28. //==============================================================================
  29. PaintRoutine::PaintRoutine()
  30. : document (nullptr),
  31. backgroundColour (ProjucerApplication::getApp().lookAndFeel.findColour (backgroundColourId))
  32. {
  33. clear();
  34. }
  35. PaintRoutine::~PaintRoutine()
  36. {
  37. elements.clear(); // do this explicitly before the scalar destructor because these
  38. // objects will be listeners on this object
  39. }
  40. //==============================================================================
  41. void PaintRoutine::changed()
  42. {
  43. if (document != nullptr)
  44. document->changed();
  45. }
  46. bool PaintRoutine::perform (UndoableAction* action, const String& actionName)
  47. {
  48. if (document != nullptr)
  49. return document->getUndoManager().perform (action, actionName);
  50. std::unique_ptr<UndoableAction> deleter (action);
  51. action->perform();
  52. return false;
  53. }
  54. void PaintRoutine::setBackgroundColour (Colour newColour) noexcept
  55. {
  56. backgroundColour = newColour;
  57. changed();
  58. }
  59. void PaintRoutine::clear()
  60. {
  61. if (elements.size() > 0)
  62. {
  63. elements.clear();
  64. changed();
  65. }
  66. }
  67. //==============================================================================
  68. class AddXmlElementAction : public UndoableAction
  69. {
  70. public:
  71. AddXmlElementAction (PaintRoutine& routine_, XmlElement* xml_)
  72. : routine (routine_), xml (xml_)
  73. {
  74. }
  75. bool perform()
  76. {
  77. showCorrectTab();
  78. PaintElement* newElement = routine.addElementFromXml (*xml, -1, false);
  79. jassert (newElement != nullptr);
  80. indexAdded = routine.indexOfElement (newElement);
  81. jassert (indexAdded >= 0);
  82. return indexAdded >= 0;
  83. }
  84. bool undo()
  85. {
  86. showCorrectTab();
  87. routine.removeElement (routine.getElement (indexAdded), false);
  88. return true;
  89. }
  90. int getSizeInUnits() { return 10; }
  91. int indexAdded;
  92. private:
  93. PaintRoutine& routine;
  94. std::unique_ptr<XmlElement> xml;
  95. void showCorrectTab() const
  96. {
  97. if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder())
  98. ed->showGraphics (&routine);
  99. }
  100. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AddXmlElementAction)
  101. };
  102. PaintElement* PaintRoutine::addElementFromXml (const XmlElement& xml, const int index, const bool undoable)
  103. {
  104. selectedPoints.deselectAll();
  105. if (undoable && document != nullptr)
  106. {
  107. AddXmlElementAction* action = new AddXmlElementAction (*this, new XmlElement (xml));
  108. document->getUndoManager().perform (action, "Add new element");
  109. return elements [action->indexAdded];
  110. }
  111. if (PaintElement* const newElement = ObjectTypes::createElementForXml (&xml, this))
  112. {
  113. elements.insert (index, newElement);
  114. changed();
  115. return newElement;
  116. }
  117. return nullptr;
  118. }
  119. PaintElement* PaintRoutine::addNewElement (PaintElement* e, const int index, const bool undoable)
  120. {
  121. if (e != nullptr)
  122. {
  123. std::unique_ptr<PaintElement> deleter (e);
  124. std::unique_ptr<XmlElement> xml (e->createXml());
  125. e = addElementFromXml (*xml, index, undoable);
  126. }
  127. return e;
  128. }
  129. //==============================================================================
  130. class DeleteElementAction : public PaintElementUndoableAction <PaintElement>
  131. {
  132. public:
  133. explicit DeleteElementAction (PaintElement* const element)
  134. : PaintElementUndoableAction <PaintElement> (element),
  135. oldIndex (-1)
  136. {
  137. xml.reset (element->createXml());
  138. oldIndex = routine.indexOfElement (element);
  139. }
  140. bool perform()
  141. {
  142. showCorrectTab();
  143. routine.removeElement (getElement(), false);
  144. return true;
  145. }
  146. bool undo()
  147. {
  148. PaintElement* newElement = routine.addElementFromXml (*xml, oldIndex, false);
  149. showCorrectTab();
  150. return newElement != nullptr;
  151. }
  152. int getSizeInUnits() { return 10; }
  153. private:
  154. std::unique_ptr<XmlElement> xml;
  155. int oldIndex;
  156. };
  157. void PaintRoutine::removeElement (PaintElement* element, const bool undoable)
  158. {
  159. if (elements.contains (element))
  160. {
  161. if (undoable)
  162. {
  163. perform (new DeleteElementAction (element),
  164. "Delete " + element->getTypeName());
  165. }
  166. else
  167. {
  168. selectedElements.deselect (element);
  169. selectedPoints.deselectAll();
  170. selectedPoints.changed (true);
  171. selectedElements.changed (true);
  172. elements.removeObject (element);
  173. changed();
  174. }
  175. }
  176. }
  177. //==============================================================================
  178. class FrontOrBackElementAction : public PaintElementUndoableAction <PaintElement>
  179. {
  180. public:
  181. FrontOrBackElementAction (PaintElement* const element, int newIndex_)
  182. : PaintElementUndoableAction <PaintElement> (element),
  183. newIndex (newIndex_)
  184. {
  185. oldIndex = routine.indexOfElement (element);
  186. }
  187. bool perform()
  188. {
  189. showCorrectTab();
  190. PaintElement* e = routine.getElement (oldIndex);
  191. routine.moveElementZOrder (oldIndex, newIndex);
  192. newIndex = routine.indexOfElement (e);
  193. return true;
  194. }
  195. bool undo()
  196. {
  197. showCorrectTab();
  198. routine.moveElementZOrder (newIndex, oldIndex);
  199. return true;
  200. }
  201. private:
  202. int newIndex, oldIndex;
  203. };
  204. void PaintRoutine::moveElementZOrder (int oldIndex, int newIndex)
  205. {
  206. jassert (elements [oldIndex] != nullptr);
  207. if (oldIndex != newIndex && elements [oldIndex] != nullptr)
  208. {
  209. elements.move (oldIndex, newIndex);
  210. changed();
  211. }
  212. }
  213. void PaintRoutine::elementToFront (PaintElement* element, const bool undoable)
  214. {
  215. if (element != nullptr && elements.contains (element))
  216. {
  217. if (undoable)
  218. perform (new FrontOrBackElementAction (element, -1), "Move elements to front");
  219. else
  220. moveElementZOrder (elements.indexOf (element), -1);
  221. }
  222. }
  223. void PaintRoutine::elementToBack (PaintElement* element, const bool undoable)
  224. {
  225. if (element != nullptr && elements.contains (element))
  226. {
  227. if (undoable)
  228. perform (new FrontOrBackElementAction (element, 0), "Move elements to back");
  229. else
  230. moveElementZOrder (elements.indexOf (element), 0);
  231. }
  232. }
  233. //==============================================================================
  234. const char* const PaintRoutine::clipboardXmlTag = "PAINTELEMENTS";
  235. void PaintRoutine::copySelectedToClipboard()
  236. {
  237. if (selectedElements.getNumSelected() == 0)
  238. return;
  239. XmlElement clip (clipboardXmlTag);
  240. for (auto* pe : elements)
  241. if (selectedElements.isSelected (pe))
  242. clip.addChildElement (pe->createXml());
  243. SystemClipboard::copyTextToClipboard (clip.toString());
  244. }
  245. void PaintRoutine::paste()
  246. {
  247. if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), clipboardXmlTag))
  248. {
  249. selectedElements.deselectAll();
  250. selectedPoints.deselectAll();
  251. for (auto* e : doc->getChildIterator())
  252. if (PaintElement* newElement = addElementFromXml (*e, -1, true))
  253. selectedElements.addToSelection (newElement);
  254. }
  255. }
  256. void PaintRoutine::deleteSelected()
  257. {
  258. const SelectedItemSet<PaintElement*> temp1 (selectedElements);
  259. const SelectedItemSet<PathPoint*> temp2 (selectedPoints);
  260. if (temp2.getNumSelected() > 0)
  261. {
  262. selectedPoints.deselectAll();
  263. selectedPoints.changed (true); // synchronous message to get rid of any property components
  264. // if any points are selected, just delete them, and not the element, which may
  265. // also be selected..
  266. for (int i = temp2.getNumSelected(); --i >= 0;)
  267. temp2.getSelectedItem (i)->deleteFromPath();
  268. changed();
  269. }
  270. else if (temp1.getNumSelected() > 0)
  271. {
  272. selectedElements.deselectAll();
  273. selectedElements.changed (true);
  274. for (int i = temp1.getNumSelected(); --i >= 0;)
  275. removeElement (temp1.getSelectedItem (i), true);
  276. changed();
  277. }
  278. }
  279. void PaintRoutine::selectAll()
  280. {
  281. if (selectedPoints.getNumSelected() > 0)
  282. {
  283. if (const PaintElementPath* path = selectedPoints.getSelectedItem (0)->owner)
  284. for (int i = 0; i < path->getNumPoints(); ++i)
  285. selectedPoints.addToSelection (path->getPoint (i));
  286. }
  287. else
  288. {
  289. for (int i = 0; i < elements.size(); ++i)
  290. selectedElements.addToSelection (elements.getUnchecked (i));
  291. }
  292. }
  293. void PaintRoutine::selectedToFront()
  294. {
  295. const SelectedItemSet<PaintElement*> temp (selectedElements);
  296. for (int i = temp.getNumSelected(); --i >= 0;)
  297. elementToFront (temp.getSelectedItem (i), true);
  298. }
  299. void PaintRoutine::selectedToBack()
  300. {
  301. const SelectedItemSet<PaintElement*> temp (selectedElements);
  302. for (int i = 0; i < temp.getNumSelected(); ++i)
  303. elementToBack (temp.getSelectedItem (i), true);
  304. }
  305. void PaintRoutine::alignTop()
  306. {
  307. if (selectedElements.getNumSelected() > 1)
  308. {
  309. auto* main = selectedElements.getSelectedItem (0);
  310. auto yPos = main->getY();
  311. for (auto* other : selectedElements)
  312. {
  313. if (other != main)
  314. other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(),
  315. yPos), main, true);
  316. }
  317. }
  318. }
  319. void PaintRoutine::alignRight()
  320. {
  321. if (selectedElements.getNumSelected() > 1)
  322. {
  323. auto* main = selectedElements.getSelectedItem (0);
  324. auto rightPos = main->getRight();
  325. for (auto* other : selectedElements)
  326. {
  327. if (other != main)
  328. other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(),
  329. other->getY()), main, true);
  330. }
  331. }
  332. }
  333. void PaintRoutine::alignBottom()
  334. {
  335. if (selectedElements.getNumSelected() > 1)
  336. {
  337. auto* main = selectedElements.getSelectedItem (0);
  338. auto bottomPos = main->getBottom();
  339. for (auto* other : selectedElements)
  340. {
  341. if (other != main)
  342. other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(),
  343. bottomPos - other->getHeight()), main, true);
  344. }
  345. }
  346. }
  347. void PaintRoutine::alignLeft()
  348. {
  349. if (selectedElements.getNumSelected() > 1)
  350. {
  351. auto* main = selectedElements.getSelectedItem (0);
  352. auto xPos = main->getX();
  353. for (auto* other : selectedElements)
  354. {
  355. if (other != main)
  356. other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (xPos,
  357. other->getY()), main, true);
  358. }
  359. }
  360. }
  361. void PaintRoutine::groupSelected()
  362. {
  363. PaintElementGroup::groupSelected (this);
  364. }
  365. void PaintRoutine::ungroupSelected()
  366. {
  367. const SelectedItemSet<PaintElement*> temp (selectedElements);
  368. for (int i = 0; i < temp.getNumSelected(); ++i)
  369. if (PaintElementGroup* const pg = dynamic_cast<PaintElementGroup*> (temp.getSelectedItem (i)))
  370. pg->ungroup (true);
  371. }
  372. void PaintRoutine::bringLostItemsBackOnScreen (const Rectangle<int>& parentArea)
  373. {
  374. for (auto* c : elements)
  375. {
  376. auto r = c->getCurrentBounds (parentArea);
  377. if (! r.intersects (parentArea))
  378. {
  379. r.setPosition (parentArea.getCentreX(), parentArea.getCentreY());
  380. c->setCurrentBounds (r, parentArea, true);
  381. }
  382. }
  383. }
  384. void PaintRoutine::startDragging (const Rectangle<int>& parentArea)
  385. {
  386. for (auto* c : elements)
  387. {
  388. auto r = c->getCurrentBounds (parentArea);
  389. c->getProperties().set ("xDragStart", r.getX());
  390. c->getProperties().set ("yDragStart", r.getY());
  391. }
  392. getDocument()->beginTransaction();
  393. }
  394. void PaintRoutine::dragSelectedComps (int dx, int dy, const Rectangle<int>& parentArea)
  395. {
  396. getDocument()->getUndoManager().undoCurrentTransactionOnly();
  397. if (document != nullptr && selectedElements.getNumSelected() > 1)
  398. {
  399. dx = document->snapPosition (dx);
  400. dy = document->snapPosition (dy);
  401. }
  402. for (int i = 0; i < selectedElements.getNumSelected(); ++i)
  403. {
  404. PaintElement* const c = selectedElements.getSelectedItem (i);
  405. const int startX = c->getProperties() ["xDragStart"];
  406. const int startY = c->getProperties() ["yDragStart"];
  407. Rectangle<int> r (c->getCurrentBounds (parentArea));
  408. if (document != nullptr && selectedElements.getNumSelected() == 1)
  409. {
  410. r.setPosition (document->snapPosition (startX + dx),
  411. document->snapPosition (startY + dy));
  412. }
  413. else
  414. {
  415. r.setPosition (startX + dx,
  416. startY + dy);
  417. }
  418. c->setCurrentBounds (r, parentArea, true);
  419. }
  420. changed();
  421. }
  422. void PaintRoutine::endDragging()
  423. {
  424. getDocument()->beginTransaction();
  425. }
  426. //==============================================================================
  427. void PaintRoutine::fillWithBackground (Graphics& g, const bool drawOpaqueBackground)
  428. {
  429. if ((! backgroundColour.isOpaque()) && drawOpaqueBackground)
  430. {
  431. g.fillCheckerBoard (Rectangle<float> ((float) g.getClipBounds().getRight(),
  432. (float) g.getClipBounds().getBottom()),
  433. 50.0f, 50.0f,
  434. Colour (0xffdddddd).overlaidWith (backgroundColour),
  435. Colour (0xffffffff).overlaidWith (backgroundColour));
  436. }
  437. else
  438. {
  439. g.fillAll (backgroundColour);
  440. }
  441. }
  442. void PaintRoutine::drawElements (Graphics& g, const Rectangle<int>& relativeTo)
  443. {
  444. Component temp;
  445. temp.setBounds (relativeTo);
  446. for (auto* e : elements)
  447. e->draw (g, getDocument()->getComponentLayout(), relativeTo);
  448. }
  449. //==============================================================================
  450. void PaintRoutine::dropImageAt (const File& f, int x, int y)
  451. {
  452. std::unique_ptr<Drawable> d (Drawable::createFromImageFile (f));
  453. if (d != nullptr)
  454. {
  455. auto bounds = d->getDrawableBounds();
  456. d.reset();
  457. auto* newElement = addNewElement (ObjectTypes::createNewImageElement (this), -1, true);
  458. if (auto* pei = dynamic_cast<PaintElementImage*> (newElement))
  459. {
  460. String resourceName (getDocument()->getResources().findUniqueName (f.getFileName()));
  461. if (auto* existingResource = getDocument()->getResources().getResourceForFile (f))
  462. {
  463. resourceName = existingResource->name;
  464. }
  465. else
  466. {
  467. MemoryBlock data;
  468. f.loadFileAsData (data);
  469. getDocument()->getResources().add (resourceName, f.getFullPathName(), data);
  470. }
  471. pei->setResource (resourceName, true);
  472. const int imageW = (int) (bounds.getRight() + 0.999f);
  473. const int imageH = (int) (bounds.getBottom() + 0.999f);
  474. RelativePositionedRectangle pr;
  475. pr.rect.setX (x - imageW / 2);
  476. pr.rect.setY (y - imageH / 2);
  477. pr.rect.setWidth (imageW);
  478. pr.rect.setHeight (imageH);
  479. pei->setPosition (pr, true);
  480. getSelectedElements().selectOnly (pei);
  481. }
  482. }
  483. }
  484. //==============================================================================
  485. const char* PaintRoutine::xmlTagName = "BACKGROUND";
  486. XmlElement* PaintRoutine::createXml() const
  487. {
  488. auto* xml = new XmlElement (xmlTagName);
  489. xml->setAttribute ("backgroundColour", backgroundColour.toString());
  490. for (auto* e : elements)
  491. xml->addChildElement (e->createXml());
  492. return xml;
  493. }
  494. bool PaintRoutine::loadFromXml (const XmlElement& xml)
  495. {
  496. if (xml.hasTagName (xmlTagName))
  497. {
  498. backgroundColour = Colour::fromString (xml.getStringAttribute ("backgroundColour", Colours::white.toString()));
  499. clear();
  500. for (auto* e : xml.getChildIterator())
  501. if (auto* newElement = ObjectTypes::createElementForXml (e, this))
  502. elements.add (newElement);
  503. return true;
  504. }
  505. return false;
  506. }
  507. void PaintRoutine::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) const
  508. {
  509. if (! backgroundColour.isTransparent())
  510. paintMethodCode << "g.fillAll (" << CodeHelpers::colourToCode (backgroundColour) << ");\n\n";
  511. for (auto* e : elements)
  512. e->fillInGeneratedCode (code, paintMethodCode);
  513. }
  514. void PaintRoutine::applyCustomPaintSnippets (StringArray& snippets)
  515. {
  516. for (auto* e : elements)
  517. e->applyCustomPaintSnippets (snippets);
  518. }