@@ -47,6 +47,7 @@ OBJECTS := \ | |||
$(OBJDIR)/jucer_ComponentDocument.o \ | |||
$(OBJDIR)/jucer_ComponentTypeManager.o \ | |||
$(OBJDIR)/jucer_DrawableDocument.o \ | |||
$(OBJDIR)/jucer_DrawableTypeHandler.o \ | |||
$(OBJDIR)/jucer_NewFileWizard.o \ | |||
$(OBJDIR)/jucer_Project.o \ | |||
$(OBJDIR)/jucer_ProjectExporter.o \ | |||
@@ -113,6 +114,11 @@ $(OBJDIR)/jucer_DrawableDocument.o: ../../Source/model/Drawable/jucer_DrawableDo | |||
@echo "Compiling jucer_DrawableDocument.cpp" | |||
@$(CXX) $(CXXFLAGS) -o "$@" -c "$<" | |||
$(OBJDIR)/jucer_DrawableTypeHandler.o: ../../Source/model/Drawable/jucer_DrawableTypeHandler.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling jucer_DrawableTypeHandler.cpp" | |||
@$(CXX) $(CXXFLAGS) -o "$@" -c "$<" | |||
$(OBJDIR)/jucer_NewFileWizard.o: ../../Source/model/Project/jucer_NewFileWizard.cpp | |||
-@mkdir -p $(OBJDIR) | |||
@echo "Compiling jucer_NewFileWizard.cpp" | |||
@@ -21,6 +21,7 @@ | |||
061C981D0E9FB70DE5A3D32C = { isa = PBXBuildFile; fileRef = 829C5DA65FB46B99756428C5; }; | |||
FEDBCD721085272D356BBAEB = { isa = PBXBuildFile; fileRef = D08D9DB99315F76093CF6EE6; }; | |||
268807D7091702D29033CC27 = { isa = PBXBuildFile; fileRef = B1471E8698D193FBCF0DD13D; }; | |||
91CCD0421DDD432C1C4903D4 = { isa = PBXBuildFile; fileRef = 330A51CC68AED92F7F5BEC15; }; | |||
06838545183964D8C1E60530 = { isa = PBXBuildFile; fileRef = 9DCB32BBD7053ACCB598CE79; }; | |||
048D33BDE7413EFE05D09901 = { isa = PBXBuildFile; fileRef = 4179D4C7BF616A4A3C3E11CA; }; | |||
9DA1913A62297D7111E0A6C9 = { isa = PBXBuildFile; fileRef = 48A4236550741B9D05208C60; }; | |||
@@ -84,6 +85,7 @@ | |||
E894E1F6D582678EE1F02763 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_Viewport.h; path = ../../Source/model/Component/Types/jucer_Viewport.h; sourceTree = SOURCE_ROOT; }; | |||
B1471E8698D193FBCF0DD13D = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = jucer_DrawableDocument.cpp; path = ../../Source/model/Drawable/jucer_DrawableDocument.cpp; sourceTree = SOURCE_ROOT; }; | |||
739F94CA6213B43D867AB0FD = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_DrawableDocument.h; path = ../../Source/model/Drawable/jucer_DrawableDocument.h; sourceTree = SOURCE_ROOT; }; | |||
330A51CC68AED92F7F5BEC15 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = jucer_DrawableTypeHandler.cpp; path = ../../Source/model/Drawable/jucer_DrawableTypeHandler.cpp; sourceTree = SOURCE_ROOT; }; | |||
BDE8CD9273E1B0D0E500D283 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_DrawableTypeHandler.h; path = ../../Source/model/Drawable/jucer_DrawableTypeHandler.h; sourceTree = SOURCE_ROOT; }; | |||
9DCB32BBD7053ACCB598CE79 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = jucer_NewFileWizard.cpp; path = ../../Source/model/Project/jucer_NewFileWizard.cpp; sourceTree = SOURCE_ROOT; }; | |||
201DF0B7B4AA3E03B1AA5144 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_NewFileWizard.h; path = ../../Source/model/Project/jucer_NewFileWizard.h; sourceTree = SOURCE_ROOT; }; | |||
@@ -204,6 +206,7 @@ | |||
E70E3BE796E91EA86CFE10FE = { isa = PBXGroup; children = ( | |||
B1471E8698D193FBCF0DD13D, | |||
739F94CA6213B43D867AB0FD, | |||
330A51CC68AED92F7F5BEC15, | |||
BDE8CD9273E1B0D0E500D283 ); name = Drawable; sourceTree = "<group>"; }; | |||
ADA17383E5554BCDF567AACC = { isa = PBXGroup; children = ( | |||
9DCB32BBD7053ACCB598CE79, | |||
@@ -416,6 +419,7 @@ | |||
061C981D0E9FB70DE5A3D32C, | |||
FEDBCD721085272D356BBAEB, | |||
268807D7091702D29033CC27, | |||
91CCD0421DDD432C1C4903D4, | |||
06838545183964D8C1E60530, | |||
048D33BDE7413EFE05D09901, | |||
9DA1913A62297D7111E0A6C9, | |||
@@ -154,6 +154,7 @@ | |||
<Filter Name="Drawable"> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableDocument.cpp"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableDocument.h"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableTypeHandler.cpp"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableTypeHandler.h"/> | |||
</Filter> | |||
<Filter Name="Project"> | |||
@@ -154,6 +154,7 @@ | |||
<Filter Name="Drawable"> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableDocument.cpp"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableDocument.h"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableTypeHandler.cpp"/> | |||
<File RelativePath="..\..\Source\model\Drawable\jucer_DrawableTypeHandler.h"/> | |||
</Filter> | |||
<Filter Name="Project"> | |||
@@ -72,6 +72,8 @@ | |||
file="Source/model/Drawable/jucer_DrawableDocument.cpp"/> | |||
<FILE id="qMcrWuCal" name="jucer_DrawableDocument.h" compile="0" resource="0" | |||
file="Source/model/Drawable/jucer_DrawableDocument.h"/> | |||
<FILE id="9y0VHaD" name="jucer_DrawableTypeHandler.cpp" compile="1" | |||
resource="0" file="Source/model/Drawable/jucer_DrawableTypeHandler.cpp"/> | |||
<FILE id="tGjXOXiR9" name="jucer_DrawableTypeHandler.h" compile="0" | |||
resource="0" file="Source/model/Drawable/jucer_DrawableTypeHandler.h"/> | |||
</GROUP> | |||
@@ -134,6 +134,7 @@ namespace Ids | |||
DECLARE_ID (resource); | |||
DECLARE_ID (className); | |||
DECLARE_ID (classDesc); | |||
DECLARE_ID (controlPoint); | |||
const Identifier class_ ("class"); | |||
const Identifier id_ ("id"); | |||
} | |||
@@ -27,80 +27,6 @@ | |||
#include "jucer_DrawableTypeHandler.h" | |||
//============================================================================== | |||
class DrawableTypeManager : public DeletedAtShutdown | |||
{ | |||
public: | |||
DrawableTypeManager() | |||
{ | |||
handlers.add (new DrawablePathHandler()); | |||
handlers.add (new DrawableImageHandler()); | |||
handlers.add (new DrawableCompositeHandler()); | |||
} | |||
~DrawableTypeManager() | |||
{ | |||
} | |||
juce_DeclareSingleton_SingleThreaded_Minimal (DrawableTypeManager); | |||
//============================================================================== | |||
int getNumHandlers() const { return handlers.size(); } | |||
DrawableTypeHandler* getHandler (const int index) const { return handlers[index]; } | |||
DrawableTypeHandler* getHandlerFor (const Identifier& type) | |||
{ | |||
for (int i = handlers.size(); --i >= 0;) | |||
if (handlers.getUnchecked(i)->getValueTreeType() == type) | |||
return handlers.getUnchecked(i); | |||
jassertfalse; | |||
return 0; | |||
} | |||
private: | |||
OwnedArray <DrawableTypeHandler> handlers; | |||
}; | |||
juce_ImplementSingleton_SingleThreaded (DrawableTypeManager); | |||
//============================================================================== | |||
DrawableTypeInstance::DrawableTypeInstance (DrawableDocument& document_, const ValueTree& state_) | |||
: document (document_), state (state_) | |||
{ | |||
} | |||
Value DrawableTypeInstance::getValue (const Identifier& name) const | |||
{ | |||
return state.getPropertyAsValue (name, document.getUndoManager()); | |||
} | |||
void DrawableTypeInstance::createProperties (Array <PropertyComponent*>& props) | |||
{ | |||
props.add (new TextPropertyComponent (getValue (Drawable::ValueTreeWrapperBase::idProperty), "Object ID", 128, false)); | |||
getHandler()->createPropertyEditors (*this, props); | |||
} | |||
DrawableTypeHandler* DrawableTypeInstance::getHandler() const | |||
{ | |||
DrawableTypeHandler* h = DrawableTypeManager::getInstance()->getHandlerFor (state.getType()); | |||
jassert (h != 0); | |||
return h; | |||
} | |||
void DrawableTypeInstance::setBounds (Drawable* drawable, const Rectangle<float>& newBounds) | |||
{ | |||
return getHandler()->setBounds (*this, drawable, newBounds); | |||
} | |||
void DrawableTypeInstance::getAllControlPoints (Array <RelativePoint>& points) | |||
{ | |||
return getHandler()->getAllControlPoints (*this, points); | |||
} | |||
//============================================================================== | |||
namespace Tags | |||
{ | |||
@@ -158,12 +84,6 @@ void DrawableDocument::checkRootObject() | |||
if (markersY == 0) | |||
markersY = new MarkerList (*this, false); | |||
/* if ((int) getCanvasWidth().getValue() <= 0) | |||
getCanvasWidth() = 500; | |||
if ((int) getCanvasHeight().getValue() <= 0) | |||
getCanvasHeight() = 500; | |||
*/ | |||
DrawableComposite::ValueTreeWrapper rootObject (getRootDrawableNode()); | |||
recursivelyUpdateIDs (rootObject); | |||
} | |||
@@ -325,35 +245,30 @@ const int menuItemOffset = 0x63451fa4; | |||
void DrawableDocument::addNewItemMenuItems (PopupMenu& menu) const | |||
{ | |||
DrawableTypeManager* const typeMan = DrawableTypeManager::getInstance(); | |||
const StringArray newItems (DrawableTypeManager::getInstance()->getNewItemList()); | |||
for (int i = 0; i < typeMan->getNumHandlers(); ++i) | |||
if (typeMan->getHandler(i)->canBeCreated) | |||
menu.addItem (i + menuItemOffset, "New " + typeMan->getHandler(i)->getDisplayName()); | |||
for (int i = 0; i < newItems.size(); ++i) | |||
menu.addItem (i + menuItemOffset, newItems[i]); | |||
} | |||
const ValueTree DrawableDocument::performNewItemMenuItem (int menuResultCode) | |||
{ | |||
DrawableTypeManager* const typeMan = DrawableTypeManager::getInstance(); | |||
const StringArray newItems (DrawableTypeManager::getInstance()->getNewItemList()); | |||
if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeMan->getNumHandlers()) | |||
int index = menuResultCode - menuItemOffset; | |||
if (index >= 0 && index < newItems.size()) | |||
{ | |||
DrawableTypeHandler* handler = typeMan->getHandler (menuResultCode - menuItemOffset); | |||
jassert (handler != 0); | |||
ValueTree state (DrawableTypeManager::getInstance() | |||
->createNewItem (index, *this, | |||
Point<float> (Random::getSystemRandom().nextFloat() * 100.0f + 100.0f, | |||
Random::getSystemRandom().nextFloat() * 100.0f + 100.0f))); | |||
if (handler != 0) | |||
{ | |||
ValueTree state (handler->createNewInstance (*this, | |||
Point<float> (Random::getSystemRandom().nextFloat() * 100.0f + 100.0f, | |||
Random::getSystemRandom().nextFloat() * 100.0f + 100.0f))); | |||
Drawable::ValueTreeWrapperBase wrapper (state); | |||
recursivelyUpdateIDs (wrapper); | |||
Drawable::ValueTreeWrapperBase wrapper (state); | |||
recursivelyUpdateIDs (wrapper); | |||
getRootDrawableNode().addDrawable (state, -1, getUndoManager()); | |||
getRootDrawableNode().addDrawable (state, -1, getUndoManager()); | |||
return state; | |||
} | |||
return state; | |||
} | |||
return ValueTree::invalid; | |||
@@ -391,23 +306,11 @@ const RelativeCoordinate DrawableDocument::findNamedCoordinate (const String& ob | |||
{ | |||
if (objectName == "parent") | |||
{ | |||
// if (edge == "right") return RelativeCoordinate ((double) getCanvasWidth().getValue(), true); | |||
// if (edge == "bottom") return RelativeCoordinate ((double) getCanvasHeight().getValue(), false); | |||
jassert (edge != "right" && edge != "bottom"); // drawables don't have a canvas size.. | |||
} | |||
if (objectName.isNotEmpty() && edge.isNotEmpty()) | |||
{ | |||
/* const ValueTree comp (getComponentWithMemberName (compName)); | |||
if (comp.isValid()) | |||
{ | |||
const RelativeRectangle coords (getCoordsFor (comp)); | |||
if (edge == RelativeCoordinate::leftName) return coords.left; | |||
if (edge == "right") return coords.right; | |||
if (edge == "top") return coords.top; | |||
if (edge == "bottom") return coords.bottom; | |||
}*/ | |||
} | |||
{ | |||
@@ -473,8 +376,7 @@ const RelativeCoordinate DrawableDocument::MarkerList::findNamedCoordinate (cons | |||
{ | |||
if (objectName == "parent") | |||
{ | |||
// if (edge == "right") return RelativeCoordinate ((double) document.getCanvasWidth().getValue(), true); | |||
// if (edge == "bottom") return RelativeCoordinate ((double) document.getCanvasHeight().getValue(), false); | |||
jassert (edge != "right" && edge != "bottom"); // drawables don't have a canvas size.. | |||
} | |||
const ValueTree marker (getMarkerNamed (objectName)); | |||
@@ -0,0 +1,491 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-10 by Raw Material Software Ltd. | |||
------------------------------------------------------------------------------ | |||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||
Public License (Version 2), as published by the Free Software Foundation. | |||
A copy of the license is included in the JUCE distribution, or can be found | |||
online at www.gnu.org/licenses. | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||
============================================================================== | |||
*/ | |||
#include "jucer_DrawableTypeHandler.h" | |||
//============================================================================== | |||
class DrawablePathHandler : public DrawableTypeHandler | |||
{ | |||
public: | |||
DrawablePathHandler() : DrawableTypeHandler ("Polygon", DrawablePath::valueTreeType) {} | |||
~DrawablePathHandler() {} | |||
static const ValueTree createNewPath (DrawableDocument& document, const Path& p) | |||
{ | |||
DrawablePath dp; | |||
dp.setPath (p); | |||
dp.setFill (Colours::lightblue.withHue (Random::getSystemRandom().nextFloat())); | |||
return dp.createValueTree (0); | |||
} | |||
static const ValueTree createNewTriangle (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Path p; | |||
p.addTriangle (approxPosition.getX(), approxPosition.getY() - 50.0f, | |||
approxPosition.getX() + 50.0f, approxPosition.getY() + 20.0f, | |||
approxPosition.getX() - 50.0f, approxPosition.getY() + 20.0f); | |||
return createNewPath (document, p); | |||
} | |||
static const ValueTree createNewRectangle (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Path p; | |||
p.addRectangle (approxPosition.getX() - 50.0f, approxPosition.getY() - 50.0f, | |||
100.0f, 100.0f); | |||
return createNewPath (document, p); | |||
} | |||
static const ValueTree createNewEllipse (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Path p; | |||
p.addEllipse (approxPosition.getX() - 50.0f, approxPosition.getY() - 50.0f, | |||
100.0f, 100.0f); | |||
return createNewPath (document, p); | |||
} | |||
class DrawablePathFillPropComp : public FillTypePropertyComponent | |||
{ | |||
public: | |||
DrawablePathFillPropComp (DrawableTypeInstance& item_, const String& name, const ValueTree& fill) | |||
: FillTypePropertyComponent (item_.getDocument().getUndoManager(), name, fill), | |||
item (item_) | |||
{} | |||
const ColourGradient getDefaultGradient() | |||
{ | |||
const Rectangle<float> bounds (item.getBounds()); | |||
return ColourGradient (Colours::blue, | |||
bounds.getX() + bounds.getWidth() * 0.3f, | |||
bounds.getY() + bounds.getHeight() * 0.3f, | |||
Colours::red, | |||
bounds.getX() + bounds.getWidth() * 0.7f, | |||
bounds.getY() + bounds.getHeight() * 0.7f, | |||
false); | |||
} | |||
private: | |||
DrawableTypeInstance item; | |||
}; | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
props.add (new DrawablePathFillPropComp (item, "Fill", wrapper.getMainFillState())); | |||
props.add (new DrawablePathFillPropComp (item, "Stroke", wrapper.getStrokeFillState())); | |||
} | |||
void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
{ | |||
} | |||
//============================================================================== | |||
class GradientControlPoint : public ControlPoint | |||
{ | |||
public: | |||
GradientControlPoint (const ValueTree& item_, | |||
const bool isStart_, const bool isStroke_) | |||
: item (item_), isStart (isStart_), isStroke (isStroke_) | |||
{} | |||
~GradientControlPoint() {} | |||
const RelativePoint getPosition() | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item); | |||
RelativePoint p; | |||
const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState(), | |||
isStart ? &p : 0, | |||
isStart ? 0 : &p, 0)); | |||
jassert (fill.isGradient()); | |||
return p; | |||
} | |||
void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item); | |||
RelativePoint p1, p2; | |||
ValueTree fillState (isStroke ? wrapper.getStrokeFillState() : wrapper.getMainFillState()); | |||
const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (fillState, &p1, &p2, 0)); | |||
jassert (fill.isGradient()); | |||
if (isStart) | |||
p1 = newPoint; | |||
else | |||
p2 = newPoint; | |||
Drawable::ValueTreeWrapperBase::writeFillType (fillState, fill, &p1, &p2, undoManager); | |||
} | |||
bool hasLine() { return false; } | |||
RelativePoint getEndOfLine() { return RelativePoint(); } | |||
private: | |||
ValueTree item; | |||
bool isStart, isStroke; | |||
}; | |||
//============================================================================== | |||
class PathControlPoint : public ControlPoint | |||
{ | |||
public: | |||
PathControlPoint (const DrawablePath::ValueTreeWrapper::Element& element_, const int cpNum_) | |||
: element (element_), cpNum (cpNum_) | |||
{} | |||
~PathControlPoint() {} | |||
const RelativePoint getPosition() | |||
{ | |||
return element.getControlPoint (cpNum); | |||
} | |||
void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
element.setControlPoint (cpNum, newPoint, undoManager); | |||
} | |||
bool hasLine() { return false; } | |||
RelativePoint getEndOfLine() { return RelativePoint(); } | |||
private: | |||
DrawablePath::ValueTreeWrapper::Element element; | |||
int cpNum; | |||
}; | |||
void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
const ValueTree pathTree (wrapper.getPathState()); | |||
const int numElements = pathTree.getNumChildren(); | |||
for (int i = 0; i < numElements; ++i) | |||
{ | |||
const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
const int numCps = e.getNumControlPoints(); | |||
for (int j = 0; j < numCps; ++j) | |||
points.add (new PathControlPoint (e, j)); | |||
} | |||
const FillType fill (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getMainFillState(), 0, 0, 0)); | |||
if (fill.isGradient()) | |||
{ | |||
points.add (new GradientControlPoint (item.getState(), true, false)); | |||
points.add (new GradientControlPoint (item.getState(), false, false)); | |||
} | |||
const FillType stroke (Drawable::ValueTreeWrapperBase::readFillType (wrapper.getStrokeFillState(), 0, 0, 0)); | |||
if (stroke.isGradient()) | |||
{ | |||
points.add (new GradientControlPoint (item.getState(), true, true)); | |||
points.add (new GradientControlPoint (item.getState(), false, true)); | |||
} | |||
} | |||
}; | |||
//============================================================================== | |||
class DrawableImageHandler : public DrawableTypeHandler | |||
{ | |||
public: | |||
DrawableImageHandler() : DrawableTypeHandler ("Image", DrawableImage::valueTreeType) {} | |||
~DrawableImageHandler() {} | |||
static const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Image tempImage (Image::ARGB, 100, 100, true); | |||
{ | |||
Graphics g (tempImage); | |||
g.fillAll (Colours::grey.withAlpha (0.3f)); | |||
g.setColour (Colours::red); | |||
g.setFont (40.0f); | |||
g.drawText ("?", 0, 0, 100, 100, Justification::centred, false); | |||
} | |||
DrawableImage di; | |||
di.setTransform (RelativePoint (approxPosition), | |||
RelativePoint (approxPosition + Point<float> (100.0f, 0.0f)), | |||
RelativePoint (approxPosition + Point<float> (0.0f, 100.0f))); | |||
return di.createValueTree (&document); | |||
} | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
} | |||
void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
{ | |||
} | |||
//============================================================================== | |||
class ImageControlPoint : public ControlPoint | |||
{ | |||
public: | |||
ImageControlPoint (const DrawableTypeInstance& item_, const int cpNum_) | |||
: item (item_), cpNum (cpNum_) | |||
{} | |||
~ImageControlPoint() {} | |||
const RelativePoint getPosition() | |||
{ | |||
DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
switch (cpNum) | |||
{ | |||
case 0: return wrapper.getTargetPositionForTopLeft(); | |||
case 1: return wrapper.getTargetPositionForTopRight(); | |||
case 2: return wrapper.getTargetPositionForBottomLeft(); | |||
default: jassertfalse; break; | |||
} | |||
return RelativePoint(); | |||
} | |||
void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
switch (cpNum) | |||
{ | |||
case 0: wrapper.setTargetPositionForTopLeft (newPoint, undoManager); break; | |||
case 1: wrapper.setTargetPositionForTopRight (newPoint, undoManager); break; | |||
case 2: wrapper.setTargetPositionForBottomLeft (newPoint, undoManager); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
bool hasLine() { return false; } | |||
RelativePoint getEndOfLine() { return RelativePoint(); } | |||
private: | |||
DrawableTypeInstance item; | |||
int cpNum; | |||
}; | |||
void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
{ | |||
for (int i = 0; i < 3; ++i) | |||
points.add (new ImageControlPoint (item, i)); | |||
} | |||
}; | |||
//============================================================================== | |||
class DrawableCompositeHandler : public DrawableTypeHandler | |||
{ | |||
public: | |||
DrawableCompositeHandler() : DrawableTypeHandler ("Group", DrawableComposite::valueTreeType) {} | |||
~DrawableCompositeHandler() {} | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
} | |||
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 RelativePoint getPosition() | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item); | |||
switch (cpNum) | |||
{ | |||
case 0: return wrapper.getTargetPositionForOrigin(); | |||
case 1: return wrapper.getTargetPositionForX1Y0(); | |||
case 2: return wrapper.getTargetPositionForX0Y1(); | |||
default: jassertfalse; break; | |||
} | |||
return RelativePoint(); | |||
} | |||
void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item); | |||
switch (cpNum) | |||
{ | |||
case 0: wrapper.setTargetPositionForOrigin (newPoint, undoManager); break; | |||
case 1: wrapper.setTargetPositionForX1Y0 (newPoint, undoManager); break; | |||
case 2: wrapper.setTargetPositionForX0Y1 (newPoint, undoManager); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
bool hasLine() { return false; } | |||
RelativePoint getEndOfLine() { return RelativePoint(); } | |||
private: | |||
ValueTree item; | |||
int cpNum; | |||
}; | |||
void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
{ | |||
for (int i = 0; i < 3; ++i) | |||
points.add (new CompositeControlPoint (item.getState(), i)); | |||
} | |||
}; | |||
//============================================================================== | |||
DrawableTypeManager::DrawableTypeManager() | |||
{ | |||
handlers.add (new DrawablePathHandler()); | |||
handlers.add (new DrawableImageHandler()); | |||
handlers.add (new DrawableCompositeHandler()); | |||
} | |||
DrawableTypeManager::~DrawableTypeManager() | |||
{ | |||
} | |||
DrawableTypeHandler* DrawableTypeManager::getHandlerFor (const Identifier& type) | |||
{ | |||
for (int i = handlers.size(); --i >= 0;) | |||
if (handlers.getUnchecked(i)->getValueTreeType() == type) | |||
return handlers.getUnchecked(i); | |||
jassertfalse; | |||
return 0; | |||
} | |||
const StringArray DrawableTypeManager::getNewItemList() | |||
{ | |||
const char* types[] = { "New Triangle", "New Rectangle", "New Ellipse", "New Image", 0 }; | |||
return StringArray (types); | |||
} | |||
const ValueTree DrawableTypeManager::createNewItem (const int index, DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
switch (index) | |||
{ | |||
case 0: return DrawablePathHandler::createNewTriangle (document, approxPosition); | |||
case 1: return DrawablePathHandler::createNewRectangle (document, approxPosition); | |||
case 2: return DrawablePathHandler::createNewEllipse (document, approxPosition); | |||
case 3: return DrawableImageHandler::createNewInstance (document, approxPosition); | |||
default: jassertfalse; break; | |||
} | |||
return ValueTree::invalid; | |||
} | |||
juce_ImplementSingleton_SingleThreaded (DrawableTypeManager); | |||
//============================================================================== | |||
DrawableTypeInstance::DrawableTypeInstance (DrawableDocument& document_, const ValueTree& state_) | |||
: document (document_), state (state_) | |||
{ | |||
} | |||
Value DrawableTypeInstance::getValue (const Identifier& name) const | |||
{ | |||
return state.getPropertyAsValue (name, document.getUndoManager()); | |||
} | |||
void DrawableTypeInstance::createProperties (Array <PropertyComponent*>& props) | |||
{ | |||
props.add (new TextPropertyComponent (getValue (Drawable::ValueTreeWrapperBase::idProperty), "Object ID", 128, false)); | |||
getHandler()->createPropertyEditors (*this, props); | |||
} | |||
DrawableTypeHandler* DrawableTypeInstance::getHandler() const | |||
{ | |||
DrawableTypeHandler* h = DrawableTypeManager::getInstance()->getHandlerFor (state.getType()); | |||
jassert (h != 0); | |||
return h; | |||
} | |||
const RelativeCoordinate DrawableTypeInstance::findNamedCoordinate (const String& objectName, const String& edge) const | |||
{ | |||
return getHandler()->findNamedCoordinate (*this, objectName, edge); | |||
} | |||
const Rectangle<float> DrawableTypeInstance::getBounds() | |||
{ | |||
OwnedArray <ControlPoint> points; | |||
getAllControlPoints (points); | |||
if (points.size() < 2) | |||
return Rectangle<float>(); | |||
DrawableTypeInstance parent (document, state.getParent()); | |||
const Point<float> p1 (points.getUnchecked(0)->getPosition().resolve (&parent)); | |||
Rectangle<float> r (p1, points.getUnchecked(1)->getPosition().resolve (&parent)); | |||
for (int i = 2; i < points.size(); ++i) | |||
r = r.getUnion (Rectangle<float> (p1, points.getUnchecked(i)->getPosition().resolve (&parent))); | |||
return r; | |||
} | |||
void DrawableTypeInstance::setBounds (Drawable* drawable, const Rectangle<float>& newBounds) | |||
{ | |||
return getHandler()->setBounds (*this, drawable, newBounds); | |||
} | |||
void DrawableTypeInstance::getAllControlPoints (OwnedArray <ControlPoint>& points) | |||
{ | |||
return getHandler()->getAllControlPoints (*this, points); | |||
} |
@@ -30,9 +30,22 @@ | |||
#include "../../utility/jucer_FillTypePropertyComponent.h" | |||
class DrawableTypeHandler; | |||
//============================================================================== | |||
class ControlPoint | |||
{ | |||
public: | |||
ControlPoint() {} | |||
virtual ~ControlPoint() {} | |||
virtual const RelativePoint getPosition() = 0; | |||
virtual void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) = 0; | |||
virtual bool hasLine() = 0; | |||
virtual RelativePoint getEndOfLine() = 0; | |||
}; | |||
//============================================================================== | |||
class DrawableTypeInstance | |||
class DrawableTypeInstance : public RelativeCoordinate::NamedCoordinateFinder | |||
{ | |||
public: | |||
DrawableTypeInstance (DrawableDocument& document_, const ValueTree& state_); | |||
@@ -43,8 +56,11 @@ public: | |||
Value getValue (const Identifier& name) const; | |||
void createProperties (Array <PropertyComponent*>& props); | |||
const Rectangle<float> getBounds(); | |||
void setBounds (Drawable* drawable, const Rectangle<float>& newBounds); | |||
void getAllControlPoints (Array <RelativePoint>& points); | |||
void getAllControlPoints (OwnedArray <ControlPoint>& points); | |||
const RelativeCoordinate findNamedCoordinate (const String& objectName, const String& edge) const; | |||
//============================================================================== | |||
DrawableTypeHandler* getHandler() const; | |||
@@ -61,31 +77,26 @@ private: | |||
class DrawableTypeHandler | |||
{ | |||
public: | |||
DrawableTypeHandler (const String& displayName_, const Identifier& valueTreeType_, bool canBeCreated_) | |||
: displayName (displayName_), valueTreeType (valueTreeType_), canBeCreated (canBeCreated_) | |||
DrawableTypeHandler (const String& displayName_, const Identifier& valueTreeType_) | |||
: displayName (displayName_), valueTreeType (valueTreeType_) | |||
{ | |||
} | |||
virtual ~DrawableTypeHandler() {} | |||
virtual const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) = 0; | |||
virtual void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) = 0; | |||
virtual void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) = 0; | |||
virtual void setBounds (DrawableTypeInstance& item, Drawable* drawable, const Rectangle<float>& newBounds) = 0; | |||
virtual void getAllControlPoints (DrawableTypeInstance& item, Array <RelativePoint>& points) = 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(); } | |||
const String& getDisplayName() const { return displayName; } | |||
const Identifier& getValueTreeType() const { return valueTreeType; } | |||
const bool canBeCreated; | |||
protected: | |||
static bool rescalePoints (RelativePoint* const points, const int numPoints, | |||
const Rectangle<float>& oldBounds, Rectangle<float> newBounds, | |||
RelativeCoordinate::NamedCoordinateFinder* nameFinder) | |||
void setBounds (DrawableTypeInstance& item, Drawable* drawable, Rectangle<float> newBounds) | |||
{ | |||
const Rectangle<float> oldBounds (drawable->getBounds()); | |||
if (oldBounds.isEmpty()) | |||
return false; | |||
return; | |||
newBounds.setSize (jmax (1.0f, newBounds.getWidth()), | |||
jmax (1.0f, newBounds.getHeight())); | |||
@@ -101,20 +112,28 @@ protected: | |||
if (xScale == 1.0 && yScale == 1.0 | |||
&& std::abs (newBounds.getX() - oldBounds.getX()) < tolerance | |||
&& std::abs (newBounds.getY() - oldBounds.getY()) < tolerance) | |||
return false; | |||
return; | |||
const double xOffset = newBounds.getX() - xScale * oldBounds.getX(); | |||
const double yOffset = newBounds.getY() - yScale * oldBounds.getY(); | |||
for (int i = 0; i < numPoints; ++i) | |||
OwnedArray<ControlPoint> points; | |||
getAllControlPoints (item, points); | |||
RelativeCoordinate::NamedCoordinateFinder* const nameFinder = drawable->getParent(); | |||
UndoManager* undoManager = item.getDocument().getUndoManager(); | |||
for (int i = 0; i < points.size(); ++i) | |||
{ | |||
const Point<float> p (points[i].resolve (nameFinder)); | |||
ControlPoint* cp = points.getUnchecked(i); | |||
RelativePoint point (cp->getPosition()); | |||
const Point<float> p (point.resolve (nameFinder)); | |||
points[i].moveToAbsolute (Point<float> ((float) (xOffset + xScale * p.getX()), | |||
(float) (yOffset + yScale * p.getY())), nameFinder); | |||
} | |||
point.moveToAbsolute (Point<float> ((float) (xOffset + xScale * p.getX()), | |||
(float) (yOffset + yScale * p.getY())), nameFinder); | |||
return true; | |||
cp->setPosition (point, undoManager); | |||
} | |||
} | |||
private: | |||
@@ -124,200 +143,28 @@ private: | |||
DrawableTypeHandler& operator= (const DrawableTypeHandler&); | |||
}; | |||
//============================================================================== | |||
class DrawablePathHandler : public DrawableTypeHandler | |||
{ | |||
public: | |||
DrawablePathHandler() : DrawableTypeHandler ("Polygon", DrawablePath::valueTreeType, true) {} | |||
~DrawablePathHandler() {} | |||
const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Path p; | |||
p.addTriangle (approxPosition.getX(), approxPosition.getY() - 50.0f, | |||
approxPosition.getX() + 50.0f, approxPosition.getY() + 20.0f, | |||
approxPosition.getX() - 50.0f, approxPosition.getY() + 20.0f); | |||
DrawablePath dp; | |||
dp.setPath (p); | |||
dp.setFill (Colours::lightblue.withHue (Random::getSystemRandom().nextFloat())); | |||
return dp.createValueTree (0); | |||
} | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
props.add (new FillTypePropertyComponent (item.getDocument().getUndoManager(), | |||
"Fill", wrapper.getMainFillState())); | |||
props.add (new FillTypePropertyComponent (item.getDocument().getUndoManager(), | |||
"Stroke", wrapper.getStrokeFillState())); | |||
} | |||
void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
{ | |||
} | |||
void setBounds (DrawableTypeInstance& item, Drawable* drawable, const Rectangle<float>& newBounds) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
RelativePointPath path; | |||
wrapper.getPath (path); | |||
Array <RelativePoint> points; | |||
int i; | |||
for (i = 0; i < path.elements.size(); ++i) | |||
{ | |||
int numPoints; | |||
RelativePoint* elementPoints = path.elements.getUnchecked(i)->getControlPoints (numPoints); | |||
for (int j = 0; j < numPoints; ++j) | |||
points.add (elementPoints[j]); | |||
} | |||
if (rescalePoints (points.getRawDataPointer(), points.size(), | |||
drawable->getBounds(), newBounds, drawable->getParent())) | |||
{ | |||
int n = 0; | |||
for (i = 0; i < path.elements.size(); ++i) | |||
{ | |||
int numPoints; | |||
RelativePoint* elementPoints = path.elements.getUnchecked(i)->getControlPoints (numPoints); | |||
for (int j = 0; j < numPoints; ++j) | |||
elementPoints[j] = points [n++]; | |||
} | |||
wrapper.setPath (path.toString(), item.getDocument().getUndoManager()); | |||
} | |||
} | |||
void getAllControlPoints (DrawableTypeInstance& item, Array <RelativePoint>& points) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
RelativePointPath path; | |||
wrapper.getPath (path); | |||
for (int i = 0; i < path.elements.size(); ++i) | |||
{ | |||
int numPoints; | |||
RelativePoint* elementPoints = path.elements.getUnchecked(i)->getControlPoints (numPoints); | |||
for (int j = 0; j < numPoints; ++j) | |||
points.add (elementPoints[j]); | |||
} | |||
} | |||
}; | |||
//============================================================================== | |||
class DrawableImageHandler : public DrawableTypeHandler | |||
class DrawableTypeManager : public DeletedAtShutdown | |||
{ | |||
public: | |||
DrawableImageHandler() : DrawableTypeHandler ("Image", DrawableImage::valueTreeType, true) {} | |||
~DrawableImageHandler() {} | |||
const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
Image tempImage (Image::ARGB, 100, 100, true); | |||
{ | |||
Graphics g (tempImage); | |||
g.fillAll (Colours::grey.withAlpha (0.3f)); | |||
g.setColour (Colours::red); | |||
g.setFont (40.0f); | |||
g.drawText ("?", 0, 0, 100, 100, Justification::centred, false); | |||
} | |||
DrawableTypeManager(); | |||
~DrawableTypeManager(); | |||
DrawableImage di; | |||
di.setTransform (RelativePoint (approxPosition), | |||
RelativePoint (approxPosition + Point<float> (100.0f, 0.0f)), | |||
RelativePoint (approxPosition + Point<float> (0.0f, 100.0f))); | |||
return di.createValueTree (&document); | |||
} | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
} | |||
juce_DeclareSingleton_SingleThreaded_Minimal (DrawableTypeManager); | |||
void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
{ | |||
} | |||
void setBounds (DrawableTypeInstance& item, Drawable* drawable, const Rectangle<float>& newBounds) | |||
{ | |||
DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
//============================================================================== | |||
int getNumHandlers() const { return handlers.size(); } | |||
DrawableTypeHandler* getHandler (const int index) const { return handlers [index]; } | |||
RelativePoint points[3] = { wrapper.getTargetPositionForTopLeft(), | |||
wrapper.getTargetPositionForTopRight(), | |||
wrapper.getTargetPositionForBottomLeft() }; | |||
DrawableTypeHandler* getHandlerFor (const Identifier& type); | |||
if (rescalePoints (points, 3, drawable->getBounds(), newBounds, drawable->getParent())) | |||
{ | |||
UndoManager* undoManager = item.getDocument().getUndoManager(); | |||
wrapper.setTargetPositionForTopLeft (points[0], undoManager); | |||
wrapper.setTargetPositionForTopRight (points[1], undoManager); | |||
wrapper.setTargetPositionForBottomLeft (points[2], undoManager); | |||
} | |||
} | |||
const StringArray getNewItemList(); | |||
const ValueTree createNewItem (const int index, DrawableDocument& document, const Point<float>& approxPosition); | |||
void getAllControlPoints (DrawableTypeInstance& item, Array <RelativePoint>& points) | |||
{ | |||
DrawableImage::ValueTreeWrapper wrapper (item.getState()); | |||
points.add (wrapper.getTargetPositionForTopLeft()); | |||
points.add (wrapper.getTargetPositionForTopRight()); | |||
points.add (wrapper.getTargetPositionForBottomLeft()); | |||
} | |||
private: | |||
OwnedArray <DrawableTypeHandler> handlers; | |||
}; | |||
//============================================================================== | |||
class DrawableCompositeHandler : public DrawableTypeHandler | |||
{ | |||
public: | |||
DrawableCompositeHandler() : DrawableTypeHandler ("Group", DrawableComposite::valueTreeType, false) {} | |||
~DrawableCompositeHandler() {} | |||
const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) | |||
{ | |||
return ValueTree::invalid; | |||
} | |||
void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
{ | |||
} | |||
void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
{ | |||
} | |||
void setBounds (DrawableTypeInstance& item, Drawable* drawable, const Rectangle<float>& newBounds) | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item.getState()); | |||
RelativePoint points[3] = { wrapper.getTargetPositionForOrigin(), | |||
wrapper.getTargetPositionForX1Y0(), | |||
wrapper.getTargetPositionForX0Y1() }; | |||
if (rescalePoints (points, 3, drawable->getBounds(), newBounds, drawable->getParent())) | |||
{ | |||
UndoManager* undoManager = item.getDocument().getUndoManager(); | |||
wrapper.setTargetPositionForOrigin (points[0], undoManager); | |||
wrapper.setTargetPositionForX1Y0 (points[1], undoManager); | |||
wrapper.setTargetPositionForX0Y1 (points[2], undoManager); | |||
} | |||
} | |||
void getAllControlPoints (DrawableTypeInstance& item, Array <RelativePoint>& points) | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item.getState()); | |||
points.add (wrapper.getTargetPositionForOrigin()); | |||
points.add (wrapper.getTargetPositionForX1Y0()); | |||
points.add (wrapper.getTargetPositionForX0Y1()); | |||
} | |||
}; | |||
#endif // __JUCER_DRAWABLETYPEHANDLER_H_7FB02E2F__ |
@@ -146,7 +146,7 @@ public: | |||
return getDocument().getCoordsFor (state); | |||
} | |||
void updateExtraComponentsForObject (const ValueTree& state, Component* parent, OwnedArray<OverlayItemComponent>& existingComps) | |||
void updateControlPointComponents (Component* parent, OwnedArray<OverlayItemComponent>& existingComps) | |||
{ | |||
} | |||
@@ -184,7 +184,6 @@ public: | |||
~DragOperation() | |||
{ | |||
getUndoManager().beginNewTransaction(); | |||
} | |||
protected: | |||
@@ -35,6 +35,7 @@ class DrawableEditorCanvas : public EditorCanvasBase, | |||
public Timer | |||
{ | |||
public: | |||
//============================================================================== | |||
DrawableEditorCanvas (DrawableEditor& editor_) | |||
: editor (editor_) | |||
{ | |||
@@ -48,6 +49,11 @@ public: | |||
shutdown(); | |||
} | |||
//============================================================================== | |||
UndoManager& getUndoManager() throw() { return *getDocument().getUndoManager(); } | |||
DrawableEditor& getEditor() throw() { return editor; } | |||
DrawableDocument& getDocument() throw() { return editor.getDocument(); } | |||
Component* createComponentHolder() | |||
{ | |||
return new DrawableComponent (this); | |||
@@ -67,7 +73,7 @@ public: | |||
else | |||
{ | |||
const Rectangle<float> damage (drawable->refreshFromValueTree (doc.getRootDrawableNode().getState(), &doc)); | |||
getComponentHolder()->repaint (damage.getSmallestIntegerContainer()); | |||
getComponentHolder()->repaint (objectSpaceToScreenSpace (damage.getSmallestIntegerContainer())); | |||
} | |||
startTimer (500); | |||
@@ -81,14 +87,10 @@ public: | |||
void setCanvasBounds (const Rectangle<int>& newBounds) {} | |||
bool canResizeCanvas() const { return false; } | |||
MarkerListBase& getMarkerList (bool isX) | |||
{ | |||
return getDocument().getMarkerList (isX); | |||
} | |||
double limitMarkerPosition (double pos) | |||
//============================================================================== | |||
const ValueTree getObjectState (const String& objectId) | |||
{ | |||
return pos; | |||
return getDocument().findDrawableState (objectId, false); | |||
} | |||
const SelectedItems::ItemType findObjectIdAt (const Point<int>& position) | |||
@@ -130,30 +132,36 @@ public: | |||
void objectDoubleClicked (const MouseEvent& e, const ValueTree& state) | |||
{ | |||
if (state.hasType (DrawablePath::valueTreeType) | |||
|| state.hasType (DrawableImage::valueTreeType) | |||
|| state.hasType (DrawableText::valueTreeType)) | |||
{ | |||
enableControlPointMode (state); | |||
} | |||
else if (state.hasType (DrawableComposite::valueTreeType)) | |||
{ | |||
// xxx | |||
} | |||
} | |||
bool hasSizeGuides() const { return false; } | |||
const ValueTree getObjectState (const String& objectId) | |||
{ | |||
return getDocument().findDrawableState (objectId, false); | |||
} | |||
void getObjectPositionDependencies (const ValueTree& state, Array<ValueTree>& deps) | |||
{ | |||
DrawableDocument& doc = getDocument(); | |||
DrawableTypeInstance item (doc, state); | |||
Array <RelativePoint> points; | |||
OwnedArray <ControlPoint> points; | |||
item.getAllControlPoints (points); | |||
StringArray anchors; | |||
for (int i = 0; i < points.size(); ++i) | |||
{ | |||
anchors.addIfNotAlreadyThere (points.getReference(i).x.getAnchorName1()); | |||
anchors.addIfNotAlreadyThere (points.getReference(i).x.getAnchorName2()); | |||
anchors.addIfNotAlreadyThere (points.getReference(i).y.getAnchorName1()); | |||
anchors.addIfNotAlreadyThere (points.getReference(i).y.getAnchorName2()); | |||
const RelativePoint p (points.getUnchecked(i)->getPosition()); | |||
anchors.addIfNotAlreadyThere (p.x.getAnchorName1()); | |||
anchors.addIfNotAlreadyThere (p.x.getAnchorName2()); | |||
anchors.addIfNotAlreadyThere (p.y.getAnchorName1()); | |||
anchors.addIfNotAlreadyThere (p.y.getAnchorName2()); | |||
} | |||
for (int i = 0; i < anchors.size(); ++i) | |||
@@ -207,12 +215,15 @@ public: | |||
return RelativeRectangle(); | |||
} | |||
//============================================================================== | |||
class ControlPointComponent : public OverlayItemComponent | |||
{ | |||
public: | |||
ControlPointComponent (DrawableEditorCanvas* canvas) | |||
: OverlayItemComponent (canvas) | |||
ControlPointComponent (DrawableEditorCanvas* canvas, const ValueTree& drawableState_, int controlPointNum_) | |||
: OverlayItemComponent (canvas), drawableState (drawableState_), | |||
controlPointNum (controlPointNum_), isDragging (false), mouseDownResult (false), selected (false) | |||
{ | |||
selectionId = getControlPointId (drawableState, controlPointNum); | |||
} | |||
~ControlPointComponent() | |||
@@ -221,29 +232,79 @@ public: | |||
void paint (Graphics& g) | |||
{ | |||
g.fillAll (Colours::pink); | |||
g.setColour (Colour (selected ? 0xaaaaaaaa : 0xaa333333)); | |||
g.drawRect (0, 0, getWidth(), getHeight()); | |||
g.setColour (Colour (selected ? 0xaa000000 : 0x99ffffff)); | |||
g.fillRect (1, 1, getWidth() - 2, getHeight() - 2); | |||
} | |||
void mouseDown (const MouseEvent& e) | |||
{ | |||
isDragging = false; | |||
if (e.mods.isPopupMenu()) | |||
{ | |||
canvas->showPopupMenu (true); | |||
} | |||
else | |||
{ | |||
mouseDownResult = canvas->getSelection().addToSelectionOnMouseDown (selectionId, e.mods); | |||
} | |||
} | |||
void mouseDrag (const MouseEvent& e) | |||
{ | |||
if (! (isDragging || e.mouseWasClicked() || e.mods.isPopupMenu())) | |||
{ | |||
canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, true, mouseDownResult); | |||
isDragging = true; | |||
canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), | |||
ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); | |||
} | |||
if (isDragging) | |||
{ | |||
canvas->continueDrag (e.getEventRelativeTo (getParentComponent())); | |||
autoScrollForMouseEvent (e); | |||
} | |||
} | |||
void mouseUp (const MouseEvent& e) | |||
{ | |||
if (isDragging) | |||
{ | |||
canvas->endDrag (e.getEventRelativeTo (getParentComponent())); | |||
} | |||
} | |||
void mouseDoubleClick (const MouseEvent& e) | |||
{ | |||
} | |||
void updatePosition (const RelativePoint& point, RelativeCoordinate::NamedCoordinateFinder* nameFinder) | |||
void updatePosition (ControlPoint& point, RelativeCoordinate::NamedCoordinateFinder* nameFinder) | |||
{ | |||
const Point<float> p (point.resolve (nameFinder)); | |||
setBoundsInTargetSpace (Rectangle<int> (roundToInt (p.getX()) - 2, roundToInt (p.getY()) - 2, 5, 5)); | |||
const Point<float> p (point.getPosition().resolve (nameFinder)); | |||
setBoundsInTargetSpace (Rectangle<int> (roundToInt (p.getX()) - 2, roundToInt (p.getY()) - 2, 7, 7)); | |||
const bool nowSelected = canvas->getSelection().isSelected (selectionId); | |||
if (selected != nowSelected) | |||
{ | |||
selected = nowSelected; | |||
repaint(); | |||
} | |||
} | |||
private: | |||
ValueTree drawableState; | |||
int controlPointNum; | |||
bool isDragging, mouseDownResult, selected; | |||
String selectionId; | |||
}; | |||
void updateExtraComponentsForObject (const ValueTree& state, Component* parent, OwnedArray<OverlayItemComponent>& comps) | |||
void updateControlPointComponents (Component* parent, OwnedArray<OverlayItemComponent>& comps) | |||
{ | |||
if (drawable == 0) | |||
{ | |||
@@ -251,11 +312,11 @@ public: | |||
return; | |||
} | |||
DrawableTypeInstance item (getDocument(), state); | |||
Array<RelativePoint> points; | |||
DrawableTypeInstance item (getDocument(), controlPointEditingTarget); | |||
OwnedArray <ControlPoint> points; | |||
item.getAllControlPoints (points); | |||
Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID()); | |||
Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID()); | |||
DrawableComposite* parentDrawable = d->getParent(); | |||
comps.removeRange (points.size(), comps.size()); | |||
@@ -269,15 +330,27 @@ public: | |||
if (c == 0) | |||
{ | |||
c = new ControlPointComponent (this); | |||
c = new ControlPointComponent (this, controlPointEditingTarget, i); | |||
comps.set (i, c); | |||
parent->addAndMakeVisible (c); | |||
} | |||
c->updatePosition (points.getReference(i), parentDrawable); | |||
c->updatePosition (*points.getUnchecked(i), parentDrawable); | |||
} | |||
} | |||
//============================================================================== | |||
MarkerListBase& getMarkerList (bool isX) | |||
{ | |||
return getDocument().getMarkerList (isX); | |||
} | |||
double limitMarkerPosition (double pos) | |||
{ | |||
return pos; | |||
} | |||
//============================================================================== | |||
SelectedItems& getSelection() | |||
{ | |||
return editor.getSelection(); | |||
@@ -303,25 +376,84 @@ public: | |||
} | |||
} | |||
bool isControlPointId (const String& itemId) | |||
{ | |||
return itemId.containsChar ('/'); | |||
} | |||
static const String getControlPointId (const ValueTree& drawableState, int index) | |||
{ | |||
return Drawable::ValueTreeWrapperBase (drawableState).getID() + "/" + String (index); | |||
} | |||
//============================================================================== | |||
class DragOperation : public EditorDragOperation | |||
class ObjectDragOperation : public EditorDragOperation | |||
{ | |||
public: | |||
DragOperation (DrawableEditorCanvas* canvas_, | |||
const MouseEvent& e, const Point<int>& mousePos, | |||
Component* snapGuideParentComp_, | |||
const ResizableBorderComponent::Zone& zone_) | |||
: EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_) | |||
ObjectDragOperation (DrawableEditorCanvas* canvas_, | |||
const MouseEvent& e, const Point<int>& mousePos, | |||
Component* snapGuideParentComp_, | |||
const ResizableBorderComponent::Zone& zone_) | |||
: EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_), drawableCanvas (canvas_) | |||
{ | |||
} | |||
~DragOperation() | |||
~ObjectDragOperation() {} | |||
protected: | |||
DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); } | |||
void getSnapPointsX (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); } | |||
void getSnapPointsY (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); } | |||
UndoManager& getUndoManager() { return *getDocument().getUndoManager(); } | |||
void getObjectDependencies (const ValueTree& state, Array<ValueTree>& deps) | |||
{ | |||
getUndoManager().beginNewTransaction(); | |||
drawableCanvas->getObjectPositionDependencies (state, deps); | |||
} | |||
const Rectangle<float> getObjectPosition (const ValueTree& state) | |||
{ | |||
return drawableCanvas->getObjectPositionFloat (state); | |||
} | |||
void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds) | |||
{ | |||
drawableCanvas->setObjectPositionFloat (state, newBounds); | |||
} | |||
float getMarkerPosition (const ValueTree& marker, bool isX) | |||
{ | |||
return 0; | |||
} | |||
private: | |||
DrawableEditorCanvas* drawableCanvas; | |||
}; | |||
//============================================================================== | |||
class ControlPointDragOperation : public EditorDragOperation | |||
{ | |||
public: | |||
ControlPointDragOperation (DrawableEditorCanvas* canvas_, | |||
const DrawableTypeInstance& drawableItem_, | |||
Drawable* drawable_, | |||
const MouseEvent& e, const Point<int>& mousePos, | |||
Component* snapGuideParentComp_, | |||
const ResizableBorderComponent::Zone& zone_) | |||
: EditorDragOperation (canvas_, e, mousePos, snapGuideParentComp_, zone_), | |||
drawableCanvas (canvas_), drawableItem (drawableItem_), drawable (drawable_) | |||
{ | |||
drawableItem.getAllControlPoints (points); | |||
} | |||
~ControlPointDragOperation() {} | |||
OwnedArray <ControlPoint> points; | |||
protected: | |||
DrawableDocument& getDocument() throw() { return static_cast <DrawableEditorCanvas*> (canvas)->getDocument(); } | |||
DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); } | |||
void getSnapPointsX (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); } | |||
void getSnapPointsY (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); } | |||
@@ -330,55 +462,91 @@ public: | |||
void getObjectDependencies (const ValueTree& state, Array<ValueTree>& deps) | |||
{ | |||
static_cast <DrawableEditorCanvas*> (canvas)->getObjectPositionDependencies (state, deps); | |||
drawableCanvas->getObjectPositionDependencies (drawableItem.getState(), deps); | |||
} | |||
const Rectangle<float> getObjectPosition (const ValueTree& state) | |||
{ | |||
return static_cast <DrawableEditorCanvas*> (canvas)->getObjectPositionFloat (state); | |||
int index = state [Ids::id_].toString().fromFirstOccurrenceOf ("/", false, false).getIntValue(); | |||
ControlPoint* cp = points[index]; | |||
if (cp == 0) | |||
return Rectangle<float>(); | |||
Point<float> p (cp->getPosition().resolve (drawable->getParent())); | |||
return Rectangle<float> (p, p); | |||
} | |||
void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds) | |||
{ | |||
static_cast <DrawableEditorCanvas*> (canvas)->setObjectPositionFloat (state, newBounds); | |||
int index = state [Ids::id_].toString().fromFirstOccurrenceOf ("/", false, false).getIntValue(); | |||
ControlPoint* cp = points[index]; | |||
if (cp != 0) | |||
{ | |||
RelativePoint p (cp->getPosition()); | |||
p.moveToAbsolute (newBounds.getPosition(), drawable->getParent()); | |||
cp->setPosition (p, getDocument().getUndoManager()); | |||
} | |||
} | |||
float getMarkerPosition (const ValueTree& marker, bool isX) | |||
{ | |||
return 0; | |||
} | |||
private: | |||
DrawableEditorCanvas* drawableCanvas; | |||
DrawableTypeInstance drawableItem; | |||
Drawable* drawable; | |||
}; | |||
//============================================================================== | |||
DragOperation* createDragOperation (const MouseEvent& e, Component* snapGuideParentComponent, | |||
const ResizableBorderComponent::Zone& zone) | |||
{ | |||
DragOperation* d = new DragOperation (this, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
Array<ValueTree> selected, unselected; | |||
EditorDragOperation* drag = 0; | |||
DrawableComposite::ValueTreeWrapper mainGroup (getDocument().getRootDrawableNode()); | |||
for (int i = mainGroup.getNumDrawables(); --i >= 0;) | |||
if (isControlPointMode()) | |||
{ | |||
const ValueTree v (mainGroup.getDrawableState (i)); | |||
Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID()); | |||
DrawableTypeInstance item (getDocument(), controlPointEditingTarget); | |||
if (editor.getSelection().isSelected (v[Drawable::ValueTreeWrapperBase::idProperty])) | |||
selected.add (v); | |||
else | |||
unselected.add (v); | |||
ControlPointDragOperation* cpd = new ControlPointDragOperation (this, item, d, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
drag = cpd; | |||
for (int i = 0; i < cpd->points.size(); ++i) | |||
{ | |||
const String pointId (getControlPointId (item.getState(), i)); | |||
ValueTree v (Ids::controlPoint); | |||
v.setProperty (Ids::id_, pointId, 0); | |||
if (editor.getSelection().isSelected (pointId)) | |||
selected.add (v); | |||
else | |||
unselected.add (v); | |||
} | |||
} | |||
else | |||
{ | |||
drag = new ObjectDragOperation (this, e, e.getPosition() - origin, snapGuideParentComponent, zone); | |||
d->initialise (selected, unselected); | |||
return d; | |||
} | |||
DrawableComposite::ValueTreeWrapper mainGroup (getDocument().getRootDrawableNode()); | |||
UndoManager& getUndoManager() | |||
{ | |||
return *getDocument().getUndoManager(); | |||
} | |||
for (int i = mainGroup.getNumDrawables(); --i >= 0;) | |||
{ | |||
const ValueTree v (mainGroup.getDrawableState (i)); | |||
DrawableEditor& getEditor() throw() { return editor; } | |||
DrawableDocument& getDocument() throw() { return editor.getDocument(); } | |||
if (editor.getSelection().isSelected (v[Drawable::ValueTreeWrapperBase::idProperty])) | |||
selected.add (v); | |||
else | |||
unselected.add (v); | |||
} | |||
} | |||
drag->initialise (selected, unselected); | |||
return drag; | |||
} | |||
void timerCallback() | |||
{ | |||
@@ -69,7 +69,7 @@ public: | |||
else | |||
{ | |||
isDragging = true; | |||
canvas->beginDrag (e.getEventRelativeTo (getParentComponent()), dragZone); | |||
canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), dragZone); | |||
canvas->showSizeGuides(); | |||
} | |||
} | |||
@@ -121,7 +121,6 @@ public: | |||
sizeGuides.getUnchecked(i)->updatePosition (bounds); | |||
} | |||
canvas->updateExtraComponentsForObject (objectState, getParentComponent(), extraEditorComps); | |||
return true; | |||
} | |||
@@ -202,7 +201,6 @@ private: | |||
const int borderThickness; | |||
OwnedArray <SizeGuideComponent> sizeGuides; | |||
bool isDragging; | |||
OwnedArray <OverlayItemComponent> extraEditorComps; | |||
const Rectangle<int> getCentreArea() const | |||
{ | |||
@@ -404,6 +402,7 @@ public: | |||
resizers.clear(); | |||
markersX.clear(); | |||
markersY.clear(); | |||
controlPoints.clear(); | |||
deleteAllChildren(); | |||
} | |||
@@ -420,7 +419,10 @@ public: | |||
if (e.mods.isPopupMenu()) | |||
{ | |||
if (underMouse.isNotEmpty() && ! getSelection().isSelected (underMouse)) | |||
{ | |||
canvas->enableResizingMode(); | |||
getSelection().selectOnly (underMouse); | |||
} | |||
canvas->showPopupMenu (underMouse.isNotEmpty()); | |||
} | |||
@@ -436,6 +438,7 @@ public: | |||
{ | |||
mouseDownCompUID = underMouse; | |||
canvas->deselectNonDraggableObjects(); | |||
canvas->enableResizingMode(); | |||
mouseDownResult = getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods); | |||
updateResizeFrames(); | |||
@@ -456,8 +459,9 @@ public: | |||
if (! isDraggingClickedComp) | |||
{ | |||
isDraggingClickedComp = true; | |||
canvas->enableResizingMode(); | |||
getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult); | |||
canvas->beginDrag (e, ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); | |||
canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()), ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); | |||
} | |||
canvas->continueDrag (e); | |||
@@ -526,15 +530,9 @@ public: | |||
SelectedItems& getSelection() { return canvas->getSelection(); } | |||
SelectedItems& getLassoSelection() { return getSelection(); } | |||
void resized() | |||
{ | |||
updateMarkers(); | |||
updateResizeFrames(); | |||
} | |||
void changeListenerCallback (void*) | |||
{ | |||
updateResizeFrames(); | |||
update(); | |||
} | |||
void modifierKeysChanged (const ModifierKeys&) | |||
@@ -571,6 +569,7 @@ public: | |||
void update() | |||
{ | |||
updateResizeFrames(); | |||
updateControlPoints(); | |||
updateMarkers(); | |||
} | |||
@@ -582,9 +581,16 @@ private: | |||
SelectedItems::ItemType mouseDownCompUID; | |||
OwnedArray <ResizeFrame> resizers; | |||
OwnedArray <MarkerComponent> markersX, markersY; | |||
OwnedArray <OverlayItemComponent> controlPoints; | |||
void updateResizeFrames() | |||
{ | |||
if (! canvas->isResizingMode()) | |||
{ | |||
resizers.clear(); | |||
return; | |||
} | |||
SelectedItems& selection = getSelection(); | |||
StringArray requiredIds; | |||
const int num = selection.getNumSelected(); | |||
@@ -630,6 +636,17 @@ private: | |||
} | |||
} | |||
void updateControlPoints() | |||
{ | |||
if (! canvas->isControlPointMode()) | |||
{ | |||
controlPoints.clear(); | |||
return; | |||
} | |||
canvas->updateControlPointComponents (this, controlPoints); | |||
} | |||
void updateMarkers (OwnedArray <MarkerComponent>& markers, const bool isX) | |||
{ | |||
MarkerListBase& markerList = canvas->getMarkerList (isX); | |||
@@ -827,6 +844,21 @@ const Rectangle<int> EditorCanvasBase::objectSpaceToScreenSpace (const Rectangle | |||
return r + origin; | |||
} | |||
void EditorCanvasBase::enableResizingMode() | |||
{ | |||
enableControlPointMode (ValueTree::invalid); | |||
} | |||
void EditorCanvasBase::enableControlPointMode (const ValueTree& objectToEdit) | |||
{ | |||
if (controlPointEditingTarget != objectToEdit) | |||
{ | |||
controlPointEditingTarget = objectToEdit; | |||
getSelection().deselectAll(); | |||
overlay->update(); | |||
} | |||
} | |||
//============================================================================== | |||
void EditorCanvasBase::paint (Graphics& g) | |||
{ | |||
@@ -938,6 +970,8 @@ void EditorCanvasBase::endDrag (const MouseEvent& e) | |||
{ | |||
dragger->drag (e, e.getPosition() - origin); | |||
dragger = 0; | |||
getUndoManager().beginNewTransaction(); | |||
} | |||
} | |||
@@ -88,6 +88,7 @@ public: | |||
virtual void deselectNonDraggableObjects() = 0; | |||
virtual void findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& area) = 0; | |||
//============================================================================== | |||
class DragOperation | |||
{ | |||
public: | |||
@@ -105,6 +106,12 @@ public: | |||
void continueDrag (const MouseEvent& e); | |||
void endDrag (const MouseEvent& e); | |||
void enableResizingMode(); | |||
void enableControlPointMode (const ValueTree& objectToEdit); | |||
bool isResizingMode() const { return ! isControlPointMode(); } | |||
bool isControlPointMode() const { return controlPointEditingTarget.isValid(); } | |||
//============================================================================== | |||
Component* getComponentHolder() const { return componentHolder; } | |||
EditorPanelBase* getPanel() const; | |||
@@ -129,14 +136,15 @@ public: | |||
}; | |||
//============================================================================== | |||
virtual void updateExtraComponentsForObject (const ValueTree& state, Component* parent, | |||
OwnedArray<OverlayItemComponent>& existingComps) = 0; | |||
virtual void updateControlPointComponents (Component* parent, | |||
OwnedArray<OverlayItemComponent>& existingComps) = 0; | |||
protected: | |||
//============================================================================== | |||
const BorderSize border; | |||
Point<int> origin; | |||
double scaleFactor; | |||
ValueTree controlPointEditingTarget; | |||
friend class OverlayItemComponent; | |||
class ResizeFrame; | |||
@@ -83,7 +83,7 @@ MainWindow::MainWindow() | |||
// don't want the window to take focus when the title-bar is clicked.. | |||
setWantsKeyboardFocus (false); | |||
//getPeer()->setCurrentRenderingEngine (0); | |||
getPeer()->setCurrentRenderingEngine (0); | |||
} | |||
MainWindow::~MainWindow() | |||
@@ -26,6 +26,8 @@ | |||
#ifndef __JUCER_FILLTYPEPROPERTYCOMPONENT_H_88CF1300__ | |||
#define __JUCER_FILLTYPEPROPERTYCOMPONENT_H_88CF1300__ | |||
class FillTypeEditorComponent; | |||
//============================================================================== | |||
class PopupFillSelector : public Component, | |||
public ChangeListener, | |||
@@ -33,8 +35,10 @@ class PopupFillSelector : public Component, | |||
public ButtonListener | |||
{ | |||
public: | |||
PopupFillSelector (const ValueTree& fillState_, UndoManager* undoManager_) | |||
: fillState (fillState_), | |||
PopupFillSelector (const ValueTree& fillState_, const ColourGradient& defaultGradient_, UndoManager* undoManager_) | |||
: gradientPicker (defaultGradient_), | |||
defaultGradient (defaultGradient_), | |||
fillState (fillState_), | |||
undoManager (undoManager_) | |||
{ | |||
colourButton.setButtonText ("Colour"); | |||
@@ -76,15 +80,6 @@ public: | |||
imageButton.removeButtonListener (this); | |||
} | |||
static void showAt (Component* comp, const ValueTree& fill, UndoManager* undoManager) | |||
{ | |||
PopupFillSelector popup (fill, undoManager); | |||
PopupMenu m; | |||
m.addCustomItem (1234, &popup, 300, 400, false); | |||
m.showAt (comp); | |||
} | |||
void resized() | |||
{ | |||
const int y = 2, w = 80, h = 22; | |||
@@ -99,18 +94,44 @@ public: | |||
void buttonClicked (Button* b) | |||
{ | |||
RelativePoint gp1, gp2; | |||
FillType currentFill (readFillType (&gp1, &gp2)); | |||
if (b == &colourButton) | |||
{ | |||
setFillType (colourPicker.getCurrentColour()); | |||
if (! currentFill.isColour()) | |||
setFillType (colourPicker.getCurrentColour()); | |||
} | |||
else if (b == &gradientButton) | |||
{ | |||
setFillType (gradientPicker.getGradient()); | |||
if (! currentFill.isGradient()) | |||
{ | |||
// 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); | |||
fillState.setProperty (Drawable::ValueTreeWrapperBase::type, temp [Drawable::ValueTreeWrapperBase::type], undoManager); | |||
newFill = readFillType (&gp1, &gp2); | |||
if (newFill.gradient->getNumColours() <= 1) | |||
{ | |||
newFill = FillType (defaultGradient); | |||
Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, undoManager); | |||
} | |||
else | |||
{ | |||
Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, &gp1, &gp2, undoManager); | |||
} | |||
refresh(); | |||
} | |||
} | |||
else if (b == &imageButton) | |||
{ | |||
setFillType (FillType (*StoredSettings::getInstance()->getFallbackImage(), | |||
AffineTransform::identity)); | |||
if (! currentFill.isTiledImage()) | |||
setFillType (FillType (*StoredSettings::getInstance()->getFallbackImage(), | |||
AffineTransform::identity)); | |||
} | |||
} | |||
@@ -122,7 +143,7 @@ public: | |||
void setFillType (const FillType& newFill) | |||
{ | |||
RelativePoint gp1, gp2; | |||
const FillType currentFill (readFillType (&gp1, &gp2)); | |||
FillType currentFill (readFillType (&gp1, &gp2)); | |||
if (currentFill != newFill) | |||
{ | |||
@@ -146,7 +167,7 @@ public: | |||
void refresh() | |||
{ | |||
const FillType newFill (readFillType (0, 0)); | |||
FillType newFill (readFillType (0, 0)); | |||
colourPicker.setVisible (newFill.isColour()); | |||
gradientPicker.setVisible (newFill.isGradient()); | |||
@@ -158,6 +179,12 @@ public: | |||
} | |||
else if (newFill.isGradient()) | |||
{ | |||
if (newFill.gradient->getNumColours() <= 1) | |||
{ | |||
newFill = FillType (defaultGradient); | |||
Drawable::ValueTreeWrapperBase::writeFillType (fillState, newFill, 0, 0, undoManager); | |||
} | |||
gradientButton.setToggleState (true, false); | |||
gradientPicker.setGradient (*newFill.gradient); | |||
} | |||
@@ -178,8 +205,8 @@ private: | |||
private ChangeListener | |||
{ | |||
public: | |||
GradientDesigner() | |||
: gradient (Colours::red, 0.0f, 0.0f, Colours::blue, 200.0f, 200.0f, false), | |||
GradientDesigner (const ColourGradient& gradient_) | |||
: gradient (gradient_), | |||
selectedPoint (-1), | |||
dragging (false), | |||
draggingNewPoint (false), | |||
@@ -302,8 +329,10 @@ private: | |||
void setGradient (const ColourGradient& newGradient) | |||
{ | |||
if (newGradient != gradient) | |||
if (newGradient != gradient || selectedPoint < 0) | |||
{ | |||
jassert (newGradient.getNumColours() > 1); | |||
gradient = newGradient; | |||
if (selectedPoint < 0) | |||
@@ -375,8 +404,10 @@ private: | |||
}; | |||
//============================================================================== | |||
FillTypeEditorComponent* owner; | |||
StoredSettings::ColourSelectorWithSwatches colourPicker; | |||
GradientDesigner gradientPicker; | |||
ColourGradient defaultGradient; | |||
ValueTree fillState; | |||
UndoManager* undoManager; | |||
@@ -404,6 +435,8 @@ public: | |||
{ | |||
} | |||
const ColourGradient getDefaultGradient() const; | |||
void paint (Graphics& g) | |||
{ | |||
g.setColour (Colours::grey); | |||
@@ -447,7 +480,12 @@ public: | |||
void mouseDown (const MouseEvent& e) | |||
{ | |||
undoManager->beginNewTransaction(); | |||
PopupFillSelector::showAt (this, fillState, undoManager); | |||
PopupFillSelector popup (fillState, getDefaultGradient(), undoManager); | |||
PopupMenu m; | |||
m.addCustomItem (1234, &popup, 300, 450, false); | |||
m.showAt (this); | |||
} | |||
void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { refresh(); } | |||
@@ -462,6 +500,7 @@ private: | |||
FillType fillType; | |||
}; | |||
//============================================================================== | |||
class FillTypePropertyComponent : public PropertyComponent | |||
{ | |||
@@ -484,6 +523,8 @@ public: | |||
editor.setBounds (getLookAndFeel().getPropertyComponentContentPosition (*this)); | |||
} | |||
virtual const ColourGradient getDefaultGradient() = 0; | |||
void refresh() {} | |||
protected: | |||
@@ -24,6 +24,7 @@ | |||
*/ | |||
#include "../jucer_Headers.h" | |||
#include "jucer_FillTypePropertyComponent.h" | |||
//============================================================================== | |||
@@ -406,3 +407,11 @@ RelativeRectangleLayoutManager::ComponentPosition::ComponentPosition (Component* | |||
: component (component_), name (name_), coords (coords_) | |||
{ | |||
} | |||
//============================================================================== | |||
const ColourGradient FillTypeEditorComponent::getDefaultGradient() const | |||
{ | |||
FillTypePropertyComponent* p = dynamic_cast <FillTypePropertyComponent*> (getParentComponent()); | |||
jassert (p != 0); | |||
return p->getDefaultGradient(); | |||
} |
@@ -16322,6 +16322,18 @@ ValueTree ValueTree::SharedObject::getChildWithName (const Identifier& typeToMat | |||
return ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::SharedObject::getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager) | |||
{ | |||
for (int i = 0; i < children.size(); ++i) | |||
if (children.getUnchecked(i)->type == typeToMatch) | |||
return ValueTree (static_cast <SharedObject*> (children.getUnchecked(i))); | |||
SharedObject* const newObject = new SharedObject (typeToMatch); | |||
addChild (newObject, -1, undoManager); | |||
return ValueTree (newObject); | |||
} | |||
ValueTree ValueTree::SharedObject::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const | |||
{ | |||
for (int i = 0; i < children.size(); ++i) | |||
@@ -16654,6 +16666,11 @@ ValueTree ValueTree::getChildWithName (const Identifier& type) const | |||
return object != 0 ? object->getChildWithName (type) : ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) | |||
{ | |||
return object != 0 ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const | |||
{ | |||
return object != 0 ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; | |||
@@ -78991,7 +79008,7 @@ int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlo | |||
jassert (point1.getX() != 987654.0f); | |||
#endif | |||
const int numEntries = jlimit (1, (colours.size() - 1) << 8, | |||
const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), | |||
3 * (int) point1.transformedBy (transform) | |||
.getDistanceFrom (point2.transformedBy (transform))); | |||
lookupTable.malloc (numEntries); | |||
@@ -83946,10 +83963,6 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
{ | |||
v.setProperty (type, "solid", undoManager); | |||
v.setProperty (colour, String::toHexString ((int) fillType.colour.getARGB()), undoManager); | |||
v.removeProperty (gradientPoint1, undoManager); | |||
v.removeProperty (gradientPoint2, undoManager); | |||
v.removeProperty (radial, undoManager); | |||
v.removeProperty (colours, undoManager); | |||
} | |||
else if (fillType.isGradient()) | |||
{ | |||
@@ -83964,19 +83977,12 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
<< " " << String::toHexString ((int) fillType.gradient->getColour(i).getARGB()); | |||
v.setProperty (colours, s.trimStart(), undoManager); | |||
v.removeProperty (colour, undoManager); | |||
} | |||
else if (fillType.isTiledImage()) | |||
{ | |||
v.setProperty (type, "image", undoManager); | |||
jassertfalse; //xxx todo | |||
v.removeProperty (gradientPoint1, undoManager); | |||
v.removeProperty (gradientPoint2, undoManager); | |||
v.removeProperty (radial, undoManager); | |||
v.removeProperty (colours, undoManager); | |||
v.removeProperty (colour, undoManager); | |||
} | |||
else | |||
{ | |||
@@ -83984,21 +83990,6 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
} | |||
} | |||
void Drawable::ValueTreeWrapperBase::replaceFillType (const Identifier& tag, const FillType& fillType, | |||
const RelativePoint* gp1, const RelativePoint* gp2, | |||
UndoManager* const undoManager) | |||
{ | |||
ValueTree v (state.getChildWithName (tag)); | |||
if (! v.isValid()) | |||
{ | |||
state.addChild (ValueTree (tag), -1, undoManager); | |||
v = state.getChildWithName (tag); | |||
} | |||
writeFillType (v, fillType, gp1, gp2, undoManager); | |||
} | |||
END_JUCE_NAMESPACE | |||
/*** End of inlined file: juce_Drawable.cpp ***/ | |||
@@ -84325,12 +84316,7 @@ ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const | |||
ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) | |||
{ | |||
const ValueTree childList (getChildList()); | |||
if (childList.isValid()) | |||
return childList; | |||
state.addChild (ValueTree (childGroupTag), 0, undoManager); | |||
return getChildList(); | |||
return state.getOrCreateChildWithName (childGroupTag, undoManager); | |||
} | |||
int DrawableComposite::ValueTreeWrapper::getNumDrawables() const | |||
@@ -84431,12 +84417,7 @@ ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const | |||
ValueTree DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) | |||
{ | |||
const ValueTree markerList (getMarkerList (xAxis)); | |||
if (markerList.isValid()) | |||
return markerList; | |||
state.addChild (ValueTree (xAxis ? markerGroupTagX : markerGroupTagY), -1, undoManager); | |||
return getMarkerList (xAxis); | |||
return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); | |||
} | |||
int DrawableComposite::ValueTreeWrapper::getNumMarkers (bool xAxis) const | |||
@@ -84919,6 +84900,8 @@ const Rectangle<float> DrawableImage::refreshFromValueTree (const ValueTree& tre | |||
|| controlPoints[1] != newControlPoint[1] | |||
|| controlPoints[2] != newControlPoint[2]) | |||
{ | |||
const Rectangle<float> damage (getBounds()); | |||
opacity = newOpacity; | |||
overlayColour = newOverlayColour; | |||
controlPoints[0] = newControlPoint[0]; | |||
@@ -84934,7 +84917,7 @@ const Rectangle<float> DrawableImage::refreshFromValueTree (const ValueTree& tre | |||
image = newImage; | |||
} | |||
return getBounds(); | |||
return damage.getUnion (getBounds()); | |||
} | |||
ImageCache::release (newImage); | |||
@@ -85118,12 +85101,16 @@ Drawable* DrawablePath::createCopy() const | |||
const Identifier DrawablePath::valueTreeType ("Path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::fill ("fill"); | |||
const Identifier DrawablePath::ValueTreeWrapper::stroke ("stroke"); | |||
const Identifier DrawablePath::ValueTreeWrapper::fill ("Fill"); | |||
const Identifier DrawablePath::ValueTreeWrapper::stroke ("Stroke"); | |||
const Identifier DrawablePath::ValueTreeWrapper::path ("Path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::jointStyle ("jointStyle"); | |||
const Identifier DrawablePath::ValueTreeWrapper::capStyle ("capStyle"); | |||
const Identifier DrawablePath::ValueTreeWrapper::strokeWidth ("strokeWidth"); | |||
const Identifier DrawablePath::ValueTreeWrapper::path ("path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); | |||
DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
: ValueTreeWrapperBase (state_) | |||
@@ -85131,6 +85118,11 @@ DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
jassert (state.hasType (valueTreeType)); | |||
} | |||
ValueTree DrawablePath::ValueTreeWrapper::getPathState() | |||
{ | |||
return state.getOrCreateChildWithName (path, 0); | |||
} | |||
ValueTree DrawablePath::ValueTreeWrapper::getMainFillState() | |||
{ | |||
ValueTree v (state.getChildWithName (fill)); | |||
@@ -85159,7 +85151,8 @@ const FillType DrawablePath::ValueTreeWrapper::getMainFill (RelativeCoordinate:: | |||
void DrawablePath::ValueTreeWrapper::setMainFill (const FillType& newFill, const RelativePoint* gp1, | |||
const RelativePoint* gp2, UndoManager* undoManager) | |||
{ | |||
replaceFillType (fill, newFill, gp1, gp2, undoManager); | |||
ValueTree v (state.getOrCreateChildWithName (fill, undoManager)); | |||
writeFillType (v, newFill, gp1, gp2, undoManager); | |||
} | |||
const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder) const | |||
@@ -85170,7 +85163,8 @@ const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate | |||
void DrawablePath::ValueTreeWrapper::setStrokeFill (const FillType& newFill, const RelativePoint* gp1, | |||
const RelativePoint* gp2, UndoManager* undoManager) | |||
{ | |||
replaceFillType (stroke, newFill, gp1, gp2, undoManager); | |||
ValueTree v (state.getOrCreateChildWithName (stroke, undoManager)); | |||
writeFillType (v, newFill, gp1, gp2, undoManager); | |||
} | |||
const PathStrokeType DrawablePath::ValueTreeWrapper::getStrokeType() const | |||
@@ -85196,15 +85190,50 @@ void DrawablePath::ValueTreeWrapper::setStrokeType (const PathStrokeType& newStr | |||
? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); | |||
} | |||
void DrawablePath::ValueTreeWrapper::getPath (RelativePointPath& p) const | |||
bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const | |||
{ | |||
return state [nonZeroWinding]; | |||
} | |||
void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) | |||
{ | |||
state.setProperty (nonZeroWinding, b, undoManager); | |||
} | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); | |||
DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) | |||
: state (state_) | |||
{ | |||
} | |||
DrawablePath::ValueTreeWrapper::Element::~Element() | |||
{ | |||
RelativePointPath newPath (state [path]); | |||
p.swapWith (newPath); | |||
} | |||
void DrawablePath::ValueTreeWrapper::setPath (const String& newPath, UndoManager* undoManager) | |||
int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const throw() | |||
{ | |||
state.setProperty (path, newPath, undoManager); | |||
const Identifier i (state.getType()); | |||
if (i == startSubPathElement || i == lineToElement) return 1; | |||
if (i == quadraticToElement) return 2; | |||
if (i == cubicToElement) return 3; | |||
return 0; | |||
} | |||
const RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const | |||
{ | |||
jassert (index >= 0 && index < getNumControlPoints()); | |||
return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); | |||
} | |||
void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) | |||
{ | |||
jassert (index >= 0 && index < getNumControlPoints()); | |||
return state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); | |||
} | |||
const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree, ImageProvider*) | |||
@@ -85232,8 +85261,7 @@ const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree | |||
const PathStrokeType newStroke (v.getStrokeType()); | |||
ScopedPointer<RelativePointPath> newRelativePath (new RelativePointPath()); | |||
v.getPath (*newRelativePath); | |||
ScopedPointer<RelativePointPath> newRelativePath (new RelativePointPath (tree)); | |||
Path newPath; | |||
newRelativePath->createPath (newPath, parent); | |||
@@ -85268,9 +85296,14 @@ const ValueTree DrawablePath::createValueTree (ImageProvider*) const | |||
v.setStrokeType (strokeType, 0); | |||
if (relativePath != 0) | |||
v.setPath (relativePath->toString(), 0); | |||
{ | |||
relativePath->writeTo (tree, 0); | |||
} | |||
else | |||
v.setPath (path.toString(), 0); | |||
{ | |||
RelativePointPath rp (path); | |||
rp.writeTo (tree, 0); | |||
} | |||
return tree; | |||
} | |||
@@ -92784,6 +92817,11 @@ RelativePoint::RelativePoint (const Point<float>& absolutePoint) | |||
{ | |||
} | |||
RelativePoint::RelativePoint (const float x_, const float y_) | |||
: x (x_, true), y (y_, false) | |||
{ | |||
} | |||
RelativePoint::RelativePoint (const RelativeCoordinate& x_, const RelativeCoordinate& y_) | |||
: x (x_), y (y_) | |||
{ | |||
@@ -92911,88 +92949,83 @@ RelativePointPath::RelativePointPath (const RelativePointPath& other) | |||
: usesNonZeroWinding (true), | |||
containsDynamicPoints (false) | |||
{ | |||
parseString (other.toString()); | |||
ValueTree state (DrawablePath::valueTreeType); | |||
writeTo (state, 0); | |||
parse (state); | |||
} | |||
RelativePointPath::RelativePointPath (const String& s) | |||
RelativePointPath::RelativePointPath (const ValueTree& drawable) | |||
: usesNonZeroWinding (true), | |||
containsDynamicPoints (false) | |||
{ | |||
parseString (s); | |||
parse (drawable); | |||
} | |||
void RelativePointPath::parseString (const String& s) | |||
RelativePointPath::RelativePointPath (const Path& path) | |||
{ | |||
int i = 0; | |||
juce_wchar marker = 'm'; | |||
int numValues = 2; | |||
RelativePoint points [3]; | |||
usesNonZeroWinding = path.isUsingNonZeroWinding(); | |||
for (;;) | |||
Path::Iterator i (path); | |||
while (i.next()) | |||
{ | |||
RelativeCoordinateHelpers::skipWhitespace (s, i); | |||
const juce_wchar firstChar = s[i]; | |||
switch (i.elementType) | |||
{ | |||
case Path::Iterator::startNewSubPath: elements.add (new StartSubPath (RelativePoint (i.x1, i.y1))); break; | |||
case Path::Iterator::lineTo: elements.add (new LineTo (RelativePoint (i.x1, i.y1))); break; | |||
case Path::Iterator::quadraticTo: elements.add (new QuadraticTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2))); break; | |||
case Path::Iterator::cubicTo: elements.add (new CubicTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2), RelativePoint (i.x3, i.y3))); break; | |||
case Path::Iterator::closePath: elements.add (new CloseSubPath()); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
} | |||
if (firstChar == 0) | |||
break; | |||
void RelativePointPath::writeTo (ValueTree state, UndoManager* undoManager) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (state); | |||
wrapper.setUsesNonZeroWinding (usesNonZeroWinding, undoManager); | |||
const juce_wchar secondChar = s[i + 1]; | |||
ValueTree pathTree (wrapper.getPathState()); | |||
pathTree.removeAllChildren (undoManager); | |||
if (secondChar == 0 || CharacterFunctions::isWhitespace (secondChar)) | |||
{ | |||
if (firstChar == 'm' || firstChar == 'l') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 1; | |||
} | |||
else if (firstChar == 'q') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 2; | |||
} | |||
else if (firstChar == 'c') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 3; | |||
} | |||
else if (firstChar == 'z') | |||
{ | |||
++i; | |||
marker = 'm'; | |||
numValues = 2; | |||
elements.add (new CloseSubPath()); | |||
continue; | |||
} | |||
else if (firstChar == 'a') | |||
{ | |||
++i; | |||
usesNonZeroWinding = false; | |||
continue; | |||
} | |||
} | |||
for (int i = 0; i < elements.size(); ++i) | |||
pathTree.addChild (elements.getUnchecked(i)->createTree(), -1, undoManager); | |||
} | |||
if (firstChar == '#') | |||
++i; | |||
void RelativePointPath::parse (const ValueTree& state) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (state); | |||
usesNonZeroWinding = wrapper.usesNonZeroWinding(); | |||
RelativePoint points[3]; | |||
const ValueTree pathTree (wrapper.getPathState()); | |||
const int num = pathTree.getNumChildren(); | |||
for (int i = 0; i < num; ++i) | |||
{ | |||
const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
for (int j = 0; j < numValues; ++j) | |||
const int numCps = e.getNumControlPoints(); | |||
for (int j = 0; j < numCps; ++j) | |||
{ | |||
const RelativeCoordinate x (RelativeCoordinateHelpers::readNextCoordinate (s, i, true)); | |||
const RelativeCoordinate y (RelativeCoordinateHelpers::readNextCoordinate (s, i, false)); | |||
points[j] = RelativePoint (x, y); | |||
points[j] = e.getControlPoint (j); | |||
containsDynamicPoints = containsDynamicPoints || points[j].isDynamic(); | |||
} | |||
switch (marker) | |||
{ | |||
case 'm': elements.add (new StartSubPath (points[0])); break; | |||
case 'l': elements.add (new LineTo (points[0])); break; | |||
case 'q': elements.add (new QuadraticTo (points[0], points[1])); break; | |||
case 'c': elements.add (new CubicTo (points[0], points[1], points[2])); break; | |||
default: jassertfalse; break; // illegal string format? | |||
} | |||
const Identifier type (e.getType()); | |||
if (type == DrawablePath::ValueTreeWrapper::Element::startSubPathElement) | |||
elements.add (new StartSubPath (points[0])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::closeSubPathElement) | |||
elements.add (new CloseSubPath()); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::lineToElement) | |||
elements.add (new LineTo (points[0])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::quadraticToElement) | |||
elements.add (new QuadraticTo (points[0], points[1])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::cubicToElement) | |||
elements.add (new CubicTo (points[0], points[1], points[2])); | |||
else | |||
jassertfalse; | |||
} | |||
} | |||
@@ -93017,27 +93050,6 @@ bool RelativePointPath::containsAnyDynamicPoints() const | |||
return containsDynamicPoints; | |||
} | |||
const String RelativePointPath::toString() const | |||
{ | |||
ElementType lastType = nullElement; | |||
MemoryOutputStream out; | |||
if (! usesNonZeroWinding) | |||
out << 'a'; | |||
for (int i = 0; i < elements.size(); ++i) | |||
{ | |||
if (out.getDataSize() > 0) | |||
out << ' '; | |||
const ElementBase* const e = elements.getUnchecked(i); | |||
e->write (out, lastType); | |||
lastType = e->type; | |||
} | |||
return out.toUTF8(); | |||
} | |||
RelativePointPath::ElementBase::ElementBase (const ElementType type_) : type (type_) | |||
{ | |||
} | |||
@@ -93047,16 +93059,11 @@ RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos) | |||
{ | |||
} | |||
void RelativePointPath::StartSubPath::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::StartSubPath::createTree() const | |||
{ | |||
const String p (startPos.toString()); | |||
if (lastTypeWritten != startSubPathElement) | |||
out << "m "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p)) | |||
out << '#'; | |||
out << p; | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::startSubPathElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, startPos.toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -93076,10 +93083,9 @@ RelativePointPath::CloseSubPath::CloseSubPath() | |||
{ | |||
} | |||
void RelativePointPath::CloseSubPath::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::CloseSubPath::createTree() const | |||
{ | |||
if (lastTypeWritten != closeSubPathElement) | |||
out << 'z'; | |||
return ValueTree (DrawablePath::ValueTreeWrapper::Element::closeSubPathElement); | |||
} | |||
void RelativePointPath::CloseSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder*) const | |||
@@ -93098,16 +93104,11 @@ RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_) | |||
{ | |||
} | |||
void RelativePointPath::LineTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::LineTo::createTree() const | |||
{ | |||
const String p (endPoint.toString()); | |||
if (lastTypeWritten != lineToElement) | |||
out << "l "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p)) | |||
out << '#'; | |||
out << p; | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::lineToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, endPoint.toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -93129,16 +93130,12 @@ RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, | |||
controlPoints[1] = endPoint; | |||
} | |||
void RelativePointPath::QuadraticTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::QuadraticTo::createTree() const | |||
{ | |||
const String p1 (controlPoints[0].toString()); | |||
if (lastTypeWritten != quadraticToElement) | |||
out << "q "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p1)) | |||
out << '#'; | |||
out << p1 << ' ' << controlPoints[1].toString(); | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::quadraticToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -93162,16 +93159,13 @@ RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const R | |||
controlPoints[2] = endPoint; | |||
} | |||
void RelativePointPath::CubicTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::CubicTo::createTree() const | |||
{ | |||
const String p1 (controlPoints[0].toString()); | |||
if (lastTypeWritten != cubicToElement) | |||
out << "c "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p1)) | |||
out << '#'; | |||
out << p1 << ' ' << controlPoints[1].toString() << ' ' << controlPoints[2].toString(); | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::cubicToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point3, controlPoints[2].toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -64,7 +64,7 @@ | |||
*/ | |||
#define JUCE_MAJOR_VERSION 1 | |||
#define JUCE_MINOR_VERSION 52 | |||
#define JUCE_BUILDNUMBER 7 | |||
#define JUCE_BUILDNUMBER 8 | |||
/** Current Juce version number. | |||
@@ -13094,12 +13094,22 @@ public: | |||
*/ | |||
ValueTree getChild (int index) const; | |||
/** Looks for a child node with the speficied type name. | |||
/** Returns the first child node with the speficied type name. | |||
If no such node is found, it'll return an invalid node. (See isValid() to find out | |||
whether a node is valid). | |||
@see getOrCreateChildWithName | |||
*/ | |||
ValueTree getChildWithName (const Identifier& type) const; | |||
/** Returns the first child node with the speficied type name, creating and adding | |||
a child with this name if there wasn't already one there. | |||
The only time this will return an invalid object is when the object that you're calling | |||
the method on is itself invalid. | |||
@see getChildWithName | |||
*/ | |||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
/** Looks for the first child node that has the speficied property value. | |||
This will scan the child nodes in order, until it finds one that has property that matches | |||
@@ -13342,6 +13352,7 @@ private: | |||
bool isAChildOf (const SharedObject* possibleParent) const; | |||
int indexOf (const ValueTree& child) const; | |||
ValueTree getChildWithName (const Identifier& type) const; | |||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const; | |||
void addChild (SharedObject* child, int index, UndoManager*); | |||
void removeChild (int childIndex, UndoManager*); | |||
@@ -42124,6 +42135,9 @@ public: | |||
/** Creates an absolute point, relative to the origin. */ | |||
RelativePoint (const Point<float>& absolutePoint); | |||
/** Creates an absolute point, relative to the origin. */ | |||
RelativePoint (float absoluteX, float absoluteY); | |||
/** Creates an absolute point from two coordinates. */ | |||
RelativePoint (const RelativeCoordinate& x, const RelativeCoordinate& y); | |||
@@ -42246,7 +42260,8 @@ public: | |||
RelativePointPath(); | |||
RelativePointPath (const RelativePointPath& other); | |||
RelativePointPath (const String& stringVersion); | |||
RelativePointPath (const ValueTree& drawable); | |||
RelativePointPath (const Path& path); | |||
~RelativePointPath(); | |||
/** Resolves this points in this path and adds them to a normal Path object. */ | |||
@@ -42255,11 +42270,8 @@ public: | |||
/** Returns true if the path contains any non-fixed points. */ | |||
bool containsAnyDynamicPoints() const; | |||
/** Returns a string version of the path. | |||
This has the same format as Path::toString(), but since it can contain RelativeCoordinate | |||
positions, it can't be parsed by the Path class if any of the points are dynamic. | |||
*/ | |||
const String toString() const; | |||
/** Writes the path to this drawable encoding. */ | |||
void writeTo (ValueTree state, UndoManager* undoManager); | |||
/** Quickly swaps the contents of this path with another. */ | |||
void swapWith (RelativePointPath& other) throw(); | |||
@@ -42284,7 +42296,7 @@ public: | |||
public: | |||
ElementBase (ElementType type); | |||
virtual ~ElementBase() {} | |||
virtual void write (OutputStream& out, ElementType lastTypeWritten) const = 0; | |||
virtual const ValueTree createTree() const = 0; | |||
virtual void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const = 0; | |||
virtual RelativePoint* getControlPoints (int& numPoints) = 0; | |||
@@ -42300,7 +42312,7 @@ public: | |||
public: | |||
StartSubPath (const RelativePoint& pos); | |||
~StartSubPath() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -42316,7 +42328,7 @@ public: | |||
public: | |||
CloseSubPath(); | |||
~CloseSubPath() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -42330,7 +42342,7 @@ public: | |||
public: | |||
LineTo (const RelativePoint& endPoint); | |||
~LineTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -42346,7 +42358,7 @@ public: | |||
public: | |||
QuadraticTo (const RelativePoint& controlPoint, const RelativePoint& endPoint); | |||
~QuadraticTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -42362,7 +42374,7 @@ public: | |||
public: | |||
CubicTo (const RelativePoint& controlPoint1, const RelativePoint& controlPoint2, const RelativePoint& endPoint); | |||
~CubicTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -42379,7 +42391,7 @@ public: | |||
private: | |||
bool containsDynamicPoints; | |||
void parseString (const String& s); | |||
void parse (const ValueTree& state); | |||
RelativePointPath& operator= (const RelativePointPath&); | |||
}; | |||
@@ -42601,13 +42613,8 @@ public: | |||
const RelativePoint* gradientPoint1, const RelativePoint* gradientPoint2, | |||
UndoManager* undoManager); | |||
protected: | |||
ValueTree state; | |||
static const Identifier type, gradientPoint1, gradientPoint2, colour, radial, colours; | |||
void replaceFillType (const Identifier& tag, const FillType& fillType, | |||
const RelativePoint* gradientPoint1, const RelativePoint* gradientPoint2, | |||
UndoManager* undoManager); | |||
}; | |||
juce_UseDebuggingNewOperator | |||
@@ -58599,7 +58606,7 @@ public: | |||
/** @internal */ | |||
static const Identifier valueTreeType; | |||
/** @internal */ | |||
const Identifier getValueTreeType() const { return valueTreeType; } | |||
const Identifier getValueTreeType() const { return valueTreeType; } | |||
/** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */ | |||
class ValueTreeWrapper : public ValueTreeWrapperBase | |||
@@ -58620,10 +58627,31 @@ public: | |||
const PathStrokeType getStrokeType() const; | |||
void setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager); | |||
void getPath (RelativePointPath& path) const; | |||
void setPath (const String& newPath, UndoManager* undoManager); | |||
bool usesNonZeroWinding() const; | |||
void setUsesNonZeroWinding (bool b, UndoManager* undoManager); | |||
class Element | |||
{ | |||
public: | |||
explicit Element (const ValueTree& state); | |||
~Element(); | |||
const Identifier getType() const throw() { return state.getType(); } | |||
int getNumControlPoints() const throw(); | |||
const RelativePoint getControlPoint (int index) const; | |||
void setControlPoint (int index, const RelativePoint& point, UndoManager* undoManager); | |||
static const Identifier startSubPathElement, closeSubPathElement, | |||
lineToElement, quadraticToElement, cubicToElement; | |||
ValueTree state; | |||
}; | |||
ValueTree getPathState(); | |||
static const Identifier fill, stroke, jointStyle, capStyle, strokeWidth, path; | |||
static const Identifier fill, stroke, path, jointStyle, capStyle, strokeWidth, | |||
nonZeroWinding, point1, point2, point3; | |||
}; | |||
juce_UseDebuggingNewOperator | |||
@@ -381,6 +381,18 @@ ValueTree ValueTree::SharedObject::getChildWithName (const Identifier& typeToMat | |||
return ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::SharedObject::getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager) | |||
{ | |||
for (int i = 0; i < children.size(); ++i) | |||
if (children.getUnchecked(i)->type == typeToMatch) | |||
return ValueTree (static_cast <SharedObject*> (children.getUnchecked(i))); | |||
SharedObject* const newObject = new SharedObject (typeToMatch); | |||
addChild (newObject, -1, undoManager); | |||
return ValueTree (newObject); | |||
} | |||
ValueTree ValueTree::SharedObject::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const | |||
{ | |||
for (int i = 0; i < children.size(); ++i) | |||
@@ -717,6 +729,11 @@ ValueTree ValueTree::getChildWithName (const Identifier& type) const | |||
return object != 0 ? object->getChildWithName (type) : ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) | |||
{ | |||
return object != 0 ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; | |||
} | |||
ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const | |||
{ | |||
return object != 0 ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; | |||
@@ -214,12 +214,22 @@ public: | |||
*/ | |||
ValueTree getChild (int index) const; | |||
/** Looks for a child node with the speficied type name. | |||
/** Returns the first child node with the speficied type name. | |||
If no such node is found, it'll return an invalid node. (See isValid() to find out | |||
whether a node is valid). | |||
@see getOrCreateChildWithName | |||
*/ | |||
ValueTree getChildWithName (const Identifier& type) const; | |||
/** Returns the first child node with the speficied type name, creating and adding | |||
a child with this name if there wasn't already one there. | |||
The only time this will return an invalid object is when the object that you're calling | |||
the method on is itself invalid. | |||
@see getChildWithName | |||
*/ | |||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
/** Looks for the first child node that has the speficied property value. | |||
This will scan the child nodes in order, until it finds one that has property that matches | |||
@@ -467,6 +477,7 @@ private: | |||
bool isAChildOf (const SharedObject* possibleParent) const; | |||
int indexOf (const ValueTree& child) const; | |||
ValueTree getChildWithName (const Identifier& type) const; | |||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||
ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const; | |||
void addChild (SharedObject* child, int index, UndoManager*); | |||
void removeChild (int childIndex, UndoManager*); | |||
@@ -33,7 +33,7 @@ | |||
*/ | |||
#define JUCE_MAJOR_VERSION 1 | |||
#define JUCE_MINOR_VERSION 52 | |||
#define JUCE_BUILDNUMBER 7 | |||
#define JUCE_BUILDNUMBER 8 | |||
/** Current Juce version number. | |||
@@ -160,7 +160,7 @@ int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlo | |||
jassert (point1.getX() != 987654.0f); | |||
#endif | |||
const int numEntries = jlimit (1, (colours.size() - 1) << 8, | |||
const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), | |||
3 * (int) point1.transformedBy (transform) | |||
.getDistanceFrom (point2.transformedBy (transform))); | |||
lookupTable.malloc (numEntries); | |||
@@ -238,10 +238,6 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
{ | |||
v.setProperty (type, "solid", undoManager); | |||
v.setProperty (colour, String::toHexString ((int) fillType.colour.getARGB()), undoManager); | |||
v.removeProperty (gradientPoint1, undoManager); | |||
v.removeProperty (gradientPoint2, undoManager); | |||
v.removeProperty (radial, undoManager); | |||
v.removeProperty (colours, undoManager); | |||
} | |||
else if (fillType.isGradient()) | |||
{ | |||
@@ -256,19 +252,12 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
<< " " << String::toHexString ((int) fillType.gradient->getColour(i).getARGB()); | |||
v.setProperty (colours, s.trimStart(), undoManager); | |||
v.removeProperty (colour, undoManager); | |||
} | |||
else if (fillType.isTiledImage()) | |||
{ | |||
v.setProperty (type, "image", undoManager); | |||
jassertfalse; //xxx todo | |||
v.removeProperty (gradientPoint1, undoManager); | |||
v.removeProperty (gradientPoint2, undoManager); | |||
v.removeProperty (radial, undoManager); | |||
v.removeProperty (colours, undoManager); | |||
v.removeProperty (colour, undoManager); | |||
} | |||
else | |||
{ | |||
@@ -276,20 +265,5 @@ void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType | |||
} | |||
} | |||
void Drawable::ValueTreeWrapperBase::replaceFillType (const Identifier& tag, const FillType& fillType, | |||
const RelativePoint* gp1, const RelativePoint* gp2, | |||
UndoManager* const undoManager) | |||
{ | |||
ValueTree v (state.getChildWithName (tag)); | |||
if (! v.isValid()) | |||
{ | |||
state.addChild (ValueTree (tag), -1, undoManager); | |||
v = state.getChildWithName (tag); | |||
} | |||
writeFillType (v, fillType, gp1, gp2, undoManager); | |||
} | |||
END_JUCE_NAMESPACE |
@@ -255,13 +255,8 @@ public: | |||
const RelativePoint* gradientPoint1, const RelativePoint* gradientPoint2, | |||
UndoManager* undoManager); | |||
protected: | |||
ValueTree state; | |||
static const Identifier type, gradientPoint1, gradientPoint2, colour, radial, colours; | |||
void replaceFillType (const Identifier& tag, const FillType& fillType, | |||
const RelativePoint* gradientPoint1, const RelativePoint* gradientPoint2, | |||
UndoManager* undoManager); | |||
}; | |||
//============================================================================== | |||
@@ -360,12 +360,7 @@ ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const | |||
ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) | |||
{ | |||
const ValueTree childList (getChildList()); | |||
if (childList.isValid()) | |||
return childList; | |||
state.addChild (ValueTree (childGroupTag), 0, undoManager); | |||
return getChildList(); | |||
return state.getOrCreateChildWithName (childGroupTag, undoManager); | |||
} | |||
int DrawableComposite::ValueTreeWrapper::getNumDrawables() const | |||
@@ -466,12 +461,7 @@ ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const | |||
ValueTree DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) | |||
{ | |||
const ValueTree markerList (getMarkerList (xAxis)); | |||
if (markerList.isValid()) | |||
return markerList; | |||
state.addChild (ValueTree (xAxis ? markerGroupTagX : markerGroupTagY), -1, undoManager); | |||
return getMarkerList (xAxis); | |||
return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); | |||
} | |||
int DrawableComposite::ValueTreeWrapper::getNumMarkers (bool xAxis) const | |||
@@ -313,6 +313,8 @@ const Rectangle<float> DrawableImage::refreshFromValueTree (const ValueTree& tre | |||
|| controlPoints[1] != newControlPoint[1] | |||
|| controlPoints[2] != newControlPoint[2]) | |||
{ | |||
const Rectangle<float> damage (getBounds()); | |||
opacity = newOpacity; | |||
overlayColour = newOverlayColour; | |||
controlPoints[0] = newControlPoint[0]; | |||
@@ -328,7 +330,7 @@ const Rectangle<float> DrawableImage::refreshFromValueTree (const ValueTree& tre | |||
image = newImage; | |||
} | |||
return getBounds(); | |||
return damage.getUnion (getBounds()); | |||
} | |||
ImageCache::release (newImage); | |||
@@ -183,12 +183,16 @@ Drawable* DrawablePath::createCopy() const | |||
//============================================================================== | |||
const Identifier DrawablePath::valueTreeType ("Path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::fill ("fill"); | |||
const Identifier DrawablePath::ValueTreeWrapper::stroke ("stroke"); | |||
const Identifier DrawablePath::ValueTreeWrapper::fill ("Fill"); | |||
const Identifier DrawablePath::ValueTreeWrapper::stroke ("Stroke"); | |||
const Identifier DrawablePath::ValueTreeWrapper::path ("Path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::jointStyle ("jointStyle"); | |||
const Identifier DrawablePath::ValueTreeWrapper::capStyle ("capStyle"); | |||
const Identifier DrawablePath::ValueTreeWrapper::strokeWidth ("strokeWidth"); | |||
const Identifier DrawablePath::ValueTreeWrapper::path ("path"); | |||
const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); | |||
const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); | |||
//============================================================================== | |||
DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
@@ -197,6 +201,11 @@ DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
jassert (state.hasType (valueTreeType)); | |||
} | |||
ValueTree DrawablePath::ValueTreeWrapper::getPathState() | |||
{ | |||
return state.getOrCreateChildWithName (path, 0); | |||
} | |||
ValueTree DrawablePath::ValueTreeWrapper::getMainFillState() | |||
{ | |||
ValueTree v (state.getChildWithName (fill)); | |||
@@ -225,7 +234,8 @@ const FillType DrawablePath::ValueTreeWrapper::getMainFill (RelativeCoordinate:: | |||
void DrawablePath::ValueTreeWrapper::setMainFill (const FillType& newFill, const RelativePoint* gp1, | |||
const RelativePoint* gp2, UndoManager* undoManager) | |||
{ | |||
replaceFillType (fill, newFill, gp1, gp2, undoManager); | |||
ValueTree v (state.getOrCreateChildWithName (fill, undoManager)); | |||
writeFillType (v, newFill, gp1, gp2, undoManager); | |||
} | |||
const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder) const | |||
@@ -236,7 +246,8 @@ const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate | |||
void DrawablePath::ValueTreeWrapper::setStrokeFill (const FillType& newFill, const RelativePoint* gp1, | |||
const RelativePoint* gp2, UndoManager* undoManager) | |||
{ | |||
replaceFillType (stroke, newFill, gp1, gp2, undoManager); | |||
ValueTree v (state.getOrCreateChildWithName (stroke, undoManager)); | |||
writeFillType (v, newFill, gp1, gp2, undoManager); | |||
} | |||
const PathStrokeType DrawablePath::ValueTreeWrapper::getStrokeType() const | |||
@@ -262,17 +273,54 @@ void DrawablePath::ValueTreeWrapper::setStrokeType (const PathStrokeType& newStr | |||
? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); | |||
} | |||
void DrawablePath::ValueTreeWrapper::getPath (RelativePointPath& p) const | |||
bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const | |||
{ | |||
return state [nonZeroWinding]; | |||
} | |||
void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) | |||
{ | |||
state.setProperty (nonZeroWinding, b, undoManager); | |||
} | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); | |||
const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); | |||
DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) | |||
: state (state_) | |||
{ | |||
} | |||
DrawablePath::ValueTreeWrapper::Element::~Element() | |||
{ | |||
RelativePointPath newPath (state [path]); | |||
p.swapWith (newPath); | |||
} | |||
void DrawablePath::ValueTreeWrapper::setPath (const String& newPath, UndoManager* undoManager) | |||
int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const throw() | |||
{ | |||
state.setProperty (path, newPath, undoManager); | |||
const Identifier i (state.getType()); | |||
if (i == startSubPathElement || i == lineToElement) return 1; | |||
if (i == quadraticToElement) return 2; | |||
if (i == cubicToElement) return 3; | |||
return 0; | |||
} | |||
const RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const | |||
{ | |||
jassert (index >= 0 && index < getNumControlPoints()); | |||
return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); | |||
} | |||
void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) | |||
{ | |||
jassert (index >= 0 && index < getNumControlPoints()); | |||
return state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); | |||
} | |||
//============================================================================== | |||
const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree, ImageProvider*) | |||
{ | |||
Rectangle<float> damageRect; | |||
@@ -298,8 +346,7 @@ const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree | |||
const PathStrokeType newStroke (v.getStrokeType()); | |||
ScopedPointer<RelativePointPath> newRelativePath (new RelativePointPath()); | |||
v.getPath (*newRelativePath); | |||
ScopedPointer<RelativePointPath> newRelativePath (new RelativePointPath (tree)); | |||
Path newPath; | |||
newRelativePath->createPath (newPath, parent); | |||
@@ -334,9 +381,14 @@ const ValueTree DrawablePath::createValueTree (ImageProvider*) const | |||
v.setStrokeType (strokeType, 0); | |||
if (relativePath != 0) | |||
v.setPath (relativePath->toString(), 0); | |||
{ | |||
relativePath->writeTo (tree, 0); | |||
} | |||
else | |||
v.setPath (path.toString(), 0); | |||
{ | |||
RelativePointPath rp (path); | |||
rp.writeTo (tree, 0); | |||
} | |||
return tree; | |||
} | |||
@@ -119,7 +119,7 @@ public: | |||
/** @internal */ | |||
static const Identifier valueTreeType; | |||
/** @internal */ | |||
const Identifier getValueTreeType() const { return valueTreeType; } | |||
const Identifier getValueTreeType() const { return valueTreeType; } | |||
//============================================================================== | |||
/** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */ | |||
@@ -141,10 +141,31 @@ public: | |||
const PathStrokeType getStrokeType() const; | |||
void setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager); | |||
void getPath (RelativePointPath& path) const; | |||
void setPath (const String& newPath, UndoManager* undoManager); | |||
bool usesNonZeroWinding() const; | |||
void setUsesNonZeroWinding (bool b, UndoManager* undoManager); | |||
static const Identifier fill, stroke, jointStyle, capStyle, strokeWidth, path; | |||
class Element | |||
{ | |||
public: | |||
explicit Element (const ValueTree& state); | |||
~Element(); | |||
const Identifier getType() const throw() { return state.getType(); } | |||
int getNumControlPoints() const throw(); | |||
const RelativePoint getControlPoint (int index) const; | |||
void setControlPoint (int index, const RelativePoint& point, UndoManager* undoManager); | |||
static const Identifier startSubPathElement, closeSubPathElement, | |||
lineToElement, quadraticToElement, cubicToElement; | |||
ValueTree state; | |||
}; | |||
ValueTree getPathState(); | |||
static const Identifier fill, stroke, path, jointStyle, capStyle, strokeWidth, | |||
nonZeroWinding, point1, point2, point3; | |||
}; | |||
//============================================================================== | |||
@@ -28,6 +28,7 @@ | |||
BEGIN_JUCE_NAMESPACE | |||
#include "juce_RelativeCoordinate.h" | |||
#include "../drawables/juce_DrawablePath.h" | |||
#include "../../../io/streams/juce_MemoryOutputStream.h" | |||
@@ -494,6 +495,11 @@ RelativePoint::RelativePoint (const Point<float>& absolutePoint) | |||
{ | |||
} | |||
RelativePoint::RelativePoint (const float x_, const float y_) | |||
: x (x_, true), y (y_, false) | |||
{ | |||
} | |||
RelativePoint::RelativePoint (const RelativeCoordinate& x_, const RelativeCoordinate& y_) | |||
: x (x_), y (y_) | |||
{ | |||
@@ -625,88 +631,83 @@ RelativePointPath::RelativePointPath (const RelativePointPath& other) | |||
: usesNonZeroWinding (true), | |||
containsDynamicPoints (false) | |||
{ | |||
parseString (other.toString()); | |||
ValueTree state (DrawablePath::valueTreeType); | |||
writeTo (state, 0); | |||
parse (state); | |||
} | |||
RelativePointPath::RelativePointPath (const String& s) | |||
RelativePointPath::RelativePointPath (const ValueTree& drawable) | |||
: usesNonZeroWinding (true), | |||
containsDynamicPoints (false) | |||
{ | |||
parseString (s); | |||
parse (drawable); | |||
} | |||
void RelativePointPath::parseString (const String& s) | |||
RelativePointPath::RelativePointPath (const Path& path) | |||
{ | |||
int i = 0; | |||
juce_wchar marker = 'm'; | |||
int numValues = 2; | |||
RelativePoint points [3]; | |||
usesNonZeroWinding = path.isUsingNonZeroWinding(); | |||
for (;;) | |||
Path::Iterator i (path); | |||
while (i.next()) | |||
{ | |||
RelativeCoordinateHelpers::skipWhitespace (s, i); | |||
const juce_wchar firstChar = s[i]; | |||
switch (i.elementType) | |||
{ | |||
case Path::Iterator::startNewSubPath: elements.add (new StartSubPath (RelativePoint (i.x1, i.y1))); break; | |||
case Path::Iterator::lineTo: elements.add (new LineTo (RelativePoint (i.x1, i.y1))); break; | |||
case Path::Iterator::quadraticTo: elements.add (new QuadraticTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2))); break; | |||
case Path::Iterator::cubicTo: elements.add (new CubicTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2), RelativePoint (i.x3, i.y3))); break; | |||
case Path::Iterator::closePath: elements.add (new CloseSubPath()); break; | |||
default: jassertfalse; break; | |||
} | |||
} | |||
} | |||
if (firstChar == 0) | |||
break; | |||
void RelativePointPath::writeTo (ValueTree state, UndoManager* undoManager) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (state); | |||
wrapper.setUsesNonZeroWinding (usesNonZeroWinding, undoManager); | |||
const juce_wchar secondChar = s[i + 1]; | |||
ValueTree pathTree (wrapper.getPathState()); | |||
pathTree.removeAllChildren (undoManager); | |||
if (secondChar == 0 || CharacterFunctions::isWhitespace (secondChar)) | |||
{ | |||
if (firstChar == 'm' || firstChar == 'l') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 1; | |||
} | |||
else if (firstChar == 'q') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 2; | |||
} | |||
else if (firstChar == 'c') | |||
{ | |||
++i; | |||
marker = firstChar; | |||
numValues = 3; | |||
} | |||
else if (firstChar == 'z') | |||
{ | |||
++i; | |||
marker = 'm'; | |||
numValues = 2; | |||
elements.add (new CloseSubPath()); | |||
continue; | |||
} | |||
else if (firstChar == 'a') | |||
{ | |||
++i; | |||
usesNonZeroWinding = false; | |||
continue; | |||
} | |||
} | |||
for (int i = 0; i < elements.size(); ++i) | |||
pathTree.addChild (elements.getUnchecked(i)->createTree(), -1, undoManager); | |||
} | |||
if (firstChar == '#') | |||
++i; | |||
void RelativePointPath::parse (const ValueTree& state) | |||
{ | |||
DrawablePath::ValueTreeWrapper wrapper (state); | |||
usesNonZeroWinding = wrapper.usesNonZeroWinding(); | |||
RelativePoint points[3]; | |||
for (int j = 0; j < numValues; ++j) | |||
const ValueTree pathTree (wrapper.getPathState()); | |||
const int num = pathTree.getNumChildren(); | |||
for (int i = 0; i < num; ++i) | |||
{ | |||
const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); | |||
const int numCps = e.getNumControlPoints(); | |||
for (int j = 0; j < numCps; ++j) | |||
{ | |||
const RelativeCoordinate x (RelativeCoordinateHelpers::readNextCoordinate (s, i, true)); | |||
const RelativeCoordinate y (RelativeCoordinateHelpers::readNextCoordinate (s, i, false)); | |||
points[j] = RelativePoint (x, y); | |||
points[j] = e.getControlPoint (j); | |||
containsDynamicPoints = containsDynamicPoints || points[j].isDynamic(); | |||
} | |||
switch (marker) | |||
{ | |||
case 'm': elements.add (new StartSubPath (points[0])); break; | |||
case 'l': elements.add (new LineTo (points[0])); break; | |||
case 'q': elements.add (new QuadraticTo (points[0], points[1])); break; | |||
case 'c': elements.add (new CubicTo (points[0], points[1], points[2])); break; | |||
default: jassertfalse; break; // illegal string format? | |||
} | |||
const Identifier type (e.getType()); | |||
if (type == DrawablePath::ValueTreeWrapper::Element::startSubPathElement) | |||
elements.add (new StartSubPath (points[0])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::closeSubPathElement) | |||
elements.add (new CloseSubPath()); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::lineToElement) | |||
elements.add (new LineTo (points[0])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::quadraticToElement) | |||
elements.add (new QuadraticTo (points[0], points[1])); | |||
else if (type == DrawablePath::ValueTreeWrapper::Element::cubicToElement) | |||
elements.add (new CubicTo (points[0], points[1], points[2])); | |||
else | |||
jassertfalse; | |||
} | |||
} | |||
@@ -731,27 +732,6 @@ bool RelativePointPath::containsAnyDynamicPoints() const | |||
return containsDynamicPoints; | |||
} | |||
const String RelativePointPath::toString() const | |||
{ | |||
ElementType lastType = nullElement; | |||
MemoryOutputStream out; | |||
if (! usesNonZeroWinding) | |||
out << 'a'; | |||
for (int i = 0; i < elements.size(); ++i) | |||
{ | |||
if (out.getDataSize() > 0) | |||
out << ' '; | |||
const ElementBase* const e = elements.getUnchecked(i); | |||
e->write (out, lastType); | |||
lastType = e->type; | |||
} | |||
return out.toUTF8(); | |||
} | |||
//============================================================================== | |||
RelativePointPath::ElementBase::ElementBase (const ElementType type_) : type (type_) | |||
{ | |||
@@ -763,16 +743,11 @@ RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos) | |||
{ | |||
} | |||
void RelativePointPath::StartSubPath::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::StartSubPath::createTree() const | |||
{ | |||
const String p (startPos.toString()); | |||
if (lastTypeWritten != startSubPathElement) | |||
out << "m "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p)) | |||
out << '#'; | |||
out << p; | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::startSubPathElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, startPos.toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -793,10 +768,9 @@ RelativePointPath::CloseSubPath::CloseSubPath() | |||
{ | |||
} | |||
void RelativePointPath::CloseSubPath::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::CloseSubPath::createTree() const | |||
{ | |||
if (lastTypeWritten != closeSubPathElement) | |||
out << 'z'; | |||
return ValueTree (DrawablePath::ValueTreeWrapper::Element::closeSubPathElement); | |||
} | |||
void RelativePointPath::CloseSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder*) const | |||
@@ -816,16 +790,11 @@ RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_) | |||
{ | |||
} | |||
void RelativePointPath::LineTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::LineTo::createTree() const | |||
{ | |||
const String p (endPoint.toString()); | |||
if (lastTypeWritten != lineToElement) | |||
out << "l "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p)) | |||
out << '#'; | |||
out << p; | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::lineToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, endPoint.toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -848,16 +817,12 @@ RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, | |||
controlPoints[1] = endPoint; | |||
} | |||
void RelativePointPath::QuadraticTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::QuadraticTo::createTree() const | |||
{ | |||
const String p1 (controlPoints[0].toString()); | |||
if (lastTypeWritten != quadraticToElement) | |||
out << "q "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p1)) | |||
out << '#'; | |||
out << p1 << ' ' << controlPoints[1].toString(); | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::quadraticToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -882,16 +847,13 @@ RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const R | |||
controlPoints[2] = endPoint; | |||
} | |||
void RelativePointPath::CubicTo::write (OutputStream& out, ElementType lastTypeWritten) const | |||
const ValueTree RelativePointPath::CubicTo::createTree() const | |||
{ | |||
const String p1 (controlPoints[0].toString()); | |||
if (lastTypeWritten != cubicToElement) | |||
out << "c "; | |||
else if (RelativeCoordinateHelpers::couldBeMistakenForPathCommand (p1)) | |||
out << '#'; | |||
out << p1 << ' ' << controlPoints[1].toString() << ' ' << controlPoints[2].toString(); | |||
ValueTree v (DrawablePath::ValueTreeWrapper::Element::cubicToElement); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); | |||
v.setProperty (DrawablePath::ValueTreeWrapper::point3, controlPoints[2].toString(), 0); | |||
return v; | |||
} | |||
void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
@@ -29,6 +29,7 @@ | |||
#include "juce_Path.h" | |||
#include "juce_Rectangle.h" | |||
#include "../../../containers/juce_OwnedArray.h" | |||
#include "../../../containers/juce_ValueTree.h" | |||
//============================================================================== | |||
@@ -289,6 +290,9 @@ public: | |||
/** Creates an absolute point, relative to the origin. */ | |||
RelativePoint (const Point<float>& absolutePoint); | |||
/** Creates an absolute point, relative to the origin. */ | |||
RelativePoint (float absoluteX, float absoluteY); | |||
/** Creates an absolute point from two coordinates. */ | |||
RelativePoint (const RelativeCoordinate& x, const RelativeCoordinate& y); | |||
@@ -416,7 +420,8 @@ public: | |||
//============================================================================== | |||
RelativePointPath(); | |||
RelativePointPath (const RelativePointPath& other); | |||
RelativePointPath (const String& stringVersion); | |||
RelativePointPath (const ValueTree& drawable); | |||
RelativePointPath (const Path& path); | |||
~RelativePointPath(); | |||
//============================================================================== | |||
@@ -426,11 +431,8 @@ public: | |||
/** Returns true if the path contains any non-fixed points. */ | |||
bool containsAnyDynamicPoints() const; | |||
/** Returns a string version of the path. | |||
This has the same format as Path::toString(), but since it can contain RelativeCoordinate | |||
positions, it can't be parsed by the Path class if any of the points are dynamic. | |||
*/ | |||
const String toString() const; | |||
/** Writes the path to this drawable encoding. */ | |||
void writeTo (ValueTree state, UndoManager* undoManager); | |||
/** Quickly swaps the contents of this path with another. */ | |||
void swapWith (RelativePointPath& other) throw(); | |||
@@ -457,7 +459,7 @@ public: | |||
public: | |||
ElementBase (ElementType type); | |||
virtual ~ElementBase() {} | |||
virtual void write (OutputStream& out, ElementType lastTypeWritten) const = 0; | |||
virtual const ValueTree createTree() const = 0; | |||
virtual void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const = 0; | |||
virtual RelativePoint* getControlPoints (int& numPoints) = 0; | |||
@@ -473,7 +475,7 @@ public: | |||
public: | |||
StartSubPath (const RelativePoint& pos); | |||
~StartSubPath() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -489,7 +491,7 @@ public: | |||
public: | |||
CloseSubPath(); | |||
~CloseSubPath() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -503,7 +505,7 @@ public: | |||
public: | |||
LineTo (const RelativePoint& endPoint); | |||
~LineTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -519,7 +521,7 @@ public: | |||
public: | |||
QuadraticTo (const RelativePoint& controlPoint, const RelativePoint& endPoint); | |||
~QuadraticTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -535,7 +537,7 @@ public: | |||
public: | |||
CubicTo (const RelativePoint& controlPoint1, const RelativePoint& controlPoint2, const RelativePoint& endPoint); | |||
~CubicTo() {} | |||
void write (OutputStream& out, ElementType lastTypeWritten) const; | |||
const ValueTree createTree() const; | |||
void addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const; | |||
RelativePoint* getControlPoints (int& numPoints); | |||
@@ -553,7 +555,7 @@ public: | |||
private: | |||
bool containsDynamicPoints; | |||
void parseString (const String& s); | |||
void parse (const ValueTree& state); | |||
RelativePointPath& operator= (const RelativePointPath&); | |||
}; | |||
@@ -282,7 +282,7 @@ public: | |||
timeOutMs); | |||
if (responseHeaders != 0) | |||
juce_getInternetFileHeaders (handle, *responseHeaders); | |||
juce_getInternetFileHeaders (handle, *responseHeaders); | |||
} | |||
~WebInputStream() | |||
@@ -1,481 +1,481 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-10 by Raw Material Software Ltd. | |||
------------------------------------------------------------------------------ | |||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||
Public License (Version 2), as published by the Free Software Foundation. | |||
A copy of the license is included in the JUCE distribution, or can be found | |||
online at www.gnu.org/licenses. | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||
============================================================================== | |||
*/ | |||
// (This file gets included by juce_linux_NativeCode.cpp, rather than being | |||
// compiled on its own). | |||
#if JUCE_INCLUDED_FILE | |||
//============================================================================== | |||
int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littleEndian) | |||
{ | |||
int numResults = 0; | |||
const int s = socket (AF_INET, SOCK_DGRAM, 0); | |||
if (s != -1) | |||
{ | |||
char buf [1024]; | |||
struct ifconf ifc; | |||
ifc.ifc_len = sizeof (buf); | |||
ifc.ifc_buf = buf; | |||
ioctl (s, SIOCGIFCONF, &ifc); | |||
for (unsigned int i = 0; i < ifc.ifc_len / sizeof (struct ifreq); ++i) | |||
{ | |||
struct ifreq ifr; | |||
strcpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name); | |||
if (ioctl (s, SIOCGIFFLAGS, &ifr) == 0 | |||
&& (ifr.ifr_flags & IFF_LOOPBACK) == 0 | |||
&& ioctl (s, SIOCGIFHWADDR, &ifr) == 0 | |||
&& numResults < maxNum) | |||
{ | |||
int64 a = 0; | |||
for (int j = 6; --j >= 0;) | |||
a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data [littleEndian ? j : (5 - j)]; | |||
*addresses++ = a; | |||
++numResults; | |||
} | |||
} | |||
close (s); | |||
} | |||
return numResults; | |||
} | |||
bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAddress, | |||
const String& emailSubject, | |||
const String& bodyText, | |||
const StringArray& filesToAttach) | |||
{ | |||
jassertfalse; // xxx todo | |||
return false; | |||
} | |||
//============================================================================== | |||
/** A HTTP input stream that uses sockets. | |||
*/ | |||
class JUCE_HTTPSocketStream | |||
{ | |||
public: | |||
//============================================================================== | |||
JUCE_HTTPSocketStream() | |||
: readPosition (0), | |||
socketHandle (-1), | |||
levelsOfRedirection (0), | |||
timeoutSeconds (15) | |||
{ | |||
} | |||
~JUCE_HTTPSocketStream() | |||
{ | |||
closeSocket(); | |||
} | |||
//============================================================================== | |||
bool open (const String& url, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost, | |||
URL::OpenStreamProgressCallback* callback, | |||
void* callbackContext, | |||
int timeOutMs) | |||
{ | |||
closeSocket(); | |||
uint32 timeOutTime = Time::getMillisecondCounter(); | |||
if (timeOutMs == 0) | |||
timeOutTime += 60000; | |||
else if (timeOutMs < 0) | |||
timeOutTime = 0xffffffff; | |||
else | |||
timeOutTime += timeOutMs; | |||
String hostName, hostPath; | |||
int hostPort; | |||
if (! decomposeURL (url, hostName, hostPath, hostPort)) | |||
return false; | |||
const struct hostent* host = 0; | |||
int port = 0; | |||
String proxyName, proxyPath; | |||
int proxyPort = 0; | |||
String proxyURL (getenv ("http_proxy")); | |||
if (proxyURL.startsWithIgnoreCase ("http://")) | |||
{ | |||
if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) | |||
return false; | |||
host = gethostbyname (proxyName.toUTF8()); | |||
port = proxyPort; | |||
} | |||
else | |||
{ | |||
host = gethostbyname (hostName.toUTF8()); | |||
port = hostPort; | |||
} | |||
if (host == 0) | |||
return false; | |||
struct sockaddr_in address; | |||
zerostruct (address); | |||
memcpy (&address.sin_addr, host->h_addr, host->h_length); | |||
address.sin_family = host->h_addrtype; | |||
address.sin_port = htons (port); | |||
socketHandle = socket (host->h_addrtype, SOCK_STREAM, 0); | |||
if (socketHandle == -1) | |||
return false; | |||
int receiveBufferSize = 16384; | |||
setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize)); | |||
setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0); | |||
#if JUCE_MAC | |||
setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0); | |||
#endif | |||
if (connect (socketHandle, (struct sockaddr*) &address, sizeof (address)) == -1) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, | |||
proxyName, proxyPort, | |||
hostPath, url, | |||
headers, postData, | |||
isPost)); | |||
size_t totalHeaderSent = 0; | |||
while (totalHeaderSent < requestHeader.getSize()) | |||
{ | |||
if (Time::getMillisecondCounter() > timeOutTime) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
const int numToSend = jmin (1024, (int) (requestHeader.getSize() - totalHeaderSent)); | |||
if (send (socketHandle, | |||
((const char*) requestHeader.getData()) + totalHeaderSent, | |||
numToSend, 0) | |||
!= numToSend) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
totalHeaderSent += numToSend; | |||
if (callback != 0 && ! callback (callbackContext, totalHeaderSent, requestHeader.getSize())) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
} | |||
const String responseHeader (readResponse (timeOutTime)); | |||
if (responseHeader.isNotEmpty()) | |||
{ | |||
//DBG (responseHeader); | |||
headerLines.clear(); | |||
headerLines.addLines (responseHeader); | |||
const int statusCode = responseHeader.fromFirstOccurrenceOf (" ", false, false) | |||
.substring (0, 3).getIntValue(); | |||
//int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); | |||
//bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); | |||
String location (findHeaderItem (headerLines, "Location:")); | |||
if (statusCode >= 300 && statusCode < 400 | |||
&& location.isNotEmpty()) | |||
{ | |||
if (! location.startsWithIgnoreCase ("http://")) | |||
location = "http://" + location; | |||
if (levelsOfRedirection++ < 3) | |||
return open (location, headers, postData, isPost, callback, callbackContext, timeOutMs); | |||
} | |||
else | |||
{ | |||
levelsOfRedirection = 0; | |||
return true; | |||
} | |||
} | |||
closeSocket(); | |||
return false; | |||
} | |||
//============================================================================== | |||
int read (void* buffer, int bytesToRead) | |||
{ | |||
fd_set readbits; | |||
FD_ZERO (&readbits); | |||
FD_SET (socketHandle, &readbits); | |||
struct timeval tv; | |||
tv.tv_sec = timeoutSeconds; | |||
tv.tv_usec = 0; | |||
if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) | |||
return 0; // (timeout) | |||
const int bytesRead = jmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); | |||
readPosition += bytesRead; | |||
return bytesRead; | |||
} | |||
//============================================================================== | |||
int readPosition; | |||
StringArray headerLines; | |||
//============================================================================== | |||
juce_UseDebuggingNewOperator | |||
private: | |||
int socketHandle, levelsOfRedirection; | |||
const int timeoutSeconds; | |||
//============================================================================== | |||
void closeSocket() | |||
{ | |||
if (socketHandle >= 0) | |||
close (socketHandle); | |||
socketHandle = -1; | |||
} | |||
const MemoryBlock createRequestHeader (const String& hostName, | |||
const int hostPort, | |||
const String& proxyName, | |||
const int proxyPort, | |||
const String& hostPath, | |||
const String& originalURL, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost) | |||
{ | |||
String header (isPost ? "POST " : "GET "); | |||
if (proxyName.isEmpty()) | |||
{ | |||
header << hostPath << " HTTP/1.0\r\nHost: " | |||
<< hostName << ':' << hostPort; | |||
} | |||
else | |||
{ | |||
header << originalURL << " HTTP/1.0\r\nHost: " | |||
<< proxyName << ':' << proxyPort; | |||
} | |||
header << "\r\nUser-Agent: JUCE/" | |||
<< JUCE_MAJOR_VERSION << '.' << JUCE_MINOR_VERSION | |||
<< "\r\nConnection: Close\r\nContent-Length: " | |||
<< postData.getSize() << "\r\n" | |||
<< headers << "\r\n"; | |||
MemoryBlock mb; | |||
mb.append (header.toUTF8(), (int) strlen (header.toUTF8())); | |||
mb.append (postData.getData(), postData.getSize()); | |||
return mb; | |||
} | |||
const String readResponse (const uint32 timeOutTime) | |||
{ | |||
int bytesRead = 0, numConsecutiveLFs = 0; | |||
MemoryBlock buffer (1024, true); | |||
while (numConsecutiveLFs < 2 && bytesRead < 32768 | |||
&& Time::getMillisecondCounter() <= timeOutTime) | |||
{ | |||
fd_set readbits; | |||
FD_ZERO (&readbits); | |||
FD_SET (socketHandle, &readbits); | |||
struct timeval tv; | |||
tv.tv_sec = timeoutSeconds; | |||
tv.tv_usec = 0; | |||
if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) | |||
return String::empty; // (timeout) | |||
buffer.ensureSize (bytesRead + 8, true); | |||
char* const dest = (char*) buffer.getData() + bytesRead; | |||
if (recv (socketHandle, dest, 1, 0) == -1) | |||
return String::empty; | |||
const char lastByte = *dest; | |||
++bytesRead; | |||
if (lastByte == '\n') | |||
++numConsecutiveLFs; | |||
else if (lastByte != '\r') | |||
numConsecutiveLFs = 0; | |||
} | |||
const String header (String::fromUTF8 ((const char*) buffer.getData())); | |||
if (header.startsWithIgnoreCase ("HTTP/")) | |||
return header.trimEnd(); | |||
return String::empty; | |||
} | |||
//============================================================================== | |||
static bool decomposeURL (const String& url, | |||
String& host, String& path, int& port) | |||
{ | |||
if (! url.startsWithIgnoreCase ("http://")) | |||
return false; | |||
const int nextSlash = url.indexOfChar (7, '/'); | |||
int nextColon = url.indexOfChar (7, ':'); | |||
if (nextColon > nextSlash && nextSlash > 0) | |||
nextColon = -1; | |||
if (nextColon >= 0) | |||
{ | |||
host = url.substring (7, nextColon); | |||
if (nextSlash >= 0) | |||
port = url.substring (nextColon + 1, nextSlash).getIntValue(); | |||
else | |||
port = url.substring (nextColon + 1).getIntValue(); | |||
} | |||
else | |||
{ | |||
port = 80; | |||
if (nextSlash >= 0) | |||
host = url.substring (7, nextSlash); | |||
else | |||
host = url.substring (7); | |||
} | |||
if (nextSlash >= 0) | |||
path = url.substring (nextSlash); | |||
else | |||
path = "/"; | |||
return true; | |||
} | |||
//============================================================================== | |||
static const String findHeaderItem (const StringArray& lines, const String& itemName) | |||
{ | |||
for (int i = 0; i < lines.size(); ++i) | |||
if (lines[i].startsWithIgnoreCase (itemName)) | |||
return lines[i].substring (itemName.length()).trim(); | |||
return String::empty; | |||
} | |||
}; | |||
//============================================================================== | |||
void* juce_openInternetFile (const String& url, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost, | |||
URL::OpenStreamProgressCallback* callback, | |||
void* callbackContext, | |||
int timeOutMs) | |||
{ | |||
ScopedPointer<JUCE_HTTPSocketStream> s (new JUCE_HTTPSocketStream()); | |||
if (s->open (url, headers, postData, isPost, callback, callbackContext, timeOutMs)) | |||
return s.release(); | |||
return 0; | |||
} | |||
void juce_closeInternetFile (void* handle) | |||
{ | |||
delete static_cast <JUCE_HTTPSocketStream*> (handle); | |||
} | |||
int juce_readFromInternetFile (void* handle, void* buffer, int bytesToRead) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
return s != 0 ? s->read (buffer, bytesToRead) : 0; | |||
} | |||
int64 juce_getInternetFileContentLength (void* handle) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
if (s != 0) | |||
{ | |||
//xxx todo | |||
jassertfalse | |||
} | |||
return -1; | |||
} | |||
bool juce_getInternetFileHeaders (void* handle, StringPairArray& headers) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
if (s != 0) | |||
{ | |||
for (int i = 0; i < s->headerLines.size(); ++i) | |||
{ | |||
const String& headersEntry = s->headerLines[i]; | |||
const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); | |||
const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); | |||
const String previousValue (headers [key]); | |||
headers.set (key, previousValue.isEmpty() ? value : (previousValue + ";" + value)); | |||
} | |||
} | |||
} | |||
int juce_seekInInternetFile (void* handle, int newPosition) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
return s != 0 ? s->readPosition : 0; | |||
} | |||
#endif | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-10 by Raw Material Software Ltd. | |||
------------------------------------------------------------------------------ | |||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||
Public License (Version 2), as published by the Free Software Foundation. | |||
A copy of the license is included in the JUCE distribution, or can be found | |||
online at www.gnu.org/licenses. | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||
============================================================================== | |||
*/ | |||
// (This file gets included by juce_linux_NativeCode.cpp, rather than being | |||
// compiled on its own). | |||
#if JUCE_INCLUDED_FILE | |||
//============================================================================== | |||
int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littleEndian) | |||
{ | |||
int numResults = 0; | |||
const int s = socket (AF_INET, SOCK_DGRAM, 0); | |||
if (s != -1) | |||
{ | |||
char buf [1024]; | |||
struct ifconf ifc; | |||
ifc.ifc_len = sizeof (buf); | |||
ifc.ifc_buf = buf; | |||
ioctl (s, SIOCGIFCONF, &ifc); | |||
for (unsigned int i = 0; i < ifc.ifc_len / sizeof (struct ifreq); ++i) | |||
{ | |||
struct ifreq ifr; | |||
strcpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name); | |||
if (ioctl (s, SIOCGIFFLAGS, &ifr) == 0 | |||
&& (ifr.ifr_flags & IFF_LOOPBACK) == 0 | |||
&& ioctl (s, SIOCGIFHWADDR, &ifr) == 0 | |||
&& numResults < maxNum) | |||
{ | |||
int64 a = 0; | |||
for (int j = 6; --j >= 0;) | |||
a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data [littleEndian ? j : (5 - j)]; | |||
*addresses++ = a; | |||
++numResults; | |||
} | |||
} | |||
close (s); | |||
} | |||
return numResults; | |||
} | |||
bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAddress, | |||
const String& emailSubject, | |||
const String& bodyText, | |||
const StringArray& filesToAttach) | |||
{ | |||
jassertfalse; // xxx todo | |||
return false; | |||
} | |||
//============================================================================== | |||
/** A HTTP input stream that uses sockets. | |||
*/ | |||
class JUCE_HTTPSocketStream | |||
{ | |||
public: | |||
//============================================================================== | |||
JUCE_HTTPSocketStream() | |||
: readPosition (0), | |||
socketHandle (-1), | |||
levelsOfRedirection (0), | |||
timeoutSeconds (15) | |||
{ | |||
} | |||
~JUCE_HTTPSocketStream() | |||
{ | |||
closeSocket(); | |||
} | |||
//============================================================================== | |||
bool open (const String& url, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost, | |||
URL::OpenStreamProgressCallback* callback, | |||
void* callbackContext, | |||
int timeOutMs) | |||
{ | |||
closeSocket(); | |||
uint32 timeOutTime = Time::getMillisecondCounter(); | |||
if (timeOutMs == 0) | |||
timeOutTime += 60000; | |||
else if (timeOutMs < 0) | |||
timeOutTime = 0xffffffff; | |||
else | |||
timeOutTime += timeOutMs; | |||
String hostName, hostPath; | |||
int hostPort; | |||
if (! decomposeURL (url, hostName, hostPath, hostPort)) | |||
return false; | |||
const struct hostent* host = 0; | |||
int port = 0; | |||
String proxyName, proxyPath; | |||
int proxyPort = 0; | |||
String proxyURL (getenv ("http_proxy")); | |||
if (proxyURL.startsWithIgnoreCase ("http://")) | |||
{ | |||
if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) | |||
return false; | |||
host = gethostbyname (proxyName.toUTF8()); | |||
port = proxyPort; | |||
} | |||
else | |||
{ | |||
host = gethostbyname (hostName.toUTF8()); | |||
port = hostPort; | |||
} | |||
if (host == 0) | |||
return false; | |||
struct sockaddr_in address; | |||
zerostruct (address); | |||
memcpy (&address.sin_addr, host->h_addr, host->h_length); | |||
address.sin_family = host->h_addrtype; | |||
address.sin_port = htons (port); | |||
socketHandle = socket (host->h_addrtype, SOCK_STREAM, 0); | |||
if (socketHandle == -1) | |||
return false; | |||
int receiveBufferSize = 16384; | |||
setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize)); | |||
setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0); | |||
#if JUCE_MAC | |||
setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0); | |||
#endif | |||
if (connect (socketHandle, (struct sockaddr*) &address, sizeof (address)) == -1) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, | |||
proxyName, proxyPort, | |||
hostPath, url, | |||
headers, postData, | |||
isPost)); | |||
size_t totalHeaderSent = 0; | |||
while (totalHeaderSent < requestHeader.getSize()) | |||
{ | |||
if (Time::getMillisecondCounter() > timeOutTime) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
const int numToSend = jmin (1024, (int) (requestHeader.getSize() - totalHeaderSent)); | |||
if (send (socketHandle, | |||
((const char*) requestHeader.getData()) + totalHeaderSent, | |||
numToSend, 0) | |||
!= numToSend) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
totalHeaderSent += numToSend; | |||
if (callback != 0 && ! callback (callbackContext, totalHeaderSent, requestHeader.getSize())) | |||
{ | |||
closeSocket(); | |||
return false; | |||
} | |||
} | |||
const String responseHeader (readResponse (timeOutTime)); | |||
if (responseHeader.isNotEmpty()) | |||
{ | |||
//DBG (responseHeader); | |||
headerLines.clear(); | |||
headerLines.addLines (responseHeader); | |||
const int statusCode = responseHeader.fromFirstOccurrenceOf (" ", false, false) | |||
.substring (0, 3).getIntValue(); | |||
//int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); | |||
//bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); | |||
String location (findHeaderItem (headerLines, "Location:")); | |||
if (statusCode >= 300 && statusCode < 400 | |||
&& location.isNotEmpty()) | |||
{ | |||
if (! location.startsWithIgnoreCase ("http://")) | |||
location = "http://" + location; | |||
if (levelsOfRedirection++ < 3) | |||
return open (location, headers, postData, isPost, callback, callbackContext, timeOutMs); | |||
} | |||
else | |||
{ | |||
levelsOfRedirection = 0; | |||
return true; | |||
} | |||
} | |||
closeSocket(); | |||
return false; | |||
} | |||
//============================================================================== | |||
int read (void* buffer, int bytesToRead) | |||
{ | |||
fd_set readbits; | |||
FD_ZERO (&readbits); | |||
FD_SET (socketHandle, &readbits); | |||
struct timeval tv; | |||
tv.tv_sec = timeoutSeconds; | |||
tv.tv_usec = 0; | |||
if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) | |||
return 0; // (timeout) | |||
const int bytesRead = jmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); | |||
readPosition += bytesRead; | |||
return bytesRead; | |||
} | |||
//============================================================================== | |||
int readPosition; | |||
StringArray headerLines; | |||
//============================================================================== | |||
juce_UseDebuggingNewOperator | |||
private: | |||
int socketHandle, levelsOfRedirection; | |||
const int timeoutSeconds; | |||
//============================================================================== | |||
void closeSocket() | |||
{ | |||
if (socketHandle >= 0) | |||
close (socketHandle); | |||
socketHandle = -1; | |||
} | |||
const MemoryBlock createRequestHeader (const String& hostName, | |||
const int hostPort, | |||
const String& proxyName, | |||
const int proxyPort, | |||
const String& hostPath, | |||
const String& originalURL, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost) | |||
{ | |||
String header (isPost ? "POST " : "GET "); | |||
if (proxyName.isEmpty()) | |||
{ | |||
header << hostPath << " HTTP/1.0\r\nHost: " | |||
<< hostName << ':' << hostPort; | |||
} | |||
else | |||
{ | |||
header << originalURL << " HTTP/1.0\r\nHost: " | |||
<< proxyName << ':' << proxyPort; | |||
} | |||
header << "\r\nUser-Agent: JUCE/" | |||
<< JUCE_MAJOR_VERSION << '.' << JUCE_MINOR_VERSION | |||
<< "\r\nConnection: Close\r\nContent-Length: " | |||
<< postData.getSize() << "\r\n" | |||
<< headers << "\r\n"; | |||
MemoryBlock mb; | |||
mb.append (header.toUTF8(), (int) strlen (header.toUTF8())); | |||
mb.append (postData.getData(), postData.getSize()); | |||
return mb; | |||
} | |||
const String readResponse (const uint32 timeOutTime) | |||
{ | |||
int bytesRead = 0, numConsecutiveLFs = 0; | |||
MemoryBlock buffer (1024, true); | |||
while (numConsecutiveLFs < 2 && bytesRead < 32768 | |||
&& Time::getMillisecondCounter() <= timeOutTime) | |||
{ | |||
fd_set readbits; | |||
FD_ZERO (&readbits); | |||
FD_SET (socketHandle, &readbits); | |||
struct timeval tv; | |||
tv.tv_sec = timeoutSeconds; | |||
tv.tv_usec = 0; | |||
if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) | |||
return String::empty; // (timeout) | |||
buffer.ensureSize (bytesRead + 8, true); | |||
char* const dest = (char*) buffer.getData() + bytesRead; | |||
if (recv (socketHandle, dest, 1, 0) == -1) | |||
return String::empty; | |||
const char lastByte = *dest; | |||
++bytesRead; | |||
if (lastByte == '\n') | |||
++numConsecutiveLFs; | |||
else if (lastByte != '\r') | |||
numConsecutiveLFs = 0; | |||
} | |||
const String header (String::fromUTF8 ((const char*) buffer.getData())); | |||
if (header.startsWithIgnoreCase ("HTTP/")) | |||
return header.trimEnd(); | |||
return String::empty; | |||
} | |||
//============================================================================== | |||
static bool decomposeURL (const String& url, | |||
String& host, String& path, int& port) | |||
{ | |||
if (! url.startsWithIgnoreCase ("http://")) | |||
return false; | |||
const int nextSlash = url.indexOfChar (7, '/'); | |||
int nextColon = url.indexOfChar (7, ':'); | |||
if (nextColon > nextSlash && nextSlash > 0) | |||
nextColon = -1; | |||
if (nextColon >= 0) | |||
{ | |||
host = url.substring (7, nextColon); | |||
if (nextSlash >= 0) | |||
port = url.substring (nextColon + 1, nextSlash).getIntValue(); | |||
else | |||
port = url.substring (nextColon + 1).getIntValue(); | |||
} | |||
else | |||
{ | |||
port = 80; | |||
if (nextSlash >= 0) | |||
host = url.substring (7, nextSlash); | |||
else | |||
host = url.substring (7); | |||
} | |||
if (nextSlash >= 0) | |||
path = url.substring (nextSlash); | |||
else | |||
path = "/"; | |||
return true; | |||
} | |||
//============================================================================== | |||
static const String findHeaderItem (const StringArray& lines, const String& itemName) | |||
{ | |||
for (int i = 0; i < lines.size(); ++i) | |||
if (lines[i].startsWithIgnoreCase (itemName)) | |||
return lines[i].substring (itemName.length()).trim(); | |||
return String::empty; | |||
} | |||
}; | |||
//============================================================================== | |||
void* juce_openInternetFile (const String& url, | |||
const String& headers, | |||
const MemoryBlock& postData, | |||
const bool isPost, | |||
URL::OpenStreamProgressCallback* callback, | |||
void* callbackContext, | |||
int timeOutMs) | |||
{ | |||
ScopedPointer<JUCE_HTTPSocketStream> s (new JUCE_HTTPSocketStream()); | |||
if (s->open (url, headers, postData, isPost, callback, callbackContext, timeOutMs)) | |||
return s.release(); | |||
return 0; | |||
} | |||
void juce_closeInternetFile (void* handle) | |||
{ | |||
delete static_cast <JUCE_HTTPSocketStream*> (handle); | |||
} | |||
int juce_readFromInternetFile (void* handle, void* buffer, int bytesToRead) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
return s != 0 ? s->read (buffer, bytesToRead) : 0; | |||
} | |||
int64 juce_getInternetFileContentLength (void* handle) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
if (s != 0) | |||
{ | |||
//xxx todo | |||
jassertfalse | |||
} | |||
return -1; | |||
} | |||
bool juce_getInternetFileHeaders (void* handle, StringPairArray& headers) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
if (s != 0) | |||
{ | |||
for (int i = 0; i < s->headerLines.size(); ++i) | |||
{ | |||
const String& headersEntry = s->headerLines[i]; | |||
const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); | |||
const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); | |||
const String previousValue (headers [key]); | |||
headers.set (key, previousValue.isEmpty() ? value : (previousValue + ";" + value)); | |||
} | |||
} | |||
} | |||
int juce_seekInInternetFile (void* handle, int newPosition) | |||
{ | |||
JUCE_HTTPSocketStream* const s = static_cast <JUCE_HTTPSocketStream*> (handle); | |||
return s != 0 ? s->readPosition : 0; | |||
} | |||
#endif |