// GridView.cpp
// e.moon 8mar99

#include "GridView.h"
#include "GridRow.h"
#include "GridColumn.h"
#include "GridViewImpl.h"

#include <Alert.h>
#include <ScrollBar.h>
#include <ScrollView.h>

#include <functional>
#include <algorithm>
#include <cmath>

#include "set_tools.h"
#include "debug_tools.h"

__USE_CORTEX_NAMESPACE

// ---------------------------------------------------------------- //

//#define D_PRINTF fprintf
//#define TEST_LOCK() (testLock(__FILE__, __LINE__))

class column_is_type { public:
	int32 type;
	column_is_type(int32 n) : type(n) {}
	bool operator()(const GridColumn* c) {
		return c && c->id() == type;
	}
};

class column_overlaps_point { public:
	float x;
	column_overlaps_point(float _x) : x(_x) {}
	bool operator()(const GridColumn* c) {
		return x >= c->xOffset() && x < c->xOffset()+c->width();
	}
};

class row_overlaps_point { public:
	float y;
	row_overlaps_point(float _y) : y(_y) {}
	bool operator()(const GridRow* c) {
		return y >= c->yOffset() && y < c->yOffset()+c->height();
	}
};

// ---------------------------------------------------------------- //
// GridView
// constants
// ---------------------------------------------------------------- //

const float				GridView::s_fDefRowResizeStep = 12.0;
const float				GridView::s_fDefRowHeight			= 24.0;
	
const float				GridView::s_fMinRowHeight			= 8.0;
const float				GridView::s_fMaxRowHeight			= 96.0;

const float				GridView::s_fGripSize = 4.0;

// ---------------------------------------------------------------- //
// GridView
// ctor/dtor
// ---------------------------------------------------------------- //

GridView::GridView(BRect frame, const char* pName, int32 resizeMode,
	int32 viewFlags, int32 layoutFlags) :
	
	BView(frame, pName, resizeMode, viewFlags),
	m_layoutFlags(layoutFlags),
	m_pContainerView(0),
	m_pScrollView(0),
	m_scrollMarginWidth(0.0),
	m_scrollMarginHeight(0.0),
	m_curFrame(frame),
	m_resizeRow(-1),
	m_fRowResizeStep(s_fDefRowResizeStep),
	m_fGangedRowHeight(s_fDefRowHeight) {
	
	initContainerView();
	updateScrollLimits();
}

GridView::~GridView() {
	// clear the row and column sets
	ptr_set_delete(m_rows.begin(), m_rows.end());
	ptr_set_delete(m_columns.begin(), m_columns.end());
}

void GridView::initContainerView() {

	// construct a basic BView to serve as a container.
	// subclasses can replace this with a view of their
	// own choosing via setContainerView()

	setContainerView(new BView(
		BRect(0,0,0,0),
		"containerView",
		B_FOLLOW_LEFT|B_FOLLOW_TOP,
		B_WILL_DRAW));
}

void GridView::dumpStructure() {
	PRINT(( "GridView::dumpStructure()\n"));

	// structure info needed: row-by-row, cell-by-cell dump
	for(uint32 row = 0; row < rows(); row++) {
		PRINT(( "%02d:", row));
		for(uint32 cell = 0;
			cell < columns() && cell < m_rows[row]->m_cells.size();
			cell++) {
			PRINT(( " "));
			GridRow::CellEntry& c = m_rows[row]->m_cells[cell];
			if(!c.pCell) {
				PRINT(( "()   "));
				continue;
			}
			PRINT(( "%02d,%02d",
				c.columnSpan, c.rowSpan));
		}
		PRINT(( "\n"));
	}
}

// ---------------------------------------------------------------- //
// row/column/cell access
// ---------------------------------------------------------------- //

// Column access
GridColumn* GridView::columnFor(int32 type) const {

	column_vector::const_iterator itFound = find_if(
		m_columns.begin(), m_columns.end(), column_is_type(type));
		
	return itFound != m_columns.end() ?
		*itFound :
			0;
}
int32 GridView::columnIndexFor(int32 type) const {

	column_vector::const_iterator itFound = find_if(
		m_columns.begin(), m_columns.end(), column_is_type(type));

	return itFound != m_columns.end() ?
		// get offset
		itFound - m_columns.begin() :
			-1;
}

int32 GridView::columnIndexFor(float xPos) const {
	if(xPos < 0.0)
		return -1;
	column_vector::const_iterator itFound = find_if(
		m_columns.begin(), m_columns.end(), column_overlaps_point(xPos));
		
	return itFound != m_columns.end() ?
		// get offset
		itFound - m_columns.begin() :
			-1;
}

int32 GridView::columns() const {

	return m_columns.size();
}
GridColumn* GridView::columnAt(int32 index) const {

	return (index < m_columns.size() && index >= 0) ?
		m_columns[index] : 0;
}

// Row access
int32 GridView::rows() const {
	return m_rows.size();
};

GridRow* GridView::rowAt(uint32 row) const {
	return (row < m_rows.size()) ? m_rows[row] : 0;
}

int32 GridView::rowIndexFor(float yPos) const {
	if(yPos < 0.0)
		return -1;
	row_vector::const_iterator itFound = find_if(
		m_rows.begin(), m_rows.end(), row_overlaps_point(yPos));
		
	return itFound != m_rows.end() ?
		// get offset
		itFound - m_rows.begin() :
			-1;
}


// Row resizing
float GridView::rowResizeStep() const {
	return m_fRowResizeStep;
}

status_t GridView::setRowResizeStep(float fStep) {
	if(fStep <= 1.0)
		return B_ERROR;
		
	m_fRowResizeStep = fStep;
	return B_OK;
}
	
// Cell access:
// looks for a cell in or overlapping the given column and row.
// returns true if a cell was found, as well as a pointer to the
// view and the real origin (top/left row and column) and size
// of the cell.

bool GridView::getCell(
	uint32 column, uint32 row,
	BView** poCellView,
	uint32* poOriginColumn, uint32* poOriginRow,
	uint32* poColumnSpan, uint32* poRowSpan) const {

	if(column >= m_columns.size() ||
		row >= m_rows.size())
		return false;
	
	// look for cell in or overlapping the given column
	if(!m_rows[row]->hitTest(column, poCellView, poOriginColumn, poColumnSpan))
		return false;
		
	// scan upward for origin row (all below it that the cell overlaps
	// have a rowSpan() of 0)
	*poOriginRow = row;
	while(!m_rows[*poOriginRow]->rowSpan(*poOriginColumn)) {
		if(!*poOriginRow) {
			PRINT((
				"* GridView::getCell(): corrupt row structure (rowSpan=0, row=0)\n"));
			return false;
		}
		--*poOriginRow;
	}
	if(poRowSpan)
		*poRowSpan = m_rows[*poOriginRow]->rowSpan(*poOriginColumn);
	if(poColumnSpan)
		*poColumnSpan = m_rows[*poOriginRow]->columnSpan(*poOriginColumn);
	
	return true;
}
	
// as above, but looks at a point
bool GridView::getCell(
	BPoint where,
	BView** poCellView,
	uint32* poOriginColumn, uint32* poOriginRow,
	uint32* poColumnSpan, uint32* poRowSpan) const {

	// find a row and column for the given point
	// +++++ this could be much faster
	int32 row=-1, column=-1;
	for(int n = 0; n < m_rows.size(); n++) {
		if(m_rows[n]->m_yOffset >= where.y) {
			if(m_rows[n]->m_yOffset + m_rows[n]->m_height < where.y) {
				row = n;
			} // else break, since the y value is out of bounds
		}
	}
	if(row < 0)
		return false;
	
	for(int n = 0; n < m_columns.size(); n++) {
		if(m_columns[n]->m_xOffset >= where.x) {
			if(m_columns[n]->m_xOffset + m_columns[n]->m_width < where.x) {
				column = n;
			} // else break, since the y value is out of bounds
		}
	}
	if(column < 0)
		return false;
	
	return getCell(column, row, poCellView,
		poOriginColumn, poOriginRow, poColumnSpan, poRowSpan);	
}

// as above, but fails if the cell's origin isn't in the
// provided row and column
bool GridView::getCell(
	uint32 column, uint32 row,
	BView** poCellView,
	uint32* poColumnSpan, uint32* poRowSpan) const {

	// check bounds	
	if(column >= m_columns.size() ||
		row >= m_rows.size())
		return false;
	
	// look for a cell
	BView* pView = m_rows[row]->cellAt(column);
	if(!pView)
		return false;
		
	// check row span (if 0, this cell starts on a previous row)
	uint32 rowSpan = m_rows[row]->rowSpan(column);
	if(!rowSpan)
		return false;

/*
	PRINT((
		"GridView::getCell(%ld, %ld): cell span [%ld,%ld]\n",
		column, row,
		m_rows[row]->columnSpan(column),
		m_rows[row]->rowSpan(column)));
*/	
	// found it:
	*poCellView = pView;
	if(poColumnSpan)
		*poColumnSpan = m_rows[row]->columnSpan(column);
	if(poRowSpan)
		*poRowSpan = rowSpan;

	return true;
}

// as above (matches only a cell with the given origin
// row/column)
bool GridView::findCell(
	uint32 column, uint32 row) const {
	
	// check bounds	
	if(column >= m_columns.size() ||
		row >= m_rows.size())
		return false;
		
	// look for a cell
	BView* pView = m_rows[row]->cellAt(column);
	if(!pView)
		return false;

	// check row span (if 0, this cell starts on a previous row)
	uint32 rowSpan = m_rows[row]->rowSpan(column);
	
	return rowSpan != 0;
}


// ---------------------------------------------------------------- //
// public operations
// ---------------------------------------------------------------- //

// Add/delete/reorder column

status_t GridView::addColumn(
	uint32 atIndex,
	GridColumn* pColumn) {
	
	ASSERT(pColumn);

	// sanity-check column info
	if(atIndex > m_columns.size()) // bad position
		return B_ERROR;
	if(columnFor(pColumn->id())) // already have column of that type?
		return B_ERROR;

	// offset cells in all following columns
	offsetColumns(atIndex, m_columns.size(), pColumn->m_width);

	// insert the column
	m_columns.insert(m_columns.begin() + atIndex, pColumn);
	
	// prepare column layout
	initColumn(m_columns[atIndex], atIndex);
	
	// insert empty cell entries in each row
	for(row_vector::iterator it = 	m_rows.begin();
		it != m_rows.end();
		it++)
		(*it)->insertColumn(atIndex);

	// update the container-view's bounds
	// 4may99 e.moon
	updateDataBounds();

	return B_OK;
}

status_t GridView::addColumn(
	uint32 atIndex,
	float width,
	uint32 id) {
	
	GridColumn* pColumn = new GridColumn(id, width);
	status_t ret = addColumn(atIndex, pColumn);
	if(ret < B_OK)
		delete pColumn;
	
	return ret;
}

status_t GridView::addColumn(
	uint32 atIndex,
	float width) {
	
	GridColumn* pColumn = new GridColumn(width);
	status_t ret = addColumn(atIndex, pColumn);
	if(ret < B_OK)
		delete pColumn;
	
	return ret;
}

		
status_t GridView::deleteColumn(
	uint32 atIndex) {
	
	ASSERT(m_columns[atIndex]);
	
	// find the column
	if(atIndex > m_columns.size())
		return B_ERROR;
	
	// offset cells in following columns
	offsetColumns(atIndex+1, m_columns.size(),
		-m_columns[atIndex]->m_width);
	
	// delete cells from row set
	for(row_vector::iterator it = 	m_rows.begin();
		it != m_rows.end();
		it++)
		(*it)->deleteColumn(atIndex);
	
	// remove from the column set & deallocate the column object
	delete m_columns[atIndex];
	m_columns.erase(m_columns.begin() + atIndex);

	// update the container-view's bounds
	// 4may99 e.moon
	updateDataBounds();

	return B_OK;
}
		
status_t GridView::moveColumn(
	uint32 fromIndex,
	uint32 toIndex) {
	
	return B_ERROR; //nyi
}
	
// Add/delete/reorder row
status_t GridView::addRow(
	uint32 atIndex,
	GridRow* pRow) {
	
	// sanity checks
	ASSERT(pRow);
	if(atIndex > m_rows.size())
		return B_ERROR;
	
	// insert it
	if(m_rows.size() <= atIndex)
		m_rows.resize(atIndex+1);
	m_rows[atIndex] = pRow;
	initRow(pRow, atIndex);

	// offset row entries & contained cells
	offsetRows(atIndex+1, m_rows.size(),
		pRow->m_height);

	// update the container-view's bounds
	// 4may99 e.moon
	updateDataBounds();

	return B_OK;
}

status_t GridView::addRow(
	uint32 atIndex,
	float height) {
	
	GridRow* pRow = new GridRow(height);
	status_t err = addRow(atIndex, pRow);
	if(err < B_OK)
		delete pRow;
	
	return B_OK;
}

status_t GridView::addRows(
	uint32 atIndex,
	float height,
	uint32 count) {
	
	status_t err = B_OK;
	while(count--) {
		err = addRow(atIndex++, height);
		if(err < B_OK)
			return err;
	}
	return err;
}

status_t GridView::deleteRow(
	uint32 atIndex) {
	
	// offset following row entries & contained cells
	offsetRows(atIndex+1, m_rows.size(),
		-m_rows[atIndex]->m_height);

	// delete it from the row set
	delete m_rows[atIndex];
	m_rows.erase(m_rows.begin() + atIndex);

	// update the container-view's bounds
	// 4may99 e.moon
	updateDataBounds();
	
	return B_OK;
}
		
status_t GridView::moveRow(
	uint32 fromIndexOrID,
	uint32 toIndexOrID) {
	
	return B_ERROR; //nyi
}

// Add/delete cell view
status_t GridView::addCell(
	BView* pCellView,
	uint32 columnIndex,
	uint32 rowIndex,
	uint32 columnSpan /*=1*/,
	uint32 rowSpan /*=1*/) {
	
	ASSERT(pCellView);
	
	// verify that the requested row & column exist
	if(columnIndex >= m_columns.size())
		return B_ERROR;
	if(rowIndex >= m_rows.size())
		return B_ERROR;
	if(!columnSpan)
		return B_ERROR;
	if(!rowSpan)
		return B_ERROR;

	GridRow* pRow = m_rows[rowIndex];

	float totalWidth;
	if(columnSpan > 1) {
		uint32 lastColumn = columnIndex + columnSpan - 1;
		totalWidth =
			(m_columns[lastColumn]->m_xOffset + m_columns[lastColumn]->cellWidth()) -
			m_columns[columnIndex]->m_xOffset;
	}
	else
		totalWidth = m_columns[columnIndex]->cellWidth();

	float totalHeight = 0.0;

	if(rowSpan > 1) {

		// validate row span
		if(rowIndex + rowSpan > m_rows.size())
			return B_ERROR;

		// see if there's room in each row
		// 4may99 e.moon
		for(int n = rowIndex; n < rowIndex + rowSpan; n++)
			if(!m_rows[n]->canAddCell(pCellView, columnIndex, columnSpan))
				return B_ERROR;

		// add the cell entries
		int curRowSpan = rowSpan;
		for(int n = rowIndex; n < rowIndex + rowSpan; n++) {
//			PRINT((
//				"GridView::addCell(): adding to row %ld\n", n));
			m_rows[n]->addCell(pCellView, columnIndex, columnSpan, curRowSpan);
			if(curRowSpan)
				curRowSpan = 0;
		}
		
		uint32 lastRow = rowIndex + rowSpan - 1;
		totalHeight =
			(m_rows[lastRow]->m_yOffset + m_rows[lastRow]->cellHeight()) -
			m_rows[rowIndex]->m_yOffset;
	}
	else {
		// see if there's room in the selected row
		if(!pRow->canAddCell(pCellView, columnIndex, columnSpan))
			return B_ERROR;

		// do it
		pRow->addCell(pCellView, columnIndex, columnSpan, 1);
		
		totalHeight = pRow->cellHeight();
	}
	
//	row.cellAt(columnIndex); // +++++ whazis for? 4may99
	
	// place & add the cell view
	pCellView->MoveTo(
		m_columns[columnIndex]->m_xOffset,
		pRow->m_yOffset);
	
	pCellView->ResizeTo(totalWidth-1.0, totalHeight-1.0);
	
//	BRect b = pCellView->Frame();
//	D_PRINTF("addCell(): cell frame (%.1f,%.1f)-(%.1f,%.1f) (row height %.1f/%.1f)\n",
//		b.left, b.top, b.right, b.bottom, pRow->m_height, pRow->cellHeight());

	ASSERT(containerView());
	// 11may99: handle views already attached to the container
	if(pCellView->Parent() != containerView())
		containerView()->AddChild(pCellView);
	
	return B_OK;
}

bool GridView::canAddCell(
	uint32 columnIndex, uint32 rowIndex,
	uint32 columnSpan, uint32 rowSpan) {

	// verify that the requested row & column exist
	if(columnIndex >= m_columns.size())
		return false;
	if(rowIndex >= m_rows.size())
		return false;
	if(!columnSpan)
		return false;
	if(!rowSpan)
		return false;

	GridRow* pRow = m_rows[rowIndex];
	BView* pCellView = 0; // dummy pointer

	float totalWidth;
	if(columnSpan > 1) {
		uint32 lastColumn = columnIndex + columnSpan - 1;
		totalWidth =
			(m_columns[lastColumn]->m_xOffset + m_columns[lastColumn]->cellWidth()) -
			m_columns[columnIndex]->m_xOffset;
	}
	else
		totalWidth = m_columns[columnIndex]->cellWidth();

//	float totalHeight = 0.0;

	if(rowSpan > 1) {

		// validate row span
		if(rowIndex + rowSpan > m_rows.size())
			return false;

		// see if there's room in each row
		for(int n = rowIndex; n < rowIndex + rowSpan; n++)
			if(!m_rows[n]->canAddCell(pCellView, columnIndex, columnSpan))
				return false;
	}
	else {
		// see if there's room in the selected row
		if(!pRow->canAddCell(pCellView, columnIndex, columnSpan))
			return false;
	}

	return true;
}

BView* GridView::removeCell(uint32 columnIndex, uint32 rowIndex) {
	
	BView* pCell = removeCellEntry(columnIndex, rowIndex);
	if(pCell && pCell->Parent())
		pCell->RemoveSelf();
	
	return pCell;
}
	
status_t GridView::resizeCell(
	uint32 columnIndex, uint32 rowIndex,
	uint32 columnSpan, uint32 rowSpan) {

	// sanity checks
	if(!columnSpan)
		return B_ERROR;
	if(!rowSpan)
		return B_ERROR;
	if(columnIndex + columnSpan > m_columns.size())
		return B_ERROR;
	if(rowIndex + rowSpan > m_rows.size())
		return B_ERROR;

	// find current cell
	BView* pCell;
	uint32 curColumnSpan, curRowSpan;
	if(!getCell(columnIndex, rowIndex, &pCell,
		&curColumnSpan, &curRowSpan))
		return B_ERROR;
		
	// make sure there's work to do
	if(curColumnSpan == columnSpan && curRowSpan == rowSpan)
		return B_OK;

	// make sure there's room
	// check previously unoccupied columns in each row
	if(columnSpan > curColumnSpan)
		for(uint32 row = rowIndex; row < rowIndex+curRowSpan; row++)
			if(!m_rows[row]->canAddCell(pCell, columnIndex+curColumnSpan,
				columnSpan - curColumnSpan))
				return B_ERROR;

	// check all columns to be occupied in each previously
	// unoccupied row
	if(rowSpan > curRowSpan)
		for(uint32 row = rowIndex + curRowSpan;
			row < rowIndex + rowSpan; row++)
			if(!m_rows[row]->canAddCell(pCell, columnIndex, columnSpan))
				return B_ERROR;
	
	// update cell entry
	m_rows[rowIndex]->m_cells[columnIndex].columnSpan = columnSpan;
	m_rows[rowIndex]->m_cells[columnIndex].rowSpan = rowSpan;

	// figure cell-size delta
	float widthDelta = 0.0;
	if(columnSpan > curColumnSpan)
		for(uint32 c = columnIndex+curColumnSpan;
			c < columnIndex+columnSpan; c++)
			widthDelta += m_columns[c]->width();
	else
		for(uint32 c = columnIndex+columnSpan;
			c < columnIndex+curColumnSpan; c++)
			widthDelta -= m_columns[c]->width();

	float heightDelta = 0.0;			
	status_t err;
	if(rowSpan > curRowSpan) {	
		// add entries for new rows
		for(uint32 row = rowIndex + curRowSpan;
			row < rowIndex + rowSpan; row++) {
			err = m_rows[row]->addCell(pCell, columnIndex, columnSpan, 0);
			ASSERT(err == B_OK);
			heightDelta += m_rows[row]->height();
		}
	} else {
		// remove entries for now-unoccupied rows
		// 27may99 fixed (curRowSpan & rowSpan were flipped)
		BView* pCur;
		for(uint32 row = rowIndex + rowSpan;
			row < rowIndex + curRowSpan; row++) {
			err = m_rows[row]->removeCell(columnIndex, &pCur);
			ASSERT(err == B_OK);
			ASSERT(pCur == pCell);
			
			heightDelta -= m_rows[row]->height();
		}
	}

	// resize view
	pCell->ResizeBy(widthDelta, heightDelta);
	
	return B_OK;
}

		
// DATA-BOUNDS/SCROLLING
	
void GridView::setScrollMargins(float width, float height) {
	m_scrollMarginWidth = width;
	m_scrollMarginHeight = height;
	updateScrollLimits();
}

void GridView::getScrollMargins(float* poWidth, float* poHeight) {
	if(poWidth)
		*poWidth = m_scrollMarginWidth;
	if(poHeight)
		*poHeight = m_scrollMarginHeight;
}

/*
// ---------------------------------------------------------------- //
// IMouseTrackingDestination impl.
// ---------------------------------------------------------------- //

// a MouseTrackingSourceView has started tracking the mouse
// (point is in screen coordinates)
void GridView::mouseTrackingBegin(
	MouseTrackingSourceView* pSource,
	uint32 buttons,
	BPoint point) {
	
	IGrip* pGrip = dynamic_cast<IGrip*>(pSource);
	if(pGrip && pGrip->gripOrientation() == IGrip::ROW) {
		m_resizeRow = pGrip->gripTargetIndex();
		ASSERT(m_resizeRow < m_rows.size());
		m_fResizeRowHeight = m_rows[m_resizeRow]->m_height;
	}
}
	
// mouse-tracking update from child view
// (point is in screen coordinates)
void GridView::mouseTrackingUpdate(
	uint32 buttons,
	float xDelta,
	float yDelta,
	BPoint point) {

	if(m_resizeRow >= 0 && yDelta != 0.0) {
		m_fResizeRowHeight += yDelta;
// reworking 5may99 - e.moon
//		updateRowSize(m_resizeRow, m_fResizeRowHeight);
	}
}
	
// mouse-tracking done
void GridView::mouseTrackingEnd() {
	m_resizeRow = -1;
}
*/

// ---------------------------------------------------------------- //
// BView implementation
// ---------------------------------------------------------------- //

// trigger layout once attached to window
void GridView::AttachedToWindow() {

	// make sure the container view is properly set up:
	ASSERT(m_pContainerView);
	
	m_pContainerView->SetResizingMode(B_FOLLOW_LEFT|B_FOLLOW_TOP);
	m_pContainerView->SetFlags(m_pContainerView->Flags() | B_WILL_DRAW);
	m_pContainerView->MoveTo(0.0, 0.0);
	
	// refresh the scrollbar limits
	updateScrollLimits();
}

void GridView::AllAttached() {}
	
// handle resize
void GridView::FrameResized(float width, float height) {
	_inherited::FrameResized(width, height);
	
	m_curFrame = Frame();

	updateScrollLimits();
}

void GridView::TargetedByScrollView(BScrollView* pScroller) {
//	m_pScrollView = pScroller;
//	updateScrollLimits();
}

// ---------------------------------------------------------------- //
// impl. operations
// ---------------------------------------------------------------- //

// removes the entry for a cell view without removing the
// child view itself from the hierarchy.  called by
// removeCell().  be careful with this one!
BView* GridView::removeCellEntry(uint32 columnIndex, uint32 rowIndex) {
	GridRow* pRow = m_rows[rowIndex];

	// verify that the requested row & column exist
	if(columnIndex >= m_columns.size())
		return 0;
	if(rowIndex >= m_rows.size())
		return 0;

	// make sure that the given row/column point to a cell origin
	BView* pOriginCell;
	uint32 originColumn;
	if(!pRow->hitTest(columnIndex, &pOriginCell, &originColumn) ||
		columnIndex != originColumn ||
		!pRow->rowSpan(columnIndex))
		return 0;

	uint32 rowSpan = pRow->rowSpan(columnIndex);
	
	// remove the cell from each row
	BView* pCell;
//	PRINT((
//		"GridView::removeCellEntry(): removing from row %ld\n", rowIndex));
	status_t err = pRow->removeCell(columnIndex, &pCell);
	ASSERT(err == B_OK);
	ASSERT(pCell);
		
	// handle following rows
	// 4may99 e.moon
	for(int n = rowIndex+1; n < rowIndex + rowSpan; n++) {
		BView* pCur;
//		PRINT((
//			"GridView::removeCellEntry(): removing from row %ld\n", n));
		err = m_rows[n]->removeCell(columnIndex, &pCur);
		ASSERT(err == B_OK);
		ASSERT(pCur == pCell);
	}

	return pCell;
}

/*
bool GridView::testLock(const char* pFile, int line) const {
	if(!isLocked()) {
		char message[256];
		sprintf(message, "GridView not locked: %s, line %d",
			pFile, line);
		(new BAlert("GridView::testLock() FAILED", message, "Sheeeit."))->Go();
		return false;
	}
	return true;
}
*/

// move cells in a given range of columns (including begin,
// excluding end) horizontally by provided offset
void GridView::offsetColumns(
	uint32 begin, uint32 end, float xOffset) {

	if(begin >= m_columns.size() || end == begin)
		return;
	ASSERT(end > begin);

	// move the views	& offset column entries
	for(uint32 row = 0; row < m_rows.size(); row++) {
		GridRow* pRow = m_rows[row];
		ASSERT(pRow);
		
		for(uint32 index = begin; index < end; index++) {
			BView* pCell = pRow->m_cells[index].pCell;
			if(pCell)
				pCell->MoveBy(xOffset, 0.0);
				
			GridColumn* pColumn = m_columns[index];
			ASSERT(pColumn);
			pColumn->m_xOffset += xOffset;
		}
	}
}
	
// move cells in given range of rows (including begin, excluding
// end) vertically by given offset
void GridView::offsetRows(
	uint32 begin, uint32 end, float yOffset) {

	if(begin >= m_rows.size() || end == begin)
		return;
	ASSERT(end > begin);
	
	for(uint32 index = begin; index < end; index++) {

		GridRow* pRow = m_rows[index];
		ASSERT(pRow);
		pRow->m_yOffset += yOffset;

		// offset cells
		for(GridRow::cell_vector::iterator it = pRow->m_cells.begin();
			it != pRow->m_cells.end();
			it++) {
			BView* pCell = (*it).pCell;
			if(pCell)
				pCell->MoveBy(0.0, yOffset);
		}
	}
}

// resizes the given column, and all cells in/overlapping it.
// A column may be sized to 0.0 (or less).
// set bOffsetFollowing to false to suppress repositioning of
// following columns.
// LOCK BEFORE CALLING

void GridView::resizeColumn(uint32 column, float width, bool bOffsetFollowing) {
	ASSERT(column < m_columns.size());

	GridColumn* pCol = m_columns[column];

	// figure how much following columns will be offset
	// +++++ TEST ME +++++
	float offset = 0.0;
	if(width < 0.0) {
		if(pCol->m_width > 0.0)
			offset = -pCol->m_width;
		// else no visible effect on following columns
	} else {
		if(pCol->m_width > 0.0)
			offset = width - pCol->m_width;
		else
			offset = width;
	}
	
	// set new width
	pCol->m_width = width;
	
	// apply new width to cells in or overlapping this column
	for(uint32 row = 0; row < m_rows.size(); row++) {
		ASSERT(m_rows[row]);
		ASSERT(m_rows[row]->m_cells.size() > column);

		// see if:
		// a) a cell overlaps the resized column of the current row, and
		// b) this row is the first in which that cell appears

		BView* pCell;
		uint32 originColumn;
		if(m_rows[row]->hitTest(column, &pCell, &originColumn) &&
			m_rows[row]->rowSpan(originColumn)) {

			// recalculate cell width
			float newWidth = 0.0;
			uint32 endColumn = originColumn + m_rows[row]->m_cells[column].columnSpan;
			for(uint32 n = originColumn; n < endColumn; n++) {
				ASSERT(m_columns[n]);
				if(m_columns[n]->m_width > 0.0) // handle columns w/ negative widths properly
					newWidth += m_columns[n]->m_width;
			}
			
			// adjust for grip or other right-hand apparatus in last column only
			newWidth -= m_columns[endColumn-1]->m_borderWidth;

			// resize cell
			pCell->ResizeTo(newWidth, m_rows[row]->cellHeight());
		}
	}
	
	if(!bOffsetFollowing)
		return; // done; the caller will handle offsets of following columns

	// move following columns
	offsetColumns(column+1, m_columns.size(), offset);
}

// [+++++ 5may99: replaces updateRowSize()]

// resizes the given scrollable row (or all scrollable rows, if ganged)
// constrains the given height to sane limits
// +++++ needs row-span support -- 5may99 e.moon
//       also needs to support non-scrollable rows
// set bOffsetFollowing to false to suppress repositioning of
// following rows.
// LOCK BEFORE CALLING

void GridView::resizeRow(uint32 row, float fHeight, bool bOffsetFollowing) {
	ASSERT(row < m_rows.size());
	
	// +++++
	
	return;
}


// initialize the given column
void GridView::initColumn(GridColumn* pColumn, uint32 destIndex) {

	if(destIndex > 0 && destIndex < m_columns.size()) {
		pColumn->m_xOffset =
			m_columns[destIndex-1]->m_xOffset +
			m_columns[destIndex-1]->m_width;
	}

//	D_PRINTF("initColumn(): width %.1f, offset %.1f\n",
//		pColumn->m_width, pColumn->m_xOffset);
}

// initialize the given row
void GridView::initRow(GridRow* pRow, int32 destIndex) { 

	// (re-)calculate row height	
	if(m_layoutFlags & ROWS_RESIZE_IN_TANDEM) {
		if(m_fGangedRowHeight == 0.0) {
			// no tandem height set; get it from this row, initializing
			// to default height first if necessary
			if(pRow->m_height == 0.0)
				pRow->m_height = s_fDefRowHeight;
			m_fGangedRowHeight = pRow->m_height;
		}
		else
			// force row to ganged height
			pRow->m_height = m_fGangedRowHeight;
	}

	// figure pixel offset of row
	if(destIndex > 0 && destIndex < m_rows.size()) {
//		D_PRINTF("initRow: basing on row %ld: %.1f/%.1f\n",
//			destIndex-1,
//			m_rows[destIndex-1]->m_yOffset,
//			m_rows[destIndex-1]->m_height);

		pRow->m_yOffset =
			m_rows[destIndex-1]->m_yOffset +
			m_rows[destIndex-1]->m_height;
	}

	if(m_layoutFlags & ROWS_RESIZABLE) {
		// build grip for row resizing
		// +++++ not implemented in simplified GridView
		//       e.moon 5may99
		
		BRect gripb = Bounds();
		gripb.bottom = pRow->m_yOffset + pRow->m_height - 1;
		gripb.top = gripb.bottom - s_fGripSize;
		/*
		pRow->m_pRowGrip = new RowGripView(destIndex, gripb);
		pRow->m_pRowGrip->setTrackingDestination(this);
		m_pRowScrollContainer->AddChild(pRow->m_pRowGrip);
		*/
		
		pRow->m_borderHeight = s_fGripSize;
	}

	
//	D_PRINTF("initRow(%ld): height %.1f, offset %.1f\n",
//		destIndex,
//		pRow->m_height, pRow->m_yOffset);
}

// replace the cell-storage view with a new BView object.
// existing cells are moved to the new view.
void GridView::setContainerView(BView* pView) {
	ASSERT(pView);
	ASSERT(pView != m_pContainerView);

	if(m_pContainerView) {
		if(m_pContainerView->Parent()) {
			BRect b = m_pContainerView->Bounds();
			pView->MoveTo(0.0, 0.0);
			pView->ResizeTo(b.Width(), b.Height());
		}
	
		BView* pNext = m_pContainerView->ChildAt(0);
		while(pNext) {
			BView* pCell = pNext;
			pNext = pNext->NextSibling();

			// move cell to new view
			m_pContainerView->RemoveChild(pCell);
			pView->AddChild(pCell);
		}
		
		RemoveChild(m_pContainerView);
		delete m_pContainerView;
	}
	
	m_pContainerView = pView;

	if(Window()) {	
		m_pContainerView->SetResizingMode(B_FOLLOW_LEFT|B_FOLLOW_TOP);
		m_pContainerView->SetFlags(m_pContainerView->Flags() | B_WILL_DRAW);
		m_pContainerView->MoveTo(0.0, 0.0);
	}

	AddChild(m_pContainerView);
}


void GridView::updateDataBounds() {
	float dataWidth = m_columns.size() ?
		m_columns.back()->m_width + m_columns.back()->m_xOffset :
		0.0;
	float dataHeight = m_rows.size() ?
		m_rows.back()->m_height + m_rows.back()->m_yOffset :
		0.0;

	// resize the container
	BRect oldBounds = containerView()->Bounds();
	containerView()->ResizeTo(dataWidth, dataHeight);
	if(oldBounds.Width() > dataWidth)
		Invalidate(
			BRect(dataWidth, oldBounds.top, oldBounds.right,
				oldBounds.bottom));
	if(oldBounds.Height() > dataHeight)
		Invalidate(	
			BRect(oldBounds.left, dataHeight, oldBounds.right,
				oldBounds.bottom));
	
	// update the scroll bars
	updateScrollLimits();
}

void GridView::updateScrollLimits() {

	float dataWidth = containerView()->Bounds().Width() -
		m_scrollMarginWidth;
	float dataHeight = containerView()->Bounds().Height() -
		m_scrollMarginHeight;
	
	float frameWidth = frameView()->Bounds().Width();
	float frameHeight = frameView()->Bounds().Height();

	// update vertical scrollbar
	BView* pScroller = (m_pScrollView) ? m_pScrollView : (BScrollView*)this;
/////	BView* pScroller = (m_pScrollView) ? m_pScrollView : this;
/////  I added the cast to BScrollView* to pacify the Metrowerks compilier on PPC
/////  -- jra

	BScrollBar* pVScroll = pScroller->ScrollBar(B_VERTICAL);
	if(pVScroll) {
		if(dataHeight <= frameHeight)
			pVScroll->SetRange(0.0, 0.0);
		else {
			pVScroll->SetRange(0.0, dataHeight - frameHeight);
			pVScroll->SetProportion(frameHeight / dataHeight);
		}
	}
	
	BScrollBar* pHScroll = pScroller->ScrollBar(B_HORIZONTAL);
	if(pHScroll) {
		if(dataWidth <= frameWidth)
			pHScroll->SetRange(0.0, 0.0);
		else {
			pHScroll->SetRange(0.0, dataWidth - frameWidth);
			pHScroll->SetProportion(frameWidth / dataWidth);
		}
	}
}

// ---------------------------------------------------------------- //
// default hook implementations
// ---------------------------------------------------------------- //

BView* GridView::frameView() {
	return this;
}

// END -- GridView.cpp --
