| @@ -761,10 +761,9 @@ void ComponentDocument::MarkerList::addMarkerMenuItems (const ValueTree& markerS | |||
| } | |||
| menu.addSeparator(); | |||
| const MarkerList& markerList = document.getMarkerList (isX); | |||
| for (int i = 0; i < markerList.size(); ++i) | |||
| document.addMarkerMenuItem (100 + i, coord, markerList.getName (markerList.getMarker (i)), | |||
| for (int i = 0; i < size(); ++i) | |||
| document.addMarkerMenuItem (100 + i, coord, getName (getMarker (i)), | |||
| String::empty, menu, isAnchor1, fullCoordName); | |||
| } | |||
| @@ -773,10 +772,8 @@ const String ComponentDocument::MarkerList::getChosenMarkerMenuItem (const Relat | |||
| if (i == 1) return isX ? "parent.left" : "parent.top"; | |||
| if (i == 2) return isX ? "parent.right" : "parent.bottom"; | |||
| const MarkerList& markerList = document.getMarkerList (isX); | |||
| if (i >= 100 && i < 10000) | |||
| return markerList.getName (markerList.getMarker (i - 100)); | |||
| return getName (getMarker (i - 100)); | |||
| jassertfalse; | |||
| return String::empty; | |||
| @@ -56,10 +56,10 @@ DrawableDocument::~DrawableDocument() | |||
| root.removeListener (this); | |||
| } | |||
| void DrawableDocument::recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d) | |||
| void DrawableDocument::recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d, StringArray& recentlyUsedIdCache) | |||
| { | |||
| if (d.getID().isEmpty()) | |||
| d.setID (createUniqueID (d.getState().getType().toString().toLowerCase() + "1"), 0); | |||
| d.setID (createUniqueID (d.getState().getType().toString().toLowerCase() + "1", recentlyUsedIdCache), 0); | |||
| if (d.getState().getType() == DrawableComposite::valueTreeType) | |||
| { | |||
| @@ -68,7 +68,7 @@ void DrawableDocument::recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d) | |||
| for (int i = 0; i < composite.getNumDrawables(); ++i) | |||
| { | |||
| Drawable::ValueTreeWrapperBase child (composite.getDrawableState (i)); | |||
| recursivelyUpdateIDs (child); | |||
| recursivelyUpdateIDs (child, recentlyUsedIdCache); | |||
| } | |||
| } | |||
| } | |||
| @@ -85,7 +85,13 @@ void DrawableDocument::checkRootObject() | |||
| markersY = new MarkerList (*this, false); | |||
| DrawableComposite::ValueTreeWrapper rootObject (getRootDrawableNode()); | |||
| recursivelyUpdateIDs (rootObject); | |||
| StringArray idCache; | |||
| recursivelyUpdateIDs (rootObject, idCache); | |||
| } | |||
| const String DrawableDocument::getUniqueId() const | |||
| { | |||
| return root [Ids::id_]; | |||
| } | |||
| //============================================================================== | |||
| @@ -201,26 +207,59 @@ ValueTree DrawableDocument::findDrawableState (const String& objectId, bool recu | |||
| return getRootDrawableNode().getDrawableWithId (objectId, recursive); | |||
| } | |||
| const String DrawableDocument::createUniqueID (const String& name) const | |||
| const String DrawableDocument::createUniqueID (const String& name, StringArray& recentlyUsedIdCache) const | |||
| { | |||
| String n (CodeHelpers::makeValidIdentifier (name, false, true, false)); | |||
| int suffix = 2; | |||
| int cacheIndex = -1; | |||
| const String withoutNumbers (n.trimCharactersAtEnd ("0123456789")); | |||
| for (int i = 0; i < recentlyUsedIdCache.size(); ++i) | |||
| { | |||
| if (recentlyUsedIdCache[i].startsWith (withoutNumbers)) | |||
| { | |||
| cacheIndex = i; | |||
| suffix = jmax (suffix, recentlyUsedIdCache[i].substring (withoutNumbers.length()).getIntValue() + 1); | |||
| n = withoutNumbers + String (suffix++); | |||
| break; | |||
| } | |||
| } | |||
| while (markersX->getMarkerNamed (n).isValid() || markersY->getMarkerNamed (n).isValid() | |||
| || findDrawableState (n, true).isValid()) | |||
| n = n.trimCharactersAtEnd ("0123456789") + String (suffix++); | |||
| n = withoutNumbers + String (suffix++); | |||
| if (cacheIndex >= 0) | |||
| recentlyUsedIdCache.set (cacheIndex, n); | |||
| else | |||
| recentlyUsedIdCache.add (n); | |||
| return n; | |||
| } | |||
| bool DrawableDocument::createItemProperties (Array <PropertyComponent*>& props, const String& itemId) | |||
| { | |||
| ValueTree drawable (findDrawableState (itemId, false)); | |||
| ValueTree drawable (findDrawableState (itemId.upToFirstOccurrenceOf ("/", false, false), false)); | |||
| if (drawable.isValid()) | |||
| { | |||
| DrawableTypeInstance item (*this, drawable); | |||
| item.createProperties (props); | |||
| if (itemId.containsChar ('/')) | |||
| { | |||
| OwnedArray <ControlPoint> points; | |||
| item.getAllControlPoints (points); | |||
| for (int i = 0; i < points.size(); ++i) | |||
| if (points.getUnchecked(i)->getID() == itemId) | |||
| points.getUnchecked(i)->createProperties (*this, props); | |||
| } | |||
| else | |||
| { | |||
| item.createProperties (props); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -264,8 +303,8 @@ const ValueTree DrawableDocument::performNewItemMenuItem (int menuResultCode) | |||
| Random::getSystemRandom().nextFloat() * 100.0f + 100.0f))); | |||
| Drawable::ValueTreeWrapperBase wrapper (state); | |||
| recursivelyUpdateIDs (wrapper); | |||
| StringArray idCache; | |||
| recursivelyUpdateIDs (wrapper, idCache); | |||
| getRootDrawableNode().addDrawable (state, -1, getUndoManager()); | |||
| return state; | |||
| @@ -274,15 +313,80 @@ const ValueTree DrawableDocument::performNewItemMenuItem (int menuResultCode) | |||
| return ValueTree::invalid; | |||
| } | |||
| const ValueTree DrawableDocument::insertSVG (const File& file, const Point<float>& position) | |||
| { | |||
| ScopedPointer<Drawable> d (Drawable::createFromImageFile (file)); | |||
| DrawableComposite* dc = dynamic_cast <DrawableComposite*> (static_cast <Drawable*> (d)); | |||
| if (dc != 0) | |||
| { | |||
| ValueTree state (dc->createValueTree (this)); | |||
| if (state.isValid()) | |||
| { | |||
| Drawable::ValueTreeWrapperBase wrapper (state); | |||
| getRootDrawableNode().addDrawable (state, -1, getUndoManager()); | |||
| StringArray idCache; | |||
| recursivelyUpdateIDs (wrapper, idCache); | |||
| return state; | |||
| } | |||
| } | |||
| return ValueTree::invalid; | |||
| } | |||
| //============================================================================== | |||
| const Image DrawableDocument::getImageForIdentifier (const var& imageIdentifier) | |||
| { | |||
| return ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize); | |||
| const String s (imageIdentifier.toString()); | |||
| if (s.startsWithIgnoreCase ("id:")) | |||
| { | |||
| jassert (project != 0); | |||
| if (project != 0) | |||
| { | |||
| Project::Item item (project->getMainGroup().findItemWithID (s.substring (3).trim())); | |||
| if (item.isValid()) | |||
| { | |||
| Image im (ImageCache::getFromFile (item.getFile())); | |||
| if (im.isValid()) | |||
| { | |||
| im.setTag (imageIdentifier); | |||
| return im; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| static Image dummy; | |||
| if (dummy.isNull()) | |||
| { | |||
| dummy = Image (Image::ARGB, 128, 128, true); | |||
| Graphics g (dummy); | |||
| g.fillAll (Colours::khaki.withAlpha (0.51f)); | |||
| g.setColour (Colours::grey); | |||
| g.drawRect (0, 0, 128, 128); | |||
| for (int i = -128; i < 128; i += 16) | |||
| g.drawLine (i, 0, i + 128, 128); | |||
| g.setColour (Colours::darkgrey); | |||
| g.drawRect (0, 0, 128, 128); | |||
| g.setFont (16.0f, Font::bold); | |||
| g.drawText ("(Image Missing)", 0, 0, 128, 128, Justification::centred, false); | |||
| } | |||
| return dummy; | |||
| } | |||
| const var DrawableDocument::getIdentifierForImage (const Image& image) | |||
| { | |||
| return var::null; //xxx todo | |||
| return image.getTag(); | |||
| } | |||
| //============================================================================== | |||
| @@ -403,50 +507,40 @@ bool DrawableDocument::MarkerList::createProperties (Array <PropertyComponent*>& | |||
| return false; | |||
| } | |||
| void DrawableDocument::addMarkerMenuItem (int i, const RelativeCoordinate& coord, const String& objectName, const String& edge, PopupMenu& menu, | |||
| bool isAnchor1, const String& fullCoordName) | |||
| void DrawableDocument::MarkerList::addMarkerMenuItem (int i, const RelativeCoordinate& coord, const String& name, const String& edge, PopupMenu& menu, | |||
| bool isAnchor1, const String& fullCoordName) | |||
| { | |||
| // RelativeCoordinate requestedCoord (findNamedCoordinate (objectName, edge, coord.isHorizontal())); | |||
| RelativeCoordinate requestedCoord (findNamedCoordinate (name, edge)); | |||
| // menu.addItem (i, name, | |||
| // ! (name == fullCoordName || requestedCoord.referencesIndirectly (fullCoordName, *this)), | |||
| // name == (isAnchor1 ? coord.getAnchor1() : coord.getAnchor2())); | |||
| menu.addItem (i, edge.isEmpty() ? name : (name + "." + edge), | |||
| ! (name == fullCoordName || (fullCoordName.isNotEmpty() && requestedCoord.references (fullCoordName, this))), | |||
| name == (isAnchor1 ? coord.getAnchorName1() : coord.getAnchorName2())); | |||
| } | |||
| void DrawableDocument::MarkerList::addMarkerMenuItems (const ValueTree& markerState, const RelativeCoordinate& coord, PopupMenu& menu, bool isAnchor1) | |||
| { | |||
| /* const String fullCoordName (getName (markerState)); | |||
| const String fullCoordName (getName (markerState)); | |||
| if (coord.isHorizontal()) | |||
| { | |||
| document.addMarkerMenuItem (1, coord, "parent", "left", menu, isAnchor1, fullCoordName); | |||
| document.addMarkerMenuItem (2, coord, "parent", "right", menu, isAnchor1, fullCoordName); | |||
| } | |||
| if (isHorizontal()) | |||
| addMarkerMenuItem (1, coord, "parent", "left", menu, isAnchor1, fullCoordName); | |||
| else | |||
| { | |||
| document.addMarkerMenuItem (1, coord, "parent", "top", menu, isAnchor1, fullCoordName); | |||
| document.addMarkerMenuItem (2, coord, "parent", "bottom", menu, isAnchor1, fullCoordName); | |||
| } | |||
| addMarkerMenuItem (1, coord, "parent", "top", menu, isAnchor1, fullCoordName); | |||
| menu.addSeparator(); | |||
| const MarkerList& markerList = document.getMarkerList (coord.isHorizontal()); | |||
| for (int i = 0; i < markerList.size(); ++i) | |||
| document.addMarkerMenuItem (100 + i, coord, markerList.getName (markerList.getMarker (i)), | |||
| String::empty, menu, isAnchor1, fullCoordName);*/ | |||
| for (int i = 0; i < size(); ++i) | |||
| addMarkerMenuItem (100 + i, coord, getName (getMarker (i)), | |||
| String::empty, menu, isAnchor1, fullCoordName); | |||
| } | |||
| const String DrawableDocument::MarkerList::getChosenMarkerMenuItem (const RelativeCoordinate& coord, int i) const | |||
| { | |||
| /* if (i == 1) return coord.isHorizontal() ? "parent.left" : "parent.top"; | |||
| if (i == 2) return coord.isHorizontal() ? "parent.right" : "parent.bottom"; | |||
| const MarkerList& markerList = document.getMarkerList (coord.isHorizontal()); | |||
| if (i == 1) return isHorizontal() ? "parent.left" : "parent.top"; | |||
| if (i >= 100 && i < 10000) | |||
| return markerList.getName (markerList.getMarker (i - 100)); | |||
| return getName (getMarker (i - 100)); | |||
| jassertfalse;*/ | |||
| jassertfalse; | |||
| return String::empty; | |||
| } | |||
| @@ -49,16 +49,20 @@ public: | |||
| bool hasChangedSinceLastSave() const; | |||
| void changed(); | |||
| Project* getProject() const throw() { return project; } | |||
| const String getUniqueId() const; | |||
| ValueTree& getRoot() { return root; } | |||
| DrawableComposite::ValueTreeWrapper getRootDrawableNode() const; | |||
| ValueTree findDrawableState (const String& objectId, bool recursive) const; | |||
| const String createUniqueID (const String& suggestion) const; | |||
| const String createUniqueID (const String& suggestion, StringArray& recentlyUsedIdCache) const; | |||
| void createItemProperties (Array <PropertyComponent*>& props, const StringArray& selectedItemIds); | |||
| void addNewItemMenuItems (PopupMenu& menu) const; | |||
| const ValueTree performNewItemMenuItem (int menuResultCode); | |||
| const ValueTree insertSVG (const File& file, const Point<float>& position); | |||
| //============================================================================== | |||
| class MarkerList : public MarkerListBase | |||
| @@ -86,6 +90,9 @@ public: | |||
| DrawableDocument& document; | |||
| DrawableComposite::ValueTreeWrapper object; | |||
| void addMarkerMenuItem (int i, const RelativeCoordinate& coord, const String& objectName, const String& edge, | |||
| PopupMenu& menu, bool isAnchor1, const String& fullCoordName); | |||
| MarkerList (const MarkerList&); | |||
| MarkerList& operator= (const MarkerList&); | |||
| }; | |||
| @@ -116,7 +123,7 @@ private: | |||
| bool saveAsXml, needsSaving; | |||
| void checkRootObject(); | |||
| void recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d); | |||
| void recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d, StringArray& recentlyUsedIdCache); | |||
| Value getRootValueUndoable (const Identifier& name) const { return root.getPropertyAsValue (name, getUndoManager()); } | |||
| Value getRootValueNonUndoable (const Identifier& name) const { return root.getPropertyAsValue (name, 0); } | |||
| @@ -127,8 +134,6 @@ private: | |||
| bool createItemProperties (Array <PropertyComponent*>& props, const String& itemId); | |||
| const RelativeCoordinate findNamedCoordinate (const String& objectName, const String& edge) const; | |||
| void addMarkerMenuItem (int i, const RelativeCoordinate& coord, const String& objectName, const String& edge, | |||
| PopupMenu& menu, bool isAnchor1, const String& fullCoordName); | |||
| }; | |||
| @@ -24,7 +24,92 @@ | |||
| */ | |||
| #include "jucer_DrawableTypeHandler.h" | |||
| #include "../../utility/jucer_ColourPropertyComponent.h" | |||
| //============================================================================== | |||
| class ControlPointPropertyComp : public CoordinatePropertyComponent | |||
| { | |||
| public: | |||
| ControlPointPropertyComp (DrawableTypeInstance& item_, ControlPoint* cp, const String& name, bool isHorizontal_, UndoManager* undoManager) | |||
| : CoordinatePropertyComponent (0, name, Value (new CoordExtractor (cp->getPositionValue (undoManager), isHorizontal_)), isHorizontal_), | |||
| item (item_) | |||
| { | |||
| nameSource = &item; | |||
| } | |||
| ~ControlPointPropertyComp() | |||
| { | |||
| } | |||
| const String pickMarker (TextButton* button, const String& currentMarker, bool isAnchor1) | |||
| { | |||
| RelativeCoordinate coord (getCoordinate()); | |||
| PopupMenu m; | |||
| item.getDocument().getMarkerList (isHorizontal).addMarkerMenuItems (ValueTree::invalid, coord, m, isAnchor1); | |||
| const int r = m.showAt (button); | |||
| if (r > 0) | |||
| return item.getDocument().getMarkerList (isHorizontal).getChosenMarkerMenuItem (coord, r); | |||
| return String::empty; | |||
| } | |||
| DrawableTypeInstance item; | |||
| //============================================================================== | |||
| class CoordExtractor : public Value::ValueSource, | |||
| public Value::Listener | |||
| { | |||
| public: | |||
| CoordExtractor (const Value& sourceValue_, const bool isX_) | |||
| : sourceValue (sourceValue_), isX (isX_) | |||
| { | |||
| sourceValue.addListener (this); | |||
| } | |||
| ~CoordExtractor() {} | |||
| const var getValue() const | |||
| { | |||
| RelativePoint p (sourceValue.toString()); | |||
| return getCoord (p).toString(); | |||
| } | |||
| void setValue (const var& newValue) | |||
| { | |||
| RelativePoint p (sourceValue.toString()); | |||
| RelativeCoordinate& coord = getCoord (p); | |||
| coord = RelativeCoordinate (newValue.toString(), isX); | |||
| const String newVal (p.toString()); | |||
| if (sourceValue != newVal) | |||
| sourceValue = newVal; | |||
| } | |||
| void valueChanged (Value&) | |||
| { | |||
| sendChangeMessage (true); | |||
| } | |||
| //============================================================================== | |||
| juce_UseDebuggingNewOperator | |||
| protected: | |||
| Value sourceValue; | |||
| bool isX; | |||
| RelativeCoordinate& getCoord (RelativePoint& p) const | |||
| { | |||
| return isX ? p.x : p.y; | |||
| } | |||
| CoordExtractor (const CoordExtractor&); | |||
| const CoordExtractor& operator= (const CoordExtractor&); | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| class DrawablePathHandler : public DrawableTypeHandler | |||
| @@ -73,7 +158,8 @@ public: | |||
| { | |||
| public: | |||
| DrawablePathFillPropComp (DrawableTypeInstance& item_, const String& name, const ValueTree& fill) | |||
| : FillTypePropertyComponent (item_.getDocument().getUndoManager(), name, fill), | |||
| : FillTypePropertyComponent (item_.getDocument().getUndoManager(), name, fill, | |||
| &item_.getDocument(), item_.getProject()), | |||
| item (item_) | |||
| {} | |||
| @@ -110,9 +196,9 @@ public: | |||
| class GradientControlPoint : public ControlPoint | |||
| { | |||
| public: | |||
| GradientControlPoint (const ValueTree& item_, | |||
| GradientControlPoint (const String& id_, const ValueTree& item_, | |||
| const bool isStart_, const bool isStroke_) | |||
| : item (item_), isStart (isStart_), isStroke (isStroke_) | |||
| : ControlPoint (id_), item (item_), isStart (isStart_), isStroke (isStroke_) | |||
| {} | |||
| ~GradientControlPoint() {} | |||
| @@ -124,7 +210,8 @@ public: | |||
| RelativePoint p; | |||
| const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState(), | |||
| isStart ? &p : 0, | |||
| isStart ? 0 : &p, 0)); | |||
| isStart ? 0 : &p, 0, | |||
| 0)); | |||
| jassert (fill.isGradient()); | |||
| return p; | |||
| } | |||
| @@ -135,7 +222,7 @@ public: | |||
| RelativePoint p1, p2; | |||
| ValueTree fillState (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState()); | |||
| const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (fillState, &p1, &p2, 0)); | |||
| const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (fillState, &p1, &p2, 0, 0)); | |||
| jassert (fill.isGradient()); | |||
| if (isStart) | |||
| @@ -143,11 +230,32 @@ public: | |||
| else | |||
| p2 = newPoint; | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, fill, &p1, &p2, undoManager); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, fill, &p1, &p2, 0, undoManager); | |||
| } | |||
| bool hasLine() { return false; } | |||
| RelativePoint getEndOfLine() { return RelativePoint(); } | |||
| bool hasLine() { return isStart; } | |||
| RelativePoint getEndOfLine() | |||
| { | |||
| RelativePoint p; | |||
| DrawablePath::ValueTreeWrapper wrapper (item); | |||
| ValueTree fillState (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState()); | |||
| Drawable::ValueTreeWrapperBase::readFillType (fillState, 0, &p, 0, 0); | |||
| return p; | |||
| } | |||
| const Value getPositionValue (UndoManager* undoManager) | |||
| { | |||
| DrawablePath::ValueTreeWrapper wrapper (item); | |||
| ValueTree fillState (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState()); | |||
| return fillState.getPropertyAsValue (isStart ? Drawable::ValueTreeWrapperBase::gradientPoint1 : Drawable::ValueTreeWrapperBase::gradientPoint2, undoManager); | |||
| } | |||
| void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableTypeInstance instance (document, item); | |||
| props.add (new ControlPointPropertyComp (instance, this, "X", true, document.getUndoManager())); | |||
| props.add (new ControlPointPropertyComp (instance, this, "Y", false, document.getUndoManager())); | |||
| } | |||
| private: | |||
| ValueTree item; | |||
| @@ -158,8 +266,10 @@ public: | |||
| class PathControlPoint : public ControlPoint | |||
| { | |||
| public: | |||
| PathControlPoint (const DrawablePath::ValueTreeWrapper::Element& element_, const int cpNum_) | |||
| : element (element_), cpNum (cpNum_) | |||
| PathControlPoint (const String& id_, | |||
| const DrawablePath::ValueTreeWrapper::Element& element_, | |||
| const DrawablePath::ValueTreeWrapper::Element& previousElement_, const int cpNum_, const int numCps_) | |||
| : ControlPoint (id_), element (element_), previousElement (previousElement_), cpNum (cpNum_), numCps (numCps_) | |||
| {} | |||
| ~PathControlPoint() {} | |||
| @@ -174,45 +284,133 @@ public: | |||
| element.setControlPoint (cpNum, newPoint, undoManager); | |||
| } | |||
| bool hasLine() { return false; } | |||
| RelativePoint getEndOfLine() { return RelativePoint(); } | |||
| const Value getPositionValue (UndoManager* undoManager) | |||
| { | |||
| return element.getControlPointValue (cpNum, undoManager); | |||
| } | |||
| bool hasLine() { return numCps > 1 && cpNum == 0 || cpNum == 1; } | |||
| RelativePoint getEndOfLine() | |||
| { | |||
| if (cpNum == 0) | |||
| return previousElement.getEndPoint(); | |||
| else | |||
| return element.getControlPoint (2); | |||
| } | |||
| void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableTypeInstance instance (document, element.getParent().getState()); | |||
| props.add (new ControlPointPropertyComp (instance, this, "X", true, document.getUndoManager())); | |||
| props.add (new ControlPointPropertyComp (instance, this, "Y", false, document.getUndoManager())); | |||
| } | |||
| private: | |||
| DrawablePath::ValueTreeWrapper::Element element; | |||
| int cpNum; | |||
| DrawablePath::ValueTreeWrapper::Element element, previousElement; | |||
| int cpNum, numCps; | |||
| }; | |||
| void getGradientControlPoints (DrawablePath::ValueTreeWrapper& wrapper, DrawableTypeInstance& item, | |||
| OwnedArray <ControlPoint>& points, const String& itemId) | |||
| { | |||
| const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getMainFillState(), 0, 0, 0, 0)); | |||
| if (fill.isGradient()) | |||
| { | |||
| points.add (new GradientControlPoint (itemId + "/gf1", item.getState(), true, false)); | |||
| points.add (new GradientControlPoint (itemId + "/gf2", item.getState(), false, false)); | |||
| } | |||
| const FillType stroke (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getStrokeFillState(), 0, 0, 0, 0)); | |||
| if (stroke.isGradient()) | |||
| { | |||
| points.add (new GradientControlPoint (itemId + "/gs1", item.getState(), true, true)); | |||
| points.add (new GradientControlPoint (itemId + "/gs1", item.getState(), false, true)); | |||
| } | |||
| } | |||
| void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
| { | |||
| DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
| const ValueTree pathTree (wrapper.getPathState()); | |||
| const int numElements = pathTree.getNumChildren(); | |||
| const String itemId (item.getID()); | |||
| for (int i = 0; i < numElements; ++i) | |||
| if (numElements > 0) | |||
| { | |||
| const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
| const int numCps = e.getNumControlPoints(); | |||
| DrawablePath::ValueTreeWrapper::Element last (pathTree.getChild(0)); | |||
| for (int j = 0; j < numCps; ++j) | |||
| points.add (new PathControlPoint (e, j)); | |||
| } | |||
| for (int i = 0; i < numElements; ++i) | |||
| { | |||
| const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
| const int numCps = e.getNumControlPoints(); | |||
| const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getMainFillState(), 0, 0, 0)); | |||
| for (int j = 0; j < numCps; ++j) | |||
| points.add (new PathControlPoint (itemId + "/" + String(i) + "/" + String(j), e, last, j, numCps)); | |||
| if (fill.isGradient()) | |||
| { | |||
| points.add (new GradientControlPoint (item.getState(), true, false)); | |||
| points.add (new GradientControlPoint (item.getState(), false, false)); | |||
| last = e; | |||
| } | |||
| } | |||
| const FillType stroke (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getStrokeFillState(), 0, 0, 0)); | |||
| getGradientControlPoints (wrapper, item, points, itemId); | |||
| } | |||
| if (stroke.isGradient()) | |||
| void getVisibleControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems& selection) | |||
| { | |||
| DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
| const ValueTree pathTree (wrapper.getPathState()); | |||
| const int numElements = pathTree.getNumChildren(); | |||
| const String itemId (item.getID()); | |||
| if (numElements > 0) | |||
| { | |||
| points.add (new GradientControlPoint (item.getState(), true, true)); | |||
| points.add (new GradientControlPoint (item.getState(), false, true)); | |||
| DrawablePath::ValueTreeWrapper::Element last (pathTree.getChild(0)); | |||
| bool lastWasSelected = false; | |||
| for (int i = 0; i < numElements; ++i) | |||
| { | |||
| const String elementIdRoot (itemId + "/" + String(i) + "/"); | |||
| const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
| int numCps = e.getNumControlPoints(); | |||
| bool pointIsSelected = false; | |||
| for (int k = numCps; --k >= 0;) | |||
| { | |||
| if (selection.isSelected (elementIdRoot + String (k))) | |||
| { | |||
| pointIsSelected = true; | |||
| break; | |||
| } | |||
| } | |||
| if (numCps > 1) | |||
| { | |||
| if (pointIsSelected || lastWasSelected) | |||
| { | |||
| for (int j = 0; j < numCps; ++j) | |||
| points.add (new PathControlPoint (elementIdRoot + String(j), e, last, j, numCps)); | |||
| } | |||
| else | |||
| { | |||
| points.add (new PathControlPoint (elementIdRoot + String (numCps - 1), e, last, numCps - 1, numCps)); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int j = 0; j < numCps; ++j) | |||
| points.add (new PathControlPoint (elementIdRoot + String(j), e, last, j, numCps)); | |||
| } | |||
| last = e; | |||
| lastWasSelected = pointIsSelected; | |||
| } | |||
| } | |||
| getGradientControlPoints (wrapper, item, points, itemId); | |||
| } | |||
| }; | |||
| @@ -244,6 +442,34 @@ public: | |||
| void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
| if (item.getDocument().getProject() != 0) | |||
| { | |||
| OwnedArray<Project::Item> images; | |||
| item.getDocument().getProject()->findAllImageItems (images); | |||
| StringArray choices; | |||
| Array<var> ids; | |||
| for (int i = 0; i < images.size(); ++i) | |||
| { | |||
| choices.add (images.getUnchecked(i)->getName().toString()); | |||
| ids.add (images.getUnchecked(i)->getImageFileID()); | |||
| } | |||
| props.add (new ChoicePropertyComponent (wrapper.getImageIdentifierValue (item.getDocument().getUndoManager()), | |||
| "Image", choices, ids)); | |||
| } | |||
| props.add (new SliderPropertyComponent (wrapper.getOpacityValue (item.getDocument().getUndoManager()), | |||
| "Opacity", 0, 1.0, 0.001)); | |||
| props.add (new ColourPropertyComponent (item.getDocument().getUndoManager(), "Overlay Colour", | |||
| wrapper.getOverlayColourValue (item.getDocument().getUndoManager()), | |||
| Colours::transparentBlack, true)); | |||
| props.add (new ResetButtonPropertyComponent (item, wrapper)); | |||
| } | |||
| void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
| @@ -254,8 +480,8 @@ public: | |||
| class ImageControlPoint : public ControlPoint | |||
| { | |||
| public: | |||
| ImageControlPoint (const DrawableTypeInstance& item_, const int cpNum_) | |||
| : item (item_), cpNum (cpNum_) | |||
| ImageControlPoint (const String& id_, const DrawableTypeInstance& item_, const int cpNum_) | |||
| : ControlPoint (id_), item (item_), cpNum (cpNum_) | |||
| {} | |||
| ~ImageControlPoint() {} | |||
| @@ -288,9 +514,29 @@ public: | |||
| } | |||
| } | |||
| const Value getPositionValue (UndoManager* undoManager) | |||
| { | |||
| DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
| switch (cpNum) | |||
| { | |||
| case 0: return item.getState().getPropertyAsValue (DrawableImage::ValueTreeWrapper::topLeft, undoManager); | |||
| case 1: return item.getState().getPropertyAsValue (DrawableImage::ValueTreeWrapper::topRight, undoManager); | |||
| case 2: return item.getState().getPropertyAsValue (DrawableImage::ValueTreeWrapper::bottomLeft, undoManager); | |||
| default: jassertfalse; break; | |||
| } | |||
| return Value(); | |||
| } | |||
| bool hasLine() { return false; } | |||
| RelativePoint getEndOfLine() { return RelativePoint(); } | |||
| void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) | |||
| { | |||
| props.add (new ControlPointPropertyComp (item, this, "X", true, document.getUndoManager())); | |||
| props.add (new ControlPointPropertyComp (item, this, "Y", false, document.getUndoManager())); | |||
| } | |||
| private: | |||
| DrawableTypeInstance item; | |||
| int cpNum; | |||
| @@ -298,9 +544,52 @@ public: | |||
| void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
| { | |||
| const String itemIDRoot (item.getID() + "/"); | |||
| for (int i = 0; i < 3; ++i) | |||
| points.add (new ImageControlPoint (item, i)); | |||
| points.add (new ImageControlPoint (itemIDRoot + String (i), item, i)); | |||
| } | |||
| void getVisibleControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems&) | |||
| { | |||
| return getAllControlPoints (item, points); | |||
| } | |||
| //============================================================================== | |||
| class ResetButtonPropertyComponent : public ButtonPropertyComponent | |||
| { | |||
| public: | |||
| ResetButtonPropertyComponent (DrawableTypeInstance& item_, | |||
| const DrawableImage::ValueTreeWrapper& wrapper_) | |||
| : ButtonPropertyComponent ("Reset", false), | |||
| item (item_), wrapper (wrapper_) | |||
| { | |||
| } | |||
| const String getButtonText() const { return "Reset to Original Size"; } | |||
| void buttonClicked() | |||
| { | |||
| Image im (item.getDocument().getImageForIdentifier (wrapper.getImageIdentifier())); | |||
| if (im.isValid()) | |||
| { | |||
| RelativePoint topLeft (wrapper.getTargetPositionForTopLeft()); | |||
| RelativePoint topRight (wrapper.getTargetPositionForTopRight()); | |||
| RelativePoint bottomLeft (wrapper.getTargetPositionForBottomLeft()); | |||
| topRight.moveToAbsolute (topLeft.resolve (&item) + Point<float> (im.getWidth(), 0.0f), &item); | |||
| bottomLeft.moveToAbsolute (topLeft.resolve (&item) + Point<float> (0.0f, im.getHeight()), &item); | |||
| wrapper.setTargetPositionForTopRight (topRight, item.getDocument().getUndoManager()); | |||
| wrapper.setTargetPositionForBottomLeft (bottomLeft, item.getDocument().getUndoManager()); | |||
| } | |||
| } | |||
| private: | |||
| DrawableTypeInstance item; | |||
| DrawableImage::ValueTreeWrapper wrapper; | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| @@ -312,33 +601,20 @@ public: | |||
| void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableComposite::ValueTreeWrapper wrapper (item.getState()); | |||
| props.add (new ResetButtonPropertyComponent (item, wrapper)); | |||
| } | |||
| void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
| { | |||
| } | |||
| const RelativeCoordinate findNamedCoordinate (const DrawableTypeInstance& item, const String& objectName, const String& edge) const | |||
| { | |||
| DrawableComposite::ValueTreeWrapper wrapper (const_cast <DrawableTypeInstance&> (item).getState()); | |||
| ValueTree markerState (wrapper.getMarkerState (true, objectName)); | |||
| if (markerState.isValid()) | |||
| return wrapper.getMarker (true, markerState).position; | |||
| markerState = wrapper.getMarkerState (false, objectName); | |||
| if (markerState.isValid()) | |||
| return wrapper.getMarker (false, markerState).position; | |||
| return RelativeCoordinate(); | |||
| } | |||
| //============================================================================== | |||
| class CompositeControlPoint : public ControlPoint | |||
| { | |||
| public: | |||
| CompositeControlPoint (const ValueTree& item_, const int cpNum_) | |||
| : item (item_), cpNum (cpNum_) | |||
| CompositeControlPoint (const String& id_, const ValueTree& item_, const int cpNum_) | |||
| : ControlPoint (id_), item (item_), cpNum (cpNum_) | |||
| {} | |||
| ~CompositeControlPoint() {} | |||
| @@ -371,9 +647,22 @@ public: | |||
| } | |||
| } | |||
| const Value getPositionValue (UndoManager* undoManager) | |||
| { | |||
| jassertfalse | |||
| return Value(); | |||
| } | |||
| bool hasLine() { return false; } | |||
| RelativePoint getEndOfLine() { return RelativePoint(); } | |||
| void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableTypeInstance instance (document, item); | |||
| props.add (new ControlPointPropertyComp (instance, this, "X", true, document.getUndoManager())); | |||
| props.add (new ControlPointPropertyComp (instance, this, "Y", false, document.getUndoManager())); | |||
| } | |||
| private: | |||
| ValueTree item; | |||
| int cpNum; | |||
| @@ -381,9 +670,47 @@ public: | |||
| void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
| { | |||
| const String itemIDRoot (item.getID() + "/"); | |||
| for (int i = 0; i < 3; ++i) | |||
| points.add (new CompositeControlPoint (item.getState(), i)); | |||
| points.add (new CompositeControlPoint (itemIDRoot + String(i), item.getState(), i)); | |||
| } | |||
| void getVisibleControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems&) | |||
| { | |||
| return getAllControlPoints (item, points); | |||
| } | |||
| //============================================================================== | |||
| class ResetButtonPropertyComponent : public ButtonPropertyComponent | |||
| { | |||
| public: | |||
| ResetButtonPropertyComponent (DrawableTypeInstance& item_, | |||
| const DrawableComposite::ValueTreeWrapper& wrapper_) | |||
| : ButtonPropertyComponent ("Reset", false), | |||
| item (item_), wrapper (wrapper_) | |||
| { | |||
| } | |||
| const String getButtonText() const { return "Reset to Original Size"; } | |||
| void buttonClicked() | |||
| { | |||
| RelativePoint topLeft (wrapper.getTargetPositionForOrigin()); | |||
| RelativePoint topRight (wrapper.getTargetPositionForX1Y0()); | |||
| RelativePoint bottomLeft (wrapper.getTargetPositionForX0Y1()); | |||
| topRight.moveToAbsolute (topLeft.resolve (&item) + Point<float> (1.0f, 0.0f), &item); | |||
| bottomLeft.moveToAbsolute (topLeft.resolve (&item) + Point<float> (0.0f, 1.0f), &item); | |||
| wrapper.setTargetPositionForX1Y0 (topRight, item.getDocument().getUndoManager()); | |||
| wrapper.setTargetPositionForX0Y1 (bottomLeft, item.getDocument().getUndoManager()); | |||
| } | |||
| private: | |||
| DrawableTypeInstance item; | |||
| DrawableComposite::ValueTreeWrapper wrapper; | |||
| }; | |||
| }; | |||
| @@ -459,7 +786,21 @@ DrawableTypeHandler* DrawableTypeInstance::getHandler() const | |||
| const RelativeCoordinate DrawableTypeInstance::findNamedCoordinate (const String& objectName, const String& edge) const | |||
| { | |||
| return getHandler()->findNamedCoordinate (*this, objectName, edge); | |||
| ValueTree v (state); | |||
| while (v.getParent().isValid() && ! v.hasType (DrawableComposite::valueTreeType)) | |||
| v = v.getParent(); | |||
| DrawableComposite::ValueTreeWrapper wrapper (v); | |||
| ValueTree markerState (wrapper.getMarkerState (true, objectName)); | |||
| if (markerState.isValid()) | |||
| return wrapper.getMarker (true, markerState).position; | |||
| markerState = wrapper.getMarkerState (false, objectName); | |||
| if (markerState.isValid()) | |||
| return wrapper.getMarker (false, markerState).position; | |||
| return RelativeCoordinate(); | |||
| } | |||
| const Rectangle<float> DrawableTypeInstance::getBounds() | |||
| @@ -485,7 +826,28 @@ void DrawableTypeInstance::setBounds (Drawable* drawable, const Rectangle<float> | |||
| return getHandler()->setBounds (*this, drawable, newBounds); | |||
| } | |||
| void DrawableTypeInstance::applyTransform (Drawable* drawable, const AffineTransform& transform) | |||
| { | |||
| OwnedArray <ControlPoint> points; | |||
| getAllControlPoints (points); | |||
| for (int i = points.size(); --i >= 0;) | |||
| { | |||
| RelativePoint rp (points.getUnchecked(i)->getPosition()); | |||
| Point<float> p (rp.resolve (drawable->getParent())); | |||
| p.applyTransform (transform); | |||
| rp.moveToAbsolute (p, drawable->getParent()); | |||
| points.getUnchecked(i)->setPosition (rp, document.getUndoManager()); | |||
| } | |||
| } | |||
| void DrawableTypeInstance::getAllControlPoints (OwnedArray <ControlPoint>& points) | |||
| { | |||
| return getHandler()->getAllControlPoints (*this, points); | |||
| } | |||
| void DrawableTypeInstance::getVisibleControlPoints (OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems& selection) | |||
| { | |||
| return getHandler()->getVisibleControlPoints (*this, points, selection); | |||
| } | |||
| @@ -28,22 +28,36 @@ | |||
| #include "jucer_DrawableDocument.h" | |||
| #include "../../utility/jucer_FillTypePropertyComponent.h" | |||
| #include "../../ui/Editor Base/jucer_EditorCanvas.h" | |||
| class DrawableTypeHandler; | |||
| //============================================================================== | |||
| class ControlPoint | |||
| { | |||
| public: | |||
| ControlPoint() {} | |||
| ControlPoint (const String& pointID_) : pointID (pointID_) {} | |||
| virtual ~ControlPoint() {} | |||
| const String& getID() const throw() { return pointID; } | |||
| virtual const RelativePoint getPosition() = 0; | |||
| virtual void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) = 0; | |||
| virtual bool hasLine() = 0; | |||
| virtual RelativePoint getEndOfLine() = 0; | |||
| virtual const Value getPositionValue (UndoManager* undoManager) = 0; | |||
| virtual void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) = 0; | |||
| private: | |||
| const String pointID; | |||
| ControlPoint (const ControlPoint&); | |||
| ControlPoint& operator= (const ControlPoint&); | |||
| }; | |||
| //============================================================================== | |||
| class DrawableTypeInstance : public RelativeCoordinate::NamedCoordinateFinder | |||
| { | |||
| @@ -52,13 +66,17 @@ public: | |||
| //============================================================================== | |||
| DrawableDocument& getDocument() throw() { return document; } | |||
| Project* getProject() { return document.getProject(); } | |||
| ValueTree& getState() throw() { return state; } | |||
| const String getID() const { return Drawable::ValueTreeWrapperBase (state).getID(); } | |||
| Value getValue (const Identifier& name) const; | |||
| void createProperties (Array <PropertyComponent*>& props); | |||
| const Rectangle<float> getBounds(); | |||
| void setBounds (Drawable* drawable, const Rectangle<float>& newBounds); | |||
| void applyTransform (Drawable* drawable, const AffineTransform& transform); | |||
| void getAllControlPoints (OwnedArray <ControlPoint>& points); | |||
| void getVisibleControlPoints (OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems& selection); | |||
| const RelativeCoordinate findNamedCoordinate (const String& objectName, const String& edge) const; | |||
| @@ -87,7 +105,7 @@ public: | |||
| virtual void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) = 0; | |||
| virtual void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) = 0; | |||
| virtual void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) = 0; | |||
| virtual const RelativeCoordinate findNamedCoordinate (const DrawableTypeInstance& item, const String& objectName, const String& edge) const { return RelativeCoordinate(); } | |||
| virtual void getVisibleControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems& selection) = 0; | |||
| const String& getDisplayName() const { return displayName; } | |||
| const Identifier& getValueTreeType() const { return valueTreeType; } | |||
| @@ -411,6 +411,25 @@ Project::Item Project::createNewItem (const File& file) | |||
| return item; | |||
| } | |||
| static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found) | |||
| { | |||
| if (item.isFile()) | |||
| { | |||
| if (item.getFile().hasFileExtension ("png;jpg;jpeg;gif")) | |||
| found.add (new Project::Item (item)); | |||
| } | |||
| else if (item.isGroup()) | |||
| { | |||
| for (int i = 0; i < item.getNumChildren(); ++i) | |||
| findImages (item.getChild (i), found); | |||
| } | |||
| } | |||
| void Project::findAllImageItems (OwnedArray<Project::Item>& items) | |||
| { | |||
| findImages (getMainGroup(), items); | |||
| } | |||
| //============================================================================== | |||
| Project::Item::Item (Project& project_, const ValueTree& node_) | |||
| : project (project_), node (node_) | |||
| @@ -426,7 +445,8 @@ Project::Item::~Item() | |||
| { | |||
| } | |||
| const String Project::Item::getID() const { return node [Ids::id_]; } | |||
| const String Project::Item::getID() const { return node [Ids::id_]; } | |||
| const String Project::Item::getImageFileID() const { return "id:" + getID(); } | |||
| bool Project::Item::isFile() const { return node.hasType (Tags::file); } | |||
| bool Project::Item::isGroup() const { return node.hasType (Tags::group) || isMainGroup(); } | |||
| @@ -166,6 +166,7 @@ public: | |||
| const String getID() const; | |||
| Item findItemWithID (const String& targetId) const; // (recursive search) | |||
| const String getImageFileID() const; | |||
| //============================================================================== | |||
| Value getName() const; | |||
| @@ -207,6 +208,8 @@ public: | |||
| Item createNewGroup(); | |||
| Item createNewItem (const File& file); | |||
| void findAllImageItems (OwnedArray<Item>& items); | |||
| //============================================================================== | |||
| class BuildConfiguration | |||
| { | |||
| @@ -175,10 +175,10 @@ public: | |||
| { | |||
| public: | |||
| DragOperation (ComponentEditorCanvas* canvas_, | |||
| const MouseEvent& e, const Point<int>& mousePos, | |||
| const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, | |||
| const ResizableBorderComponent::Zone& zone_) | |||
| : EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_) | |||
| : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, false) | |||
| { | |||
| } | |||
| @@ -262,12 +262,18 @@ public: | |||
| ComponentDocument& doc = getDocument(); | |||
| return (float) doc.getMarkerList (isX).getCoordinate (marker).resolve (&doc); | |||
| } | |||
| void transformObject (ValueTree& state, const AffineTransform& transform) | |||
| { | |||
| } | |||
| }; | |||
| DragOperation* createDragOperation (const MouseEvent& e, Component* snapGuideParentComponent, | |||
| const ResizableBorderComponent::Zone& zone) | |||
| bool canRotate() const { return false; } | |||
| DragOperation* createDragOperation (const Point<int>& mouseDownPos, Component* snapGuideParentComponent, | |||
| const ResizableBorderComponent::Zone& zone, bool isRotating) | |||
| { | |||
| DragOperation* d = new DragOperation (this, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
| DragOperation* d = new DragOperation (this, mouseDownPos, snapGuideParentComponent, zone); | |||
| Array<ValueTree> selected, unselected; | |||
| @@ -115,14 +115,57 @@ const StringArray DrawableEditor::getSelectedIds() const | |||
| void DrawableEditor::deleteSelection() | |||
| { | |||
| getUndoManager()->beginNewTransaction(); | |||
| DrawableComposite::ValueTreeWrapper root (getDocument().getRootDrawableNode()); | |||
| const StringArray ids (getSelectedIds()); | |||
| for (int i = ids.size(); --i >= 0;) | |||
| { | |||
| const ValueTree v (root.getDrawableWithId (ids[i], false)); | |||
| root.removeDrawable (v, getUndoManager()); | |||
| } | |||
| getUndoManager()->beginNewTransaction(); | |||
| } | |||
| void DrawableEditor::selectionToFront() | |||
| { | |||
| getUndoManager()->beginNewTransaction(); | |||
| DrawableComposite::ValueTreeWrapper root (getDocument().getRootDrawableNode()); | |||
| int index = 0; | |||
| for (int i = root.getNumDrawables(); --i >= 0;) | |||
| { | |||
| const Drawable::ValueTreeWrapperBase d (root.getDrawableState (index)); | |||
| if (getSelection().isSelected (d.getID())) | |||
| root.moveDrawableOrder (index, -1, getUndoManager()); | |||
| else | |||
| ++index; | |||
| } | |||
| getUndoManager()->beginNewTransaction(); | |||
| } | |||
| void DrawableEditor::selectionToBack() | |||
| { | |||
| getUndoManager()->beginNewTransaction(); | |||
| DrawableComposite::ValueTreeWrapper root (getDocument().getRootDrawableNode()); | |||
| int index = root.getNumDrawables() - 1; | |||
| for (int i = root.getNumDrawables(); --i >= 0;) | |||
| { | |||
| const Drawable::ValueTreeWrapperBase d (root.getDrawableState (index)); | |||
| if (getSelection().isSelected (d.getID())) | |||
| root.moveDrawableOrder (index, 0, getUndoManager()); | |||
| else | |||
| --index; | |||
| } | |||
| getUndoManager()->beginNewTransaction(); | |||
| } | |||
| void DrawableEditor::showNewShapeMenu (Component* componentToAttachTo) | |||
| @@ -130,7 +173,11 @@ void DrawableEditor::showNewShapeMenu (Component* componentToAttachTo) | |||
| PopupMenu m; | |||
| getDocument().addNewItemMenuItems (m); | |||
| const int r = m.showAt (componentToAttachTo); | |||
| getDocument().performNewItemMenuItem (r); | |||
| ValueTree newItem (getDocument().performNewItemMenuItem (r)); | |||
| if (newItem.isValid()) | |||
| getSelection().selectOnly (Drawable::ValueTreeWrapperBase (newItem).getID()); | |||
| } | |||
| //============================================================================== | |||
| @@ -213,13 +260,13 @@ bool DrawableEditor::perform (const InvocationInfo& info) | |||
| switch (info.commandID) | |||
| { | |||
| case CommandIDs::undo: | |||
| getDocument().getUndoManager()->beginNewTransaction(); | |||
| getDocument().getUndoManager()->undo(); | |||
| getUndoManager()->beginNewTransaction(); | |||
| getUndoManager()->undo(); | |||
| return true; | |||
| case CommandIDs::redo: | |||
| getDocument().getUndoManager()->beginNewTransaction(); | |||
| getDocument().getUndoManager()->redo(); | |||
| getUndoManager()->beginNewTransaction(); | |||
| getUndoManager()->redo(); | |||
| return true; | |||
| case CommandIDs::toFront: | |||
| @@ -59,6 +59,7 @@ public: | |||
| //============================================================================== | |||
| DrawableDocument& getDocument() const { return *drawableDocument; } | |||
| UndoManager* getUndoManager() const { return getDocument().getUndoManager(); } | |||
| EditorCanvasBase::SelectedItems& getSelection() { return selection; } | |||
| @@ -32,6 +32,7 @@ | |||
| //============================================================================== | |||
| class DrawableEditorCanvas : public EditorCanvasBase, | |||
| public FileDragAndDropTarget, | |||
| public Timer | |||
| { | |||
| public: | |||
| @@ -124,9 +125,7 @@ public: | |||
| } | |||
| else | |||
| { | |||
| getDocument().addNewItemMenuItems (m); | |||
| const int r = m.show(); | |||
| getDocument().performNewItemMenuItem (r); | |||
| editor.showNewShapeMenu (0); | |||
| } | |||
| } | |||
| @@ -210,6 +209,21 @@ public: | |||
| 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(); | |||
| @@ -221,9 +235,10 @@ public: | |||
| public: | |||
| ControlPointComponent (DrawableEditorCanvas* canvas, const ValueTree& drawableState_, int controlPointNum_) | |||
| : OverlayItemComponent (canvas), drawableState (drawableState_), | |||
| controlPointNum (controlPointNum_), isDragging (false), mouseDownResult (false), selected (false) | |||
| controlPointNum (controlPointNum_), isDragging (false), mouseDownResult (false), selected (false), | |||
| sizeNormal (7), sizeOver (11) | |||
| { | |||
| selectionId = getControlPointId (drawableState, controlPointNum); | |||
| setRepaintsOnMouseActivity (true); | |||
| } | |||
| ~ControlPointComponent() | |||
| @@ -232,11 +247,24 @@ public: | |||
| void paint (Graphics& g) | |||
| { | |||
| Rectangle<int> r (getLocalBounds()); | |||
| if (! isMouseOverOrDragging()) | |||
| r = r.reduced ((sizeOver - sizeNormal) / 2, (sizeOver - sizeNormal) / 2); | |||
| g.setColour (Colour (selected ? 0xaaaaaaaa : 0xaa333333)); | |||
| g.drawRect (0, 0, getWidth(), getHeight()); | |||
| g.drawRect (r); | |||
| g.setColour (Colour (selected ? 0xaa000000 : 0x99ffffff)); | |||
| g.fillRect (1, 1, getWidth() - 2, getHeight() - 2); | |||
| 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) | |||
| @@ -261,7 +289,7 @@ public: | |||
| isDragging = true; | |||
| canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), | |||
| ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); | |||
| ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre), false, Point<float>()); | |||
| } | |||
| if (isDragging) | |||
| @@ -273,9 +301,12 @@ public: | |||
| void mouseUp (const MouseEvent& e) | |||
| { | |||
| if (isDragging) | |||
| if (! e.mods.isPopupMenu()) | |||
| { | |||
| canvas->endDrag (e.getEventRelativeTo (getParentComponent())); | |||
| if (isDragging) | |||
| canvas->endDrag (e.getEventRelativeTo (getParentComponent())); | |||
| else | |||
| canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, false, mouseDownResult); | |||
| } | |||
| } | |||
| @@ -283,10 +314,50 @@ public: | |||
| { | |||
| } | |||
| class LineComponent : public OverlayItemComponent | |||
| { | |||
| public: | |||
| LineComponent (EditorCanvasBase* canvas) | |||
| : OverlayItemComponent (canvas) | |||
| {} | |||
| ~LineComponent() {} | |||
| void setLine (const Line<float>& newLine) | |||
| { | |||
| if (line != newLine) | |||
| { | |||
| line = newLine; | |||
| setBoundsInTargetSpace (Rectangle<float> (line.getStart(), line.getEnd()) | |||
| .getSmallestIntegerContainer().expanded (2, 2)); | |||
| repaint(); | |||
| } | |||
| } | |||
| void paint (Graphics& g) | |||
| { | |||
| g.setColour (Colours::black.withAlpha (0.6f)); | |||
| g.drawLine (Line<float> (pointToLocalSpace (line.getStart()), | |||
| pointToLocalSpace (line.getEnd())), 1.0f); | |||
| } | |||
| bool hitTest (int, int) | |||
| { | |||
| return false; | |||
| } | |||
| private: | |||
| Line<float> line; | |||
| }; | |||
| void updatePosition (ControlPoint& point, RelativeCoordinate::NamedCoordinateFinder* nameFinder) | |||
| { | |||
| selectionId = point.getID(); | |||
| const Point<float> p (point.getPosition().resolve (nameFinder)); | |||
| setBoundsInTargetSpace (Rectangle<int> (roundToInt (p.getX()) - 2, roundToInt (p.getY()) - 2, 7, 7)); | |||
| setBoundsInTargetSpace (Rectangle<int> (roundToInt (p.getX()) - sizeOver / 2, | |||
| roundToInt (p.getY()) - sizeOver / 2, | |||
| sizeOver, sizeOver)); | |||
| const bool nowSelected = canvas->getSelection().isSelected (selectionId); | |||
| @@ -295,6 +366,21 @@ public: | |||
| selected = nowSelected; | |||
| repaint(); | |||
| } | |||
| if (point.hasLine()) | |||
| { | |||
| if (line == 0) | |||
| { | |||
| line = new LineComponent (canvas); | |||
| getParentComponent()->addAndMakeVisible (line, 0); | |||
| } | |||
| line->setLine (Line<float> (p, point.getEndOfLine().resolve (nameFinder))); | |||
| } | |||
| else | |||
| { | |||
| line = 0; | |||
| } | |||
| } | |||
| private: | |||
| @@ -302,6 +388,8 @@ public: | |||
| int controlPointNum; | |||
| bool isDragging, mouseDownResult, selected; | |||
| String selectionId; | |||
| ScopedPointer <LineComponent> line; | |||
| const int sizeNormal, sizeOver; | |||
| }; | |||
| void updateControlPointComponents (Component* parent, OwnedArray<OverlayItemComponent>& comps) | |||
| @@ -314,7 +402,7 @@ public: | |||
| DrawableTypeInstance item (getDocument(), controlPointEditingTarget); | |||
| OwnedArray <ControlPoint> points; | |||
| item.getAllControlPoints (points); | |||
| item.getVisibleControlPoints (points, getSelection()); | |||
| Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID()); | |||
| DrawableComposite* parentDrawable = d->getParent(); | |||
| @@ -366,12 +454,31 @@ public: | |||
| if (drawable != 0) | |||
| { | |||
| for (int i = drawable->getNumDrawables(); --i >= 0;) | |||
| if (isControlPointMode()) | |||
| { | |||
| Drawable* d = drawable->getDrawable (i); | |||
| DrawableTypeInstance item (getDocument(), controlPointEditingTarget); | |||
| OwnedArray <ControlPoint> points; | |||
| item.getVisibleControlPoints (points, getSelection()); | |||
| const Rectangle<float> floatArea (area.toFloat()); | |||
| for (int i = 0; i < points.size(); ++i) | |||
| { | |||
| const Point<float> 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()); | |||
| if (d->getBounds().intersects (floatArea)) | |||
| itemsFound.add (d->getName()); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -381,20 +488,14 @@ public: | |||
| return itemId.containsChar ('/'); | |||
| } | |||
| static const String getControlPointId (const ValueTree& drawableState, int index) | |||
| { | |||
| return Drawable::ValueTreeWrapperBase (drawableState).getID() + "/" + String (index); | |||
| } | |||
| //============================================================================== | |||
| class ObjectDragOperation : public EditorDragOperation | |||
| { | |||
| public: | |||
| ObjectDragOperation (DrawableEditorCanvas* canvas_, | |||
| const MouseEvent& e, const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, | |||
| const ResizableBorderComponent::Zone& zone_) | |||
| : EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_), drawableCanvas (canvas_) | |||
| ObjectDragOperation (DrawableEditorCanvas* canvas_, const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, const ResizableBorderComponent::Zone& zone_, bool isRotating) | |||
| : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, isRotating), | |||
| drawableCanvas (canvas_) | |||
| { | |||
| } | |||
| @@ -423,6 +524,11 @@ public: | |||
| drawableCanvas->setObjectPositionFloat (state, newBounds); | |||
| } | |||
| void transformObject (ValueTree& state, const AffineTransform& transform) | |||
| { | |||
| drawableCanvas->transformObject (state, transform); | |||
| } | |||
| float getMarkerPosition (const ValueTree& marker, bool isX) | |||
| { | |||
| return 0; | |||
| @@ -438,14 +544,14 @@ public: | |||
| public: | |||
| ControlPointDragOperation (DrawableEditorCanvas* canvas_, | |||
| const DrawableTypeInstance& drawableItem_, | |||
| Drawable* drawable_, | |||
| const MouseEvent& e, const Point<int>& mousePos, | |||
| DrawableComposite* drawable_, | |||
| const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, | |||
| const ResizableBorderComponent::Zone& zone_) | |||
| : EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_), | |||
| : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, false), | |||
| drawableCanvas (canvas_), drawableItem (drawableItem_), drawable (drawable_) | |||
| { | |||
| drawableItem.getAllControlPoints (points); | |||
| drawableItem.getVisibleControlPoints (points, canvas_->getSelection()); | |||
| } | |||
| ~ControlPointDragOperation() {} | |||
| @@ -467,27 +573,31 @@ public: | |||
| const Rectangle<float> getObjectPosition (const ValueTree& state) | |||
| { | |||
| int index = state [Ids::id_].toString().fromFirstOccurrenceOf ("/", false, false).getIntValue(); | |||
| int index = state [Ids::id_]; | |||
| ControlPoint* cp = points[index]; | |||
| if (cp == 0) | |||
| return Rectangle<float>(); | |||
| Point<float> p (cp->getPosition().resolve (drawable->getParent())); | |||
| Point<float> p (cp->getPosition().resolve (drawable)); | |||
| return Rectangle<float> (p, p); | |||
| } | |||
| void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds) | |||
| { | |||
| int index = state [Ids::id_].toString().fromFirstOccurrenceOf ("/", false, false).getIntValue(); | |||
| int index = state [Ids::id_]; | |||
| ControlPoint* cp = points[index]; | |||
| if (cp != 0) | |||
| { | |||
| RelativePoint p (cp->getPosition()); | |||
| p.moveToAbsolute (newBounds.getPosition(), drawable->getParent()); | |||
| 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; | |||
| @@ -496,30 +606,30 @@ public: | |||
| private: | |||
| DrawableEditorCanvas* drawableCanvas; | |||
| DrawableTypeInstance drawableItem; | |||
| Drawable* drawable; | |||
| DrawableComposite* drawable; | |||
| }; | |||
| //============================================================================== | |||
| DragOperation* createDragOperation (const MouseEvent& e, Component* snapGuideParentComponent, | |||
| const ResizableBorderComponent::Zone& zone) | |||
| bool canRotate() const { return true; } | |||
| DragOperation* createDragOperation (const Point<int>& mouseDownPos, Component* snapGuideParentComponent, | |||
| const ResizableBorderComponent::Zone& zone, bool isRotating) | |||
| { | |||
| Array<ValueTree> selected, unselected; | |||
| EditorDragOperation* drag = 0; | |||
| if (isControlPointMode()) | |||
| { | |||
| Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID()); | |||
| DrawableTypeInstance item (getDocument(), controlPointEditingTarget); | |||
| ControlPointDragOperation* cpd = new ControlPointDragOperation (this, item, d, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
| ControlPointDragOperation* cpd = new ControlPointDragOperation (this, item, drawable, mouseDownPos, snapGuideParentComponent, zone); | |||
| drag = cpd; | |||
| for (int i = 0; i < cpd->points.size(); ++i) | |||
| { | |||
| const String pointId (getControlPointId (item.getState(), i)); | |||
| const String pointId (cpd->points.getUnchecked(i)->getID()); | |||
| ValueTree v (Ids::controlPoint); | |||
| v.setProperty (Ids::id_, pointId, 0); | |||
| v.setProperty (Ids::id_, i, 0); | |||
| if (editor.getSelection().isSelected (pointId)) | |||
| selected.add (v); | |||
| @@ -529,9 +639,8 @@ public: | |||
| } | |||
| else | |||
| { | |||
| drag = new ObjectDragOperation (this, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
| DrawableComposite::ValueTreeWrapper mainGroup (getDocument().getRootDrawableNode()); | |||
| drag = new ObjectDragOperation (this, mouseDownPos, snapGuideParentComponent, zone, isRotating); | |||
| for (int i = mainGroup.getNumDrawables(); --i >= 0;) | |||
| { | |||
| @@ -556,6 +665,35 @@ public: | |||
| 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<int> (x, y).toFloat()))); | |||
| if (newItem.isValid()) | |||
| getSelection().selectOnly (Drawable::ValueTreeWrapperBase (newItem).getID()); | |||
| } | |||
| else if (f.hasFileExtension ("jpg;jpeg;gif;png")) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class DrawableComponent : public Component | |||
| { | |||
| @@ -32,7 +32,8 @@ | |||
| */ | |||
| class DrawableTreeViewItem : public JucerTreeViewBase, | |||
| public ValueTree::Listener, | |||
| public ChangeListener | |||
| public ChangeListener, | |||
| public AsyncUpdater | |||
| { | |||
| public: | |||
| DrawableTreeViewItem (DrawableEditor& editor_, const ValueTree& drawableRoot) | |||
| @@ -51,18 +52,25 @@ public: | |||
| //============================================================================== | |||
| void valueTreePropertyChanged (ValueTree& tree, const Identifier& property) | |||
| { | |||
| if (property == Drawable::ValueTreeWrapperBase::idProperty) | |||
| repaintItem(); | |||
| } | |||
| void valueTreeChildrenChanged (ValueTree& tree) | |||
| { | |||
| if (tree == node.getState()) | |||
| refreshSubItems(); | |||
| if (tree == node.getState() || tree.isAChildOf (node.getState())) | |||
| triggerAsyncUpdate(); | |||
| } | |||
| void valueTreeParentChanged (ValueTree& tree) | |||
| { | |||
| } | |||
| void handleAsyncUpdate() | |||
| { | |||
| refreshSubItems(); | |||
| } | |||
| //============================================================================== | |||
| // TreeViewItem stuff.. | |||
| bool mightContainSubItems() | |||
| @@ -157,9 +165,14 @@ public: | |||
| return String::empty; | |||
| } | |||
| static const String getDragIdFor (DrawableEditor& editor) | |||
| { | |||
| return drawableItemDragType + editor.getDocument().getUniqueId(); | |||
| } | |||
| const String getDragSourceDescription() | |||
| { | |||
| return drawableItemDragType; | |||
| return getDragIdFor (editor); | |||
| } | |||
| //============================================================================== | |||
| @@ -175,11 +188,94 @@ public: | |||
| bool isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent) | |||
| { | |||
| return false; | |||
| return node.getState().getType() == DrawableComposite::valueTreeType | |||
| && sourceDescription == getDragIdFor (editor) | |||
| && editor.getSelection().getNumSelected() > 0; | |||
| } | |||
| void itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex) | |||
| { | |||
| if (editor.getSelection().getNumSelected() > 0) | |||
| { | |||
| TreeView* tree = getOwnerView(); | |||
| const ScopedPointer <XmlElement> oldOpenness (tree->getOpennessState (false)); | |||
| Array <ValueTree> selectedComps; | |||
| // scan the source tree rather than look at the selection manager, because it might | |||
| // be from a different editor, and the order needs to be correct. | |||
| getAllSelectedNodesInTree (sourceComponent, selectedComps); | |||
| insertItems (selectedComps, insertIndex); | |||
| if (oldOpenness != 0) | |||
| tree->restoreOpennessState (*oldOpenness); | |||
| } | |||
| } | |||
| static void getAllSelectedNodesInTree (Component* componentInTree, Array<ValueTree>& selectedItems) | |||
| { | |||
| TreeView* tree = dynamic_cast <TreeView*> (componentInTree); | |||
| if (tree == 0) | |||
| tree = componentInTree->findParentComponentOfClass ((TreeView*) 0); | |||
| if (tree != 0) | |||
| { | |||
| const int numSelected = tree->getNumSelectedItems(); | |||
| for (int i = 0; i < numSelected; ++i) | |||
| { | |||
| DrawableTreeViewItem* const item = dynamic_cast <DrawableTreeViewItem*> (tree->getSelectedItem (i)); | |||
| if (item != 0) | |||
| selectedItems.add (item->node.getState()); | |||
| } | |||
| } | |||
| } | |||
| void insertItems (Array <ValueTree>& items, int insertIndex) | |||
| { | |||
| DrawableComposite::ValueTreeWrapper composite (node.getState()); | |||
| int i; | |||
| for (i = items.size(); --i >= 0;) | |||
| if (node.getState() == items.getReference(i) || composite.getState().isAChildOf (items.getReference(i))) // Check for recursion. | |||
| return; | |||
| // Don't include any nodes that are children of other selected nodes.. | |||
| for (i = items.size(); --i >= 0;) | |||
| { | |||
| const ValueTree& n = items.getReference(i); | |||
| for (int j = items.size(); --j >= 0;) | |||
| { | |||
| if (j != i && n.isAChildOf (items.getReference(j))) | |||
| { | |||
| items.remove (i); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| // Remove and re-insert them one at a time.. | |||
| for (i = 0; i < items.size(); ++i) | |||
| { | |||
| ValueTree& n = items.getReference(i); | |||
| int index = composite.indexOfDrawable (n); | |||
| if (index >= 0 && index < insertIndex) | |||
| --insertIndex; | |||
| if (index >= 0) | |||
| { | |||
| composite.moveDrawableOrder (index, insertIndex++, editor.getDocument().getUndoManager()); | |||
| } | |||
| else | |||
| { | |||
| n.getParent().removeChild (n, editor.getDocument().getUndoManager()); | |||
| composite.addDrawable (n, insertIndex++, editor.getDocument().getUndoManager()); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| @@ -38,7 +38,9 @@ public: | |||
| objectState (objectState_), | |||
| objectId (objectId_), | |||
| borderThickness (4), | |||
| isDragging (false) | |||
| isDragging (false), | |||
| isRotating (false), | |||
| canRotate (canvas_->canRotate()) | |||
| { | |||
| jassert (objectState.isValid()); | |||
| } | |||
| @@ -49,8 +51,13 @@ public: | |||
| void paint (Graphics& g) | |||
| { | |||
| g.setColour (resizableBorderColour); | |||
| g.drawRect (0, 0, getWidth(), getHeight(), borderThickness); | |||
| if (! canvas->isRotating()) | |||
| { | |||
| g.setColour (resizableBorderColour); | |||
| g.drawRect (0, 0, getWidth(), getHeight(), borderThickness); | |||
| g.fillRect (rotateArea); | |||
| } | |||
| } | |||
| void mouseEnter (const MouseEvent& e) { updateDragZone (e.getPosition()); } | |||
| @@ -60,36 +67,47 @@ public: | |||
| void mouseDown (const MouseEvent& e) | |||
| { | |||
| updateDragZone (e.getPosition()); | |||
| isDragging = false; | |||
| if (e.mods.isPopupMenu()) | |||
| { | |||
| isDragging = false; | |||
| canvas->showPopupMenu (true); | |||
| } | |||
| else | |||
| { | |||
| isDragging = true; | |||
| canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), dragZone); | |||
| canvas->showSizeGuides(); | |||
| } | |||
| } | |||
| void mouseDrag (const MouseEvent& e) | |||
| { | |||
| if (! (isDragging || e.mods.isPopupMenu() || e.mouseWasClicked())) | |||
| { | |||
| isDragging = true; | |||
| bool isRotating = rotateArea.contains (e.getMouseDownPosition()); | |||
| canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()), | |||
| dragZone, isRotating, canvas->getObjectPosition (objectState).getCentre().toFloat()); | |||
| if (! isRotating) | |||
| canvas->showSizeGuides(); | |||
| repaint(); | |||
| } | |||
| if (isDragging) | |||
| { | |||
| canvas->continueDrag (e.getEventRelativeTo (getParentComponent())); | |||
| canvas->continueDrag (e); | |||
| autoScrollForMouseEvent (e); | |||
| } | |||
| } | |||
| void mouseUp (const MouseEvent& e) | |||
| { | |||
| if (isDragging) | |||
| if (isDragging || isRotating) | |||
| { | |||
| isRotating = false; | |||
| canvas->hideSizeGuides(); | |||
| canvas->endDrag (e.getEventRelativeTo (getParentComponent())); | |||
| canvas->endDrag (e); | |||
| updateDragZone (e.getPosition()); | |||
| repaint(); | |||
| } | |||
| } | |||
| @@ -101,7 +119,7 @@ public: | |||
| bool hitTest (int x, int y) | |||
| { | |||
| if (ModifierKeys::getCurrentModifiers().isAnyModifierKeyDown()) | |||
| return ! getCentreArea().contains (x, y); | |||
| return rotateArea.contains (x, y) || ! getCentreArea().contains (x, y); | |||
| return true; | |||
| } | |||
| @@ -114,6 +132,9 @@ public: | |||
| const Rectangle<int> bounds (canvas->getObjectPosition (objectState)); | |||
| setBoundsInTargetSpace (bounds.expanded (borderThickness, borderThickness)); | |||
| if (canRotate) | |||
| rotateArea = Rectangle<int> (2, 2, 10, 10); | |||
| int i; | |||
| for (i = sizeGuides.size(); --i >= 0;) | |||
| { | |||
| @@ -126,7 +147,6 @@ public: | |||
| const String& getTargetObjectID() const { return objectId; } | |||
| //============================================================================== | |||
| class SizeGuideComponent : public OverlayItemComponent, | |||
| public ComponentListener | |||
| @@ -200,7 +220,8 @@ private: | |||
| ResizableBorderComponent::Zone dragZone; | |||
| const int borderThickness; | |||
| OwnedArray <SizeGuideComponent> sizeGuides; | |||
| bool isDragging; | |||
| Rectangle<int> rotateArea; | |||
| bool isDragging, canRotate, isRotating; | |||
| const Rectangle<int> getCentreArea() const | |||
| { | |||
| @@ -467,7 +488,9 @@ public: | |||
| isDraggingClickedComp = true; | |||
| canvas->enableResizingMode(); | |||
| getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult); | |||
| canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()), ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); | |||
| canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()), | |||
| ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre), | |||
| false, Point<float>()); | |||
| } | |||
| if (isDraggingClickedComp) | |||
| @@ -843,6 +866,16 @@ const Point<int> EditorCanvasBase::objectSpaceToScreenSpace (const Point<int>& p | |||
| return p + origin; | |||
| } | |||
| const Point<float> EditorCanvasBase::screenSpaceToObjectSpace (const Point<float>& p) const | |||
| { | |||
| return p - origin.toFloat(); | |||
| } | |||
| const Point<float> EditorCanvasBase::objectSpaceToScreenSpace (const Point<float>& p) const | |||
| { | |||
| return p + origin.toFloat(); | |||
| } | |||
| const Rectangle<int> EditorCanvasBase::screenSpaceToObjectSpace (const Rectangle<int>& r) const | |||
| { | |||
| return r - origin; | |||
| @@ -868,6 +901,11 @@ void EditorCanvasBase::enableControlPointMode (const ValueTree& objectToEdit) | |||
| } | |||
| } | |||
| bool EditorCanvasBase::isRotating() const | |||
| { | |||
| return dragger != 0 && dragger->isRotating(); | |||
| } | |||
| //============================================================================== | |||
| void EditorCanvasBase::paint (Graphics& g) | |||
| { | |||
| @@ -927,6 +965,9 @@ void EditorCanvasBase::handleAsyncUpdate() | |||
| const Rectangle<int> canvasBounds (getCanvasBounds()); | |||
| const Point<int> newOrigin (jmax (0, -canvasBounds.getX()), jmax (0, -canvasBounds.getY())); | |||
| const int newWidth = jmax (canvasBounds.getWidth(), canvasBounds.getRight()) + border.getLeftAndRight(); | |||
| const int newHeight = jmax (canvasBounds.getHeight(), canvasBounds.getBottom()) + border.getTopAndBottom(); | |||
| if (origin != newOrigin) | |||
| { | |||
| repaint(); | |||
| @@ -936,16 +977,16 @@ void EditorCanvasBase::handleAsyncUpdate() | |||
| setBounds (jmin (0, getX() + oldOrigin.getX() - origin.getX()), | |||
| jmin (0, getY() + oldOrigin.getY() - origin.getY()), | |||
| jmax (canvasBounds.getWidth(), canvasBounds.getRight()) + border.getLeftAndRight(), | |||
| jmax (canvasBounds.getHeight(), canvasBounds.getBottom()) + border.getTopAndBottom()); | |||
| newWidth, newHeight); | |||
| } | |||
| else if (getWidth() != newWidth || getHeight() != newHeight) | |||
| { | |||
| setSize (newWidth, newHeight); | |||
| } | |||
| else | |||
| { | |||
| setSize (jmax (canvasBounds.getWidth(), canvasBounds.getRight()) + border.getLeftAndRight(), | |||
| jmax (canvasBounds.getHeight(), canvasBounds.getBottom()) + border.getTopAndBottom()); | |||
| overlay->update(); | |||
| } | |||
| overlay->update(); | |||
| } | |||
| void EditorCanvasBase::resized() | |||
| @@ -954,6 +995,7 @@ void EditorCanvasBase::resized() | |||
| overlay->setBounds (getLocalBounds()); | |||
| resizeFrame->setBounds (getLocalBounds()); | |||
| overlay->update(); | |||
| handleUpdateNowIfNeeded(); | |||
| } | |||
| //============================================================================== | |||
| @@ -962,25 +1004,33 @@ void EditorCanvasBase::hideSizeGuides() { overlay->hideSizeGuides(); } | |||
| //============================================================================== | |||
| void EditorCanvasBase::beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone) | |||
| void EditorCanvasBase::beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone, | |||
| bool isRotating, const Point<float>& rotationCentre) | |||
| { | |||
| dragger = createDragOperation (e, overlay, zone); | |||
| dragger = createDragOperation (e.getEventRelativeTo (overlay).getPosition() - origin, overlay, zone, isRotating); | |||
| dragger->setRotationCentre (rotationCentre); | |||
| repaint(); | |||
| } | |||
| void EditorCanvasBase::continueDrag (const MouseEvent& e) | |||
| { | |||
| MouseEvent e2 (e.getEventRelativeTo (overlay)); | |||
| if (dragger != 0) | |||
| dragger->drag (e, e.getPosition() - origin); | |||
| dragger->drag (e2, e2.getPosition() - origin); | |||
| } | |||
| void EditorCanvasBase::endDrag (const MouseEvent& e) | |||
| { | |||
| if (dragger != 0) | |||
| { | |||
| dragger->drag (e, e.getPosition() - origin); | |||
| MouseEvent e2 (e.getEventRelativeTo (overlay)); | |||
| dragger->drag (e2, e2.getPosition() - origin); | |||
| dragger = 0; | |||
| getUndoManager().beginNewTransaction(); | |||
| repaint(); | |||
| } | |||
| } | |||
| @@ -999,3 +1049,10 @@ void EditorCanvasBase::OverlayItemComponent::setBoundsInTargetSpace (const Recta | |||
| setBounds (canvas->objectSpaceToScreenSpace (r) | |||
| + canvas->getComponentHolder()->relativePositionToOtherComponent (getParentComponent(), Point<int>())); | |||
| } | |||
| const Point<float> EditorCanvasBase::OverlayItemComponent::pointToLocalSpace (const Point<float>& p) const | |||
| { | |||
| return canvas->objectSpaceToScreenSpace (p) | |||
| + (canvas->getComponentHolder()->relativePositionToOtherComponent (getParentComponent(), Point<int>()) | |||
| - getPosition()).toFloat(); | |||
| } | |||
| @@ -96,13 +96,19 @@ public: | |||
| virtual ~DragOperation() {} | |||
| virtual void drag (const MouseEvent& e, const Point<int>& newPos) = 0; | |||
| virtual void setRotationCentre (const Point<float>& rotationCentre) = 0; | |||
| virtual bool isRotating() const = 0; | |||
| }; | |||
| virtual DragOperation* createDragOperation (const MouseEvent& e, | |||
| virtual DragOperation* createDragOperation (const Point<int>& mouseDownPos, | |||
| Component* snapGuideParentComponent, | |||
| const ResizableBorderComponent::Zone& zone) = 0; | |||
| const ResizableBorderComponent::Zone& zone, | |||
| bool isRotating) = 0; | |||
| void beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone); | |||
| virtual bool canRotate() const = 0; | |||
| void beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone, | |||
| bool isRotating, const Point<float>& rotationCentre); | |||
| void continueDrag (const MouseEvent& e); | |||
| void endDrag (const MouseEvent& e); | |||
| @@ -111,6 +117,7 @@ public: | |||
| bool isResizingMode() const { return ! isControlPointMode(); } | |||
| bool isControlPointMode() const { return controlPointEditingTarget.isValid(); } | |||
| bool isRotating() const; | |||
| //============================================================================== | |||
| Component* getComponentHolder() const { return componentHolder; } | |||
| @@ -118,7 +125,9 @@ public: | |||
| const Point<int>& getOrigin() const throw() { return origin; } | |||
| const Point<int> screenSpaceToObjectSpace (const Point<int>& p) const; | |||
| const Point<float> screenSpaceToObjectSpace (const Point<float>& p) const; | |||
| const Point<int> objectSpaceToScreenSpace (const Point<int>& p) const; | |||
| const Point<float> objectSpaceToScreenSpace (const Point<float>& p) const; | |||
| const Rectangle<int> screenSpaceToObjectSpace (const Rectangle<int>& r) const; | |||
| const Rectangle<int> objectSpaceToScreenSpace (const Rectangle<int>& r) const; | |||
| @@ -130,6 +139,7 @@ public: | |||
| ~OverlayItemComponent(); | |||
| void setBoundsInTargetSpace (const Rectangle<int>& r); | |||
| const Point<float> pointToLocalSpace (const Point<float>& p) const; | |||
| protected: | |||
| EditorCanvasBase* canvas; | |||
| @@ -33,13 +33,14 @@ | |||
| class EditorDragOperation : public EditorCanvasBase::DragOperation | |||
| { | |||
| public: | |||
| EditorDragOperation (EditorCanvasBase* canvas_, const MouseEvent& e, const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, | |||
| const ResizableBorderComponent::Zone& zone_) | |||
| EditorDragOperation (EditorCanvasBase* canvas_, const Point<int>& mousePos, | |||
| Component* snapGuideParentComp_, const ResizableBorderComponent::Zone& zone_, | |||
| bool rotating_) | |||
| : canvas (canvas_), | |||
| snapGuideParentComp (snapGuideParentComp_), | |||
| zone (zone_), | |||
| mouseDownPos (mousePos) | |||
| mouseDownPos (mousePos), | |||
| rotating (rotating_) | |||
| { | |||
| } | |||
| @@ -141,6 +142,16 @@ public: | |||
| getUndoManager().beginNewTransaction(); | |||
| } | |||
| void setRotationCentre (const Point<float>& rotationCentre) | |||
| { | |||
| centre = rotationCentre; | |||
| } | |||
| bool isRotating() const | |||
| { | |||
| return rotating; | |||
| } | |||
| //============================================================================== | |||
| struct SnapLine | |||
| { | |||
| @@ -195,31 +206,35 @@ public: | |||
| { | |||
| getUndoManager().undoCurrentTransactionOnly(); | |||
| // (can't use getOffsetFromDragStart() because of auto-scrolling) | |||
| Point<int> distance (newPos - mouseDownPos); | |||
| if (! isDraggingLeftRight()) | |||
| distance = distance.withX (0); | |||
| if (rotating) | |||
| { | |||
| const float angle = centre.getAngleToPoint (mouseDownPos.toFloat()) - centre.getAngleToPoint (newPos.toFloat()); | |||
| const AffineTransform transform (AffineTransform::rotation (angle, centre.getX(), centre.getY())); | |||
| if (! isDraggingUpDown()) | |||
| distance = distance.withY (0); | |||
| for (int i = 0; i < updateList.size(); ++i) | |||
| transformObject (updateList.getReference(i), transform); | |||
| } | |||
| else | |||
| { | |||
| // (can't use getOffsetFromDragStart() because of auto-scrolling) | |||
| Point<int> distance (newPos - mouseDownPos); | |||
| if (! isDraggingLeftRight()) | |||
| distance = distance.withX (0); | |||
| snapGuides.clear(); | |||
| if (! isDraggingUpDown()) | |||
| distance = distance.withY (0); | |||
| if (canvas->getPanel()->isSnappingEnabled() != (e.mods.isCommandDown() || e.mods.isCtrlDown())) | |||
| { | |||
| performSnap (verticalSnapTargets, getVerticalSnapPositions (distance), true, distance); | |||
| performSnap (horizontalSnapTargets, getHorizontalSnapPositions (distance), false, distance); | |||
| } | |||
| snapGuides.clear(); | |||
| for (int i = 0; i < updateList.size(); ++i) | |||
| dragItem (updateList.getReference(i), distance, originalPositions.getReference(i)); | |||
| } | |||
| if (canvas->getPanel()->isSnappingEnabled() != (e.mods.isCommandDown() || e.mods.isCtrlDown())) | |||
| { | |||
| performSnap (verticalSnapTargets, getVerticalSnapPositions (distance), true, distance); | |||
| performSnap (horizontalSnapTargets, getHorizontalSnapPositions (distance), false, distance); | |||
| } | |||
| void dragItem (ValueTree& v, const Point<int>& distance, const Rectangle<float>& originalPos) | |||
| { | |||
| const Rectangle<float> newBounds (zone.resizeRectangleBy (originalPos, Point<float> ((float) distance.getX(), | |||
| (float) distance.getY()))); | |||
| setObjectPosition (v, newBounds); | |||
| for (int i = 0; i < updateList.size(); ++i) | |||
| dragItem (updateList.getReference(i), distance, originalPositions.getReference(i)); | |||
| } | |||
| } | |||
| protected: | |||
| @@ -231,6 +246,7 @@ protected: | |||
| virtual void getObjectDependencies (const ValueTree& state, Array<ValueTree>& deps) = 0; | |||
| virtual const Rectangle<float> getObjectPosition (const ValueTree& state) = 0; | |||
| virtual void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds) = 0; | |||
| virtual void transformObject (ValueTree& state, const AffineTransform& transform) = 0; | |||
| virtual UndoManager& getUndoManager() = 0; | |||
| @@ -246,6 +262,15 @@ private: | |||
| OwnedArray<Component> snapGuides; | |||
| Component* snapGuideParentComp; | |||
| Point<int> mouseDownPos; | |||
| Point<float> centre; | |||
| bool rotating; | |||
| void dragItem (ValueTree& v, const Point<int>& distance, const Rectangle<float>& originalPos) | |||
| { | |||
| const Rectangle<float> newBounds (zone.resizeRectangleBy (originalPos, Point<float> ((float) distance.getX(), | |||
| (float) distance.getY()))); | |||
| setObjectPosition (v, newBounds); | |||
| } | |||
| void getCompleteDependencyList (const ValueTree& object, Array <ValueTree>& deps, const Array<ValueTree>& activeObjects) | |||
| { | |||
| @@ -31,44 +31,41 @@ | |||
| ItemPreviewComponent::ItemPreviewComponent (const File& file_) | |||
| : file (file_) | |||
| { | |||
| facts.add (file.getFullPathName()); | |||
| tryToLoadImage (file.createInputStream()); | |||
| facts.removeEmptyStrings (true); | |||
| } | |||
| ItemPreviewComponent::ItemPreviewComponent (InputStream* input, const String& name) | |||
| { | |||
| facts.add (name); | |||
| tryToLoadImage (input); | |||
| facts.removeEmptyStrings (true); | |||
| tryToLoadImage(); | |||
| } | |||
| ItemPreviewComponent::~ItemPreviewComponent() | |||
| { | |||
| } | |||
| void ItemPreviewComponent::tryToLoadImage (InputStream* in) | |||
| void ItemPreviewComponent::tryToLoadImage() | |||
| { | |||
| if (in != 0) | |||
| { | |||
| ScopedPointer <InputStream> input (in); | |||
| facts.clear(); | |||
| facts.add (file.getFullPathName()); | |||
| image = Image(); | |||
| ScopedPointer <InputStream> input (file.createInputStream()); | |||
| if (input != 0) | |||
| { | |||
| const int64 totalSize = input->getTotalLength(); | |||
| ImageFileFormat* format = ImageFileFormat::findImageFormatForStream (*input); | |||
| input = 0; | |||
| String formatName; | |||
| if (format != 0) | |||
| formatName = " " + format->getFormatName(); | |||
| image = ImageFileFormat::loadFrom (*input); | |||
| image = ImageCache::getFromFile (file); | |||
| if (image.isValid()) | |||
| facts.add (String (image.getWidth()) + " x " + String (image.getHeight()) + formatName); | |||
| const int64 totalSize = input->getTotalLength(); | |||
| if (totalSize > 0) | |||
| facts.add (File::descriptionOfSizeInBytes (totalSize)); | |||
| } | |||
| facts.removeEmptyStrings (true); | |||
| } | |||
| void ItemPreviewComponent::paint (Graphics& g) | |||
| @@ -35,7 +35,6 @@ class ItemPreviewComponent : public Component | |||
| public: | |||
| //============================================================================== | |||
| // This will delete the stream | |||
| ItemPreviewComponent (InputStream* input, const String& name); | |||
| ItemPreviewComponent (const File& file); | |||
| ~ItemPreviewComponent(); | |||
| @@ -50,7 +49,7 @@ private: | |||
| File file; | |||
| Image image; | |||
| void tryToLoadImage (InputStream* input); | |||
| void tryToLoadImage(); | |||
| }; | |||
| @@ -26,19 +26,25 @@ | |||
| #ifndef __JUCER_FILLTYPEPROPERTYCOMPONENT_H_88CF1300__ | |||
| #define __JUCER_FILLTYPEPROPERTYCOMPONENT_H_88CF1300__ | |||
| #include "../model/Project/jucer_Project.h" | |||
| class FillTypeEditorComponent; | |||
| //============================================================================== | |||
| class PopupFillSelector : public Component, | |||
| public ChangeListener, | |||
| public ValueTree::Listener, | |||
| public ButtonListener | |||
| public ButtonListener, | |||
| public AsyncUpdater | |||
| { | |||
| public: | |||
| PopupFillSelector (const ValueTree& fillState_, const ColourGradient& defaultGradient_, UndoManager* undoManager_) | |||
| PopupFillSelector (const ValueTree& fillState_, const ColourGradient& defaultGradient_, | |||
| Drawable::ImageProvider* imageProvider_, Project* project, UndoManager* undoManager_) | |||
| : gradientPicker (defaultGradient_), | |||
| defaultGradient (defaultGradient_), | |||
| tilePicker (imageProvider_, project), | |||
| fillState (fillState_), | |||
| imageProvider (imageProvider_), | |||
| undoManager (undoManager_) | |||
| { | |||
| colourButton.setButtonText ("Colour"); | |||
| @@ -60,6 +66,9 @@ public: | |||
| addChildComponent (&gradientPicker); | |||
| gradientPicker.addChangeListener (this); | |||
| addChildComponent (&tilePicker); | |||
| tilePicker.addChangeListener (this); | |||
| fillState.addListener (this); | |||
| colourButton.setRadioGroupId (123); | |||
| @@ -90,6 +99,7 @@ public: | |||
| const Rectangle<int> content (2, y + h + 4, getWidth() - 4, getHeight() - (y + h + 6)); | |||
| colourPicker.setBounds (content); | |||
| gradientPicker.setBounds (content); | |||
| tilePicker.setBounds (content); | |||
| } | |||
| void buttonClicked (Button* b) | |||
| @@ -109,7 +119,7 @@ public: | |||
| // Use a cunning trick to make the wrapper dig out the earlier gradient settings, if there are any.. | |||
| FillType newFill (defaultGradient); | |||
| ValueTree temp ("dummy"); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (temp, newFill, 0, 0, 0); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (temp, newFill, 0, 0, 0, 0); | |||
| fillState.setProperty (Drawable::ValueTreeWrapperBase::type, temp [Drawable::ValueTreeWrapperBase::type], undoManager); | |||
| newFill = readFillType (&gp1, &gp2); | |||
| @@ -117,11 +127,11 @@ public: | |||
| if (newFill.gradient->getNumColours() <= 1) | |||
| { | |||
| newFill = FillType (defaultGradient); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, undoManager); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, imageProvider, undoManager); | |||
| } | |||
| else | |||
| { | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, &gp1, &gp2, undoManager); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, &gp1, &gp2, imageProvider, undoManager); | |||
| } | |||
| refresh(); | |||
| @@ -137,7 +147,7 @@ public: | |||
| const FillType readFillType (RelativePoint* gp1, RelativePoint* gp2) const | |||
| { | |||
| return Drawable::ValueTreeWrapperBase::readFillType (fillState, gp1, gp2, 0); | |||
| return Drawable::ValueTreeWrapperBase::readFillType (fillState, gp1, gp2, 0, imageProvider); | |||
| } | |||
| void setFillType (const FillType& newFill) | |||
| @@ -150,7 +160,7 @@ public: | |||
| if (undoManager != 0) | |||
| undoManager->undoCurrentTransactionOnly(); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, &gp1, &gp2, undoManager); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, &gp1, &gp2, imageProvider, undoManager); | |||
| refresh(); | |||
| } | |||
| } | |||
| @@ -163,6 +173,8 @@ public: | |||
| setFillType (colourPicker.getCurrentColour()); | |||
| else if (currentFill.isGradient()) | |||
| setFillType (gradientPicker.getGradient()); | |||
| else if (currentFill.isTiledImage()) | |||
| setFillType (tilePicker.getFill()); | |||
| } | |||
| void refresh() | |||
| @@ -171,6 +183,7 @@ public: | |||
| colourPicker.setVisible (newFill.isColour()); | |||
| gradientPicker.setVisible (newFill.isGradient()); | |||
| tilePicker.setVisible (newFill.isTiledImage()); | |||
| if (newFill.isColour()) | |||
| { | |||
| @@ -182,7 +195,7 @@ public: | |||
| if (newFill.gradient->getNumColours() <= 1) | |||
| { | |||
| newFill = FillType (defaultGradient); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, undoManager); | |||
| Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, imageProvider, undoManager); | |||
| } | |||
| gradientButton.setToggleState (true, false); | |||
| @@ -190,12 +203,14 @@ public: | |||
| } | |||
| else | |||
| { | |||
| tilePicker.setFill (newFill); | |||
| imageButton.setToggleState (true, false); | |||
| } | |||
| } | |||
| void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { refresh(); } | |||
| void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) { refresh(); } | |||
| void handleAsyncUpdate() { refresh(); } | |||
| void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { triggerAsyncUpdate(); } | |||
| void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) { triggerAsyncUpdate(); } | |||
| void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) {} | |||
| private: | |||
| @@ -433,12 +448,124 @@ private: | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class TiledFillDesigner : public Component, | |||
| public ChangeBroadcaster, | |||
| public ComboBoxListener, | |||
| public SliderListener | |||
| { | |||
| public: | |||
| TiledFillDesigner (Drawable::ImageProvider* imageProvider_, Project* project_) | |||
| : imageProvider (imageProvider_), project (project_) | |||
| { | |||
| addAndMakeVisible (&imageBox); | |||
| addAndMakeVisible (&opacitySlider); | |||
| opacitySlider.setRange (0.0, 1.0, 0.001); | |||
| sliderLabel.setText ("Opacity:", false); | |||
| sliderLabel.attachToComponent (&opacitySlider, false); | |||
| OwnedArray<Project::Item> images; | |||
| project->findAllImageItems (images); | |||
| for (int i = 0; i < images.size(); ++i) | |||
| imageBox.addItem (images.getUnchecked(i)->getName().toString(), i + 1); | |||
| imageBox.setTextWhenNothingSelected ("Select an image..."); | |||
| opacitySlider.addListener (this); | |||
| imageBox.addListener (this); | |||
| } | |||
| ~TiledFillDesigner() | |||
| { | |||
| } | |||
| const FillType getFill() const | |||
| { | |||
| return fill; | |||
| } | |||
| void setFill (const FillType& newFill) | |||
| { | |||
| if (fill != newFill) | |||
| { | |||
| fill = newFill; | |||
| OwnedArray<Project::Item> images; | |||
| project->findAllImageItems (images); | |||
| const String currentID (imageProvider->getIdentifierForImage (fill.image).toString()); | |||
| int idToSelect = -1; | |||
| for (int i = 0; i < images.size(); ++i) | |||
| { | |||
| if (images.getUnchecked(i)->getImageFileID() == currentID) | |||
| { | |||
| idToSelect = i + 1; | |||
| break; | |||
| } | |||
| } | |||
| imageBox.setSelectedId (idToSelect, true); | |||
| opacitySlider.setValue (fill.getOpacity(), false, false); | |||
| } | |||
| } | |||
| void resized() | |||
| { | |||
| imageBox.setBounds (20, 10, getWidth() - 40, 22); | |||
| opacitySlider.setBounds (20, 60, getWidth() - 40, 22); | |||
| } | |||
| void sliderValueChanged (Slider* slider) | |||
| { | |||
| if (opacitySlider.getValue() != fill.getOpacity()) | |||
| { | |||
| FillType f (fill); | |||
| f.setOpacity ((float) opacitySlider.getValue()); | |||
| setFill (f); | |||
| sendChangeMessage (this); | |||
| } | |||
| } | |||
| void comboBoxChanged (ComboBox* comboBoxThatHasChanged) | |||
| { | |||
| OwnedArray<Project::Item> images; | |||
| project->findAllImageItems (images); | |||
| Project::Item* item = images [imageBox.getSelectedId() - 1]; | |||
| if (item != 0) | |||
| { | |||
| Image im (imageProvider->getImageForIdentifier (item->getImageFileID())); | |||
| if (im.isValid() && im != fill.image) | |||
| { | |||
| FillType f (fill); | |||
| f.image = im; | |||
| setFill (f); | |||
| sendChangeMessage (this); | |||
| } | |||
| } | |||
| } | |||
| private: | |||
| FillType fill; | |||
| Drawable::ImageProvider* imageProvider; | |||
| Project* project; | |||
| ComboBox imageBox; | |||
| Slider opacitySlider; | |||
| Label sliderLabel; | |||
| }; | |||
| //============================================================================== | |||
| FillTypeEditorComponent* owner; | |||
| StoredSettings::ColourSelectorWithSwatches colourPicker; | |||
| GradientDesigner gradientPicker; | |||
| TiledFillDesigner tilePicker; | |||
| ColourGradient defaultGradient; | |||
| ValueTree fillState; | |||
| Drawable::ImageProvider* imageProvider; | |||
| UndoManager* undoManager; | |||
| TextButton colourButton, gradientButton, imageButton; | |||
| @@ -454,8 +581,10 @@ class FillTypeEditorComponent : public Component, | |||
| public ValueTree::Listener | |||
| { | |||
| public: | |||
| FillTypeEditorComponent (const ValueTree& fillState_, UndoManager* undoManager_) | |||
| : fillState (fillState_), undoManager (undoManager_) | |||
| FillTypeEditorComponent (const ValueTree& fillState_, Drawable::ImageProvider* imageProvider_, | |||
| Project* project_, UndoManager* undoManager_) | |||
| : fillState (fillState_), undoManager (undoManager_), | |||
| imageProvider (imageProvider_), project (project_) | |||
| { | |||
| fillState.addListener (this); | |||
| refresh(); | |||
| @@ -498,7 +627,7 @@ public: | |||
| void refresh() | |||
| { | |||
| const FillType newFill (Drawable::ValueTreeWrapperBase::readFillType (fillState, 0, 0, 0)); | |||
| const FillType newFill (Drawable::ValueTreeWrapperBase::readFillType (fillState, 0, 0, 0, imageProvider)); | |||
| if (newFill != fillType) | |||
| { | |||
| @@ -511,7 +640,7 @@ public: | |||
| { | |||
| undoManager->beginNewTransaction(); | |||
| PopupFillSelector popup (fillState, getDefaultGradient(), undoManager); | |||
| PopupFillSelector popup (fillState, getDefaultGradient(), imageProvider, project, undoManager); | |||
| PopupMenu m; | |||
| m.addCustomItem (1234, &popup, 300, 450, false); | |||
| @@ -526,7 +655,9 @@ public: | |||
| private: | |||
| ValueTree fillState; | |||
| Drawable::ImageProvider* imageProvider; | |||
| UndoManager* undoManager; | |||
| Project* project; | |||
| FillType fillType; | |||
| }; | |||
| @@ -536,9 +667,10 @@ class FillTypePropertyComponent : public PropertyComponent | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| FillTypePropertyComponent (UndoManager* undoManager, const String& name, const ValueTree& fill) | |||
| FillTypePropertyComponent (UndoManager* undoManager, const String& name, const ValueTree& fill, | |||
| Drawable::ImageProvider* imageProvider, Project* project) | |||
| : PropertyComponent (name), | |||
| editor (fill, undoManager) | |||
| editor (fill, imageProvider, project, undoManager) | |||
| { | |||
| jassert (fill.isValid()); | |||
| addAndMakeVisible (&editor); | |||