root/src/kits/interface/layouter/CollapsingLayouter.cpp
/*
 * Copyright 2011, Haiku, Inc.
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include "CollapsingLayouter.h"

#include "ComplexLayouter.h"
#include "OneElementLayouter.h"
#include "SimpleLayouter.h"

#include <ObjectList.h>
#include <Size.h>


class CollapsingLayouter::ProxyLayoutInfo : public LayoutInfo {
public:
        ProxyLayoutInfo(LayoutInfo* target, int32 elementCount)
                :
                fTarget(target),
                fElementCount(elementCount)
        {
                fElements = new int32[elementCount];
        }

        ~ProxyLayoutInfo()
        {
                delete[] fElements;
                delete fTarget;
        }

        void
        LayoutTarget(Layouter* layouter, float size)
        {
                if (layouter)
                        layouter->Layout(fTarget, size);
        }

        void
        SetElementPosition(int32 element, int32 position)
        {
                fElements[element] = position;
        }

        float
        ElementLocation(int32 element)
        {
                if (element < 0 || element >= fElementCount || fElements[element] < 0)
                        return 0;
                return fTarget->ElementLocation(fElements[element]);
        }

        float
        ElementSize(int32 element)
        {
                if (element < 0 || element >= fElementCount || fElements[element] < 0)
                        return 0;
                return fTarget->ElementSize(fElements[element]);
        }

        float
        ElementRangeSize(int32 element, int32 length)
        {
                if (element < 0 || element >= fElementCount || fElements[element] < 0)
                        return 0;
                return fTarget->ElementRangeSize(fElements[element], length);
        }

private:
        int32*                                  fElements;
        LayoutInfo*                             fTarget;
        int32                                   fElementCount;
};


struct CollapsingLayouter::Constraint {
        int32 length;
        float min;
        float max;
        float preferred;
};


struct CollapsingLayouter::ElementInfo {
        float weight;
        int32 position;
        bool valid;
        BObjectList<Constraint, true> constraints;

        ElementInfo()
                :
                weight(0),
                position(-1),
                valid(false),
                constraints(5)
        {
        }

        ~ElementInfo()
        {
        }

        void SetTo(const ElementInfo& other)
        {
                weight = other.weight;
                position = other.position;
                valid = other.valid;
                for (int32 i = other.constraints.CountItems() - 1; i >= 0; i--)
                        constraints.AddItem(new Constraint(*other.constraints.ItemAt(i)));
        }
};


CollapsingLayouter::CollapsingLayouter(int32 elementCount, float spacing)
        :
        fElementCount(elementCount),
        fElements(new ElementInfo[elementCount]),
        fValidElementCount(0),
        fHaveMultiElementConstraints(false),
        fSpacing(spacing),
        fLayouter(NULL)
{
}


CollapsingLayouter::~CollapsingLayouter()
{
        delete[] fElements;
        delete fLayouter;
}


void
CollapsingLayouter::AddConstraints(int32 element, int32 length, float min,
        float max, float preferred)
{
        if (min == B_SIZE_UNSET && max == B_SIZE_UNSET)
                return;
        if (element < 0 || length <= 0 || element + length > fElementCount)
                return;

        Constraint* constraint = new Constraint();
        constraint->length = length;
        constraint->min = min;
        constraint->max = max;
        constraint->preferred = preferred;

        if (length > 1)
                fHaveMultiElementConstraints = true;
        
        int32 validElements = fValidElementCount;

        for (int32 i = element; i < element + length; i++) {
                if (fElements[i].valid == false) {
                        fElements[i].valid = true;
                        fValidElementCount++;
                }
        }

        fElements[element].constraints.AddItem(constraint);
        if (fValidElementCount > validElements) {
                delete fLayouter;
                fLayouter = NULL;
        }

        if (fLayouter)
                _AddConstraints(element, constraint);
        
}


void
CollapsingLayouter::SetWeight(int32 element, float weight)
{
        if (element < 0 || element >= fElementCount)
                return;

        ElementInfo& elementInfo = fElements[element];
        elementInfo.weight = weight;

        if (fLayouter && elementInfo.position >= 0)
                fLayouter->SetWeight(elementInfo.position, weight);
}


float
CollapsingLayouter::MinSize()
{
        _ValidateLayouter();
        return fLayouter ? fLayouter->MinSize() : 0;
}


float
CollapsingLayouter::MaxSize()
{
        _ValidateLayouter();
        return fLayouter ? fLayouter->MaxSize() : B_SIZE_UNLIMITED;
}


float
CollapsingLayouter::PreferredSize()
{
        _ValidateLayouter();
        return fLayouter ? fLayouter->PreferredSize() : 0;
}


LayoutInfo*
CollapsingLayouter::CreateLayoutInfo()
{
        _ValidateLayouter();

        LayoutInfo* info = fLayouter ? fLayouter->CreateLayoutInfo() : NULL;
        return new ProxyLayoutInfo(info, fElementCount);
}


void
CollapsingLayouter::Layout(LayoutInfo* layoutInfo, float size)
{
        _ValidateLayouter();
        ProxyLayoutInfo* info = static_cast<ProxyLayoutInfo*>(layoutInfo);
        for (int32 i = 0; i < fElementCount; i++) {
                info->SetElementPosition(i, fElements[i].position);
        }

        info->LayoutTarget(fLayouter, size);
}


Layouter*
CollapsingLayouter::CloneLayouter()
{
        CollapsingLayouter* clone = new CollapsingLayouter(fElementCount, fSpacing);
        for (int32 i = 0; i < fElementCount; i++)
                clone->fElements[i].SetTo(fElements[i]);

        clone->fValidElementCount = fValidElementCount;
        clone->fHaveMultiElementConstraints = fHaveMultiElementConstraints;

        if (fLayouter)
                clone->fLayouter = fLayouter->CloneLayouter();
        return clone;
}


void
CollapsingLayouter::_ValidateLayouter()
{
        if (fLayouter)
                return;

        _CreateLayouter();
        _DoCollapse();
        _AddConstraints();
        _SetWeights();
}


Layouter*
CollapsingLayouter::_CreateLayouter()
{
        if (fLayouter)
                return fLayouter;

        if (fValidElementCount == 0) {
                fLayouter =  NULL;
        } else if (fValidElementCount == 1) {
                fLayouter =  new OneElementLayouter();
        } else if (fHaveMultiElementConstraints) {
                fLayouter =  new ComplexLayouter(fValidElementCount, fSpacing);
        } else {
                fLayouter = new SimpleLayouter(fValidElementCount, fSpacing);
        }

        return fLayouter;
}


void
CollapsingLayouter::_DoCollapse()
{
        int32 shift = 0;
        for (int32 i = 0; i < fElementCount; i++) {
                ElementInfo& element = fElements[i];
                if (!element.valid) {
                        shift++;
                        element.position = -1;
                        continue;
                } else {
                        element.position = i - shift;
                }
        }
}


void
CollapsingLayouter::_AddConstraints()
{
        if (fLayouter == NULL)
                return;

        for (int32 i = 0; i < fElementCount; i++) {
                ElementInfo& element = fElements[i];
                for (int32 i = element.constraints.CountItems() - 1; i >= 0; i--)
                        _AddConstraints(element.position, element.constraints.ItemAt(i));
        }
}


void
CollapsingLayouter::_AddConstraints(int32 position, const Constraint* c)
{
        fLayouter->AddConstraints(position, c->length, c->min, c->max,
                c->preferred);
}


void
CollapsingLayouter::_SetWeights()
{
        if (!fLayouter)
                return;

        for (int32 i = 0; i < fElementCount; i++) {
                fLayouter->SetWeight(fElements[i].position, fElements[i].weight);
        }
}