/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include "../Application/jucer_Headers.h" #include "jucer_PaintRoutine.h" #include "jucer_JucerDocument.h" #include "jucer_ObjectTypes.h" #include "PaintElements/jucer_PaintElementUndoableAction.h" #include "PaintElements/jucer_PaintElementPath.h" #include "PaintElements/jucer_PaintElementImage.h" #include "PaintElements/jucer_PaintElementGroup.h" #include "UI/jucer_JucerDocumentEditor.h" #include "../Application/jucer_Application.h" //============================================================================== PaintRoutine::PaintRoutine() : document (nullptr), backgroundColour (ProjucerApplication::getApp().lookAndFeel.findColour (backgroundColourId)) { clear(); } PaintRoutine::~PaintRoutine() { elements.clear(); // do this explicitly before the scalar destructor because these // objects will be listeners on this object } //============================================================================== void PaintRoutine::changed() { if (document != nullptr) document->changed(); } bool PaintRoutine::perform (UndoableAction* action, const String& actionName) { if (document != nullptr) return document->getUndoManager().perform (action, actionName); std::unique_ptr deleter (action); action->perform(); return false; } void PaintRoutine::setBackgroundColour (Colour newColour) noexcept { backgroundColour = newColour; changed(); } void PaintRoutine::clear() { if (elements.size() > 0) { elements.clear(); changed(); } } //============================================================================== class AddXmlElementAction : public UndoableAction { public: AddXmlElementAction (PaintRoutine& routine_, XmlElement* xml_) : routine (routine_), xml (xml_) { } bool perform() { showCorrectTab(); PaintElement* newElement = routine.addElementFromXml (*xml, -1, false); jassert (newElement != nullptr); indexAdded = routine.indexOfElement (newElement); jassert (indexAdded >= 0); return indexAdded >= 0; } bool undo() { showCorrectTab(); routine.removeElement (routine.getElement (indexAdded), false); return true; } int getSizeInUnits() { return 10; } int indexAdded; private: PaintRoutine& routine; std::unique_ptr xml; void showCorrectTab() const { if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder()) ed->showGraphics (&routine); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AddXmlElementAction) }; PaintElement* PaintRoutine::addElementFromXml (const XmlElement& xml, const int index, const bool undoable) { selectedPoints.deselectAll(); if (undoable && document != nullptr) { AddXmlElementAction* action = new AddXmlElementAction (*this, new XmlElement (xml)); document->getUndoManager().perform (action, "Add new element"); return elements [action->indexAdded]; } if (PaintElement* const newElement = ObjectTypes::createElementForXml (&xml, this)) { elements.insert (index, newElement); changed(); return newElement; } return nullptr; } PaintElement* PaintRoutine::addNewElement (PaintElement* e, const int index, const bool undoable) { if (e != nullptr) { std::unique_ptr deleter (e); std::unique_ptr xml (e->createXml()); e = addElementFromXml (*xml, index, undoable); } return e; } //============================================================================== class DeleteElementAction : public PaintElementUndoableAction { public: explicit DeleteElementAction (PaintElement* const element) : PaintElementUndoableAction (element), oldIndex (-1) { xml.reset (element->createXml()); oldIndex = routine.indexOfElement (element); } bool perform() { showCorrectTab(); routine.removeElement (getElement(), false); return true; } bool undo() { PaintElement* newElement = routine.addElementFromXml (*xml, oldIndex, false); showCorrectTab(); return newElement != nullptr; } int getSizeInUnits() { return 10; } private: std::unique_ptr xml; int oldIndex; }; void PaintRoutine::removeElement (PaintElement* element, const bool undoable) { if (elements.contains (element)) { if (undoable) { perform (new DeleteElementAction (element), "Delete " + element->getTypeName()); } else { selectedElements.deselect (element); selectedPoints.deselectAll(); selectedPoints.changed (true); selectedElements.changed (true); elements.removeObject (element); changed(); } } } //============================================================================== class FrontOrBackElementAction : public PaintElementUndoableAction { public: FrontOrBackElementAction (PaintElement* const element, int newIndex_) : PaintElementUndoableAction (element), newIndex (newIndex_) { oldIndex = routine.indexOfElement (element); } bool perform() { showCorrectTab(); PaintElement* e = routine.getElement (oldIndex); routine.moveElementZOrder (oldIndex, newIndex); newIndex = routine.indexOfElement (e); return true; } bool undo() { showCorrectTab(); routine.moveElementZOrder (newIndex, oldIndex); return true; } private: int newIndex, oldIndex; }; void PaintRoutine::moveElementZOrder (int oldIndex, int newIndex) { jassert (elements [oldIndex] != nullptr); if (oldIndex != newIndex && elements [oldIndex] != nullptr) { elements.move (oldIndex, newIndex); changed(); } } void PaintRoutine::elementToFront (PaintElement* element, const bool undoable) { if (element != nullptr && elements.contains (element)) { if (undoable) perform (new FrontOrBackElementAction (element, -1), "Move elements to front"); else moveElementZOrder (elements.indexOf (element), -1); } } void PaintRoutine::elementToBack (PaintElement* element, const bool undoable) { if (element != nullptr && elements.contains (element)) { if (undoable) perform (new FrontOrBackElementAction (element, 0), "Move elements to back"); else moveElementZOrder (elements.indexOf (element), 0); } } //============================================================================== const char* const PaintRoutine::clipboardXmlTag = "PAINTELEMENTS"; void PaintRoutine::copySelectedToClipboard() { if (selectedElements.getNumSelected() == 0) return; XmlElement clip (clipboardXmlTag); for (auto* pe : elements) if (selectedElements.isSelected (pe)) clip.addChildElement (pe->createXml()); SystemClipboard::copyTextToClipboard (clip.toString()); } void PaintRoutine::paste() { if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), clipboardXmlTag)) { selectedElements.deselectAll(); selectedPoints.deselectAll(); for (auto* e : doc->getChildIterator()) if (PaintElement* newElement = addElementFromXml (*e, -1, true)) selectedElements.addToSelection (newElement); } } void PaintRoutine::deleteSelected() { const SelectedItemSet temp1 (selectedElements); const SelectedItemSet temp2 (selectedPoints); if (temp2.getNumSelected() > 0) { selectedPoints.deselectAll(); selectedPoints.changed (true); // synchronous message to get rid of any property components // if any points are selected, just delete them, and not the element, which may // also be selected.. for (int i = temp2.getNumSelected(); --i >= 0;) temp2.getSelectedItem (i)->deleteFromPath(); changed(); } else if (temp1.getNumSelected() > 0) { selectedElements.deselectAll(); selectedElements.changed (true); for (int i = temp1.getNumSelected(); --i >= 0;) removeElement (temp1.getSelectedItem (i), true); changed(); } } void PaintRoutine::selectAll() { if (selectedPoints.getNumSelected() > 0) { if (const PaintElementPath* path = selectedPoints.getSelectedItem (0)->owner) for (int i = 0; i < path->getNumPoints(); ++i) selectedPoints.addToSelection (path->getPoint (i)); } else { for (int i = 0; i < elements.size(); ++i) selectedElements.addToSelection (elements.getUnchecked (i)); } } void PaintRoutine::selectedToFront() { const SelectedItemSet temp (selectedElements); for (int i = temp.getNumSelected(); --i >= 0;) elementToFront (temp.getSelectedItem(i), true); } void PaintRoutine::selectedToBack() { const SelectedItemSet temp (selectedElements); for (int i = 0; i < temp.getNumSelected(); ++i) elementToBack (temp.getSelectedItem(i), true); } void PaintRoutine::alignTop() { if (selectedElements.getNumSelected() > 1) { auto* main = selectedElements.getSelectedItem (0); auto yPos = main->getY(); for (auto* other : selectedElements) { if (other != main) other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(), yPos), main, true); } } } void PaintRoutine::alignRight() { if (selectedElements.getNumSelected() > 1) { auto* main = selectedElements.getSelectedItem (0); auto rightPos = main->getRight(); for (auto* other : selectedElements) { if (other != main) other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(), other->getY()), main, true); } } } void PaintRoutine::alignBottom() { if (selectedElements.getNumSelected() > 1) { auto* main = selectedElements.getSelectedItem (0); auto bottomPos = main->getBottom(); for (auto* other : selectedElements) { if (other != main) other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(), bottomPos - other->getHeight()), main, true); } } } void PaintRoutine::alignLeft() { if (selectedElements.getNumSelected() > 1) { auto* main = selectedElements.getSelectedItem (0); auto xPos = main->getX(); for (auto* other : selectedElements) { if (other != main) other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (xPos, other->getY()), main, true); } } } void PaintRoutine::groupSelected() { PaintElementGroup::groupSelected (this); } void PaintRoutine::ungroupSelected() { const SelectedItemSet temp (selectedElements); for (int i = 0; i < temp.getNumSelected(); ++i) if (PaintElementGroup* const pg = dynamic_cast (temp.getSelectedItem (i))) pg->ungroup (true); } void PaintRoutine::bringLostItemsBackOnScreen (const Rectangle& parentArea) { for (auto* c : elements) { auto r = c->getCurrentBounds (parentArea); if (! r.intersects (parentArea)) { r.setPosition (parentArea.getCentreX(), parentArea.getCentreY()); c->setCurrentBounds (r, parentArea, true); } } } void PaintRoutine::startDragging (const Rectangle& parentArea) { for (auto* c : elements) { auto r = c->getCurrentBounds (parentArea); c->getProperties().set ("xDragStart", r.getX()); c->getProperties().set ("yDragStart", r.getY()); } getDocument()->beginTransaction(); } void PaintRoutine::dragSelectedComps (int dx, int dy, const Rectangle& parentArea) { getDocument()->getUndoManager().undoCurrentTransactionOnly(); if (document != nullptr && selectedElements.getNumSelected() > 1) { dx = document->snapPosition (dx); dy = document->snapPosition (dy); } for (int i = 0; i < selectedElements.getNumSelected(); ++i) { PaintElement* const c = selectedElements.getSelectedItem (i); const int startX = c->getProperties() ["xDragStart"]; const int startY = c->getProperties() ["yDragStart"]; Rectangle r (c->getCurrentBounds (parentArea)); if (document != nullptr && selectedElements.getNumSelected() == 1) { r.setPosition (document->snapPosition (startX + dx), document->snapPosition (startY + dy)); } else { r.setPosition (startX + dx, startY + dy); } c->setCurrentBounds (r, parentArea, true); } changed(); } void PaintRoutine::endDragging() { getDocument()->beginTransaction(); } //============================================================================== void PaintRoutine::fillWithBackground (Graphics& g, const bool drawOpaqueBackground) { if ((! backgroundColour.isOpaque()) && drawOpaqueBackground) { g.fillCheckerBoard (Rectangle ((float) g.getClipBounds().getRight(), (float) g.getClipBounds().getBottom()), 50.0f, 50.0f, Colour (0xffdddddd).overlaidWith (backgroundColour), Colour (0xffffffff).overlaidWith (backgroundColour)); } else { g.fillAll (backgroundColour); } } void PaintRoutine::drawElements (Graphics& g, const Rectangle& relativeTo) { Component temp; temp.setBounds (relativeTo); for (auto* e : elements) e->draw (g, getDocument()->getComponentLayout(), relativeTo); } //============================================================================== void PaintRoutine::dropImageAt (const File& f, int x, int y) { std::unique_ptr d (Drawable::createFromImageFile (f)); if (d != nullptr) { auto bounds = d->getDrawableBounds(); d.reset(); auto* newElement = addNewElement (ObjectTypes::createNewImageElement (this), -1, true); if (auto* pei = dynamic_cast (newElement)) { String resourceName (getDocument()->getResources().findUniqueName (f.getFileName())); if (auto* existingResource = getDocument()->getResources().getResourceForFile (f)) { resourceName = existingResource->name; } else { MemoryBlock data; f.loadFileAsData (data); getDocument()->getResources().add (resourceName, f.getFullPathName(), data); } pei->setResource (resourceName, true); const int imageW = (int) (bounds.getRight() + 0.999f); const int imageH = (int) (bounds.getBottom() + 0.999f); RelativePositionedRectangle pr; pr.rect.setX (x - imageW / 2); pr.rect.setY (y - imageH / 2); pr.rect.setWidth (imageW); pr.rect.setHeight (imageH); pei->setPosition (pr, true); getSelectedElements().selectOnly (pei); } } } //============================================================================== const char* PaintRoutine::xmlTagName = "BACKGROUND"; XmlElement* PaintRoutine::createXml() const { auto* xml = new XmlElement (xmlTagName); xml->setAttribute ("backgroundColour", backgroundColour.toString()); for (auto* e : elements) xml->addChildElement (e->createXml()); return xml; } bool PaintRoutine::loadFromXml (const XmlElement& xml) { if (xml.hasTagName (xmlTagName)) { backgroundColour = Colour::fromString (xml.getStringAttribute ("backgroundColour", Colours::white.toString())); clear(); for (auto* e : xml.getChildIterator()) if (auto* newElement = ObjectTypes::createElementForXml (e, this)) elements.add (newElement); return true; } return false; } void PaintRoutine::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) const { if (! backgroundColour.isTransparent()) paintMethodCode << "g.fillAll (" << CodeHelpers::colourToCode (backgroundColour) << ");\n\n"; for (auto* e : elements) e->fillInGeneratedCode (code, paintMethodCode); } void PaintRoutine::applyCustomPaintSnippets (StringArray& snippets) { for (auto* e : elements) e->applyCustomPaintSnippets (snippets); }