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.

633 lines
17KB

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