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


#include <GridLayout.h>

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

#include <ControlLook.h>
#include <LayoutItem.h>
#include <List.h>
#include <Message.h>

#include "ViewLayoutItem.h"


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


enum {
        MAX_COLUMN_ROW_COUNT    = 1024,
};


namespace {
        // a placeholder we put in our grid array to make a cell occupied
        BLayoutItem* const OCCUPIED_GRID_CELL = (BLayoutItem*)0x1;

        const char* const kRowSizesField = "BGridLayout:rowsizes";
                // kRowSizesField = {min, max}
        const char* const kRowWeightField = "BGridLayout:rowweight";
        const char* const kColumnSizesField = "BGridLayout:columnsizes";
                // kColumnSizesField = {min, max}
        const char* const kColumnWeightField = "BGridLayout:columnweight";
        const char* const kItemDimensionsField = "BGridLayout:item:dimensions";
                // kItemDimensionsField = {x, y, width, height}
}


struct BGridLayout::ItemLayoutData {
        Dimensions      dimensions;

        ItemLayoutData()
        {
                dimensions.x = 0;
                dimensions.y = 0;
                dimensions.width = 1;
                dimensions.height = 1;
        }
};


class BGridLayout::RowInfoArray {
public:
        RowInfoArray()
        {
        }

        ~RowInfoArray()
        {
                for (int32 i = 0; Info* info = (Info*)fInfos.ItemAt(i); i++)
                        delete info;
        }

        int32 Count() const
        {
                return fInfos.CountItems();
        }

        float Weight(int32 index) const
        {
                if (Info* info = _InfoAt(index))
                        return info->weight;
                return 1;
        }

        void SetWeight(int32 index, float weight)
        {
                if (Info* info = _InfoAt(index, true))
                        info->weight = weight;
        }

        float MinSize(int32 index) const
        {
                if (Info* info = _InfoAt(index))
                        return info->minSize;
                return B_SIZE_UNSET;
        }

        void SetMinSize(int32 index, float size)
        {
                if (Info* info = _InfoAt(index, true))
                        info->minSize = size;
        }

        float MaxSize(int32 index) const
        {
                if (Info* info = _InfoAt(index))
                        return info->maxSize;
                return B_SIZE_UNSET;
        }

        void SetMaxSize(int32 index, float size)
        {
                if (Info* info = _InfoAt(index, true))
                        info->maxSize = size;
        }

private:
        struct Info {
                float   weight;
                float   minSize;
                float   maxSize;
        };

        Info* _InfoAt(int32 index) const
        {
                return (Info*)fInfos.ItemAt(index);
        }

        Info* _InfoAt(int32 index, bool resize)
        {
                if (index < 0 || index >= MAX_COLUMN_ROW_COUNT)
                        return NULL;

                // resize, if necessary and desired
                int32 count = Count();
                if (index >= count) {
                        if (!resize)
                                return NULL;

                        for (int32 i = count; i <= index; i++) {
                                Info* info = new Info;
                                info->weight = 1;
                                info->minSize = B_SIZE_UNSET;
                                info->maxSize = B_SIZE_UNSET;
                                fInfos.AddItem(info);
                        }
                }

                return _InfoAt(index);
        }

        BList           fInfos;
};


BGridLayout::BGridLayout(float horizontal, float vertical)
        :
        fGrid(NULL),
        fColumnCount(0),
        fRowCount(0),
        fRowInfos(new RowInfoArray),
        fColumnInfos(new RowInfoArray),
        fMultiColumnItems(0),
        fMultiRowItems(0)
{
        SetSpacing(horizontal, vertical);
}


BGridLayout::BGridLayout(BMessage* from)
        :
        BTwoDimensionalLayout(BUnarchiver::PrepareArchive(from)),
        fGrid(NULL),
        fColumnCount(0),
        fRowCount(0),
        fRowInfos(new RowInfoArray),
        fColumnInfos(new RowInfoArray),
        fMultiColumnItems(0),
        fMultiRowItems(0)
{
        BUnarchiver unarchiver(from);
        int32 columns;
        from->GetInfo(kColumnWeightField, NULL, &columns);

        int32 rows;
        from->GetInfo(kRowWeightField, NULL, &rows);

        // sets fColumnCount && fRowCount on success
        if (!_ResizeGrid(columns, rows)) {
                unarchiver.Finish(B_NO_MEMORY);
                return;
        }

        for (int32 i = 0; i < fRowCount; i++) {
                float getter;
                if (from->FindFloat(kRowWeightField, i, &getter) == B_OK)
                        fRowInfos->SetWeight(i, getter);

                if (from->FindFloat(kRowSizesField, i * 2, &getter) == B_OK)
                        fRowInfos->SetMinSize(i, getter);

                if (from->FindFloat(kRowSizesField, i * 2 + 1, &getter) == B_OK)
                        fRowInfos->SetMaxSize(i, getter);
        }

        for (int32 i = 0; i < fColumnCount; i++) {
                float getter;
                if (from->FindFloat(kColumnWeightField, i, &getter) == B_OK)
                        fColumnInfos->SetWeight(i, getter);

                if (from->FindFloat(kColumnSizesField, i * 2, &getter) == B_OK)
                        fColumnInfos->SetMinSize(i, getter);

                if (from->FindFloat(kColumnSizesField, i * 2 + 1, &getter) == B_OK)
                        fColumnInfos->SetMaxSize(i, getter);
        }
}


BGridLayout::~BGridLayout()
{
        delete fRowInfos;
        delete fColumnInfos;

        for (int32 i = 0; i < fColumnCount; i++)
                delete[] fGrid[i];
        delete[] fGrid;
}


int32
BGridLayout::CountColumns() const
{
        return fColumnCount;
}


int32
BGridLayout::CountRows() const
{
        return fRowCount;
}


float
BGridLayout::HorizontalSpacing() const
{
        return fHSpacing;
}


float
BGridLayout::VerticalSpacing() const
{
        return fVSpacing;
}


void
BGridLayout::SetHorizontalSpacing(float spacing)
{
        spacing = BControlLook::ComposeSpacing(spacing);
        if (spacing != fHSpacing) {
                fHSpacing = spacing;

                InvalidateLayout();
        }
}


void
BGridLayout::SetVerticalSpacing(float spacing)
{
        spacing = BControlLook::ComposeSpacing(spacing);
        if (spacing != fVSpacing) {
                fVSpacing = spacing;

                InvalidateLayout();
        }
}


void
BGridLayout::SetSpacing(float horizontal, float vertical)
{
        horizontal = BControlLook::ComposeSpacing(horizontal);
        vertical = BControlLook::ComposeSpacing(vertical);
        if (horizontal != fHSpacing || vertical != fVSpacing) {
                fHSpacing = horizontal;
                fVSpacing = vertical;

                InvalidateLayout();
        }
}


float
BGridLayout::ColumnWeight(int32 column) const
{
        return fColumnInfos->Weight(column);
}


void
BGridLayout::SetColumnWeight(int32 column, float weight)
{
        fColumnInfos->SetWeight(column, weight);
}


float
BGridLayout::MinColumnWidth(int32 column) const
{
        return fColumnInfos->MinSize(column);
}


void
BGridLayout::SetMinColumnWidth(int32 column, float width)
{
        fColumnInfos->SetMinSize(column, width);
}


float
BGridLayout::MaxColumnWidth(int32 column) const
{
        return fColumnInfos->MaxSize(column);
}


void
BGridLayout::SetMaxColumnWidth(int32 column, float width)
{
        fColumnInfos->SetMaxSize(column, width);
}


float
BGridLayout::RowWeight(int32 row) const
{
        return fRowInfos->Weight(row);
}


void
BGridLayout::SetRowWeight(int32 row, float weight)
{
        fRowInfos->SetWeight(row, weight);
}


float
BGridLayout::MinRowHeight(int row) const
{
        return fRowInfos->MinSize(row);
}


void
BGridLayout::SetMinRowHeight(int32 row, float height)
{
        fRowInfos->SetMinSize(row, height);
}


float
BGridLayout::MaxRowHeight(int32 row) const
{
        return fRowInfos->MaxSize(row);
}


void
BGridLayout::SetMaxRowHeight(int32 row, float height)
{
        fRowInfos->SetMaxSize(row, height);
}


BLayoutItem*
BGridLayout::ItemAt(int32 column, int32 row) const
{
        if (column < 0 || column >= CountColumns()
                || row < 0 || row >= CountRows())
                return NULL;

        return fGrid[column][row];
}


BLayoutItem*
BGridLayout::AddView(BView* child)
{
        return BTwoDimensionalLayout::AddView(child);
}


BLayoutItem*
BGridLayout::AddView(int32 index, BView* child)
{
        return BTwoDimensionalLayout::AddView(index, child);
}


BLayoutItem*
BGridLayout::AddView(BView* child, int32 column, int32 row, int32 columnCount,
        int32 rowCount)
{
        if (!child)
                return NULL;

        BLayoutItem* item = new BViewLayoutItem(child);
        if (!AddItem(item, column, row, columnCount, rowCount)) {
                delete item;
                return NULL;
        }

        return item;
}


bool
BGridLayout::AddItem(BLayoutItem* item)
{
        // find a free spot
        for (int32 row = 0; row < fRowCount; row++) {
                for (int32 column = 0; column < fColumnCount; column++) {
                        if (_IsGridCellEmpty(column, row))
                                return AddItem(item, column, row, 1, 1);
                }
        }

        // no free spot, start a new column
        return AddItem(item, fColumnCount, 0, 1, 1);
}


bool
BGridLayout::AddItem(int32 index, BLayoutItem* item)
{
        return AddItem(item);
}


bool
BGridLayout::AddItem(BLayoutItem* item, int32 column, int32 row,
        int32 columnCount, int32 rowCount)
{
        if (!_AreGridCellsEmpty(column, row, columnCount, rowCount))
                return false;

        bool success = BTwoDimensionalLayout::AddItem(-1, item);
        if (!success)
                return false;

        // set item dimensions
        if (ItemLayoutData* data = _LayoutDataForItem(item)) {
                data->dimensions.x = column;
                data->dimensions.y = row;
                data->dimensions.width = columnCount;
                data->dimensions.height = rowCount;
        }

        if (!_InsertItemIntoGrid(item)) {
                RemoveItem(item);
                return false;
        }

        if (columnCount > 1)
                fMultiColumnItems++;
        if (rowCount > 1)
                fMultiRowItems++;

        return success;
}


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

        for (int32 i = 0; i < fRowCount && result == B_OK; i++) {
                result = into->AddFloat(kRowWeightField, fRowInfos->Weight(i));
                if (result == B_OK)
                        result = into->AddFloat(kRowSizesField, fRowInfos->MinSize(i));
                if (result == B_OK)
                        result = into->AddFloat(kRowSizesField, fRowInfos->MaxSize(i));
        }

        for (int32 i = 0; i < fColumnCount && result == B_OK; i++) {
                result = into->AddFloat(kColumnWeightField, fColumnInfos->Weight(i));
                if (result == B_OK)
                        result = into->AddFloat(kColumnSizesField, fColumnInfos->MinSize(i));
                if (result == B_OK)
                        result = into->AddFloat(kColumnSizesField, fColumnInfos->MaxSize(i));
        }

        return archiver.Finish(result);
}


status_t
BGridLayout::AllArchived(BMessage* into) const
{
        return BTwoDimensionalLayout::AllArchived(into);
}


status_t
BGridLayout::AllUnarchived(const BMessage* from)
{
        return BTwoDimensionalLayout::AllUnarchived(from);
}


BArchivable*
BGridLayout::Instantiate(BMessage* from)
{
        if (validate_instantiation(from, "BGridLayout"))
                return new BGridLayout(from);
        return NULL;
}


status_t
BGridLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
{
        ItemLayoutData* data =  _LayoutDataForItem(item);

        status_t result = into->AddInt32(kItemDimensionsField, data->dimensions.x);
        if (result == B_OK)
                result = into->AddInt32(kItemDimensionsField, data->dimensions.y);

        if (result == B_OK)
                result = into->AddInt32(kItemDimensionsField, data->dimensions.width);

        if (result == B_OK)
                result = into->AddInt32(kItemDimensionsField, data->dimensions.height);

        return result;
}


status_t
BGridLayout::ItemUnarchived(const BMessage* from,
        BLayoutItem* item, int32 index)
{
        ItemLayoutData* data = _LayoutDataForItem(item);
        Dimensions& dimensions = data->dimensions;

        index *= 4;
                // each item stores 4 int32s into kItemDimensionsField
        status_t result = from->FindInt32(kItemDimensionsField, index, &dimensions.x);
        if (result == B_OK)
                result = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y);

        if (result == B_OK)
                result = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width);

        if (result == B_OK) {
                result = from->FindInt32(kItemDimensionsField,
                        ++index, &dimensions.height);
        }

        if (result != B_OK)
                return result;

        if (!_AreGridCellsEmpty(dimensions.x, dimensions.y,
                dimensions.width, dimensions.height))
                return B_BAD_DATA;

        if (!_InsertItemIntoGrid(item))
                return B_NO_MEMORY;

        if (dimensions.width > 1)
                fMultiColumnItems++;

        if (dimensions.height > 1)
                fMultiRowItems++;

        return result;
}


bool
BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
{
        item->SetLayoutData(new(nothrow) ItemLayoutData);
        return item->LayoutData() != NULL;
}


void
BGridLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
{
        ItemLayoutData* data = _LayoutDataForItem(item);
        Dimensions itemDimensions = data->dimensions;
        item->SetLayoutData(NULL);
        delete data;

        if (itemDimensions.width > 1)
                fMultiColumnItems--;

        if (itemDimensions.height > 1)
                fMultiRowItems--;

        // remove the item from the grid
        for (int x = 0; x < itemDimensions.width; x++) {
                for (int y = 0; y < itemDimensions.height; y++)
                        fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL;
        }

        // check whether we can shrink the grid
        if (itemDimensions.x + itemDimensions.width == fColumnCount
                || itemDimensions.y + itemDimensions.height == fRowCount) {
                int32 columnCount = fColumnCount;
                int32 rowCount = fRowCount;

                // check for empty columns
                bool empty = true;
                for (; columnCount > 0; columnCount--) {
                        for (int32 row = 0; empty && row < rowCount; row++)
                                empty &= (fGrid[columnCount - 1][row] == NULL);

                        if (!empty)
                                break;
                }

                // check for empty rows
                empty = true;
                for (; rowCount > 0; rowCount--) {
                        for (int32 column = 0; empty && column < columnCount; column++)
                                empty &= (fGrid[column][rowCount - 1] == NULL);

                        if (!empty)
                                break;
                }

                // resize the grid
                if (columnCount != fColumnCount || rowCount != fRowCount)
                        _ResizeGrid(columnCount, rowCount);
        }
}


bool
BGridLayout::HasMultiColumnItems()
{
        return fMultiColumnItems > 0;
}


bool
BGridLayout::HasMultiRowItems()
{
        return fMultiRowItems > 0;
}


int32
BGridLayout::InternalCountColumns()
{
        return fColumnCount;
}


int32
BGridLayout::InternalCountRows()
{
        return fRowCount;
}


void
BGridLayout::GetColumnRowConstraints(orientation orientation, int32 index,
        ColumnRowConstraints* constraints)
{
        if (orientation == B_HORIZONTAL) {
                constraints->min = MinColumnWidth(index);
                constraints->max = MaxColumnWidth(index);
                constraints->weight = ColumnWeight(index);
        } else {
                constraints->min = MinRowHeight(index);
                constraints->max = MaxRowHeight(index);
                constraints->weight = RowWeight(index);
        }
}


void
BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions)
{
        if (ItemLayoutData* data = _LayoutDataForItem(item))
                *dimensions = data->dimensions;
}


bool
BGridLayout::_IsGridCellEmpty(int32 column, int32 row)
{
        if (column < 0 || row < 0)
                return false;

        if (column >= fColumnCount || row >= fRowCount)
                return true;

        return (fGrid[column][row] == NULL);
}


bool
BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount,
        int32 rowCount)
{
        if (column < 0 || row < 0)
                return false;
        int32 toColumn = min_c(column + columnCount, fColumnCount);
        int32 toRow = min_c(row + rowCount, fRowCount);

        for (int32 x = column; x < toColumn; x++) {
                for (int32 y = row; y < toRow; y++) {
                        if (fGrid[x][y] != NULL)
                                return false;
                }
        }

        return true;
}


bool
BGridLayout::_InsertItemIntoGrid(BLayoutItem* item)
{
        BGridLayout::ItemLayoutData* data = _LayoutDataForItem(item);
        int32 column = data->dimensions.x;
        int32 columnCount = data->dimensions.width;
        int32 row = data->dimensions.y;
        int32 rowCount = data->dimensions.height;

        // resize the grid, if necessary
        int32 newColumnCount = max_c(fColumnCount, column + columnCount);
        int32 newRowCount = max_c(fRowCount, row + rowCount);
        if (newColumnCount > fColumnCount || newRowCount > fRowCount) {
                if (!_ResizeGrid(newColumnCount, newRowCount))
                        return false;
        }

        // enter the item in the grid
        for (int32 x = 0; x < columnCount; x++) {
                for (int32 y = 0; y < rowCount; y++) {
                        if (x == 0 && y == 0)
                                fGrid[column + x][row + y] = item;
                        else
                                fGrid[column + x][row + y] = OCCUPIED_GRID_CELL;
                }
        }

        return true;
}


bool
BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount)
{
        if (columnCount == fColumnCount && rowCount == fRowCount)
                return true;

        int32 rowsToKeep = min_c(rowCount, fRowCount);

        // allocate new grid
        BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount];
        if (grid == NULL)
                return false;

        memset(grid, 0, sizeof(BLayoutItem**) * columnCount);

        bool success = true;
        for (int32 i = 0; i < columnCount; i++) {
                BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount];
                if (!column) {
                        success = false;
                        break;
                }
                grid[i] = column;

                memset(column, 0, sizeof(BLayoutItem*) * rowCount);
                if (i < fColumnCount && rowsToKeep > 0)
                        memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep);
        }

        // if everything went fine, set the new grid
        if (success) {
                swap(grid, fGrid);
                swap(columnCount, fColumnCount);
                swap(rowCount, fRowCount);
        }

        // delete the old, respectively on error the partially created grid
        for (int32 i = 0; i < columnCount; i++)
                delete[] grid[i];

        delete[] grid;

        return success;
}


BGridLayout::ItemLayoutData*
BGridLayout::_LayoutDataForItem(BLayoutItem* item) const
{
        if (!item)
                return NULL;
        return (ItemLayoutData*)item->LayoutData();
}


status_t
BGridLayout::Perform(perform_code d, void* arg)
{
        return BTwoDimensionalLayout::Perform(d, arg);
}


void BGridLayout::_ReservedGridLayout1() {}
void BGridLayout::_ReservedGridLayout2() {}
void BGridLayout::_ReservedGridLayout3() {}
void BGridLayout::_ReservedGridLayout4() {}
void BGridLayout::_ReservedGridLayout5() {}
void BGridLayout::_ReservedGridLayout6() {}
void BGridLayout::_ReservedGridLayout7() {}
void BGridLayout::_ReservedGridLayout8() {}
void BGridLayout::_ReservedGridLayout9() {}
void BGridLayout::_ReservedGridLayout10() {}