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.

586 lines
17KB

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