/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #ifndef __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__ #define __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__ #include "jucer_DrawableEditor.h" #include "../../model/Drawable/jucer_DrawableTypeHandler.h" //============================================================================== class DrawableEditorCanvas : public EditorCanvasBase, public FileDragAndDropTarget, public Timer { public: //============================================================================== DrawableEditorCanvas (DrawableEditor& editor_) : editor (editor_) { initialise(); getDocument().getRoot().addListener (this); } ~DrawableEditorCanvas() { getDocument().getRoot().removeListener (this); shutdown(); } //============================================================================== UndoManager& getUndoManager() throw() { return *getDocument().getUndoManager(); } DrawableEditor& getEditor() throw() { return editor; } DrawableDocument& getDocument() throw() { return editor.getDocument(); } Component* createComponentHolder() { return new DrawableComponent (this); } void documentChanged() { DrawableDocument& doc = getDocument(); if (drawable == 0) { Drawable* newDrawable = Drawable::createFromValueTree (doc.getRootDrawableNode().getState(), &doc); drawable = dynamic_cast (newDrawable); drawable->resetBoundingBoxToContentArea(); jassert (drawable != 0); getComponentHolder()->repaint(); } else { doc.getRootDrawableNode().resetBoundingBoxToContentArea (0); const Rectangle damage (drawable->refreshFromValueTree (doc.getRootDrawableNode().getState(), &doc)); getComponentHolder()->repaint (objectSpaceToScreenSpace (damage.getSmallestIntegerContainer())); } startTimer (500); } const Rectangle getCanvasBounds() { return drawable->getBounds().getSmallestIntegerContainer(); } void setCanvasBounds (const Rectangle& newBounds) {} bool canResizeCanvas() const { return false; } //============================================================================== const ValueTree getObjectState (const String& objectId) { return getDocument().findDrawableState (objectId, false); } const SelectedItems::ItemType findObjectIdAt (const Point& position) { if (drawable != 0) { for (int i = drawable->getNumDrawables(); --i >= 0;) { Drawable* d = drawable->getDrawable (i); if (d->hitTest ((float) position.getX(), (float) position.getY())) return d->getName(); } } return String::empty; } void showPopupMenu (bool isClickOnSelectedObject) { PopupMenu m; if (isClickOnSelectedObject) { m.addCommandItem (commandManager, CommandIDs::toFront); m.addCommandItem (commandManager, CommandIDs::toBack); m.addSeparator(); m.addCommandItem (commandManager, StandardApplicationCommandIDs::del); const int r = m.show(); (void) r; } else { editor.showNewShapeMenu (0); } } void objectDoubleClicked (const MouseEvent& e, const ValueTree& state) { if (state.hasType (DrawablePath::valueTreeType) || state.hasType (DrawableImage::valueTreeType) || state.hasType (DrawableText::valueTreeType) || state.hasType (DrawableComposite::valueTreeType)) { enableControlPointMode (state); } else if (state.hasType (DrawableComposite::valueTreeType)) { // xxx } } bool hasSizeGuides() const { return false; } void getObjectPositionDependencies (const ValueTree& state, Array& deps) { DrawableDocument& doc = getDocument(); DrawableTypeInstance item (doc, state); OwnedArray points; item.getAllControlPoints (points); StringArray anchors; for (int i = 0; i < points.size(); ++i) { const RelativePoint p (points.getUnchecked(i)->getPosition()); anchors.addIfNotAlreadyThere (p.x.getAnchorName1()); anchors.addIfNotAlreadyThere (p.x.getAnchorName2()); anchors.addIfNotAlreadyThere (p.y.getAnchorName1()); anchors.addIfNotAlreadyThere (p.y.getAnchorName2()); } for (int i = 0; i < anchors.size(); ++i) { const String anchor (anchors[i]); if (anchor.isNotEmpty() && ! anchor.startsWith ("parent.")) { const ValueTree v (doc.findDrawableState (anchor.upToFirstOccurrenceOf (".", false, false), false)); if (v.isValid()) deps.add (v); } } } const Rectangle getObjectPositionFloat (const ValueTree& state) { if (drawable != 0) { Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID()); if (d != 0) return d->getBounds(); } return Rectangle(); } void setObjectPositionFloat (const ValueTree& state, const Rectangle& newPos) { if (drawable != 0) { Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID()); if (d != 0) { d->refreshFromValueTree (state, &getDocument()); DrawableTypeInstance di (getDocument(), state); di.setBounds (d, newPos); } } } const Rectangle getObjectPosition (const ValueTree& state) { return getObjectPositionFloat (state).getSmallestIntegerContainer(); } void transformObject (ValueTree& state, const AffineTransform& transform) { if (drawable != 0) { Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID()); if (d != 0) { d->refreshFromValueTree (state, &getDocument()); DrawableTypeInstance di (getDocument(), state); di.applyTransform (d, transform); } } } RelativeRectangle getObjectCoords (const ValueTree& state) { return RelativeRectangle(); } //============================================================================== class ControlPointComponent : public OverlayItemComponent { public: ControlPointComponent (DrawableEditorCanvas* canvas, const ValueTree& drawableState_, int controlPointNum_) : OverlayItemComponent (canvas), drawableState (drawableState_), controlPointNum (controlPointNum_), isDragging (false), mouseDownResult (false), selected (false), sizeNormal (7), sizeOver (11) { setRepaintsOnMouseActivity (true); } ~ControlPointComponent() { } void paint (Graphics& g) { Rectangle r (getLocalBounds()); if (! isMouseOverOrDragging()) r = r.reduced ((sizeOver - sizeNormal) / 2, (sizeOver - sizeNormal) / 2); g.setColour (Colour (selected ? 0xaaaaaaaa : 0xaa333333)); g.drawRect (r); g.setColour (Colour (selected ? 0xaa000000 : 0x99ffffff)); g.fillRect (r.reduced (1, 1)); } bool hitTest (int x, int y) { if (isMouseOverOrDragging()) return true; return getLocalBounds().reduced ((sizeOver - sizeNormal) / 2, (sizeOver - sizeNormal) / 2).contains (x, y); } void mouseDown (const MouseEvent& e) { isDragging = false; if (e.mods.isPopupMenu()) { canvas->showPopupMenu (true); } else { mouseDownResult = canvas->getSelection().addToSelectionOnMouseDown (selectionId, e.mods); } } void mouseDrag (const MouseEvent& e) { if (! (isDragging || e.mouseWasClicked() || e.mods.isPopupMenu())) { canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, true, mouseDownResult); isDragging = true; canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre), false, Point()); } if (isDragging) { canvas->continueDrag (e.getEventRelativeTo (getParentComponent())); autoScrollForMouseEvent (e); } } void mouseUp (const MouseEvent& e) { if (! e.mods.isPopupMenu()) { if (isDragging) canvas->endDrag (e.getEventRelativeTo (getParentComponent())); else canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, false, mouseDownResult); } } void mouseDoubleClick (const MouseEvent& e) { } class LineComponent : public OverlayItemComponent { public: LineComponent (EditorCanvasBase* canvas) : OverlayItemComponent (canvas) {} ~LineComponent() {} void setLine (const Line& newLine) { if (line != newLine) { line = newLine; setBoundsInTargetSpace (Rectangle (line.getStart(), line.getEnd()) .getSmallestIntegerContainer().expanded (2, 2)); repaint(); } } void paint (Graphics& g) { g.setColour (Colours::black.withAlpha (0.6f)); g.drawLine (Line (pointToLocalSpace (line.getStart()), pointToLocalSpace (line.getEnd())), 1.0f); } bool hitTest (int, int) { return false; } private: Line line; }; void updatePosition (ControlPoint& point, RelativeCoordinate::NamedCoordinateFinder* nameFinder) { selectionId = point.getID(); const Point p (point.getPosition().resolve (nameFinder)); setBoundsInTargetSpace (Rectangle (roundToInt (p.getX()) - sizeOver / 2, roundToInt (p.getY()) - sizeOver / 2, sizeOver, sizeOver)); const bool nowSelected = canvas->getSelection().isSelected (selectionId); if (selected != nowSelected) { selected = nowSelected; repaint(); } if (point.hasLine()) { if (line == 0) { line = new LineComponent (canvas); getParentComponent()->addAndMakeVisible (line, 0); } line->setLine (Line (p, point.getEndOfLine().resolve (nameFinder))); } else { line = 0; } } private: ValueTree drawableState; int controlPointNum; bool isDragging, mouseDownResult, selected; String selectionId; ScopedPointer line; const int sizeNormal, sizeOver; }; void updateControlPointComponents (Component* parent, OwnedArray& comps) { if (drawable == 0) { comps.clear(); return; } DrawableTypeInstance item (getDocument(), controlPointEditingTarget); OwnedArray points; item.getVisibleControlPoints (points, getSelection()); Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID()); DrawableComposite* parentDrawable = d->getParent(); comps.removeRange (points.size(), comps.size()); BigInteger requiredIndexes; requiredIndexes.setRange (0, points.size(), true); for (int i = 0; i < points.size(); ++i) { ControlPointComponent* c = dynamic_cast (comps[i]); if (c == 0) { c = new ControlPointComponent (this, controlPointEditingTarget, i); comps.set (i, c); parent->addAndMakeVisible (c); } c->updatePosition (*points.getUnchecked(i), parentDrawable); } } //============================================================================== MarkerListBase& getMarkerList (bool isX) { return getDocument().getMarkerList (isX); } double limitMarkerPosition (double pos) { return pos; } //============================================================================== SelectedItems& getSelection() { return editor.getSelection(); } void deselectNonDraggableObjects() { } void findLassoItemsInArea (Array & itemsFound, const Rectangle& area) { const Rectangle floatArea (area.toFloat()); if (drawable != 0) { if (isControlPointMode()) { DrawableTypeInstance item (getDocument(), controlPointEditingTarget); OwnedArray points; item.getVisibleControlPoints (points, getSelection()); const Rectangle floatArea (area.toFloat()); for (int i = 0; i < points.size(); ++i) { const Point p (points.getUnchecked(i)->getPosition().resolve (drawable)); if (floatArea.contains (p)) itemsFound.add (points.getUnchecked(i)->getID()); } } else { for (int i = drawable->getNumDrawables(); --i >= 0;) { Drawable* d = drawable->getDrawable (i); if (d->getBounds().intersects (floatArea)) itemsFound.add (d->getName()); } } } } bool isControlPointId (const String& itemId) { return itemId.containsChar ('/'); } //============================================================================== class ObjectDragOperation : public EditorDragOperation { public: ObjectDragOperation (DrawableEditorCanvas* canvas_, const Point& mousePos, Component* snapGuideParentComp_, const ResizableBorderComponent::Zone& zone_, bool isRotating) : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, isRotating), drawableCanvas (canvas_) { } ~ObjectDragOperation() {} protected: DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); } void getSnapPointsX (Array& points, bool /*includeCentre*/) { points.add (0.0f); } void getSnapPointsY (Array& points, bool /*includeCentre*/) { points.add (0.0f); } UndoManager& getUndoManager() { return *getDocument().getUndoManager(); } void getObjectDependencies (const ValueTree& state, Array& deps) { drawableCanvas->getObjectPositionDependencies (state, deps); } const Rectangle getObjectPosition (const ValueTree& state) { return drawableCanvas->getObjectPositionFloat (state); } void setObjectPosition (ValueTree& state, const Rectangle& newBounds) { drawableCanvas->setObjectPositionFloat (state, newBounds); } void transformObject (ValueTree& state, const AffineTransform& transform) { drawableCanvas->transformObject (state, transform); } float getMarkerPosition (const ValueTree& marker, bool isX) { return 0; } private: DrawableEditorCanvas* drawableCanvas; }; //============================================================================== class ControlPointDragOperation : public EditorDragOperation { public: ControlPointDragOperation (DrawableEditorCanvas* canvas_, const DrawableTypeInstance& drawableItem_, DrawableComposite* drawable_, const Point& mousePos, Component* snapGuideParentComp_, const ResizableBorderComponent::Zone& zone_) : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, false), drawableCanvas (canvas_), drawableItem (drawableItem_), drawable (drawable_) { drawableItem.getVisibleControlPoints (points, canvas_->getSelection()); } ~ControlPointDragOperation() {} OwnedArray points; protected: DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); } void getSnapPointsX (Array& points, bool /*includeCentre*/) { points.add (0.0f); } void getSnapPointsY (Array& points, bool /*includeCentre*/) { points.add (0.0f); } UndoManager& getUndoManager() { return *getDocument().getUndoManager(); } void getObjectDependencies (const ValueTree& state, Array& deps) { drawableCanvas->getObjectPositionDependencies (drawableItem.getState(), deps); } const Rectangle getObjectPosition (const ValueTree& state) { int index = state [Ids::id_]; ControlPoint* cp = points[index]; if (cp == 0) return Rectangle(); Point p (cp->getPosition().resolve (drawable)); return Rectangle (p, p); } void setObjectPosition (ValueTree& state, const Rectangle& newBounds) { int index = state [Ids::id_]; ControlPoint* cp = points[index]; if (cp != 0) { RelativePoint p (cp->getPosition()); p.moveToAbsolute (newBounds.getPosition(), drawable); cp->setPosition (p, getDocument().getUndoManager()); } } void transformObject (ValueTree& state, const AffineTransform& transform) { } float getMarkerPosition (const ValueTree& marker, bool isX) { return 0; } private: DrawableEditorCanvas* drawableCanvas; DrawableTypeInstance drawableItem; DrawableComposite* drawable; }; //============================================================================== bool canRotate() const { return true; } DragOperation* createDragOperation (const Point& mouseDownPos, Component* snapGuideParentComponent, const ResizableBorderComponent::Zone& zone, bool isRotating) { Array selected, unselected; EditorDragOperation* drag = 0; if (isControlPointMode()) { DrawableTypeInstance item (getDocument(), controlPointEditingTarget); ControlPointDragOperation* cpd = new ControlPointDragOperation (this, item, drawable, mouseDownPos, snapGuideParentComponent, zone); drag = cpd; for (int i = 0; i < cpd->points.size(); ++i) { const String pointId (cpd->points.getUnchecked(i)->getID()); ValueTree v (Ids::controlPoint); v.setProperty (Ids::id_, i, 0); if (editor.getSelection().isSelected (pointId)) selected.add (v); else unselected.add (v); } } else { DrawableComposite::ValueTreeWrapper mainGroup (getDocument().getRootDrawableNode()); drag = new ObjectDragOperation (this, mouseDownPos, snapGuideParentComponent, zone, isRotating); for (int i = mainGroup.getNumDrawables(); --i >= 0;) { const ValueTree v (mainGroup.getDrawableState (i)); if (editor.getSelection().isSelected (v[Drawable::ValueTreeWrapperBase::idProperty])) selected.add (v); else unselected.add (v); } } drag->initialise (selected, unselected); return drag; } void timerCallback() { stopTimer(); if (! Component::isMouseButtonDownAnywhere()) getUndoManager().beginNewTransaction(); } //============================================================================== bool isInterestedInFileDrag (const StringArray& files) { for (int i = files.size(); --i >= 0;) if (File (files[i]).hasFileExtension ("svg;jpg;jpeg;gif;png")) return true; return false; } void filesDropped (const StringArray& files, int x, int y) { for (int i = files.size(); --i >= 0;) { const File f (files[i]); if (f.hasFileExtension ("svg")) { ValueTree newItem (getDocument().insertSVG (f, screenSpaceToObjectSpace (Point (x, y).toFloat()))); if (newItem.isValid()) getSelection().selectOnly (Drawable::ValueTreeWrapperBase (newItem).getID()); } else if (f.hasFileExtension ("jpg;jpeg;gif;png")) { } } } //============================================================================== class DrawableComponent : public Component { public: DrawableComponent (DrawableEditorCanvas* canvas_) : canvas (canvas_) { setOpaque (true); } ~DrawableComponent() { } void updateDrawable() { repaint(); } void paint (Graphics& g) { canvas->handleUpdateNowIfNeeded(); g.fillAll (Colours::white); const Point origin (canvas->getOrigin()); g.setOrigin (origin.getX(), origin.getY()); if (origin.getX() > 0) { g.setColour (Colour::greyLevel (0.87f)); g.drawVerticalLine (0, -10000.0f, 10000.0f); } if (origin.getY() > 0) { g.setColour (Colour::greyLevel (0.87f)); g.drawHorizontalLine (0, -10000.0f, 10000.0f); } canvas->drawable->draw (g, 1.0f); } private: DrawableEditorCanvas* canvas; DrawableEditor& getEditor() const { return canvas->getEditor(); } }; ScopedPointer drawable; private: //============================================================================== DrawableEditor& editor; }; #endif // __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__