root/src/kits/interface/TwoDimensionalLayout.cpp
/*
 * Copyright 2006-2010, Ingo Weinhold <ingo_weinhold@gmx.de>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */


#include <TwoDimensionalLayout.h>

#include <stdio.h>

#include <ControlLook.h>
#include <LayoutContext.h>
#include <LayoutItem.h>
#include <LayoutUtils.h>
#include <List.h>
#include <Message.h>
#include <View.h>

#include <Referenceable.h>

#include "CollapsingLayouter.h"


// Some words of explanation:
//
// This class is the base class for BLayouts that organize their items
// on a grid, with each item covering one or more grid cells (always a
// rectangular area). The derived classes only need to implement the
// hooks reporting the constraints for the items and additional constraints
// for the rows and columns. This class does all the layouting.
//
// The basic idea of the layout process is simple. The horizontal and the
// vertical dimensions are laid out independently and the items are set to the
// resulting locations and sizes. The "height for width" feature makes the
// height depend on the width, which makes things a bit more complicated.
// The horizontal dimension must be laid out first and and the results are
// fed into the vertical layout process.
//
// The AlignLayoutWith() feature, which allows to align layouts for different
// views with each other, causes the need for the three inner *Layouter classes.
// For each set of layouts aligned with each other with respect to one
// dimension that dimension must be laid out together. The class responsible
// is CompoundLayouter; one instance exists per such set. The derived class
// VerticalCompoundLayouter is a specialization for the vertical dimension
// which additionally takes care of the "height for width" feature. Per layout
// a single LocalLayouter exists, which comprises the required glue layout
// code and serves as a proxy for the layout, providing service methods
// needed by the CompoundLayouter.

// TODO: Check for memory leaks!

//#define DEBUG_LAYOUT

// CompoundLayouter
class BTwoDimensionalLayout::CompoundLayouter : public BReferenceable {
public:
                                                                CompoundLayouter(orientation orientation);
        virtual                                         ~CompoundLayouter();

                        orientation                     Orientation();

        virtual Layouter*                       GetLayouter(bool minMax);

                        LayoutInfo*                     GetLayoutInfo();

                        void                            AddLocalLayouter(LocalLayouter* localLayouter);
                        void                            RemoveLocalLayouter(
                                                                        LocalLayouter* localLayouter);

                        status_t                        AddAlignedLayoutsToArchive(BArchiver* archiver,
                                                                        LocalLayouter* requestedBy);

                        void                            AbsorbCompoundLayouter(CompoundLayouter* other);

        virtual void                            InvalidateLayout();
                        bool                            IsMinMaxValid();
                        void                            ValidateMinMax();
                        void                            Layout(float size, LocalLayouter* localLayouter,
                                                                        BLayoutContext* context);

protected:
        virtual void                            DoLayout(float size,
                                                                        LocalLayouter* localLayouter,
                                                                        BLayoutContext* context);

                        Layouter*                       fLayouter;
                        LayoutInfo*                     fLayoutInfo;
                        orientation                     fOrientation;
                        BList                           fLocalLayouters;
                        BLayoutContext*         fLayoutContext;
                        float                           fLastLayoutSize;

                        void                            _PrepareItems();

                        int32                           _CountElements();
                        bool                            _HasMultiElementItems();

                        void                            _AddConstraints(Layouter* layouter);

                        float                           _Spacing();
};

// VerticalCompoundLayouter
class BTwoDimensionalLayout::VerticalCompoundLayouter
        : public CompoundLayouter, private BLayoutContextListener {
public:
                                                                VerticalCompoundLayouter();

        virtual Layouter*                       GetLayouter(bool minMax);

        virtual void                            InvalidateLayout();

                        void                            InvalidateHeightForWidth();

                        void                            InternalGetHeightForWidth(
                                                                        LocalLayouter* localLayouter,
                                                                        BLayoutContext* context,
                                                                        bool realLayout, float* minHeight,
                                                                        float* maxHeight, float* preferredHeight);

protected:
        virtual void                            DoLayout(float size,
                                                                        LocalLayouter* localLayouter,
                                                                        BLayoutContext* context);

private:
                        Layouter*                       fHeightForWidthLayouter;
                        float                           fCachedMinHeightForWidth;
                        float                           fCachedMaxHeightForWidth;
                        float                           fCachedPreferredHeightForWidth;
                        BLayoutContext*         fHeightForWidthLayoutContext;

                        bool                            _HasHeightForWidth();

                        bool                            _SetHeightForWidthLayoutContext(
                                                                        BLayoutContext* context);

        // BLayoutContextListener
        virtual void                            LayoutContextLeft(BLayoutContext* context);
};

// LocalLayouter
class BTwoDimensionalLayout::LocalLayouter : private BLayoutContextListener {
public:
                                                                LocalLayouter(BTwoDimensionalLayout* layout);
                                                                ~LocalLayouter();

        // interface for the BTwoDimensionalLayout class

                        BSize                           MinSize();
                        BSize                           MaxSize();
                        BSize                           PreferredSize();

                        void                            InvalidateLayout();
                        void                            Layout(BSize size);

                        BRect                           ItemFrame(Dimensions itemDimensions);

                        void                            ValidateMinMax();

                        void                            DoHorizontalLayout(float width);

                        void                            InternalGetHeightForWidth(float width,
                                                                        float* minHeight, float* maxHeight,
                                                                        float* preferredHeight);

                        void                            AlignWith(LocalLayouter* other,
                                                                        orientation orientation);

        // Archiving stuff
                        status_t                        AddAlignedLayoutsToArchive(BArchiver* archiver);
                        status_t                        AddOwnerToArchive(BArchiver* archiver,
                                                                        CompoundLayouter* requestedBy,
                                                                        bool& _wasAvailable);
                        status_t                        AlignLayoutsFromArchive(BUnarchiver* unarchiver,
                                                                        orientation posture);


        // interface for the compound layout context

                        void                            PrepareItems(
                                                                        CompoundLayouter* compoundLayouter);
                        int32                           CountElements(
                                                                        CompoundLayouter* compoundLayouter);
                        bool                            HasMultiElementItems(
                                                                        CompoundLayouter* compoundLayouter);

                        void                            AddConstraints(
                                                                        CompoundLayouter* compoundLayouter,
                                                                        Layouter* layouter);

                        float                           Spacing(CompoundLayouter* compoundLayouter);

                        bool                            HasHeightForWidth();

                        bool                            AddHeightForWidthConstraints(
                                                                        VerticalCompoundLayouter* compoundLayouter,
                                                                        Layouter* layouter,
                                                                        BLayoutContext* context);
                        void                            SetHeightForWidthConstraintsAdded(bool added);

                        void                            SetCompoundLayouter(
                                                                        CompoundLayouter* compoundLayouter,
                                                                        orientation orientation);

                        void                            InternalInvalidateLayout(
                                                                        CompoundLayouter* compoundLayouter);

        // implementation private
private:
                        BTwoDimensionalLayout* fLayout;
                        CompoundLayouter*       fHLayouter;
                        VerticalCompoundLayouter* fVLayouter;
                        BList                           fHeightForWidthItems;

        // active layout context when doing last horizontal layout
                        BLayoutContext*         fHorizontalLayoutContext;
                        float                           fHorizontalLayoutWidth;
                        bool                            fHeightForWidthConstraintsAdded;

                        void                            _SetHorizontalLayoutContext(
                                                                        BLayoutContext* context, float width);

        // BLayoutContextListener
        virtual void                            LayoutContextLeft(BLayoutContext* context);
};


// #pragma mark -

// archiving constants
namespace {
        const char* const kHAlignedLayoutField = "BTwoDimensionalLayout:"
                "halignedlayout";
        const char* const kVAlignedLayoutField = "BTwoDimensionalLayout:"
                "valignedlayout";
        const char* const kInsetsField = "BTwoDimensionalLayout:insets";
        const char* const kSpacingField = "BTwoDimensionalLayout:spacing";
                // kSpacingField = {fHSpacing, fVSpacing}
}


BTwoDimensionalLayout::BTwoDimensionalLayout()
        :
        fLeftInset(0),
        fRightInset(0),
        fTopInset(0),
        fBottomInset(0),
        fHSpacing(0),
        fVSpacing(0),
        fLocalLayouter(new LocalLayouter(this))
{
}


BTwoDimensionalLayout::BTwoDimensionalLayout(BMessage* from)
        :
        BAbstractLayout(from),
        fLeftInset(0),
        fRightInset(0),
        fTopInset(0),
        fBottomInset(0),
        fHSpacing(0),
        fVSpacing(0),
        fLocalLayouter(new LocalLayouter(this))
{
        BRect insets;
        from->FindRect(kInsetsField, &insets);
        SetInsets(insets.left, insets.top, insets.right, insets.bottom);

        from->FindFloat(kSpacingField, 0, &fHSpacing);
        from->FindFloat(kSpacingField, 1, &fVSpacing);
}


BTwoDimensionalLayout::~BTwoDimensionalLayout()
{
        delete fLocalLayouter;
}


void
BTwoDimensionalLayout::SetInsets(float left, float top, float right,
        float bottom)
{
        fLeftInset = BControlLook::ComposeSpacing(left);
        fTopInset = BControlLook::ComposeSpacing(top);
        fRightInset = BControlLook::ComposeSpacing(right);
        fBottomInset = BControlLook::ComposeSpacing(bottom);

        InvalidateLayout();
}


void
BTwoDimensionalLayout::SetInsets(float horizontal, float vertical)
{
        fLeftInset = BControlLook::ComposeSpacing(horizontal);
        fRightInset = fLeftInset;

        fTopInset = BControlLook::ComposeSpacing(vertical);
        fBottomInset = fTopInset;

        InvalidateLayout();
}


void
BTwoDimensionalLayout::SetInsets(float insets)
{
        fLeftInset = BControlLook::ComposeSpacing(insets);
        fRightInset = fLeftInset;
        fTopInset = fLeftInset;
        fBottomInset = fLeftInset;

        InvalidateLayout();
}


void
BTwoDimensionalLayout::GetInsets(float* left, float* top, float* right,
        float* bottom) const
{
        if (left)
                *left = fLeftInset;
        if (top)
                *top = fTopInset;
        if (right)
                *right = fRightInset;
        if (bottom)
                *bottom = fBottomInset;
}


void
BTwoDimensionalLayout::AlignLayoutWith(BTwoDimensionalLayout* other,
        orientation orientation)
{
        if (!other || other == this)
                return;

        fLocalLayouter->AlignWith(other->fLocalLayouter, orientation);

        InvalidateLayout();
}


BSize
BTwoDimensionalLayout::BaseMinSize()
{
        _ValidateMinMax();
        return AddInsets(fLocalLayouter->MinSize());
}


BSize
BTwoDimensionalLayout::BaseMaxSize()
{
        _ValidateMinMax();
        return AddInsets(fLocalLayouter->MaxSize());
}


BSize
BTwoDimensionalLayout::BasePreferredSize()
{
        _ValidateMinMax();
        return AddInsets(fLocalLayouter->PreferredSize());
}


BAlignment
BTwoDimensionalLayout::BaseAlignment()
{
        return BAbstractLayout::BaseAlignment();
}


bool
BTwoDimensionalLayout::HasHeightForWidth()
{
        _ValidateMinMax();
        return fLocalLayouter->HasHeightForWidth();
}


void
BTwoDimensionalLayout::GetHeightForWidth(float width, float* min, float* max,
        float* preferred)
{
        if (!HasHeightForWidth())
                return;

        float outerSpacing = fLeftInset + fRightInset - 1;
        fLocalLayouter->InternalGetHeightForWidth(BLayoutUtils::SubtractDistances(
                width, outerSpacing), min, max, preferred);
        AddInsets(min, max, preferred);
}


void
BTwoDimensionalLayout::SetFrame(BRect frame)
{
        BAbstractLayout::SetFrame(frame);
}


status_t
BTwoDimensionalLayout::Archive(BMessage* into, bool deep) const
{
        BArchiver archiver(into);
        status_t err = BAbstractLayout::Archive(into, deep);

        if (err == B_OK) {
                BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset);
                err = into->AddRect(kInsetsField, insets);
        }

        if (err == B_OK)
                err = into->AddFloat(kSpacingField, fHSpacing);

        if (err == B_OK)
                err = into->AddFloat(kSpacingField, fVSpacing);

        return archiver.Finish(err);
}


status_t
BTwoDimensionalLayout::AllArchived(BMessage* into) const
{
        BArchiver archiver(into);

        status_t err = BLayout::AllArchived(into);
        if (err == B_OK)
                err = fLocalLayouter->AddAlignedLayoutsToArchive(&archiver);
        return err;
}


status_t
BTwoDimensionalLayout::AllUnarchived(const BMessage* from)
{
        status_t err = BLayout::AllUnarchived(from);
        if (err != B_OK)
                return err;

        BUnarchiver unarchiver(from);
        err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_HORIZONTAL);
        if (err == B_OK)
                err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_VERTICAL);

        return err;
}


status_t
BTwoDimensionalLayout::ItemArchived(BMessage* into, BLayoutItem* item,
        int32 index) const
{
        return BAbstractLayout::ItemArchived(into, item, index);
}


status_t
BTwoDimensionalLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item,
        int32 index)
{
        return BAbstractLayout::ItemUnarchived(from, item, index);
}




void
BTwoDimensionalLayout::LayoutInvalidated(bool children)
{
        fLocalLayouter->InvalidateLayout();
}


void
BTwoDimensionalLayout::DoLayout()
{
        _ValidateMinMax();

        // layout the horizontal/vertical elements
        BSize size(SubtractInsets(LayoutArea().Size()));

#ifdef DEBUG_LAYOUT
printf("BTwoDimensionalLayout::DerivedLayoutItems(): view: %p"
        " size: (%.1f, %.1f)\n", View(), size.Width(), size.Height());
#endif

        fLocalLayouter->Layout(size);

        // layout the items
        BPoint itemOffset(LayoutArea().LeftTop());
        int itemCount = CountItems();
        for (int i = 0; i < itemCount; i++) {
                BLayoutItem* item = ItemAt(i);
                if (item->IsVisible()) {
                        Dimensions itemDimensions;
                        GetItemDimensions(item, &itemDimensions);
                        BRect frame = fLocalLayouter->ItemFrame(itemDimensions);
                        frame.left += fLeftInset;
                        frame.top += fTopInset;
                        frame.right += fLeftInset;
                        frame.bottom += fTopInset;
                        frame.OffsetBy(itemOffset);
{
#ifdef DEBUG_LAYOUT
printf("  frame for item %2d (view: %p): ", i, item->View());
frame.PrintToStream();
#endif
//BSize min(item->MinSize());
//BSize max(item->MaxSize());
//printf("    min: (%.1f, %.1f), max: (%.1f, %.1f)\n", min.width, min.height,
//      max.width, max.height);
//if (item->HasHeightForWidth()) {
//float minHeight, maxHeight, preferredHeight;
//item->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight,
//      &preferredHeight);
//printf("    hfw: min: %.1f, max: %.1f, pref: %.1f\n", minHeight, maxHeight,
//      preferredHeight);
//}
}

                        item->AlignInFrame(frame);
                }
//else
//printf("  item %2d not visible", i);
        }
}


BSize
BTwoDimensionalLayout::AddInsets(BSize size)
{
        size.width = BLayoutUtils::AddDistances(size.width,
                fLeftInset + fRightInset - 1);
        size.height = BLayoutUtils::AddDistances(size.height,
                fTopInset + fBottomInset - 1);
        return size;
}


void
BTwoDimensionalLayout::AddInsets(float* minHeight, float* maxHeight,
        float* preferredHeight)
{
        float insets = fTopInset + fBottomInset - 1;
        if (minHeight)
                *minHeight = BLayoutUtils::AddDistances(*minHeight, insets);
        if (maxHeight)
                *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets);
        if (preferredHeight)
                *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets);
}


BSize
BTwoDimensionalLayout::SubtractInsets(BSize size)
{
        size.width = BLayoutUtils::SubtractDistances(size.width,
                fLeftInset + fRightInset - 1);
        size.height = BLayoutUtils::SubtractDistances(size.height,
                fTopInset + fBottomInset - 1);
        return size;
}


void
BTwoDimensionalLayout::PrepareItems(orientation orientation)
{
}


bool
BTwoDimensionalLayout::HasMultiColumnItems()
{
        return false;
}


bool
BTwoDimensionalLayout::HasMultiRowItems()
{
        return false;
}


void
BTwoDimensionalLayout::_ValidateMinMax()
{
        fLocalLayouter->ValidateMinMax();
}


// #pragma mark - CompoundLayouter


BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter(
        orientation orientation)
        :
        fLayouter(NULL),
        fLayoutInfo(NULL),
        fOrientation(orientation),
        fLocalLayouters(10),
        fLayoutContext(NULL),
        fLastLayoutSize(-1)
{
}


BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter()
{
        delete fLayouter;
        delete fLayoutInfo;
}


orientation
BTwoDimensionalLayout::CompoundLayouter::Orientation()
{
        return fOrientation;
}


Layouter*
BTwoDimensionalLayout::CompoundLayouter::GetLayouter(bool minMax)
{
        return fLayouter;
}


LayoutInfo*
BTwoDimensionalLayout::CompoundLayouter::GetLayoutInfo()
{
        return fLayoutInfo;
}


void
BTwoDimensionalLayout::CompoundLayouter::AddLocalLayouter(
        LocalLayouter* localLayouter)
{
        if (localLayouter) {
                if (!fLocalLayouters.HasItem(localLayouter)) {
                        fLocalLayouters.AddItem(localLayouter);
                        InvalidateLayout();
                }
        }
}


void
BTwoDimensionalLayout::CompoundLayouter::RemoveLocalLayouter(
        LocalLayouter* localLayouter)
{
        if (fLocalLayouters.RemoveItem(localLayouter))
                InvalidateLayout();
}


status_t
BTwoDimensionalLayout::CompoundLayouter::AddAlignedLayoutsToArchive(
        BArchiver* archiver, LocalLayouter* requestedBy)
{
        // The LocalLayouter* that really owns us is at index 0, layouts
        // at other indices are aligned to this one.
        if (requestedBy != fLocalLayouters.ItemAt(0))
                return B_OK;

        status_t err;
        for (int32 i = fLocalLayouters.CountItems() - 1; i > 0; i--) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);

                bool wasAvailable;
                err = layouter->AddOwnerToArchive(archiver, this, wasAvailable);
                if (err != B_OK && wasAvailable)
                        return err;
        }
        return B_OK;
}


void
BTwoDimensionalLayout::CompoundLayouter::AbsorbCompoundLayouter(
        CompoundLayouter* other)
{
        if (other == this)
                return;

        int32 count = other->fLocalLayouters.CountItems();
        for (int32 i = count - 1; i >= 0; i--) {
                LocalLayouter* layouter
                        = (LocalLayouter*)other->fLocalLayouters.ItemAt(i);
                AddLocalLayouter(layouter);
                layouter->SetCompoundLayouter(this, fOrientation);
        }

        InvalidateLayout();
}


void
BTwoDimensionalLayout::CompoundLayouter::InvalidateLayout()
{
        if (!fLayouter)
                return;

        delete fLayouter;
        delete fLayoutInfo;

        fLayouter = NULL;
        fLayoutInfo = NULL;
        fLayoutContext = NULL;

        // notify all local layouters to invalidate the respective views
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                layouter->InternalInvalidateLayout(this);
        }
}


bool
BTwoDimensionalLayout::CompoundLayouter::IsMinMaxValid()
{
        return (fLayouter != NULL);
}


void
BTwoDimensionalLayout::CompoundLayouter::ValidateMinMax()
{
        if (IsMinMaxValid())
                return;

        fLastLayoutSize = -1;

        // create the layouter
        _PrepareItems();

        int elementCount = _CountElements();

        fLayouter = new CollapsingLayouter(elementCount, _Spacing());

        // tell the layouter about our constraints
        // TODO: We should probably ignore local layouters whose view is hidden.
        // It's a bit tricky to find out, whether the view is hidden, though, since
        // this doesn't necessarily mean only hidden relative to the parent, but
        // hidden relative to a common parent.
        _AddConstraints(fLayouter);

        fLayoutInfo = fLayouter->CreateLayoutInfo();
}


void
BTwoDimensionalLayout::CompoundLayouter::Layout(float size,
        LocalLayouter* localLayouter, BLayoutContext* context)
{
        ValidateMinMax();

        if (context != fLayoutContext || fLastLayoutSize != size) {
                DoLayout(size, localLayouter, context);
                fLayoutContext = context;
                fLastLayoutSize = size;
        }
}


void
BTwoDimensionalLayout::CompoundLayouter::DoLayout(float size,
        LocalLayouter* localLayouter, BLayoutContext* context)
{
        fLayouter->Layout(fLayoutInfo, size);
}


void
BTwoDimensionalLayout::CompoundLayouter::_PrepareItems()
{
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                layouter->PrepareItems(this);
        }
}


int32
BTwoDimensionalLayout::CompoundLayouter::_CountElements()
{
        int32 elementCount = 0;
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                int32 layouterCount = layouter->CountElements(this);
                elementCount = max_c(elementCount, layouterCount);
        }

        return elementCount;
}


bool
BTwoDimensionalLayout::CompoundLayouter::_HasMultiElementItems()
{
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                if (layouter->HasMultiElementItems(this))
                        return true;
        }

        return false;
}


void
BTwoDimensionalLayout::CompoundLayouter::_AddConstraints(Layouter* layouter)
{
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* localLayouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                localLayouter->AddConstraints(this, layouter);
        }
}


float
BTwoDimensionalLayout::CompoundLayouter::_Spacing()
{
        if (!fLocalLayouters.IsEmpty())
                return ((LocalLayouter*)fLocalLayouters.ItemAt(0))->Spacing(this);
        return 0;
}


// #pragma mark - VerticalCompoundLayouter


BTwoDimensionalLayout::VerticalCompoundLayouter::VerticalCompoundLayouter()
        :
        CompoundLayouter(B_VERTICAL),
        fHeightForWidthLayouter(NULL),
        fCachedMinHeightForWidth(0),
        fCachedMaxHeightForWidth(0),
        fCachedPreferredHeightForWidth(0),
        fHeightForWidthLayoutContext(NULL)
{
}


Layouter*
BTwoDimensionalLayout::VerticalCompoundLayouter::GetLayouter(bool minMax)
{
        return (minMax || !_HasHeightForWidth()
                ? fLayouter : fHeightForWidthLayouter);
}


void
BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateLayout()
{
        CompoundLayouter::InvalidateLayout();

        InvalidateHeightForWidth();
}


void
BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateHeightForWidth()
{
        if (fHeightForWidthLayouter != NULL) {
                delete fHeightForWidthLayouter;
                fHeightForWidthLayouter = NULL;

                // also make sure we're not reusing the old layout info
                fLastLayoutSize = -1;

                int32 count = fLocalLayouters.CountItems();
                for (int32 i = 0; i < count; i++) {
                        LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                        layouter->SetHeightForWidthConstraintsAdded(false);
                }
        }
}


void
BTwoDimensionalLayout::VerticalCompoundLayouter::InternalGetHeightForWidth(
        LocalLayouter* localLayouter, BLayoutContext* context, bool realLayout,
        float* minHeight, float* maxHeight, float* preferredHeight)
{
        bool updateCachedInfo = false;

        if (_SetHeightForWidthLayoutContext(context)
                || fHeightForWidthLayouter == NULL) {
                // Either the layout context changed or we haven't initialized the
                // height for width layouter yet. We create it and init it now.

                // clone the vertical layouter
                delete fHeightForWidthLayouter;
                delete fLayoutInfo;
                fHeightForWidthLayouter = fLayouter->CloneLayouter();
                fLayoutInfo = fHeightForWidthLayouter->CreateLayoutInfo();

                // add the children's height for width constraints
                int32 count = fLocalLayouters.CountItems();
                for (int32 i = 0; i < count; i++) {
                        LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                        if (layouter->HasHeightForWidth()) {
                                layouter->AddHeightForWidthConstraints(this,
                                        fHeightForWidthLayouter, context);
                        }
                }

                updateCachedInfo = true;
        } else if (localLayouter->HasHeightForWidth()) {
                // There is a height for width layouter and it has been initialized
                // in the current layout context. So we just add the height for width
                // constraints of the calling local layouter, if they haven't been
                // added yet.
                updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this,
                        fHeightForWidthLayouter, context);
        }

        // update cached height for width info, if something changed
        if (updateCachedInfo) {
                // get the height for width info
                fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize();
                fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize();
                fCachedPreferredHeightForWidth
                        = fHeightForWidthLayouter->PreferredSize();
        }

        if (minHeight)
                *minHeight = fCachedMinHeightForWidth;
        if (maxHeight)
                *maxHeight = fCachedMaxHeightForWidth;
        if (preferredHeight)
                *preferredHeight = fCachedPreferredHeightForWidth;
}


void
BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size,
        LocalLayouter* localLayouter, BLayoutContext* context)
{
        Layouter* layouter;
        if (_HasHeightForWidth()) {
                float minHeight, maxHeight, preferredHeight;
                InternalGetHeightForWidth(localLayouter, context, true, &minHeight,
                        &maxHeight, &preferredHeight);
                size = max_c(size, minHeight);
                layouter = fHeightForWidthLayouter;
        } else
                layouter = fLayouter;

        layouter->Layout(fLayoutInfo, size);
}


bool
BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth()
{
        int32 count = fLocalLayouters.CountItems();
        for (int32 i = 0; i < count; i++) {
                LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i);
                if (layouter->HasHeightForWidth())
                        return true;
        }

        return false;
}


bool
BTwoDimensionalLayout::VerticalCompoundLayouter
        ::_SetHeightForWidthLayoutContext(BLayoutContext* context)
{
        if (context == fHeightForWidthLayoutContext)
                return false;

        if (fHeightForWidthLayoutContext != NULL) {
                fHeightForWidthLayoutContext->RemoveListener(this);
                fHeightForWidthLayoutContext = NULL;
        }

        // We can ignore the whole context business, if we have no more than one
        // local layouter. We use the layout context only to recognize when calls
        // of different local layouters belong to the same context.
        if (fLocalLayouters.CountItems() <= 1)
                return false;

        fHeightForWidthLayoutContext = context;

        if (fHeightForWidthLayoutContext != NULL)
                fHeightForWidthLayoutContext->AddListener(this);

        InvalidateHeightForWidth();

        return true;
}


void
BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft(
        BLayoutContext* context)
{
        fHeightForWidthLayoutContext = NULL;
}


// #pragma mark - LocalLayouter


BTwoDimensionalLayout::LocalLayouter::LocalLayouter(
                BTwoDimensionalLayout* layout)
        :
        fLayout(layout),
        fHLayouter(new CompoundLayouter(B_HORIZONTAL)),
        fVLayouter(new VerticalCompoundLayouter),
        fHeightForWidthItems(),
        fHorizontalLayoutContext(NULL),
        fHorizontalLayoutWidth(0),
        fHeightForWidthConstraintsAdded(false)
{
        fHLayouter->AddLocalLayouter(this);
        fVLayouter->AddLocalLayouter(this);
}


BTwoDimensionalLayout::LocalLayouter::~LocalLayouter()
{
        if (fHLayouter != NULL) {
                fHLayouter->RemoveLocalLayouter(this);
                fHLayouter->ReleaseReference();
        }

        if (fVLayouter != NULL) {
                fVLayouter->RemoveLocalLayouter(this);
                fVLayouter->ReleaseReference();
        }
}


BSize
BTwoDimensionalLayout::LocalLayouter::MinSize()
{
        return BSize(fHLayouter->GetLayouter(true)->MinSize(),
                fVLayouter->GetLayouter(true)->MinSize());
}


BSize
BTwoDimensionalLayout::LocalLayouter::MaxSize()
{
        return BSize(fHLayouter->GetLayouter(true)->MaxSize(),
                fVLayouter->GetLayouter(true)->MaxSize());
}


BSize
BTwoDimensionalLayout::LocalLayouter::PreferredSize()
{
        return BSize(fHLayouter->GetLayouter(true)->PreferredSize(),
                fVLayouter->GetLayouter(true)->PreferredSize());
}


void
BTwoDimensionalLayout::LocalLayouter::InvalidateLayout()
{
        fHLayouter->InvalidateLayout();
        fVLayouter->InvalidateLayout();
}


void
BTwoDimensionalLayout::LocalLayouter::Layout(BSize size)
{
        DoHorizontalLayout(size.width);
        fVLayouter->Layout(size.height, this, fLayout->LayoutContext());
}


BRect
BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions)
{
        LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();
        LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo();
        float x = hLayoutInfo->ElementLocation(itemDimensions.x);
        float y = vLayoutInfo->ElementLocation(itemDimensions.y);
        float width = hLayoutInfo->ElementRangeSize(itemDimensions.x,
                itemDimensions.width);
        float height = vLayoutInfo->ElementRangeSize(itemDimensions.y,
                itemDimensions.height);
        return BRect(x, y, x + width, y + height);
}


void
BTwoDimensionalLayout::LocalLayouter::ValidateMinMax()
{
        if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid())
                return;

        if (!fHLayouter->IsMinMaxValid())
                fHeightForWidthItems.MakeEmpty();

        _SetHorizontalLayoutContext(NULL, -1);

        fHLayouter->ValidateMinMax();
        fVLayouter->ValidateMinMax();
        fLayout->ResetLayoutInvalidation();
}


void
BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width)
{
        BLayoutContext* context = fLayout->LayoutContext();
        if (fHorizontalLayoutContext != context
                        || width != fHorizontalLayoutWidth) {
                _SetHorizontalLayoutContext(context, width);
                fHLayouter->Layout(width, this, context);
                fVLayouter->InvalidateHeightForWidth();
        }
}


void
BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width,
        float* minHeight, float* maxHeight, float* preferredHeight)
{
        DoHorizontalLayout(width);
        fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false,
                minHeight, maxHeight, preferredHeight);
}


void
BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other,
        orientation orientation)
{
        if (orientation == B_HORIZONTAL)
                other->fHLayouter->AbsorbCompoundLayouter(fHLayouter);
        else
                other->fVLayouter->AbsorbCompoundLayouter(fVLayouter);
}


status_t
BTwoDimensionalLayout::LocalLayouter::AddAlignedLayoutsToArchive(
        BArchiver* archiver)
{
        status_t err = fHLayouter->AddAlignedLayoutsToArchive(archiver, this);

        if (err == B_OK)
                err = fVLayouter->AddAlignedLayoutsToArchive(archiver, this);

        return err;
}


status_t
BTwoDimensionalLayout::LocalLayouter::AddOwnerToArchive(BArchiver* archiver,
        CompoundLayouter* requestedBy, bool& _wasAvailable)
{
        const char* field = kHAlignedLayoutField;
        if (requestedBy == fVLayouter)
                field = kVAlignedLayoutField;

        if ((_wasAvailable = archiver->IsArchived(fLayout)))
                return archiver->AddArchivable(field, fLayout);

        return B_NAME_NOT_FOUND;
}


status_t
BTwoDimensionalLayout::LocalLayouter::AlignLayoutsFromArchive(
        BUnarchiver* unarchiver, orientation posture)
{
        const char* field = kHAlignedLayoutField;
        if (posture == B_VERTICAL)
                field = kVAlignedLayoutField;

        int32 count;
        status_t err = unarchiver->ArchiveMessage()->GetInfo(field, NULL, &count);
        if (err == B_NAME_NOT_FOUND)
                return B_OK;

        BTwoDimensionalLayout* retriever;
        for (int32 i = 0; i < count && err == B_OK; i++) {
                err = unarchiver->FindObject(field, i,
                        BUnarchiver::B_DONT_ASSUME_OWNERSHIP, retriever);

                if (err == B_OK)
                        retriever->AlignLayoutWith(fLayout, posture);
        }

        return err;
}


void
BTwoDimensionalLayout::LocalLayouter::PrepareItems(
        CompoundLayouter* compoundLayouter)
{
        fLayout->PrepareItems(compoundLayouter->Orientation());
}


int32
BTwoDimensionalLayout::LocalLayouter::CountElements(
        CompoundLayouter* compoundLayouter)
{
        if (compoundLayouter->Orientation() == B_HORIZONTAL)
                return fLayout->InternalCountColumns();
        else
                return fLayout->InternalCountRows();
}


bool
BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems(
        CompoundLayouter* compoundLayouter)
{
        if (compoundLayouter->Orientation() == B_HORIZONTAL)
                return fLayout->HasMultiColumnItems();
        else
                return fLayout->HasMultiRowItems();
}


void
BTwoDimensionalLayout::LocalLayouter::AddConstraints(
        CompoundLayouter* compoundLayouter, Layouter* layouter)
{
        enum orientation orientation = compoundLayouter->Orientation();
        int itemCount = fLayout->CountItems();
        if (itemCount > 0) {
                for (int i = 0; i < itemCount; i++) {
                        BLayoutItem* item = fLayout->ItemAt(i);
                        if (item->IsVisible()) {
                                Dimensions itemDimensions;
                                fLayout->GetItemDimensions(item, &itemDimensions);

                                BSize min = item->MinSize();
                                BSize max = item->MaxSize();
                                BSize preferred = item->PreferredSize();

                                if (orientation == B_HORIZONTAL) {
                                        layouter->AddConstraints(
                                                itemDimensions.x,
                                                itemDimensions.width,
                                                min.width,
                                                max.width,
                                                preferred.width);

                                        if (item->HasHeightForWidth())
                                                fHeightForWidthItems.AddItem(item);

                                } else {
                                        layouter->AddConstraints(
                                                itemDimensions.y,
                                                itemDimensions.height,
                                                min.height,
                                                max.height,
                                                preferred.height);
                                }
                        }
                }

                // add column/row constraints
                ColumnRowConstraints constraints;
                int elementCount = CountElements(compoundLayouter);
                for (int element = 0; element < elementCount; element++) {
                        fLayout->GetColumnRowConstraints(orientation, element,
                                &constraints);
                        layouter->SetWeight(element, constraints.weight);
                        layouter->AddConstraints(element, 1, constraints.min,
                                constraints.max, constraints.min);
                }
        }
}


float
BTwoDimensionalLayout::LocalLayouter::Spacing(
        CompoundLayouter* compoundLayouter)
{
        return (compoundLayouter->Orientation() == B_HORIZONTAL
                ? fLayout->fHSpacing : fLayout->fVSpacing);
}


bool
BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth()
{
        return !fHeightForWidthItems.IsEmpty();
}


bool
BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints(
        VerticalCompoundLayouter* compoundLayouter, Layouter* layouter,
        BLayoutContext* context)
{
        if (context != fHorizontalLayoutContext)
                return false;

        if (fHeightForWidthConstraintsAdded)
                return false;

        LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo();

        // add the children's height for width constraints
        int32 itemCount = fHeightForWidthItems.CountItems();
        for (int32 i = 0; i < itemCount; i++) {
                BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i);
                Dimensions itemDimensions;
                fLayout->GetItemDimensions(item, &itemDimensions);

                float minHeight, maxHeight, preferredHeight;
                item->GetHeightForWidth(
                        hLayoutInfo->ElementRangeSize(itemDimensions.x,
                                itemDimensions.width),
                        &minHeight, &maxHeight, &preferredHeight);
                layouter->AddConstraints(
                        itemDimensions.y,
                        itemDimensions.height,
                        minHeight,
                        maxHeight,
                        preferredHeight);
        }

        SetHeightForWidthConstraintsAdded(true);

        return true;
}


void
BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded(
        bool added)
{
        fHeightForWidthConstraintsAdded = added;
}


void
BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter(
        CompoundLayouter* compoundLayouter, orientation orientation)
{
        CompoundLayouter* oldCompoundLayouter;
        if (orientation == B_HORIZONTAL) {
                oldCompoundLayouter = fHLayouter;
                fHLayouter = compoundLayouter;
        } else {
                oldCompoundLayouter = fVLayouter;
                fVLayouter = static_cast<VerticalCompoundLayouter*>(compoundLayouter);
        }

        if (compoundLayouter == oldCompoundLayouter)
                return;

        if (oldCompoundLayouter != NULL) {
                oldCompoundLayouter->RemoveLocalLayouter(this);
                oldCompoundLayouter->ReleaseReference();
        }

        if (compoundLayouter != NULL)
                compoundLayouter->AcquireReference();

        InternalInvalidateLayout(compoundLayouter);
}


void
BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout(
        CompoundLayouter* compoundLayouter)
{
        _SetHorizontalLayoutContext(NULL, -1);

        fLayout->BLayout::InvalidateLayout();
}


void
BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext(
        BLayoutContext* context, float width)
{
        if (context != fHorizontalLayoutContext) {
                if (fHorizontalLayoutContext != NULL)
                        fHorizontalLayoutContext->RemoveListener(this);

                fHorizontalLayoutContext = context;

                if (fHorizontalLayoutContext != NULL)
                        fHorizontalLayoutContext->AddListener(this);
        }

        fHorizontalLayoutWidth = width;
}


void
BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context)
{
        fHorizontalLayoutContext = NULL;
        fHorizontalLayoutWidth = -1;
}


status_t
BTwoDimensionalLayout::Perform(perform_code code, void* _data)
{
        return BAbstractLayout::Perform(code, _data);
}


void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout1() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout2() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout3() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout4() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout5() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout6() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout7() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout8() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout9() {}
void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout10() {}