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.

641 lines
19KB

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