haiku/src/kits/interface/GridLayout.cpp
John Scipione e724b26f23 Remove enum elaborated type specifier
...from orientation params. Elaborated type specifiers are not needed
for C++ code and removing them makes doxygen happy. Verified working
on both gcc2h and gcc4h builds.
2013-07-08 19:29:11 -04:00

824 lines
16 KiB
C++

/*
* Copyright 2010-2011, Haiku Inc.
* Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
* All rights reserved. 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;
}
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(row, column))
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 err = BTwoDimensionalLayout::Archive(into, deep);
for (int32 i = 0; i < fRowCount && err == B_OK; i++) {
err = into->AddFloat(kRowWeightField, fRowInfos->Weight(i));
if (err == B_OK)
err = into->AddFloat(kRowSizesField, fRowInfos->MinSize(i));
if (err == B_OK)
err = into->AddFloat(kRowSizesField, fRowInfos->MaxSize(i));
}
for (int32 i = 0; i < fColumnCount && err == B_OK; i++) {
err = into->AddFloat(kColumnWeightField, fColumnInfos->Weight(i));
if (err == B_OK)
err = into->AddFloat(kColumnSizesField, fColumnInfos->MinSize(i));
if (err == B_OK)
err = into->AddFloat(kColumnSizesField, fColumnInfos->MaxSize(i));
}
return archiver.Finish(err);
}
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 err = into->AddInt32(kItemDimensionsField, data->dimensions.x);
if (err == B_OK)
err = into->AddInt32(kItemDimensionsField, data->dimensions.y);
if (err == B_OK)
err = into->AddInt32(kItemDimensionsField, data->dimensions.width);
if (err == B_OK)
err = into->AddInt32(kItemDimensionsField, data->dimensions.height);
return err;
}
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 err = from->FindInt32(kItemDimensionsField, index, &dimensions.x);
if (err == B_OK)
err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y);
if (err == B_OK)
err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width);
if (err == B_OK) {
err = from->FindInt32(kItemDimensionsField,
++index, &dimensions.height);
}
if (err != B_OK)
return err;
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 err;
}
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)
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() {}