/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
	
	Created 3/18/96 - Tim Craycroft (tj, x3672)

	To Do:
	
*/


// PowerPlant
#include <URegions.h>

// Mac Lib
#include "UserInterface_Defines.h"
#include "UGraphicGizmos.h"
#include "LTableHeader.h"
#include "CSimpleDividedView.h"

#include <math.h>

// Flags

typedef UInt16 HeaderFlagsT;
static const HeaderFlagsT cFlagHeaderSortedBackwards = 0x0001;

// Move me somewhere useful !!!
static void	LocalToGlobalRect(Rect& r);

//======================================
class StCurrentPort
//======================================
{
public:
	StCurrentPort(GrafPtr inPort) 	{ mPort = UQDGlobals::GetCurrentPort(); SetPort((GrafPtr)inPort); }
	~StCurrentPort() 				{ SetPort(mPort); }

private:
	GrafPtr	mPort;
};

//-----------------------------------
LTableHeader::LTableHeader( LStream *inStream )
	: LView(inStream), LBroadcaster()
//-----------------------------------
{
	ResIDT theBevelTraitsID;
	
	mColumnData = NULL;
	mColumnCount = mLastVisibleColumn = 0;
	
	*inStream >> mHeaderFlags;
		
	*inStream >> theBevelTraitsID;
	UGraphicGizmos::LoadBevelTraits(theBevelTraitsID, mSortedBevel);
	*inStream >> theBevelTraitsID;
	UGraphicGizmos::LoadBevelTraits(theBevelTraitsID, mUnsortedBevel);
	
	*inStream >> mReverseModifiers;
	
	*inStream >> mColumnListResID;
	
	ResizeImageTo(mFrameSize.width, mFrameSize.height, false);
}
	
//-----------------------------------
LTableHeader::~LTableHeader()
//-----------------------------------
{
	if (mColumnData != NULL) ::DisposeHandle( (Handle) mColumnData);
	mColumnData = NULL;
}

//-----------------------------------
void LTableHeader::FinishCreateSelf()
//-----------------------------------
{
	// Load up the column data	
	Handle columnData = ::GetResource('Cols', mColumnListResID);
	ThrowIfNULL_(columnData);
	::DetachResource((Handle)columnData);

	// LHandleStream will dispose of columnData
	LHandleStream streamMe(columnData);	
	ReadColumnState(&streamMe, false);

	// position the column header panes
	LView::FinishCreateSelf();
	PositionColumnHeaders(true);		
}

//-----------------------------------
void LTableHeader::DrawSelf()
//	Draw each column, draw any space to the right of the rightmost
//	column, and draw the column-hiding widget, if necessary.
//-----------------------------------
{
	UInt16	headerWidth = GetHeaderWidth();
	if (headerWidth == 0)
		return;
	
	StColorPenState::Normalize();
				
	// Draw bottom frame line
	
	Rect r;
	CalcLocalFrameRect(r);
	::MoveTo(r.left, r.bottom - 1);
	::LineTo(r.right - 1, r.bottom - 1);
				
	// Draw the column backgrounds and frames, carrying r.right
	// out of the loop so we know where the rightmost column ends
	
	RgnHandle updateRgn = GetLocalUpdateRgn();
	for (int i = 1; i <= mLastVisibleColumn; i++)
	{
		Rect		sect;
	
		LocateColumn(i, r);
		SColumnData* cData = GetColumnData(i);
		
		// Checking against the update rgn is necessary because we sometimes
		// call Draw(), with a valid rgn to draw only particular columns
		// in order to reduce flicker.

		// If we didn't do this check we would end up erasing other columns,
		// as Draw would not redraw their header subpanes

		if ( updateRgn == NULL || ::SectRect(&r, &((**updateRgn).rgnBBox), &sect))
		{
			Boolean isSortedColumn = cData->GetPaneID() == GetColumnPaneID(mSortedColumn);
			DrawColumnBackground(r, isSortedColumn);
			if (isSortedColumn && cData->HasSortIcon())
			{
				// Draw the little icon
				const Int16 kSortIconWidth = 14; // which allows 1 pixel each side.
				ResIDT iconID = mSortedBackwards ? 14504 : 14505;
				Rect iconFrame = r;
				iconFrame.left = iconFrame.right - kSortIconWidth;
				::PlotIconID(&iconFrame, kAlignAbsoluteCenter, kTransformNone, iconID);
			}
		}
	}
	if (updateRgn)
		::DisposeRgn(updateRgn);

	// If a fixed-width column is rightmost, 
	// we may have some space to fill in between
	// it and the right edge of the view	
	if (headerWidth > r.right )
	{
		r.left = r.right;
		r.right = headerWidth;
		
		DrawColumnBackground(r, false);
	}
	CalcLocalFrameRect(r);
	r.left = r.right - kColumnHidingWidgetWidth;
	DrawColumnBackground(r, false);

	// Draw the column hiding widget
	if (CanHideColumns())
	{
		SInt16	iconID;
		r.top -= 1; // Hate this, but the position had to be adjusted. - jrm.
		r.bottom -= 1; // Hate this, but the position had to be adjusted. - jrm.
		r.left -= 1;		
		if (mLastVisibleColumn <= 1) 
			if (mColumnCount == 1)
				iconID = kColumnHiderDisabledIcon;
			else
				iconID = kColumnHiderHideDisabledIcon;
		else
			if (mLastVisibleColumn == mColumnCount)
				iconID = kColumnHiderShowDisabledIcon;
			else	
				iconID = kColumnHiderEnabledIcon;	
		::PlotIconID(&r, atNone, ttNone, iconID);
	}	
} // LTableHeader::DrawSelf

//-----------------------------------
void LTableHeader::DrawColumnBackground(
	const Rect 	& inWhere,
	Boolean	  	inSortColumn)
//	Bevel and fill the column header rectangle. 
//-----------------------------------
{
	Rect r = inWhere;
	r.right -= 1;	// For frame borders
	r.bottom -= 1;
	const SBevelColorDesc& colors = (inSortColumn) ? mSortedBevel : mUnsortedBevel;
	 
	UGraphicGizmos::BevelRect(r, 1, colors.topBevelColor, colors.bottomBevelColor);

	::PmForeColor(colors.fillColor);
	::InsetRect(&r,1,1);
	::PaintRect(&r);
	::InsetRect(&r,-1,-1);
	
	::ForeColor(blackColor);
	::MoveTo(r.right, r.top);
	::LineTo(r.right, r.bottom - 1);
}									

//-----------------------------------
void LTableHeader::AdjustCursor(Point inPortPt,
							const EventRecord &inMacEvent)
//	Switch the cursor if it's over a column resize area. 
//	
//	We only give subviews a chance to change the cursor
//	if we're not over a resize area.
//-----------------------------------
{
#pragma unused(inMacEvent)
	
	Point		localPt = inPortPt;
	ColumnIndexT		column;
	
	PortToLocalPoint(localPt);
	if (IsInHorizontalSizeArea(localPt, column))
		::SetCursor(*GetCursor(kHorizontalResizeCursor));
	else
		LView::AdjustCursor(inPortPt, inMacEvent);
} // LTableHeader::AdjustCursor

//-----------------------------------
Boolean LTableHeader::IsInHorizontalSizeArea(
	Point 			inLocalPt, 
	ColumnIndexT& 	outLeftColumn)
//	Returns true if the given local pt is in a column-resize area. 
//	Also returns the index of the column to the left of that area
//	(the column to be resized).
//-----------------------------------
{
	
	SInt16 left = inLocalPt.h + 2;
	SInt16 right = inLocalPt.h - 2;
	
	// Start at the division between the first and second columns
	// Go up to the division between the last visible and the following one,
	// if there is one.
	SInt16 lastColumnToCheck = mLastVisibleColumn;
	if (mColumnCount > mLastVisibleColumn)
		lastColumnToCheck++;
	SColumnData* cRightNeigbor = *mColumnData + 1;
	for (outLeftColumn = 1;
		outLeftColumn < lastColumnToCheck;
		outLeftColumn++,cRightNeigbor++)
	{
		SInt16 columnRightEdge = cRightNeigbor->columnPosition;
		if (left 	>= columnRightEdge &&
			right 	<= columnRightEdge)
		{
			// OK, it's a column boundary.
			return (cRightNeigbor - 1)->CanResize();
		}
	}
	outLeftColumn = 0xFFFF;	
	return false;
} // LTableHeader::IsInHorizontalSizeArea

//-----------------------------------
void LTableHeader::Click(SMouseDownEvent &inEvent)
//	Handles all clicks in the view.  We never pass the click
//	down to subviews. 
//	
//	Handles changing sort column, resizing columns, moving 
//	columns, and showing/hiding columns.
//-----------------------------------
{
	// LPane::Click ususally does this...
	PortToLocalPoint(inEvent.whereLocal);
	
	// Handle column resize
	ColumnIndexT column;
	if ( IsInHorizontalSizeArea(inEvent.whereLocal, column))
	{
		TrackColumnResize(inEvent, column);
	}
	else
	{
		// Handle clicks in the column hiding widget
		if (CanHideColumns() && (inEvent.whereLocal.h > GetHeaderWidth())) 
		{
			if (mColumnCount == 1 && mLastVisibleColumn == 1)
				return; // no hiding/showing possible.
			// The right arrow hides the rightmost column and the left arrow
			// shows it.
			enum { leftSide, rightSide } side
				= inEvent.whereLocal.h >= (mFrameSize.width - (kColumnHidingWidgetWidth/2))
				? rightSide
				: leftSide;
			if (mLastVisibleColumn > 1 && side == rightSide)
				ShowHideRightmostColumn(false); // hide
			else if (mLastVisibleColumn < mColumnCount && side == leftSide)
				ShowHideRightmostColumn(true); // show.
		}
		else
		{
			// Click in column header, either drag it,  
			// or set it to be the sort column
			
			column = FindColumn(inEvent.whereLocal);
			if (column != 0)
			{
				if (mLastVisibleColumn > 1 && ::WaitMouseMoved(inEvent.macEvent.where))
					TrackColumnDrag(inEvent, column);
				else
				{
					Boolean sortReverse = false;
					if (mReverseModifiers != 0)
						sortReverse = (inEvent.macEvent.modifiers & mReverseModifiers) != 0;
					else
					{
						// Toggle mode if click again on sorted column.
						sortReverse = CycleSortDirection ( column );
					}
					SetSortedColumn(column, sortReverse);
				}
			}
		}
	}
} // LTableHeader::Click


//
// CycleSortDirection
//
// If the user clicks in the header of the already sorted column, they want to reverse
// the direction of the sort. This can be extended to become a tri-state switch that "unsorts"
// the column (revert to natural order) after doing forward/reverse sort, but this routine 
// doesn't do that.
// 
bool
LTableHeader :: CycleSortDirection ( ColumnIndexT & ioColumn )
{
	return (ioColumn == mSortedColumn) ? !mSortedBackwards : mSortedBackwards;

} // ToggleSortedColumn


//-----------------------------------
void LTableHeader::TrackColumnDrag(
	const			SMouseDownEvent& inEvent,
	ColumnIndexT		inColumn)
//	Tracks the dragging of column inColumn, moving it,
//	and redrawing as necessary.
//-----------------------------------
{
	Point		dropPoint;	
	StRegion	dragRgn;
	Rect		dragRect;
	Rect		dragBounds;
	Rect		dragSlop;
	
	if (!FocusDraw()) return;
	
	// Create the rgn we're dragging
	//
	// This is virtualize so subclasses can make a 
	// more interesting rectangle...
	//
	ComputeColumnDragRect(inColumn, dragRect);
	::RectRgn(dragRgn, &dragRect);
	
	// Compute the bounds and slop of the drag
	CalcLocalFrameRect(dragBounds);
	LocalToGlobalRect(dragBounds);	
	dragSlop = dragBounds;
	::InsetRect(&dragSlop, -5, -5);
	
	// drag me
	{
		StCurrentPort	portState(LMGetWMgrPort());
		const Rect 		wideopen = { 0xFFFF, 0xFFFF, 0x7FFF, 0x7FFF };
		
		ClipRect(&wideopen);
		*(long*)(&dropPoint) = ::DragGrayRgn(
									dragRgn, 
									inEvent.macEvent.where, 
									&dragBounds,
									&dragSlop, 
									kVerticalConstraint,
									NULL);
	
	}
	
	if ((dropPoint.h != (SInt16) 0x8000 || dropPoint.v != (SInt16) 0x8000)	&&
		(dropPoint.h != 0 || dropPoint.v != 0))
	{
		ColumnIndexT	dropColumn;		
	
		// find the column over which we finished the drag
		dropPoint.h += inEvent.whereLocal.h;
		dropPoint.v += inEvent.whereLocal.v;
		
		dropColumn = FindColumn(dropPoint);
		
		// move the column we were dragging
		if (dropColumn != 0 && inColumn != dropColumn) {
			MoveColumn(inColumn, dropColumn);	
		}
	}
} // LTableHeader::TrackColumnDrag

//-----------------------------------
void LTableHeader::TrackColumnResize(
	const SMouseDownEvent	&inEvent,
	ColumnIndexT			inLeftColumn	)
//	Tracks resizing of a column, redrawing as necessary.
//-----------------------------------
{
	Rect			dragBoundsRect;
	Rect			slopRect;
	StRegion		dragRgn;
	Rect			dragRect;
		
	CheckVisible(inLeftColumn);

	if (!FocusDraw()) return;
	
	
	// Compute the actual region we're dragging
	//
	// This is virtualized so subclasses can reflect the size-change
	// in some other view's space (like down a table column)
	
	SColumnData* cData = GetColumnData(inLeftColumn);
	ComputeResizeDragRect(inLeftColumn, dragRect);
	::RectRgn(dragRgn, &dragRect);	
	
	// Compute drag bounds and slop
	CalcLocalFrameRect(dragBoundsRect);
	dragBoundsRect.left = (inLeftColumn != 1) ? cData->columnPosition + 4 : 4;
	
	LocalToGlobalRect(dragBoundsRect);
	slopRect = dragBoundsRect;
	InsetRect(&slopRect, -5, -5);
	
	// drag me
	Point dragDiff;	
	{
		StCurrentPort portState(LMGetWMgrPort());
		const Rect wideopen = { 0xFFFF, 0xFFFF, 0x7FFF, 0x7FFF };
		
		::ClipRect(&wideopen);

		// Track the drag, then resize the column if it was a valid drag
		*(long *)&dragDiff = ::DragGrayRgn(
								dragRgn, 
								inEvent.macEvent.where, 
								&dragBoundsRect,
								&slopRect, 
								kVerticalConstraint,
								NULL);
	}
	if ((dragDiff.h != (SInt16) 0x8000 || dragDiff.v != (SInt16) 0x8000)	&&
		(dragDiff.h != 0     || dragDiff.v != 0 ))
	{
		if (cData->CanResizeBy(dragDiff.h))
			ResizeColumn(inLeftColumn, dragDiff.h);
	}
} // LTableHeader::TrackColumnResize

//-----------------------------------
void LTableHeader::ResizeColumn(
	ColumnIndexT	inLeftColumn,
	SInt16			inLeftColumnDelta	)
//	Resize inLeftColumn by inLeftColumnDelta.	
//	Columns to the right of inLeftColumn are resized
//	proportionally.
//-----------------------------------
{
	if (inLeftColumnDelta == 0)
		return;

	SColumnData* thisColData = GetColumnData(inLeftColumn);
	
	thisColData->columnWidth += inLeftColumnDelta;
	SColumnData* nextColData = thisColData + 1;
	int i = inLeftColumn + 1;
	Boolean oneColumnCanResize = false;
	for (; i <= mLastVisibleColumn; i++, nextColData++)
		if (nextColData->CanResize())
		{
			oneColumnCanResize = true;
			break;
		}

	SInt16 newSpace = GetHeaderWidth() - (thisColData->columnPosition + thisColData->columnWidth);
	SInt16 oldSpace = newSpace + inLeftColumnDelta;

	// Use proportional distribution if there's a resizable column on the right, AND either
	// we're shrinking the column or there's enough slack in the columns to the right.
	Boolean useProportionalRedistribution = false;
	if (oneColumnCanResize)
	{
		if (inLeftColumnDelta < 0 && inLeftColumn != mLastVisibleColumn)
			useProportionalRedistribution = true;
		else if (inLeftColumnDelta > 0
				&& newSpace >= GetMinWidthOfRange(inLeftColumn + 1, mLastVisibleColumn))
			useProportionalRedistribution = true;
	}

	if (useProportionalRedistribution)
	{
		RedistributeSpace(inLeftColumn + 1, mLastVisibleColumn, newSpace, oldSpace);
		ComputeColumnPositions();
		PositionColumnHeaders(false);
		RedrawColumns(inLeftColumn, mLastVisibleColumn);
		return;
	}
	else if (inLeftColumnDelta > 0)
	{
		// This is the case when we resized the column to the rightmost edge, or so
		// far that there is not enough space for all the columns between
		// the resized column and the edge.  To redistribute the space would reduce the sizes
		// of all the resizable columns in this range to zero.  This would be bad (and was
		// bug number 62743).
		
		// Move columns offscreen one at a time until there's enough room for the remainder.
		while (mLastVisibleColumn > inLeftColumn
				&& newSpace < GetWidthOfRange(inLeftColumn + 1, mLastVisibleColumn))
		{
			mLastVisibleColumn--;
		}
	}
	else if (inLeftColumnDelta < 0)
	{
		// Move as many columns onscreen as will fit
		while (mLastVisibleColumn + 1 <= mColumnCount
				&& GetWidthOfRange(inLeftColumn + 1, mLastVisibleColumn + 1) <= newSpace)
		{
			mLastVisibleColumn++;
		}
	}
	// Finally,  expand this column to the right just enough to fit all the
	// columns that we showed or left showing, and then  compute all the positions.
	UInt16 widthsToRight = GetWidthOfRange(inLeftColumn + 1, mLastVisibleColumn);
	thisColData->columnWidth = GetHeaderWidth() - widthsToRight - thisColData->columnPosition;
	ComputeColumnPositions();
	PositionColumnHeaders(false);
	RedrawColumns(inLeftColumn, mLastVisibleColumn);
} // LTableHeader::ResizeColumn

//-----------------------------------
UInt16 LTableHeader::GetMinWidthOfRange(
	ColumnIndexT	inFromColumn,
	ColumnIndexT	inToColumn	) const
//-----------------------------------
{
	Assert_(inFromColumn > 0);
	Assert_(inToColumn > 0);
	if (inToColumn > mLastVisibleColumn)
		inToColumn = mLastVisibleColumn;
	if (inFromColumn > inToColumn)
		return 0;

	SColumnData*	cData = &(*mColumnData)[inFromColumn - 1];
	if (inToColumn > mLastVisibleColumn)
		inToColumn = mLastVisibleColumn;
	
	SInt16 minWidth = 0;
	for (int i = inFromColumn; i <= inToColumn; i++, cData++)
	{
		if (cData->CanResize())
			minWidth += SColumnData::kMinWidth;
		else
			minWidth += cData->columnWidth;
	}
	return minWidth;
} // LTableHeader::GetMinWidthOfRange

//-----------------------------------
UInt16 LTableHeader::GetWidthOfRange(
	ColumnIndexT	inFromColumn,
	ColumnIndexT	inToColumn	) const
//-----------------------------------
{
	Assert_(inFromColumn > 0);
	Assert_(inToColumn > 0);
	if (inToColumn > mLastVisibleColumn)
		inToColumn = mLastVisibleColumn;
	if (inFromColumn > inToColumn)
		return 0;

	SColumnData*	cData = &(*mColumnData)[inFromColumn - 1];
	if (inToColumn > mLastVisibleColumn)
		inToColumn = mLastVisibleColumn;
	
	SInt16 width = 0;
	for (int i = inFromColumn; i <= inToColumn; i++, cData++)
		width += cData->columnWidth;
	return width;
} // LTableHeader::GetMinWidthOfRange

//-----------------------------------
void LTableHeader::SetRightmostVisibleColumn(ColumnIndexT inLastDesiredColumn)
//-----------------------------------
{
	CheckLegal(inLastDesiredColumn);
	ColumnIndexT currentLastVisible = mLastVisibleColumn;
	while (mLastVisibleColumn < inLastDesiredColumn)
	{
		ShowHideRightmostColumn(true);
		if (mLastVisibleColumn == currentLastVisible)
			return; // prevent infinite loop
		currentLastVisible = mLastVisibleColumn;
	}
	while (mLastVisibleColumn > inLastDesiredColumn)
	{
		ShowHideRightmostColumn(false);
		if (mLastVisibleColumn == currentLastVisible)
			return; // prevent infinite loop
		currentLastVisible = mLastVisibleColumn;
	}
} // LTableHeader::SetRightmostVisibleColumn

//-----------------------------------
void LTableHeader::ShowHideRightmostColumn(Boolean inShow)
//	Show or hide the rightmost column.	
//	mLastVisibleColumn is the rightmost visible column.
//-----------------------------------
{
	Assert_((inShow && mLastVisibleColumn < mColumnCount) ||
			(!inShow && mLastVisibleColumn > 1));
					
	ColumnIndexT savedLastVisibleColumn = mLastVisibleColumn;
	Boolean ok;
	
	UInt16 headerWidth = GetHeaderWidth();
#ifdef DEBUG
	SColumnData*	cData = GetColumnData(1);
	SInt16 width = 0;
	for (int i = 1; i <= mLastVisibleColumn; i++, cData++)
		width += cData->columnWidth;
	Assert_(width == headerWidth);
#endif // DEBUG
	UInt16 newWidth, oldWidth;
	UInt16 colWidth;
	if (inShow)
	{
		mLastVisibleColumn++;
		SColumnData* column = GetColumnData(mLastVisibleColumn);
		if (column->columnWidth < SColumnData::kMinWidth)
			column->columnWidth = SColumnData::kMinWidth;
		colWidth = column->columnWidth;
		if (colWidth < headerWidth / 3)
		{
			// Move new column in at full size, shrinking existing columns to make room
			oldWidth = headerWidth;
			newWidth = headerWidth - colWidth;
			ok = RedistributeSpace(1, mLastVisibleColumn - 1, newWidth, oldWidth);
		}
		else
		{
			// Move them all in proportionally.
			oldWidth = headerWidth + colWidth;
			newWidth = headerWidth;
			ok = RedistributeSpace(1, mLastVisibleColumn, newWidth, oldWidth);
		}
	}
	else
	{
		newWidth = headerWidth;
		colWidth = GetColumnWidth(mLastVisibleColumn);	// col being hidden
		mLastVisibleColumn--;
		oldWidth = headerWidth - colWidth;
		ok = RedistributeSpace(1, mLastVisibleColumn, newWidth, oldWidth);
	}
	if (!ok)
	{
		// It didn't work.  Restore the original visible column count
		// So undo!  There are probably no resizable columns to allow the change.
		mLastVisibleColumn = savedLastVisibleColumn;
		return;
	}
	ComputeColumnPositions();
	PositionColumnHeaders(false);
	RedrawColumns(1, mLastVisibleColumn);
} // LTableHeader::ShowHideRightmostColumn

//-----------------------------------
UInt16 LTableHeader::SumOfVisibleResizableWidths() const
//----------------------------------- 
{
	UInt16 result = 0;
	SColumnData*	cData = *mColumnData;
	// Using weighted proportions.  Set d = the total of the proportion
	// values for the VISIBLE columns
	for (int i = 1; i <= mLastVisibleColumn; i++, cData++)
	{
		if (cData->CanResize())
			result += cData->columnWidth;
	}
	return result;
} // LTableHeader::SumOfVisibleResizableWidths

//-----------------------------------
Boolean LTableHeader::RedistributeSpace(
	ColumnIndexT	inFromColumn,
	ColumnIndexT	inToColumn,
	SInt16	inNewSpace,
	SInt16	inOldSpace)
//	Given old and new amounts of horizontal space, this resizes a range of columns
//  such that they each have the same percentage of space in the new space as they
//	did in the old space. 	
//	
//	Fixed-width columns are not resized, of course.
//	
//	Assumes all resizable column widths are in weighted proportion.
//	Converts all these widths to absolute pixel values in such a way that the
//	"visible" columns take up the full image size.  The proportions are maintained, so
//	there's no need to convert to percentages: there's nothing magic about the number
//	100.
//	
//	If the last visible column does not reach the right edge exactly, the last visible
//	resizable column will be extended or shrunk to make it do so. (this is largely
//	due to round-off errors in our arithmetic).	
//-----------------------------------
{
	CheckVisible(inFromColumn);
	CheckVisible(inToColumn);
	Assert_(inFromColumn <= inToColumn);
	Assert_(inOldSpace > 0);
	if (inOldSpace <= 0 || inNewSpace <= 0)
		return false;

	SInt16 fixedWidth = GetFixedWidthInRange(inFromColumn, inToColumn);

	// Loop through columns, resizing them in proportion.  This keeps the proportional
	// sizes equal.  While looping, note the last resizable column in the range, which will
	// be used for adjustments.  Also tally the new resizable total width in the range.
	SColumnData* cData = GetColumnData(inFromColumn);
	UInt16 sumOfWidths = 0;								// Tally to be used after loop completes
	float newSpaceFloat = inNewSpace - fixedWidth;		// Take invariant conversion out of the loop
	float oldSpaceFloat = inOldSpace - fixedWidth;		// ...
	for (int i = inFromColumn; i <= inToColumn; i++, cData++)
	{
		if (cData->CanResize())
		{
			// Resize this column to take the same proportional amount of space, rounding
			// as appropriate
			SInt16 newWidth = (newSpaceFloat * cData->columnWidth + 0.5) / oldSpaceFloat;
			if (newWidth >= SColumnData::kMinWidth || newWidth > cData->columnWidth)
				cData->columnWidth = newWidth;
		}
		// For columns in range (only), add to tally.
		sumOfWidths += cData->columnWidth;
	}
	// Adjust the resizable columns to remove any rounding error.  We used to add this to the
	// last column, but that would make it get bigger, and bigger, and bigger,...
	// So now, go around to each resizable column in rotation and make it eat some of the
	// error, until it all fits exactly.
	Int16 roundingError = inNewSpace - sumOfWidths;
	if (roundingError == 0)
		return true; // perfick!
	if (sumOfWidths == inOldSpace)
		return false; // Oh, diddums.  No resizable columns
	// OK, if we get this far, then some columns were able to resize
	// (sumOfWidths != inOldSpace), and there's merely a rounding error to adjust.
	int signedUnit = roundingError < 0 ? -1 : +1;
#ifdef DEBUG
	int maxRound =  (inToColumn - inFromColumn + 2) / 2;
	Assert_(roundingError * signedUnit <= maxRound);
#endif
	static int rovingIndex = 0;
	const Int16 initialError = roundingError;
	const int initialIndex = rovingIndex;
	while (roundingError)
	{
		rovingIndex++;
		if (rovingIndex > inToColumn)
			rovingIndex = inFromColumn;
		if (initialIndex == rovingIndex && initialError == roundingError)
		{
			Assert_(false); // not making progress!  No resizable columns?
			return false;
		}
		cData = GetColumnData(rovingIndex);
		if (cData->CanResizeBy(signedUnit))
		{
			cData->columnWidth += signedUnit;
			roundingError -= signedUnit;
		}
	}
	return true;
} // LTableHeader::RedistributeSpace
									
//-----------------------------------
SInt16 LTableHeader::GetFixedWidthInRange(
	ColumnIndexT	inFromColumn,
	ColumnIndexT	inToColumn	)
//	Returns the horizontal space taken by all fixed-width
//	columns in a given column range (inclusive).
//-----------------------------------
{
	CheckVisible(inFromColumn);
	CheckVisible(inToColumn);
	Assert_(inFromColumn <= inToColumn);

	SColumnData*	cData = GetColumnData(inFromColumn);
	SInt16 fixedWidth = 0;
	if (inToColumn > mLastVisibleColumn)
		inToColumn = mLastVisibleColumn;
	
	for (int i = inFromColumn; i <= inToColumn; i++, cData++)
	{
		if (!cData->CanResize())
			fixedWidth += cData->columnWidth;
	}
	return fixedWidth;
} // LTableHeader::GetFixedWidthInRange

//-----------------------------------
UInt16 LTableHeader::GetHeaderWidth() const
//	Returns the width of all the visible columns,
//	not including the column hiding widget.
//-----------------------------------
{
	Int16 result = mImageSize.width - kColumnHidingWidgetWidth;
	return result < 0 ? 0 : (UInt16)result;
}
						
//-----------------------------------
void LTableHeader::MoveColumn(ColumnIndexT inColumn, ColumnIndexT inMoveTo)
//	If inColumn > inMoveTo, moves inColumn BEFORE inMoveTo.
//	If inColumn < inMoveTo, moves inColumn AFTER inMoveTo.
//-----------------------------------
{
	SColumnData	*	cData 		= *mColumnData,
				*	shiftFrom, 
				*	shiftTo;
	SColumnData		swap;
	Boolean			adjustedSort = false;
	
	// save off the data for the column we're moving
	::BlockMoveData(&(cData[inColumn-1]), &swap, sizeof(SColumnData));
	
	// Handle case where we're moving the sorted column		
	if (mSortedColumn == inColumn)
	{
		adjustedSort 	= true;
		mSortedColumn 	= inMoveTo;
	}
	
	//
	// Figure out which direction we're moving the column
	// and how we need to shift the data in the columndata array
	//
	SInt32 delta = inColumn - inMoveTo;
	if (delta > 0)
	{
		shiftFrom	= &(cData[inMoveTo-1]);
		shiftTo		= shiftFrom + 1;
		
		if (!adjustedSort && mSortedColumn >= inMoveTo && mSortedColumn < inColumn)
			mSortedColumn += 1;
	}
	else
	{
		delta 		= -delta;
		shiftTo 	= &(cData[inColumn-1]);
		shiftFrom	= shiftTo + 1;
		
		if (!adjustedSort && mSortedColumn > inColumn && mSortedColumn <=inMoveTo)
			mSortedColumn -= 1;
	}
	
	// Shift the data in the columnData array and then copy in the data
	// of the column we're moving
	::BlockMoveData(shiftFrom, shiftTo, (delta*sizeof(SColumnData)));
	::BlockMoveData(&swap, &(cData[inMoveTo-1]), sizeof(SColumnData));
	
	// resposition everything
	ComputeColumnPositions();
	PositionColumnHeaders(false);
	
	
	if (inColumn > inMoveTo)
		RedrawColumns(inMoveTo, inColumn);
	else
		RedrawColumns(inColumn, inMoveTo);
} // LTableHeader::MoveColumn
	
//-----------------------------------
void LTableHeader::ComputeColumnPositions()
//	Computes the horizontal position of each column, assuming 
//	each column's width is valid in pixels.
//	
//	If the rightmost visible column doesn't fill the column area, 
//	it will be extended to do so, UNLESS it is a fixed-width column.
//-----------------------------------
{
	UInt16 headerWidth = GetHeaderWidth();
	if (headerWidth == 0)
		return;
	// Compute each visible column's position
	SColumnData* cData = *mColumnData;
	SInt16 accumulator = 0;
	for (int i = 1; i <= mLastVisibleColumn; i++, cData++)
	{
		cData->columnPosition = accumulator;
		accumulator += cData->columnWidth;
	}
	
	// If the last column is resizable, expand or shrink it
	// as necessary to reach the edge of the header
	cData--;
	if (cData->CanResize())
	{
		if ( (cData->columnPosition + cData->columnWidth) != headerWidth)
			cData->columnWidth = headerWidth - cData->columnPosition;
	}
} // LTableHeader::ComputeColumnPositions

//-----------------------------------
void LTableHeader::ComputeResizeDragRect(ColumnIndexT		inLeftColumn,
										Rect	&	outDragRect		)
//	Computes rectangle to be dragged when resizing a column.
//-----------------------------------
{
	outDragRect.top 	= 	0;
	outDragRect.bottom	=	mFrameSize.height + 100;
	outDragRect.left	=	GetColumnPosition(inLeftColumn+1) - 1;
	outDragRect.right	=	outDragRect.left + 1;
	
	LocalToGlobalRect(outDragRect);
}										

//-----------------------------------
void LTableHeader::ComputeColumnDragRect(
	ColumnIndexT		inColumn,
	Rect			&outDragRect)
//-----------------------------------
{
	LocateColumn(inColumn, outDragRect);
	outDragRect.bottom += 100;	
	LocalToGlobalRect(outDragRect);
}										

//-----------------------------------
void LTableHeader::SetSortedColumn(
	ColumnIndexT 	inColumn, 
	Boolean			inReverse,
	Boolean			inRefresh	)
//-----------------------------------
{
	if (!CanColumnSort(inColumn))
		return;
	if (inColumn == mSortedColumn && inReverse == mSortedBackwards)
		return;
	SInt16 oldSorted = mSortedColumn;
	mSortedColumn = inColumn;
	mSortedBackwards = inReverse;
	if (inRefresh && FocusDraw())
	{	
		
		
		if (oldSorted != 0) {
			RedrawColumns(oldSorted, oldSorted);
		}
		
		if (mSortedColumn != 0) {
			RedrawColumns(mSortedColumn, mSortedColumn);
		}
	}
	// Tell any listeners that the sort column changed
	LTableHeader::SortChange	changeRecord;
	
	changeRecord.sortColumnID 	= GetColumnPaneID(mSortedColumn);
	changeRecord.sortColumn		= mSortedColumn;
	changeRecord.reverseSort	= mSortedBackwards;	
	BroadcastMessage(msg_SortedColumnChanged, &changeRecord);
}

//-----------------------------------
void LTableHeader::SetWithoutSort(
	ColumnIndexT 	inColumn, 
	Boolean			inReverse,
	Boolean			inRefresh	)
//-----------------------------------
{
	if (!CanColumnSort(inColumn))
		return;
	if (inColumn == mSortedColumn && inReverse == mSortedBackwards)
		return;
	SInt16 oldSorted = mSortedColumn;
	mSortedColumn = inColumn;
	mSortedBackwards = inReverse;
	if( inRefresh )
		Refresh();	
}

//-----------------------------------
void LTableHeader::SimulateClick(PaneIDT inID)
//-----------------------------------
{
	PaneIDT result; 
	
	ColumnIndexT index = GetSortedColumn(result);
	
	if ( (result == inID) || !IsEnabled() || !IsColumnVisible(inID)) return;
	
	SetSortedColumn(ColumnFromID(inID), IsSortedBackwards(), true);
}

//-----------------------------------
void LTableHeader::SetSortOrder(Boolean inReverse)
//-----------------------------------
{
	PaneIDT result; 
	
	ColumnIndexT index = GetSortedColumn(result);
	
	if ( !result || !IsEnabled() || !IsColumnVisible(result)) return;
	
	SetSortedColumn(index, inReverse, true);
}

//-----------------------------------
void LTableHeader::RedrawColumns(ColumnIndexT inFrom, ColumnIndexT inTo)
//	Immediately redraws the given column range.
//	We don't do update events so we can avoid the flicker
//	of update rgn being erased to white.
//-----------------------------------
{
	UInt16 headerWidth = GetHeaderWidth();
	
	// Compute the top-left of the refresh area
	Rect r;
	LocateColumn(inFrom, r);
	LocalToPortPoint(topLeft(r));
	
	// Get the bottom right of the refresh area
	if (inFrom == inTo)
	{
		LocalToPortPoint(botRight(r));
	}
	else
	{
		Rect right;
		
		if (inTo <= mLastVisibleColumn)
		{
			LocateColumn(inTo, right);
			LocalToPortPoint(botRight(right));	
		}
		else
		{
			right.right = headerWidth;
			LocalToPortPoint(botRight(right));
		}
		
		r.bottom = right.bottom;
		r.right  = right.right;
	}

	// Redraw
	StRegion rgn;
	::RectRgn(rgn, &r);	
	Draw(rgn);
}

//-----------------------------------
void LTableHeader::LocateColumn( ColumnIndexT inColumn,
	 						Rect & outWhere) const
//	Computes the bounding rect of the given column 
//	in local coordinates.
//-----------------------------------
{
	outWhere.top 	= 0;
	outWhere.bottom = mFrameSize.height;
	
	outWhere.left 	= GetColumnPosition(inColumn);
	outWhere.right	= outWhere.left + GetColumnWidth(inColumn);
}

//-----------------------------------
 LTableHeader::ColumnIndexT LTableHeader::FindColumn( Point inLocalPoint ) const
//	Given a local point, returns the index of the
//	column containing the point.  Returns 0 for no
//	column.
//-----------------------------------
{
	int	i;

	if (inLocalPoint.h >= 0 && inLocalPoint.h <= GetHeaderWidth())
	{
		for (i = mLastVisibleColumn; i > 0; i--)
		{
			if (inLocalPoint.h >= GetColumnPosition(i))
				return i;
		}
	}
	return 0;
}

//-----------------------------------
void LTableHeader::InvalColumn( ColumnIndexT inColumn)
//	Invalidates a column header area.
//-----------------------------------
{
	if (FocusDraw())
	{
		Rect	r;
		
		LocateColumn(inColumn, r);
		LocalToPortPoint(topLeft(r));
		LocalToPortPoint(botRight(r));
		InvalPortRect(&r);
	}
}

//-----------------------------------
void LTableHeader::PositionColumnHeaders(Boolean inRefresh)
//	Positions all the column header panes to be aligned
//	with the current column positions.
//-----------------------------------
{
	if (GetHeaderWidth() == 0)
		return;
	int i;
	for (i = 1; i <= mLastVisibleColumn; i++)
	{
		PositionOneColumnHeader(i, inRefresh);
		LPane* headerPane = GetColumnPane(i);
		if (headerPane)
		{
			if (GetColumnData(i)->CanDisplayTitle())
				headerPane->Show();
			else
				headerPane->Hide();
			if (!inRefresh)
				headerPane->DontRefresh(true);
		}
	}
	for ( ; i <= mColumnCount; i++)
	{
		LPane* headerPane = GetColumnPane(i);
		if (headerPane)
		{
			headerPane->Hide();
			if (!inRefresh)
				headerPane->DontRefresh(true);
		}
	}
} // LTableHeader::PositionColumnHeaders

//-----------------------------------
void LTableHeader::PositionOneColumnHeader( ColumnIndexT inColumn, Boolean inRefresh)
//	Positions a column header pane to be aligned with the its 
//	column position.
//	
//	The column header pane is sized to the width of the column. 
//	Its vertical position is not changed.
//-----------------------------------
{
	Rect columnBounds;
	LocateColumn(inColumn, columnBounds);
	LPane* headerPane = GetColumnPane(inColumn);
	
	// Position the pane horizontally within the column
	if (headerPane)
	{
		SDimension16	paneSize;				
		headerPane->GetFrameSize(paneSize);

		SPoint32 paneLocation;
		headerPane->GetFrameLocation(paneLocation);

		Int32 horizDelta
			= (columnBounds.left + kColumnMargin) - paneLocation.h + mFrameLocation.h;
		Int32 paneWidth	= columnBounds.right - (columnBounds.left + (kColumnMargin * 2));
				
		headerPane->ResizeFrameTo(paneWidth, paneSize.height, inRefresh);	
		headerPane->MoveBy( horizDelta, 0, inRefresh);										
	}	
} // LTableHeader::PositionOneColumnHeader

//-----------------------------------
PaneIDT LTableHeader::GetColumnPaneID(ColumnIndexT inColumn) const
//	Returns the Pane id of the given column's header-pane.	
//	Returns 0 if there is no header-pane.
//-----------------------------------
{
	CheckLegalHigh(inColumn);
	if (inColumn != 0)
		return (GetColumnData(inColumn)->GetPaneID());
	return 0;
}

//-----------------------------------
LPane *	LTableHeader::GetColumnPane(ColumnIndexT inColumn)
//-----------------------------------
{
	LView	*super = GetSuperView();
	
	CheckLegal(inColumn);
	Assert_(super != NULL);
	
	PaneIDT id = GetColumnPaneID(inColumn);
	
	if (id != 0)
		return super->FindPaneByID(id);
	return NULL;
}

//-----------------------------------
SInt16 LTableHeader::GetColumnWidth(ColumnIndexT inColumn) const
//-----------------------------------
{
	CheckLegal(inColumn);
	// widths are always in absolute when this is called.
	return GetColumnData(inColumn)->columnWidth;
}

//-----------------------------------
SInt16 LTableHeader::GetColumnPosition(ColumnIndexT inColumn) const
//-----------------------------------
{
	CheckLegal(inColumn);
	return ( GetColumnData(inColumn)->columnPosition);
}

//-----------------------------------
Boolean	LTableHeader::CanColumnResize(ColumnIndexT inColumn) const
//-----------------------------------
{
	CheckLegal(inColumn);

	// check all of the columns to the right to see if they can be resized
	// if they can't be resized we should NOT be able to resize this column
	// example:  | resizCol1 | resizCol2 | fixedCol3 |
	// if we are working off:            ^
	// we shouldn't be able to resize (shrink) here because there is nowhere
	// for any extra space to go and we don't erase where column was.
	// note that inColumn is 1-based but mColumnData (within loop) is 0-based
	
	SColumnData* cData = GetColumnData(CountVisibleColumns());
	Boolean canFurtherRightResize = false;
	for (int i = CountVisibleColumns(); i > inColumn; i--,cData--)
	{
		if (cData->CanResize())
		{
			canFurtherRightResize = true;
			break;
		}
	}
	return canFurtherRightResize && GetColumnData(inColumn)->CanResize();
} // LTableHeader::CanColumnResize

//-----------------------------------
Boolean	LTableHeader::CanColumnSort(ColumnIndexT inColumn) const
//-----------------------------------
{
	CheckLegal(inColumn);
	return GetColumnData(inColumn)->CanSort();
}

//-----------------------------------
LTableHeader::ColumnIndexT LTableHeader::GetSortedColumn(PaneIDT &outHeaderPane) const
//-----------------------------------
{ 
	outHeaderPane = GetColumnPaneID(mSortedColumn);
	return mSortedColumn; 
}

//-----------------------------------
LTableHeader::ColumnIndexT LTableHeader::ColumnFromID(PaneIDT inPaneID) const
//-----------------------------------
{ 
	SColumnData* c = *mColumnData;
	for (int i = 1; i <= mColumnCount; i++,c++)
		if (c->paneID == inPaneID)
			return i;
	return 0;
}

//-----------------------------------
Boolean LTableHeader::CanHideColumns() const
//-----------------------------------
{
	 return mHeaderFlags & kHeaderCanHideColumns == kHeaderCanHideColumns;
}

//-----------------------------------
void LTableHeader::WriteColumnState( LStream * inStream)	
//	Writes out the current column state with column
//	widths stored as percentages (except for fixed-width)
//	columns.
//-----------------------------------
{
	Assert_(mColumnData != NULL);
	
	HeaderFlagsT flags = 0;
	
	if ( mSortedBackwards ) flags |= cFlagHeaderSortedBackwards;
	
	*inStream << mColumnCount;
	*inStream << mLastVisibleColumn;
	*inStream << mSortedColumn;
	*inStream << (HeaderFlagsT) flags;			// pad
	
	UInt32	dataSize = mColumnCount * sizeof(SColumnData);
	
	StHandleLocker locker((Handle)mColumnData);
	
	// write out the widths
	inStream->PutBytes(*mColumnData, dataSize);

} // LTableHeader::WriteColumnState

//-----------------------------------
void LTableHeader::ReadColumnState( LStream * inStream, Boolean inMoveHeaders)		
//	Reads in column state for the header, converts the column widths
//	to absolute pixel values, computes column positions, and optionally
//	positions the column header panes.	
//-----------------------------------
{
	*inStream >> mColumnCount;
	*inStream >> mLastVisibleColumn;
	*inStream >> mSortedColumn;
	HeaderFlagsT flags;
	*inStream >> flags;
	mSortedBackwards = (flags & cFlagHeaderSortedBackwards) != 0;

//	Assert_(mColumnCount > 0);
	// Columns for RDF views are taken from the RDF database as opposed to
	// stored in a resource. Therefore allow ReadColumnState to handle case
	// where there are no columns specified in 'Cols' resource
	if (mColumnCount > 0)
	{
		CheckLegal(mLastVisibleColumn);
		CheckLegalHigh(mSortedColumn);

		UInt32	dataSize = mColumnCount * sizeof(SColumnData);
	
		if (mColumnData != NULL) ::DisposeHandle((Handle)mColumnData);
		mColumnData = (SColumnData **) ::NewHandle(dataSize);
		ThrowIfNULL_(mColumnData);

		StHandleLocker locker((Handle)mColumnData);
		inStream->GetBytes(*mColumnData, dataSize);

		ConvertWidthsToAbsolute();
		ComputeColumnPositions();
		
		if (inMoveHeaders)
			PositionColumnHeaders(true);
	}
		
	Refresh();
} // LTableHeader::ReadColumnState

//-----------------------------------
void LTableHeader::ConvertWidthsToAbsolute()
//-----------------------------------
{	
	SColumnData*	cData = *mColumnData;
	UInt16 oldSpace = 0;
	for (int i = 1; i <= mLastVisibleColumn; i++, cData++)
		oldSpace += cData->columnWidth;
	RedistributeSpace(1, mLastVisibleColumn, GetHeaderWidth(), oldSpace);
} //  LTableHeader::ConvertWidthsToAbsolute

//-----------------------------------
void LTableHeader::ResizeFrameBy(
	Int16	inWidthDelta,
	Int16	inHeightDelta,
	Boolean	inRefresh)
//	When the frame is resized, we resize our image to match,
//	first converting to percentages so that we can resize
//	our columns proportionally.
//-----------------------------------
{
	ResizeImageBy(	inWidthDelta, 
					inHeightDelta, 
					inRefresh	);
	if ( mColumnCount != 0 )
	{									
		// Move the columns and column headers
		ConvertWidthsToAbsolute();
		ComputeColumnPositions();
		PositionColumnHeaders();
	}	
	LView::ResizeFrameBy(inWidthDelta, inHeightDelta, inRefresh);
}								

//-----------------------------------
static void LocalToGlobalRect(Rect& r)
//-----------------------------------
{
	::LocalToGlobal((Point *) &(r.top));
	::LocalToGlobal((Point *) &(r.bottom));
}
