root/src/libs/icon/shape/VectorPath.cpp
/*
 * Copyright 2006-2012, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 */

#include "VectorPath.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <agg_basics.h>
#include <agg_bounding_rect.h>
#include <agg_conv_curve.h>
#include <agg_curves.h>
#include <agg_math.h>

#ifdef ICON_O_MATIC
#       include <debugger.h>
#       include <typeinfo>
#endif // ICON_O_MATIC

#include <Message.h>
#include <TypeConstants.h>

#ifdef ICON_O_MATIC
#       include "support.h"

#       include "CommonPropertyIDs.h"
#       include "IconProperty.h"
#       include "Icons.h"
#       include "Property.h"
#       include "PropertyObject.h"
#endif // ICON_O_MATIC

#include "Transformable.h"

#define obj_new(type, n)                ((type *)malloc ((n) * sizeof(type)))
#define obj_renew(p, type, n)   ((type *)realloc ((void *)p, (n) * sizeof(type)))
#define obj_free                                free

#define ALLOC_CHUNKS 20

_USING_ICON_NAMESPACE


bool
get_path_storage(agg::path_storage& path, const control_point* points,
        int32 count, bool closed)
{
        if (count > 1) {
                path.move_to(points[0].point.x, points[0].point.y);

                for (int32 i = 1; i < count; i++) {
                        path.curve4(points[i - 1].point_out.x, points[i - 1].point_out.y,
                                points[i].point_in.x, points[i].point_in.y,
                                points[i].point.x, points[i].point.y);
                }
                if (closed) {
                        // curve from last to first control point
                        path.curve4(
                                points[count - 1].point_out.x, points[count - 1].point_out.y,
                                points[0].point_in.x, points[0].point_in.y,
                                points[0].point.x, points[0].point.y);
                        path.close_polygon();
                }

                return true;
        }
        return false;
}


// #pragma mark -


#ifdef ICON_O_MATIC
PathListener::PathListener()
{
}


PathListener::~PathListener()
{
}
#endif


// #pragma mark -


VectorPath::VectorPath()
        :
#ifdef ICON_O_MATIC
        BArchivable(),
        IconObject("<path>"),
        fListeners(20),
#endif
        fPath(NULL),
        fClosed(false),
        fPointCount(0),
        fAllocCount(0),
        fCachedBounds(0.0, 0.0, -1.0, -1.0)
{
}


VectorPath::VectorPath(const VectorPath& from)
        :
#ifdef ICON_O_MATIC
        BArchivable(),
        IconObject(from),
        fListeners(20),
#endif
        fPath(NULL),
        fClosed(false),
        fPointCount(0),
        fAllocCount(0),
        fCachedBounds(0.0, 0.0, -1.0, -1.0)
{
        *this = from;
}


VectorPath::VectorPath(BMessage* archive)
        :
#ifdef ICON_O_MATIC
        BArchivable(),
        IconObject(archive),
        fListeners(20),
#endif
        fPath(NULL),
        fClosed(false),
        fPointCount(0),
        fAllocCount(0),
        fCachedBounds(0.0, 0.0, -1.0, -1.0)
{
        if (!archive)
                return;

        type_code typeFound;
        int32 countFound;
        if (archive->GetInfo("point", &typeFound, &countFound) >= B_OK
                && typeFound == B_POINT_TYPE
                && _SetPointCount(countFound)) {
                memset((void*)fPath, 0, fAllocCount * sizeof(control_point));

                BPoint point;
                BPoint pointIn;
                BPoint pointOut;
                bool connected;
                for (int32 i = 0; i < fPointCount
                                && archive->FindPoint("point", i, &point) >= B_OK
                                && archive->FindPoint("point in", i, &pointIn) >= B_OK
                                && archive->FindPoint("point out", i, &pointOut) >= B_OK
                                && archive->FindBool("connected", i, &connected) >= B_OK; i++) {
                        fPath[i].point = point;
                        fPath[i].point_in = pointIn;
                        fPath[i].point_out = pointOut;
                        fPath[i].connected = connected;
                }
        }
        if (archive->FindBool("path closed", &fClosed) < B_OK)
                fClosed = false;

}


VectorPath::~VectorPath()
{
        if (fPath)
                obj_free(fPath);

#ifdef ICON_O_MATIC
        if (fListeners.CountItems() > 0) {
                PathListener* listener = (PathListener*)fListeners.ItemAt(0);
                char message[512];
                sprintf(message, "VectorPath::~VectorPath() - "
                                 "there are still listeners attached! %p/%s",
                                 listener, typeid(*listener).name());
                debugger(message);
        }
#endif
}


// #pragma mark -


#ifdef ICON_O_MATIC

PropertyObject*
VectorPath::MakePropertyObject() const
{
        PropertyObject* object = IconObject::MakePropertyObject();
        if (!object)
                return NULL;

        // closed
        object->AddProperty(new BoolProperty(PROPERTY_CLOSED, fClosed));

        // archived path
        BMessage* archive = new BMessage();
        if (Archive(archive) == B_OK) {
                object->AddProperty(new IconProperty(PROPERTY_PATH,
                        kPathPropertyIconBits, kPathPropertyIconWidth,
                        kPathPropertyIconHeight, kPathPropertyIconFormat, archive));
        }

        return object;
}


bool
VectorPath::SetToPropertyObject(const PropertyObject* object)
{
        AutoNotificationSuspender _(this);
        IconObject::SetToPropertyObject(object);

        SetClosed(object->Value(PROPERTY_CLOSED, fClosed));

        IconProperty* pathProperty = dynamic_cast<IconProperty*>(
                object->FindProperty(PROPERTY_PATH));
        if (pathProperty && pathProperty->Message()) {
                VectorPath archivedPath(pathProperty->Message());
                *this = archivedPath;
        }

        return HasPendingNotifications();
}


status_t
VectorPath::Archive(BMessage* into, bool deep) const
{
        status_t ret = IconObject::Archive(into, deep);
        if (ret != B_OK)
                return ret;

        if (fPointCount > 0) {
                // improve BMessage efficency by preallocating storage for all points
                // with the first call
                ret = into->AddData("point", B_POINT_TYPE, &fPath[0].point,
                        sizeof(BPoint), true, fPointCount);
                if (ret >= B_OK) {
                        ret = into->AddData("point in", B_POINT_TYPE, &fPath[0].point_in,
                                sizeof(BPoint), true, fPointCount);
                }
                if (ret >= B_OK) {
                        ret = into->AddData("point out", B_POINT_TYPE, &fPath[0].point_out,
                                sizeof(BPoint), true, fPointCount);
                }
                if (ret >= B_OK) {
                        ret = into->AddData("connected", B_BOOL_TYPE, &fPath[0].connected,
                                sizeof(bool), true, fPointCount);
                }

                // add the rest of the points
                for (int32 i = 1; i < fPointCount && ret >= B_OK; i++) {
                        ret = into->AddData("point", B_POINT_TYPE, &fPath[i].point,
                                sizeof(BPoint));
                        if (ret >= B_OK) {
                                ret = into->AddData("point in", B_POINT_TYPE, &fPath[i].point_in,
                                        sizeof(BPoint));
                        }
                        if (ret >= B_OK) {
                                ret = into->AddData("point out", B_POINT_TYPE,
                                        &fPath[i].point_out, sizeof(BPoint));
                        }
                        if (ret >= B_OK) {
                                ret = into->AddData("connected", B_BOOL_TYPE,
                                        &fPath[i].connected, sizeof(bool));
                        }
                }
        }

        if (ret >= B_OK)
                ret = into->AddBool("path closed", fClosed);
        else
                fprintf(stderr, "failed adding points!\n");

        if (ret < B_OK)
                fprintf(stderr, "failed adding close!\n");

        // finish off
        if (ret < B_OK)
                ret = into->AddString("class", "VectorPath");

        return ret;
}

#endif // ICON_O_MATIC


// #pragma mark -


VectorPath&
VectorPath::operator=(const VectorPath& from)
{
        _SetPointCount(from.fPointCount);
        fClosed = from.fClosed;
        if (fPath) {
                memcpy((void*)fPath, from.fPath, fPointCount * sizeof(control_point));
                fCachedBounds = from.fCachedBounds;
        } else {
                fprintf(stderr, "VectorPath() -> allocation failed in operator=!\n");
                fAllocCount = 0;
                fPointCount = 0;
                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
        }
        Notify();

        return *this;
}


bool
VectorPath::operator==(const VectorPath& other) const
{
        if (fClosed != other.fClosed)
                return false;

        if (fPointCount != other.fPointCount)
                return false;

        if (fPath == NULL && other.fPath == NULL)
                return true;

        if (fPath == NULL || other.fPath == NULL)
                return false;

        for (int32 i = 0; i < fPointCount; i++) {
                if (fPath[i].point != other.fPath[i].point
                        || fPath[i].point_in != other.fPath[i].point_in
                        || fPath[i].point_out != other.fPath[i].point_out
                        || fPath[i].connected != other.fPath[i].connected) {
                        return false;
                }
        }

        return true;
}


void
VectorPath::MakeEmpty()
{
        _SetPointCount(0);
}


// #pragma mark -


bool
VectorPath::AddPoint(BPoint point)
{
        int32 index = fPointCount;

        if (_SetPointCount(fPointCount + 1)) {
                _SetPoint(index, point);
                _NotifyPointAdded(index);
                return true;
        }

        return false;
}


bool
VectorPath::AddPoint(const BPoint& point, const BPoint& pointIn,
        const BPoint& pointOut, bool connected)
{
        int32 index = fPointCount;

        if (_SetPointCount(fPointCount + 1)) {
                _SetPoint(index, point, pointIn, pointOut, connected);
                _NotifyPointAdded(index);
                return true;
        }

        return false;
}


bool
VectorPath::AddPoint(BPoint point, int32 index)
{
        if (index < 0)
                index = 0;
        if (index > fPointCount)
                index = fPointCount;

        if (_SetPointCount(fPointCount + 1)) {
                // handle insert
                if (index < fPointCount - 1) {
                        for (int32 i = fPointCount; i > index; i--) {
                                fPath[i].point = fPath[i - 1].point;
                                fPath[i].point_in = fPath[i - 1].point_in;
                                fPath[i].point_out = fPath[i - 1].point_out;
                                fPath[i].connected = fPath[i - 1].connected;
                        }
                }
                _SetPoint(index, point);
                _NotifyPointAdded(index);
                return true;
        }
        return false;
}


bool
VectorPath::RemovePoint(int32 index)
{
        if (index >= 0 && index < fPointCount) {
                if (index < fPointCount - 1) {
                        // move points
                        for (int32 i = index; i < fPointCount - 1; i++) {
                                fPath[i].point = fPath[i + 1].point;
                                fPath[i].point_in = fPath[i + 1].point_in;
                                fPath[i].point_out = fPath[i + 1].point_out;
                                fPath[i].connected = fPath[i + 1].connected;
                        }
                }
                fPointCount -= 1;

                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

                _NotifyPointRemoved(index);
                return true;
        }
        return false;
}


bool
VectorPath::SetPoint(int32 index, BPoint point)
{
        if (index == fPointCount)
                index = 0;
        if (index >= 0 && index < fPointCount) {
                BPoint offset = point - fPath[index].point;
                fPath[index].point = point;
                fPath[index].point_in += offset;
                fPath[index].point_out += offset;

                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

                _NotifyPointChanged(index);
                return true;
        }
        return false;
}


bool
VectorPath::SetPoint(int32 index, BPoint point, BPoint pointIn, BPoint pointOut,
        bool connected)
{
        if (index == fPointCount)
                index = 0;
        if (index >= 0 && index < fPointCount) {
                fPath[index].point = point;
                fPath[index].point_in = pointIn;
                fPath[index].point_out = pointOut;
                fPath[index].connected = connected;

                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

                _NotifyPointChanged(index);
                return true;
        }
        return false;
}


bool
VectorPath::SetPointIn(int32 i, BPoint point)
{
        if (i == fPointCount)
                i = 0;
        if (i >= 0 && i < fPointCount) {
                // first, set the "in" point
                fPath[i].point_in = point;
                // now see what to do about the "out" point
                if (fPath[i].connected) {
                        // keep all three points in one line
                        BPoint v = fPath[i].point - fPath[i].point_in;
                        float distIn = sqrtf(v.x * v.x + v.y * v.y);
                        if (distIn > 0.0) {
                                float distOut = agg::calc_distance(
                                        fPath[i].point.x, fPath[i].point.y,
                                        fPath[i].point_out.x, fPath[i].point_out.y);
                                float scale = (distIn + distOut) / distIn;
                                v.x *= scale;
                                v.y *= scale;
                                fPath[i].point_out = fPath[i].point_in + v;
                        }
                }

                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

                _NotifyPointChanged(i);
                return true;
        }
        return false;
}


bool
VectorPath::SetPointOut(int32 i, BPoint point, bool mirrorDist)
{
        if (i == fPointCount)
                i = 0;
        if (i >= 0 && i < fPointCount) {
                // first, set the "out" point
                fPath[i].point_out = point;
                // now see what to do about the "out" point
                if (mirrorDist) {
                        // mirror "in" point around main control point
                        BPoint v = fPath[i].point - fPath[i].point_out;
                        fPath[i].point_in = fPath[i].point + v;
                } else if (fPath[i].connected) {
                        // keep all three points in one line
                        BPoint v = fPath[i].point - fPath[i].point_out;
                        float distOut = sqrtf(v.x * v.x + v.y * v.y);
                        if (distOut > 0.0) {
                                float distIn = agg::calc_distance(
                                        fPath[i].point.x, fPath[i].point.y,
                                        fPath[i].point_in.x, fPath[i].point_in.y);
                                float scale = (distIn + distOut) / distOut;
                                v.x *= scale;
                                v.y *= scale;
                                fPath[i].point_in = fPath[i].point_out + v;
                        }
                }

                fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

                _NotifyPointChanged(i);
                return true;
        }
        return false;
}


bool
VectorPath::SetInOutConnected(int32 index, bool connected)
{
        if (index >= 0 && index < fPointCount) {
                fPath[index].connected = connected;
                _NotifyPointChanged(index);
                return true;
        }
        return false;
}


// #pragma mark -


bool
VectorPath::GetPointAt(int32 index, BPoint& point) const
{
        if (index == fPointCount)
                index = 0;
        if (index >= 0 && index < fPointCount) {
                point = fPath[index].point;
                return true;
        }
        return false;
}


bool
VectorPath::GetPointInAt(int32 index, BPoint& point) const
{
        if (index == fPointCount)
                index = 0;
        if (index >= 0 && index < fPointCount) {
                point = fPath[index].point_in;
                return true;
        }
        return false;
}


bool
VectorPath::GetPointOutAt(int32 index, BPoint& point) const
{
        if (index == fPointCount)
                index = 0;
        if (index >= 0 && index < fPointCount) {
                point = fPath[index].point_out;
                return true;
        }
        return false;
}


bool
VectorPath::GetPointsAt(int32 index, BPoint& point, BPoint& pointIn,
        BPoint& pointOut, bool* connected) const
{
        if (index >= 0 && index < fPointCount) {
                point = fPath[index].point;
                pointIn = fPath[index].point_in;
                pointOut = fPath[index].point_out;

                if (connected)
                        *connected = fPath[index].connected;

                return true;
        }
        return false;
}


int32
VectorPath::CountPoints() const
{
        return fPointCount;
}


// #pragma mark -


#ifdef ICON_O_MATIC

static float
distance_to_curve(const BPoint& p, const BPoint& a, const BPoint& aOut,
        const BPoint& bIn, const BPoint& b)
{
        agg::curve4_inc curve(a.x, a.y, aOut.x, aOut.y, bIn.x, bIn.y, b.x, b.y);

        float segDist = FLT_MAX;
        double x1, y1, x2, y2;
        unsigned cmd = curve.vertex(&x1, &y1);
        while (!agg::is_stop(cmd)) {
                cmd = curve.vertex(&x2, &y2);
                // first figure out if point is between segment start and end points
                double a = agg::calc_distance(p.x, p.y, x2, y2);
                double b = agg::calc_distance(p.x, p.y, x1, y1);

                float currentDist = min_c(a, b);

                if (a > 0.0 && b > 0.0) {
                        double c = agg::calc_distance(x1, y1, x2, y2);

                        double alpha = acos((b * b + c * c - a * a) / (2 * b * c));
                        double beta = acos((a * a + c * c - b * b) / (2 * a * c));

                        if (alpha <= M_PI_2 && beta <= M_PI_2) {
                                currentDist = fabs(agg::calc_line_point_distance(x1, y1, x2, y2,
                                        p.x, p.y));
                        }
                }

                if (currentDist < segDist)
                        segDist = currentDist;

                x1 = x2;
                y1 = y2;
        }
        return segDist;
}


bool
VectorPath::GetDistance(BPoint p, float* distance, int32* index) const
{
        if (fPointCount > 1) {
                // generate a curve for each segment of the path
                // then iterate over the segments of the curve measuring the distance
                *distance = FLT_MAX;

                for (int32 i = 0; i < fPointCount - 1; i++) {
                        float segDist = distance_to_curve(p, fPath[i].point,
                                fPath[i].point_out, fPath[i + 1].point_in, fPath[i + 1].point);
                        if (segDist < *distance) {
                                *distance = segDist;
                                *index = i + 1;
                        }
                }
                if (fClosed) {
                        float segDist = distance_to_curve(p, fPath[fPointCount - 1].point,
                                fPath[fPointCount - 1].point_out, fPath[0].point_in,
                                fPath[0].point);
                        if (segDist < *distance) {
                                *distance = segDist;
                                *index = fPointCount;
                        }
                }
                return true;
        }
        return false;
}


bool
VectorPath::FindBezierScale(int32 index, BPoint point, double* scale) const
{
        if (index >= 0 && index < fPointCount && scale) {
                int maxStep = 1000;

                double t = 0.0;
                double dt = 1.0 / maxStep;

                *scale = 0.0;
                double min = FLT_MAX;

                BPoint curvePoint;
                for (int step = 1; step < maxStep; step++) {
                        t += dt;

                        GetPoint(index, t, curvePoint);
                        double d = agg::calc_distance(curvePoint.x, curvePoint.y,
                                point.x, point.y);

                        if (d < min) {
                                min = d;
                                *scale = t;
                        }
                }
                return true;
        }
        return false;
}


bool
VectorPath::GetPoint(int32 index, double t, BPoint& point) const
{
        if (index >= 0 && index < fPointCount) {
                double t1 = (1 - t) * (1 - t) * (1 - t);
                double t2 = (1 - t) * (1 - t) * t * 3;
                double t3 = (1 - t) * t * t * 3;
                double t4 = t * t * t;

                if (index < fPointCount - 1) {
                        point.x = fPath[index].point.x * t1 + fPath[index].point_out.x * t2
                                + fPath[index + 1].point_in.x * t3
                                + fPath[index + 1].point.x * t4;

                        point.y = fPath[index].point.y * t1 + fPath[index].point_out.y * t2
                                + fPath[index + 1].point_in.y * t3
                                + fPath[index + 1].point.y * t4;
                } else if (fClosed) {
                        point.x = fPath[fPointCount - 1].point.x * t1
                                + fPath[fPointCount - 1].point_out.x * t2
                                + fPath[0].point_in.x * t3 + fPath[0].point.x * t4;

                        point.y = fPath[fPointCount - 1].point.y * t1
                                + fPath[fPointCount - 1].point_out.y * t2
                                + fPath[0].point_in.y * t3 + fPath[0].point.y * t4;
                }

                return true;
        }
        return false;
}

#endif // ICON_O_MATIC


void
VectorPath::SetClosed(bool closed)
{
        if (fClosed != closed) {
                fClosed = closed;
                _NotifyClosedChanged();
                Notify();
        }
}


BRect
VectorPath::Bounds() const
{
        // the bounds of the actual curves, not the control points!
        if (!fCachedBounds.IsValid())
                 fCachedBounds = _Bounds();
        return fCachedBounds;
}


BRect
VectorPath::_Bounds() const
{
        agg::path_storage path;

        BRect b;
        if (get_path_storage(path, fPath, fPointCount, fClosed)) {
                agg::conv_curve<agg::path_storage> curve(path);

                uint32 pathID[1];
                pathID[0] = 0;
                double left, top, right, bottom;

                agg::bounding_rect(curve, pathID, 0, 1, &left, &top, &right, &bottom);

                b.Set(left, top, right, bottom);
        } else if (fPointCount == 1) {
                b.Set(fPath[0].point.x, fPath[0].point.y, fPath[0].point.x,
                        fPath[0].point.y);
        } else {
                b.Set(0.0, 0.0, -1.0, -1.0);
        }
        return b;
}


BRect
VectorPath::ControlPointBounds() const
{
        if (fPointCount > 0) {
                BRect r(fPath[0].point, fPath[0].point);
                for (int32 i = 0; i < fPointCount; i++) {
                        // include point
                        r.left = min_c(r.left, fPath[i].point.x);
                        r.top = min_c(r.top, fPath[i].point.y);
                        r.right = max_c(r.right, fPath[i].point.x);
                        r.bottom = max_c(r.bottom, fPath[i].point.y);
                        // include "in" point
                        r.left = min_c(r.left, fPath[i].point_in.x);
                        r.top = min_c(r.top, fPath[i].point_in.y);
                        r.right = max_c(r.right, fPath[i].point_in.x);
                        r.bottom = max_c(r.bottom, fPath[i].point_in.y);
                        // include "out" point
                        r.left = min_c(r.left, fPath[i].point_out.x);
                        r.top = min_c(r.top, fPath[i].point_out.y);
                        r.right = max_c(r.right, fPath[i].point_out.x);
                        r.bottom = max_c(r.bottom, fPath[i].point_out.y);
                }
                return r;
        }
        return BRect(0.0, 0.0, -1.0, -1.0);
}


void
VectorPath::Iterate(Iterator* iterator, float smoothScale) const
{
        if (fPointCount > 1) {
                // generate a curve for each segment of the path
                // then iterate over the segments of the curve
                agg::curve4_inc curve;
                curve.approximation_scale(smoothScale);

                for (int32 i = 0; i < fPointCount - 1; i++) {
                        iterator->MoveTo(fPath[i].point);
                        curve.init(fPath[i].point.x, fPath[i].point.y,
                                fPath[i].point_out.x, fPath[i].point_out.y,
                                fPath[i + 1].point_in.x, fPath[i + 1].point_in.y,
                                fPath[i + 1].point.x, fPath[i + 1].point.y);

                        double x, y;
                        unsigned cmd = curve.vertex(&x, &y);
                        while (!agg::is_stop(cmd)) {
                                BPoint p(x, y);
                                iterator->LineTo(p);
                                cmd = curve.vertex(&x, &y);
                        }
                }
                if (fClosed) {
                        iterator->MoveTo(fPath[fPointCount - 1].point);
                        curve.init(fPath[fPointCount - 1].point.x,
                                fPath[fPointCount - 1].point.y,
                                fPath[fPointCount - 1].point_out.x,
                                fPath[fPointCount - 1].point_out.y,
                                fPath[0].point_in.x, fPath[0].point_in.y,
                                fPath[0].point.x, fPath[0].point.y);

                        double x, y;
                        unsigned cmd = curve.vertex(&x, &y);
                        while (!agg::is_stop(cmd)) {
                                BPoint p(x, y);
                                iterator->LineTo(p);
                                cmd = curve.vertex(&x, &y);
                        }
                }
        }
}


void
VectorPath::CleanUp()
{
        if (fPointCount == 0)
                return;

        bool notify = false;

        // remove last point if it is coincident with the first
        if (fClosed && fPointCount >= 1) {
                if (fPath[0].point == fPath[fPointCount - 1].point) {
                        fPath[0].point_in = fPath[fPointCount - 1].point_in;
                        _SetPointCount(fPointCount - 1);
                        notify = true;
                }
        }

        for (int32 i = 0; i < fPointCount; i++) {
                // check for unnecessary, duplicate points
                if (i > 0) {
                        if (fPath[i - 1].point == fPath[i].point
                                && fPath[i - 1].point == fPath[i - 1].point_out
                                && fPath[i].point == fPath[i].point_in) {
                                // the previous point can be removed
                                BPoint in = fPath[i - 1].point_in;
                                if (RemovePoint(i - 1)) {
                                        i--;
                                        fPath[i].point_in = in;
                                        notify = true;
                                }
                        }
                }
                // re-establish connections of in-out control points if
                // they line up with the main control point
                if (fPath[i].point_in == fPath[i].point_out
                        || fPath[i].point == fPath[i].point_out
                        || fPath[i].point == fPath[i].point_in
                        || (fabs(agg::calc_line_point_distance(fPath[i].point_in.x,
                                        fPath[i].point_in.y, fPath[i].point.x, fPath[i].point.y,
                                        fPath[i].point_out.x, fPath[i].point_out.y)) < 0.01
                                && fabs(agg::calc_line_point_distance(fPath[i].point_out.x,
                                        fPath[i].point_out.y, fPath[i].point.x, fPath[i].point.y,
                                        fPath[i].point_in.x, fPath[i].point_in.y)) < 0.01)) {
                        fPath[i].connected = true;
                        notify = true;
                }
        }

        if (notify)
                _NotifyPathChanged();
}


void
VectorPath::Reverse()
{
        VectorPath temp(*this);
        int32 index = 0;
        for (int32 i = fPointCount - 1; i >= 0; i--) {
                temp.SetPoint(index, fPath[i].point, fPath[i].point_out,
                        fPath[i].point_in, fPath[i].connected);
                index++;
        }
        *this = temp;

        _NotifyPathReversed();
}


void
VectorPath::ApplyTransform(const Transformable& transform)
{
        if (transform.IsIdentity())
                return;

        for (int32 i = 0; i < fPointCount; i++) {
                transform.Transform(&(fPath[i].point));
                transform.Transform(&(fPath[i].point_out));
                transform.Transform(&(fPath[i].point_in));
        }

        _NotifyPathChanged();
}


void
VectorPath::PrintToStream() const
{
        for (int32 i = 0; i < fPointCount; i++) {
                printf("point %" B_PRId32 ": (%f, %f) -> (%f, %f) -> (%f, %f) (%d)\n", i,
                        fPath[i].point_in.x, fPath[i].point_in.y,
                        fPath[i].point.x, fPath[i].point.y,
                        fPath[i].point_out.x, fPath[i].point_out.y, fPath[i].connected);
        }
}


bool
VectorPath::GetAGGPathStorage(agg::path_storage& path) const
{
        return get_path_storage(path, fPath, fPointCount, fClosed);
}


// #pragma mark -


#ifdef ICON_O_MATIC

bool
VectorPath::AddListener(PathListener* listener)
{
        if (listener && !fListeners.HasItem((void*)listener))
                return fListeners.AddItem((void*)listener);
        return false;
}


bool
VectorPath::RemoveListener(PathListener* listener)
{
        return fListeners.RemoveItem((void*)listener);
}


int32
VectorPath::CountListeners() const
{
        return fListeners.CountItems();
}


PathListener*
VectorPath::ListenerAtFast(int32 index) const
{
        return (PathListener*)fListeners.ItemAtFast(index);
}

#endif // ICON_O_MATIC


// #pragma mark -


void
VectorPath::_SetPoint(int32 index, BPoint point)
{
        fPath[index].point = point;
        fPath[index].point_in = point;
        fPath[index].point_out = point;

        fPath[index].connected = true;

        fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
}


void
VectorPath::_SetPoint(int32 index, const BPoint& point, const BPoint& pointIn,
        const BPoint& pointOut, bool connected)
{
        fPath[index].point = point;
        fPath[index].point_in = pointIn;
        fPath[index].point_out = pointOut;

        fPath[index].connected = connected;

        fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
}


// #pragma mark -


bool
VectorPath::_SetPointCount(int32 count)
{
        // handle reallocation if we run out of room
        if (count >= fAllocCount) {
                fAllocCount = ((count) / ALLOC_CHUNKS + 1) * ALLOC_CHUNKS;
                if (fPath)
                        fPath = obj_renew(fPath, control_point, fAllocCount);
                else
                        fPath = obj_new(control_point, fAllocCount);

                if (fPath != NULL) {
                        memset((void*)(fPath + fPointCount), 0,
                                (fAllocCount - fPointCount) * sizeof(control_point));
                }
        }

        // update point count
        if (fPath) {
                fPointCount = count;
        } else {
                // reallocation might have failed
                fPointCount = 0;
                fAllocCount = 0;
                fprintf(stderr, "VectorPath::_SetPointCount(%" B_PRId32 ") - allocation failed!\n",
                        count);
        }

        fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);

        return fPath != NULL;
}


// #pragma mark -


#ifdef ICON_O_MATIC

void
VectorPath::_NotifyPointAdded(int32 index) const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PointAdded(index);
        }
}


void
VectorPath::_NotifyPointChanged(int32 index) const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PointChanged(index);
        }
}


void
VectorPath::_NotifyPointRemoved(int32 index) const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PointRemoved(index);
        }
}


void
VectorPath::_NotifyPathChanged() const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PathChanged();
        }
}


void
VectorPath::_NotifyClosedChanged() const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PathClosedChanged();
        }
}


void
VectorPath::_NotifyPathReversed() const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
                listener->PathReversed();
        }
}

#endif // ICON_O_MATIC