root/src/libs/icon/shape/Shape.cpp
/*
 * Copyright 2006, 2023, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Zardshard
 */

#include "Shape.h"

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

#include <new>
#include <limits.h>
#include <stdio.h>

#include "agg_bounding_rect.h"

#ifdef ICON_O_MATIC
# include "CommonPropertyIDs.h"
# include "Property.h"
# include "PropertyObject.h"
#endif // ICON_O_MATIC
#include "Container.h"
#include "PathTransformer.h"
#include "Style.h"
#include "TransformerFactory.h"
#include "VectorPath.h"

using std::nothrow;


#ifdef ICON_O_MATIC
ShapeListener::ShapeListener()
{
}


ShapeListener::~ShapeListener()
{
}
#endif // ICON_O_MATIC


// #pragma mark -


Shape::Shape(::Style* style)
#ifdef ICON_O_MATIC
        : IconObject("<shape>"),
          Transformable(),
          Observer(),
          ContainerListener<VectorPath>(),
#else
        : Transformable(),
#endif

          fPaths(new (nothrow) Container<VectorPath>(false)),
          fStyle(NULL),

          fPathSource(fPaths),
          fTransformers(true),
          fNeedsUpdate(true),

          fLastBounds(0, 0, -1, -1),

          fHinting(false)

#ifdef ICON_O_MATIC
        , fListeners(8)
#endif
{
        SetStyle(style);

        fTransformers.AddListener(this);

#ifdef ICON_O_MATIC
        if (fPaths)
                fPaths->AddListener(this);
#endif
}


Shape::Shape(const Shape& other)
#ifdef ICON_O_MATIC
        : IconObject(other),
          Transformable(other),
          Observer(),
          ContainerListener<VectorPath>(),
#else
        : Transformable(other),
#endif

          fPaths(new (nothrow) Container<VectorPath>(false)),
          fStyle(NULL),

          fPathSource(fPaths),
          fTransformers(true),
          fNeedsUpdate(true),

          fLastBounds(0, 0, -1, -1),

          fHinting(false)

#ifdef ICON_O_MATIC
        , fListeners(8)
#endif
{
        SetStyle(other.fStyle);

        fTransformers.AddListener(this);

        if (fPaths) {
#ifdef ICON_O_MATIC
                fPaths->AddListener(this);
#endif

                // copy the path references from
                // the other shape
                if (other.fPaths) {
                        int32 count = other.fPaths->CountItems();
                        for (int32 i = 0; i < count; i++) {
                                if (!fPaths->AddItem(other.fPaths->ItemAtFast(i)))
                                        break;
                        }
                }
        }
        // clone vertex transformers
        int32 count = other.Transformers()->CountItems();
        for (int32 i = 0; i < count; i++) {
                Transformer* original = other.Transformers()->ItemAtFast(i);
                Transformer* cloned = original->Clone();
                if (!fTransformers.AddItem(cloned)) {
                        delete cloned;
                        break;
                }
        }
}


Shape::~Shape()
{
        fPaths->MakeEmpty();
#ifdef ICON_O_MATIC
        fPaths->RemoveListener(this);
#endif
        delete fPaths;

        fTransformers.MakeEmpty();
        fTransformers.RemoveListener(this);

        SetStyle(NULL);
}


// #pragma mark -


status_t
Shape::Unarchive(BMessage* archive)
{
#ifdef ICON_O_MATIC
        // IconObject properties
        status_t ret = IconObject::Unarchive(archive);
        if (ret < B_OK)
                return ret;
#else
        status_t ret;
#endif

        // hinting
        if (archive->FindBool("hinting", &fHinting) < B_OK)
                fHinting = false;

        // recreate transformers
        BMessage transformerArchive;
        for (int32 i = 0;
                 archive->FindMessage("transformer", i,
                         &transformerArchive) == B_OK;
                 i++) {
                Transformer* transformer
                        = TransformerFactory::TransformerFor(
                                &transformerArchive, VertexSource(), this);
                if (!transformer || !fTransformers.AddItem(transformer)) {
                        delete transformer;
                }
        }

        // read transformation
        int32 size = Transformable::matrix_size;
        const void* matrix;
        ssize_t dataSize = size * sizeof(double);
        ret = archive->FindData("transformation", B_DOUBLE_TYPE,
                &matrix, &dataSize);
        if (ret == B_OK && dataSize == (ssize_t)(size * sizeof(double)))
                LoadFrom((const double*)matrix);

        return B_OK;
}


#ifdef ICON_O_MATIC
status_t
Shape::Archive(BMessage* into, bool deep) const
{
        status_t ret = IconObject::Archive(into, deep);

        // hinting
        if (ret == B_OK)
                ret = into->AddBool("hinting", fHinting);

        // transformers
        if (ret == B_OK) {
                int32 count = fTransformers.CountItems();
                for (int32 i = 0; i < count; i++) {
                        Transformer* transformer = fTransformers.ItemAtFast(i);
                        BMessage transformerArchive;
                        ret = transformer->Archive(&transformerArchive);
                        if (ret == B_OK)
                                ret = into->AddMessage("transformer", &transformerArchive);
                        if (ret < B_OK)
                                break;
                }
        }

        // transformation
        if (ret == B_OK) {
                int32 size = Transformable::matrix_size;
                double matrix[size];
                StoreTo(matrix);
                ret = into->AddData("transformation", B_DOUBLE_TYPE,
                        matrix, size * sizeof(double));
        }

        return ret;
}


PropertyObject*
Shape::MakePropertyObject() const
{
        PropertyObject* object = IconObject::MakePropertyObject();
        return object;
}


bool
Shape::SetToPropertyObject(const PropertyObject* object)
{
        IconObject::SetToPropertyObject(object);
        return true;
}


// #pragma mark -


void
Shape::TransformationChanged()
{
        // TODO: notify appearance change
        _NotifyRerender();
}


// #pragma mark -


void
Shape::ObjectChanged(const Observable* object)
{
        // simply pass on the event for now
        // (a path, transformer or the style changed,
        // the shape needs to be re-rendered)
        _NotifyRerender();
}


// #pragma mark -


void
Shape::ItemAdded(VectorPath* path, int32 index)
{
        path->AcquireReference();
        path->AddListener(this);
        _NotifyRerender();
}


void
Shape::ItemRemoved(VectorPath* path)
{
        path->RemoveListener(this);
        _NotifyRerender();
        path->ReleaseReference();
}


// #pragma mark -


void
Shape::PointAdded(int32 index)
{
        _NotifyRerender();
}


void
Shape::PointRemoved(int32 index)
{
        _NotifyRerender();
}


void
Shape::PointChanged(int32 index)
{
        _NotifyRerender();
}


void
Shape::PathChanged()
{
        _NotifyRerender();
}


void
Shape::PathClosedChanged()
{
        _NotifyRerender();
}


void
Shape::PathReversed()
{
        _NotifyRerender();
}
#endif // ICON_O_MATIC


// #pragma mark -


void
Shape::ItemAdded(Transformer* transformer, int32 index)
{
#ifdef ICON_O_MATIC
        transformer->AddObserver(this);

        // TODO: merge Observable and ShapeListener interface
        _NotifyRerender();
#else
        fNeedsUpdate = true;
#endif
}


void
Shape::ItemRemoved(Transformer* transformer)
{
#ifdef ICON_O_MATIC
        transformer->RemoveObserver(this);

        _NotifyRerender();
#else
        fNeedsUpdate = true;
#endif
}


// #pragma mark -


status_t
Shape::InitCheck() const
{
        return fPaths ? B_OK : B_NO_MEMORY;
}


// #pragma mark -


void
Shape::SetStyle(::Style* style)
{
        if (fStyle == style)
                return;

#ifdef ICON_O_MATIC
        if (fStyle) {
                fStyle->RemoveObserver(this);
                fStyle->ReleaseReference();
        }
        ::Style* oldStyle = fStyle;
#endif

        fStyle = style;

#ifdef ICON_O_MATIC
        if (fStyle) {
                fStyle->AcquireReference();
                fStyle->AddObserver(this);
        }

        _NotifyStyleChanged(oldStyle, fStyle);
#endif
}


// #pragma mark -


BRect
Shape::Bounds(bool updateLast) const
{
        // TODO: what about sub-paths?!?
        // the problem is that the path ids are
        // nowhere stored while converting VectorPath
        // to agg::path_storage, but it is also unclear
        // if those would mean anything later on in
        // the Transformer pipeline
        uint32 pathID[1];
        pathID[0] = 0;
        double left, top, right, bottom;

        ::VertexSource& source = const_cast<Shape*>(this)->VertexSource();
        agg::conv_transform< ::VertexSource, Transformable>
                transformedSource(source, *this);
        agg::bounding_rect(transformedSource, pathID, 0, 1,
                &left, &top, &right, &bottom);

        BRect bounds(left, top, right, bottom);

        if (updateLast)
                fLastBounds = bounds;

        return bounds;
}


::VertexSource&
Shape::VertexSource()
{
        ::VertexSource* source = &fPathSource;

        int32 count = fTransformers.CountItems();
        for (int32 i = 0; i < count; i++) {
                PathTransformer* t = dynamic_cast<PathTransformer*>(fTransformers.ItemAtFast(i));
                if (t != NULL) {
                        t->SetSource(*source);
                        source = t;
                }
        }

        if (fNeedsUpdate) {
                fPathSource.Update(source->WantsOpenPaths(),
                        source->ApproximationScale());
                fNeedsUpdate = false;
        }

        return *source;
}


void
Shape::SetGlobalScale(double scale)
{
        fPathSource.SetGlobalScale(scale);
}


// #pragma mark -


#ifdef ICON_O_MATIC
bool
Shape::AddListener(ShapeListener* listener)
{
        if (listener && !fListeners.HasItem((void*)listener))
                return fListeners.AddItem((void*)listener);
        return false;
}


bool
Shape::RemoveListener(ShapeListener* listener)
{
        return fListeners.RemoveItem((void*)listener);
}


// #pragma mark -


void
Shape::_NotifyStyleChanged(::Style* oldStyle, ::Style* newStyle) const
{
        BList listeners(fListeners);
        int32 count = listeners.CountItems();
        for (int32 i = 0; i < count; i++) {
                ShapeListener* listener
                        = (ShapeListener*)listeners.ItemAtFast(i);
                listener->StyleChanged(oldStyle, newStyle);
        }
        // TODO: merge Observable and ShapeListener interface
        _NotifyRerender();
}


void
Shape::_NotifyRerender() const
{
        fNeedsUpdate = true;
        Notify();
}
#endif // ICON_O_MATIC


void
Shape::SetHinting(bool hinting)
{
        if (fHinting == hinting)
                return;

        fHinting = hinting;
        Notify();
}