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.

583 lines
17KB

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