root/src/kits/interface/Layout.cpp
/*
 * Copyright 2010, Haiku Inc.
 * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */


#include <Layout.h>

#include <algorithm>
#include <new>
#include <syslog.h>

#include <AutoDeleter.h>
#include <LayoutContext.h>
#include <Message.h>
#include <View.h>
#include <ViewPrivate.h>

#include "ViewLayoutItem.h"


using BPrivate::AutoDeleter;

using std::nothrow;
using std::swap;


namespace {
        // flags for our state
        const uint32 B_LAYOUT_INVALID = 0x80000000UL; // needs layout
        const uint32 B_LAYOUT_CACHE_INVALID = 0x40000000UL; // needs recalculation
        const uint32 B_LAYOUT_REQUIRED = 0x20000000UL; // needs layout
        const uint32 B_LAYOUT_IN_PROGRESS = 0x10000000UL;
        const uint32 B_LAYOUT_ALL_CLEAR = 0UL;

        // handy masks to check various states
        const uint32 B_LAYOUT_INVALIDATION_ILLEGAL
                = B_LAYOUT_CACHE_INVALID | B_LAYOUT_IN_PROGRESS;
        const uint32 B_LAYOUT_NECESSARY
                = B_LAYOUT_INVALID | B_LAYOUT_REQUIRED | B_LAYOUT_CACHE_INVALID;
        const uint32 B_RELAYOUT_NOT_OK
                = B_LAYOUT_INVALID | B_LAYOUT_IN_PROGRESS;

        const char* const kLayoutItemField = "BLayout:items";


        struct ViewRemover {
                inline void operator()(BView* view) {
                        if (view)
                                BView::Private(view).RemoveSelf();
                }
        };
}


BLayout::BLayout()
        :
        fState(B_LAYOUT_ALL_CLEAR),
        fAncestorsVisible(true),
        fInvalidationDisabled(0),
        fContext(NULL),
        fOwner(NULL),
        fTarget(NULL),
        fItems(20)
{
}


BLayout::BLayout(BMessage* from)
        :
        BLayoutItem(BUnarchiver::PrepareArchive(from)),
        fState(B_LAYOUT_ALL_CLEAR),
        fAncestorsVisible(true),
        fInvalidationDisabled(0),
        fContext(NULL),
        fOwner(NULL),
        fTarget(NULL),
        fItems(20)
{
        BUnarchiver unarchiver(from);

        int32 i = 0;
        while (unarchiver.EnsureUnarchived(kLayoutItemField, i++) == B_OK)
                ;
}


BLayout::~BLayout()
{
        // in case we have a view, but have been added to a layout as a BLayoutItem
        // we will get deleted before our view, so we should tell it that we're
        // going, so that we aren't double-freed.
        if (fOwner && this == fOwner->GetLayout())
                fOwner->_LayoutLeft(this);

        if (CountItems() > 0) {
                debugger("Deleting a BLayout that still has items. Subclass hooks "
                        "will not be called");
        }
}


BView*
BLayout::Owner() const
{
        return fOwner;
}


BView*
BLayout::TargetView() const
{
        return fTarget;
}


BView*
BLayout::View()
{
        return fOwner;
}


BLayoutItem*
BLayout::AddView(BView* child)
{
        return AddView(-1, child);
}


BLayoutItem*
BLayout::AddView(int32 index, BView* child)
{
        BLayoutItem* item = child->GetLayout();
        ObjectDeleter<BLayoutItem> itemDeleter(NULL);
        if (!item) {
                item = new(nothrow) BViewLayoutItem(child);
                itemDeleter.SetTo(item);
        }

        if (item && AddItem(index, item)) {
                itemDeleter.Detach();
                return item;
        }

        return NULL;
}


bool
BLayout::AddItem(BLayoutItem* item)
{
        return AddItem(-1, item);
}


bool
BLayout::AddItem(int32 index, BLayoutItem* item)
{
        if (!fTarget || !item || fItems.HasItem(item))
                return false;

        // if the item refers to a BView, we make sure it is added to the parent
        // view
        BView* view = item->View();
        AutoDeleter<BView, ViewRemover> remover(NULL);
                // In case of errors, we don't want to leave this view added where it
                // shouldn't be.
        if (view && view->fParent != fTarget) {
                if (!fTarget->_AddChild(view, NULL))
                        return false;
                else
                        remover.SetTo(view);
        }

        // validate the index
        if (index < 0 || index > fItems.CountItems())
                index = fItems.CountItems();

        if (!fItems.AddItem(item, index))
                return false;

        if (!ItemAdded(item, index)) {
                fItems.RemoveItem(index);
                return false;
        }

        item->SetLayout(this);
        if (!fAncestorsVisible)
                item->AncestorVisibilityChanged(fAncestorsVisible);
        InvalidateLayout();
        remover.Detach();
        return true;
}


bool
BLayout::RemoveView(BView* child)
{
        bool removed = false;

        // a view can have any number of layout items - we need to remove them all
        int32 remaining = BView::Private(child).CountLayoutItems();
        for (int32 i = CountItems() - 1; i >= 0 && remaining > 0; i--) {
                BLayoutItem* item = ItemAt(i);

                if (item->View() != child)
                        continue;

                RemoveItem(i);
                if (item != child->GetLayout())
                        delete item;

                remaining--;
                removed = true;
        }

        return removed;
}


bool
BLayout::RemoveItem(BLayoutItem* item)
{
        int32 index = IndexOfItem(item);
        return (index >= 0 ? RemoveItem(index) != NULL : false);
}


BLayoutItem*
BLayout::RemoveItem(int32 index)
{
        if (index < 0 || index >= fItems.CountItems())
                return NULL;

        BLayoutItem* item = (BLayoutItem*)fItems.RemoveItem(index);
        ItemRemoved(item, index);
        item->SetLayout(NULL);

        // If this is the last item in use that refers to its BView,
        // that BView now needs to be removed. UNLESS fTarget is NULL,
        // in which case we leave the view as is. (See SetTarget() for more info)
        BView* view = item->View();
        if (fTarget && view && BView::Private(view).CountLayoutItems() == 0)
                view->_RemoveSelf();

        InvalidateLayout();
        return item;
}


BLayoutItem*
BLayout::ItemAt(int32 index) const
{
        return (BLayoutItem*)fItems.ItemAt(index);
}


int32
BLayout::CountItems() const
{
        return fItems.CountItems();
}


int32
BLayout::IndexOfItem(const BLayoutItem* item) const
{
        return fItems.IndexOf(item);
}


int32
BLayout::IndexOfView(BView* child) const
{
        if (child == NULL)
                return -1;

        // A BView can have many items, so we just do our best and return the
        // index of the first one in this layout.
        BView::Private viewPrivate(child);
        int32 itemCount = viewPrivate.CountLayoutItems();
        for (int32 i = 0; i < itemCount; i++) {
                BLayoutItem* item = viewPrivate.LayoutItemAt(i);
                if (item->Layout() == this)
                        return IndexOfItem(item);
        }
        return -1;
}


bool
BLayout::AncestorsVisible() const
{
        return fAncestorsVisible;
}


void
BLayout::InvalidateLayout(bool children)
{
        // printf("BLayout(%p)::InvalidateLayout(%i) : state %x, disabled %li\n",
        // this, children, (unsigned int)fState, fInvalidationDisabled);

        if (fTarget && fTarget->IsLayoutInvalidationDisabled())
                return;
        if (fInvalidationDisabled > 0
                || (fState & B_LAYOUT_INVALIDATION_ILLEGAL) != 0) {
                return;
        }

        fState |= B_LAYOUT_NECESSARY;
        LayoutInvalidated(children);

        if (children) {
                for (int32 i = CountItems() - 1; i >= 0; i--)
                        ItemAt(i)->InvalidateLayout(children);
        }

        if (fOwner)
                fOwner->InvalidateLayout(children);

        if (BLayout* nestedIn = Layout()) {
                nestedIn->InvalidateLayout();
        } else if (fOwner) {
                // If we weren't added as a BLayoutItem, we still have to invalidate
                // whatever layout our owner is in.
                fOwner->_InvalidateParentLayout();
        }
}


void
BLayout::RequireLayout()
{
        fState |= B_LAYOUT_REQUIRED;
}


bool
BLayout::IsValid()
{
        return (fState & B_LAYOUT_INVALID) == 0;
}


void
BLayout::DisableLayoutInvalidation()
{
        fInvalidationDisabled++;
}


void
BLayout::EnableLayoutInvalidation()
{
        if (fInvalidationDisabled > 0)
                fInvalidationDisabled--;
}


void
BLayout::LayoutItems(bool force)
{
        if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
                return;

        if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0)
                return; // wait for parent layout to lay us out.

        if (fTarget && fTarget->LayoutContext())
                return;

        BLayoutContext context;
        _LayoutWithinContext(force, &context);
}


void
BLayout::Relayout(bool immediate)
{
        if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) {
                fState |= B_LAYOUT_REQUIRED;
                LayoutItems(false);
        }
}


void
BLayout::_LayoutWithinContext(bool force, BLayoutContext* context)
{
// printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n",
// this, force, context, (unsigned int)fState, fContext);

        if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
                return;

        BLayoutContext* oldContext = fContext;
        fContext = context;

        if (fOwner && BView::Private(fOwner).WillLayout()) {
                // in this case, let our owner decide whether or not to have us
                // do our layout, if they do, we won't end up here again.
                fOwner->_Layout(force, context);
        } else {
                fState |= B_LAYOUT_IN_PROGRESS;
                DoLayout();
                // we must ensure that all items are laid out, layouts with a view will
                // have their layout process triggered by their view, but nested
                // view-less layouts must have their layout triggered here (if it hasn't
                // already been triggered).
                int32 nestedLayoutCount = fNestedLayouts.CountItems();
                for (int32 i = 0; i < nestedLayoutCount; i++) {
                        BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
                        if ((layout->fState & B_LAYOUT_NECESSARY) != 0)
                                layout->_LayoutWithinContext(force, context);
                }
                fState = B_LAYOUT_ALL_CLEAR;
        }

        fContext = oldContext;
}


BRect
BLayout::LayoutArea()
{
        BRect area(Frame());
        if (fOwner)
                area.OffsetTo(B_ORIGIN);
        return area;
}


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

        if (deep) {
                int32 count = CountItems();
                for (int32 i = 0; i < count && err == B_OK; i++) {
                        BLayoutItem* item = ItemAt(i);
                        err = archiver.AddArchivable(kLayoutItemField, item, deep);

                        if (err == B_OK) {
                                err = ItemArchived(into, item, i);
                                if (err != B_OK)
                                        syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i);
                        }
                }
        }

        return archiver.Finish(err);
}


status_t
BLayout::AllArchived(BMessage* archive) const
{
        return BLayoutItem::AllArchived(archive);
}


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

        int32 itemCount = 0;
        unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount);
        for (int32 i = 0; i < itemCount && err == B_OK; i++) {
                BLayoutItem* item;
                err = unarchiver.FindObject(kLayoutItemField,
                        i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item);
                if (err != B_OK)
                        return err;

                if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) {
                        fItems.RemoveItem(i);
                        return B_ERROR;
                }

                err = ItemUnarchived(from, item, i);
                if (err != B_OK) {
                        fItems.RemoveItem(i);
                        ItemRemoved(item, i);
                        return err;
                }

                item->SetLayout(this);
                unarchiver.AssumeOwnership(item);
        }

        InvalidateLayout();
        return err;
}


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


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


bool
BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
{
        return true;
}


void
BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
{
}


void
BLayout::LayoutInvalidated(bool children)
{
}


void
BLayout::OwnerChanged(BView* was)
{
}


void
BLayout::AttachedToLayout()
{
        if (!fOwner) {
                Layout()->fNestedLayouts.AddItem(this);
                SetTarget(Layout()->TargetView());
        }
}


void
BLayout::DetachedFromLayout(BLayout* from)
{
        if (!fOwner) {
                from->fNestedLayouts.RemoveItem(this);
                SetTarget(NULL);
        }
}


void
BLayout::AncestorVisibilityChanged(bool shown)
{
        if (fAncestorsVisible == shown)
                return;

        fAncestorsVisible = shown;
        VisibilityChanged(shown);
}


void
BLayout::VisibilityChanged(bool show)
{
        if (fOwner)
                return;

        for (int32 i = CountItems() - 1; i >= 0; i--)
                ItemAt(i)->AncestorVisibilityChanged(show);
}


void
BLayout::ResetLayoutInvalidation()
{
        fState &= ~B_LAYOUT_CACHE_INVALID;
}


BLayoutContext*
BLayout::LayoutContext() const
{
        return fContext;
}


void
BLayout::SetOwner(BView* owner)
{
        if (fOwner == owner)
                return;

        SetTarget(owner);
        swap(fOwner, owner);

        OwnerChanged(owner);
                // call hook
}


void
BLayout::SetTarget(BView* target)
{
        if (fTarget != target) {
                /* With fTarget NULL, RemoveItem() will not remove the views from their
                 * parent. This ensures that the views are not lost to the void.
                 */
                fTarget = NULL;

                // remove and delete all items
                for (int32 i = CountItems() - 1; i >= 0; i--)
                        delete RemoveItem(i);

                fTarget = target;

                InvalidateLayout();
        }
}


// Binary compatibility stuff


status_t
BLayout::Perform(perform_code code, void* _data)
{
        return BLayoutItem::Perform(code, _data);
}


void BLayout::_ReservedLayout1() {}
void BLayout::_ReservedLayout2() {}
void BLayout::_ReservedLayout3() {}
void BLayout::_ReservedLayout4() {}
void BLayout::_ReservedLayout5() {}
void BLayout::_ReservedLayout6() {}
void BLayout::_ReservedLayout7() {}
void BLayout::_ReservedLayout8() {}
void BLayout::_ReservedLayout9() {}
void BLayout::_ReservedLayout10() {}