/*	XGHeader.cpp
 *
 *		Multi-column header bar for list views.
 */

/**********************************************************************

  XGHeader.cpp is Copyright 1998-1999 Sassafras Software Inc.
  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XScroll.h>
#include <XDraw.h>
#include <XCursorUtil.h>
#include <XStringUtil.h>
#include <XHeader.h>

#include <string.h>

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGHeader::XGHeader
 *
 *		Build me
 */

XGHeader::XGHeader (XGView *owner, XGArgStream &stream) : XGView(owner,stream)
{
	fColumns = stream.ReadInteger();
	fResID = stream.ReadInteger();
	fLeaveRoom = stream.ReadBoolean();
	fResizable = stream.ReadBoolean();
	fClickable = stream.ReadBoolean();
	/*fMultiple = */stream.ReadBoolean();
	/*fRemovable = */stream.ReadBoolean();
	/*fChangable = */stream.ReadBoolean();

	IInitView();
}

XGHeader::XGHeader (XGView *parent, XGHeaderInitRecord &init) : XGView(parent, init.v)
{
	fColumns = init.columns;
	fResID = init.resID;
	fLeaveRoom = init.leaveroom;
	fResizable = init.resizable;
	fClickable = init.clickable;
	/*fMultiple = init.multiple;*/
	/*fRemovable = init.removable;*/
	/*fChangable = init.changable;*/

	IInitView();
}

XGHeader::~XGHeader ()
{

}

void XGHeader::AttachView (XGView *view)
{
	fView = view;
}

void XGHeader::SetColumnLabel (long ix, char *name)
{
	char s[256];

	if (ix >= 0 && ix < fColumns) {
		try {
			XRect r;
			XGDraw draw(this);
			while (ix >= fLabels.GetNumStrings())
				fLabels.Insert(fLabels.GetNumStrings(), "");
			fLabels.GetString(ix, s);
			if (strcmp(s, name)) {
				fLabels.SetString(ix, name);
				fPositions[ix].minWidth = draw.StringWidth(name) + 16;
				if (fPositions[ix].minWidth > fPositions[ix].maxWidth)
					fPositions[ix].minWidth = fPositions[ix].maxWidth;
				GetColumnBox(ix, &r);
				InvalView(&r);
			}
		} catch (...) { }
	}
}

long XGHeader::SetColumnWidth (long ix, long width, bool shift)
{
	if (width < 0)
		width = 0;
	width -= GetColumnWidth(ix);
	return(GrowColumnWidth(ix, width, shift));
}

long XGHeader::GrowColumnWidth (long ix, long delta, bool shift)
{
	XRect r, box;
	int width, cix;

	if (ix < 0 || ix >= fColumns - 1)		/* the last column has infinite width */
		return(0);

	width = fPositions[ix + 1].left - fPositions[ix].left;
	if (delta < 0) {
		if (delta < fPositions[ix].minWidth - width)
			delta = fPositions[ix].minWidth - width;
	} else {
		if (delta > fPositions[ix].maxWidth - width)
			delta = fPositions[ix].maxWidth - width;
	}
	if (delta) {
		cix = ix + 1;
		if (shift) {
			while (cix < fColumns)
				fPositions[cix++].left += delta;
		} else {
			if (cix < fColumns - 1) {
				width = fPositions[cix + 1].left - fPositions[cix].left;
				if (delta > 0) {
					if (delta > width - fPositions[cix].minWidth)
						delta = width - fPositions[cix].minWidth;
				} else {
					if (delta < width - fPositions[cix].maxWidth)
						delta = width - fPositions[cix].maxWidth;
				}
			}
			fPositions[cix].left += delta;
		}
		if (delta) {
			r = GetContentRect();
			box = r;
			box.left = fPositions[ix + 1].left;
			if (delta > 0)
				box.left -= delta;
			if (!shift && (ix < fColumns - 2))
				box.right = fPositions[ix + 2].left;
			box.right--;
			ScrollView(delta, 0, &box, KXGColorBtnFace);

			if (delta < 0) {
				r.left = box.right;
				r.right = r.left + 1;
				InvalView(&r);
			}

			r.right = fPositions[ix + 1].left;
			r.left = r.right - 5;
			if (delta > 0)
				r.left -= delta;
			InvalView(&r);
			UpdateView();

			BroadcastChange(ix, delta, shift, fLastState < 0);
		}
	}
	return(delta);
}

long XGHeader::GetColumnWidth (long ix)
{
	XRect r;

	GetColumnBox(ix, &r);
	return(r.right - r.left);
}

void XGHeader::GetColumnBox (long ix, XRect *box)
{
	XRect r = GetContentRect();

	box->top = 0;
	box->bottom = r.bottom - r.top;
	if (ix < 0 || ix >= fColumns) {
		box->left = 0;
		box->right = 0;
	} else {
		box->left = fPositions[ix].left;
		ix += 1;
		if (ix == fColumns)
			box->right = r.right - r.left;
		else
			box->right = fPositions[ix].left;
	}
}

long XGHeader::GetColumnMinWidth (long ix)
{
	if (ix >= 0 && ix < fColumns)
		return(fPositions[ix].minWidth);
	return(0);
}

long XGHeader::GetColumnMaxWidth (long ix)
{
	if (ix >= 0 && ix < fColumns)
		return(fPositions[ix].maxWidth);
	return(0);
}

void XGHeader::SetColumnMinWidth (long ix, long width)
{
	if (ix >= 0 && ix < fColumns)
		fPositions[ix].minWidth = width;
}

void XGHeader::SetColumnMaxWidth (long ix, long width)
{
	if (ix >= 0 && ix < fColumns)
		fPositions[ix].maxWidth = width;
}

long XGHeader::GetColumnHilite (void)
{
	long ix;

	for (ix = 0; ix < fColumns; ix++) {
		if (fPositions[ix].hilited)
			return(ix);
	}
	return(-1);
}

short XGHeader::GetColumnHilite (long ix)
{
	if (ix >= 0 && ix < fColumns)
		return(fPositions[ix].hilited);
	return(false);
}

void XGHeader::SetColumnHilite (long ix, short hilite)
{
	XRect r;

	if (ix >= 0 && ix < fColumns) {
		fPositions[ix].hilited = hilite;
		GetColumnBox(ix, &r);
		InvalView(&r);
		UpdateView();
	}
}

long XGHeader::InsertColumn (long before)
{
	XGHeaderPositionRecord posn;

	if (before >= 0 && before < fColumns) {
		posn.left = fPositions[before].left;
	} else {
		before = fColumns;
		posn.left = 10000;
	}
	posn.minWidth = 0;
	posn.maxWidth = 1000;
	posn.hilited = false;
	try {
		fPositions.Insert(before, posn);
		fColumns++;
		InvalView();
	} catch (...) {
		before = -1;
	}
	return(before);
}

void XGHeader::DeleteColumn (long ix)
{
	if (ix >= 0 && ix < fColumns) {
		SetColumnMinWidth(ix, 0);
		SetColumnWidth(ix, 0);
		fPositions.Delete(ix);
		fColumns--;
	}
}
		
void XGHeader::DoDrawView(XRect)
{
	long i;
	XRect r, vr, sr;
	XGDraw draw(this);

	draw.SetFont(XGFont::LoadFont(1));	
	vr = GetContentRect();

	draw.Draw3DRect(vr, KXGEBackground);
	for (i = 0; i < fColumns; i++) {
		GetColumnBox(i, &r);
		if (::SectRect(&vr, &r, &sr)) {
			draw.ClipRect(&r);

			::InsetRect(&r, 0, -1);
			r.right += 1;
			if (fLeaveRoom & (i == fColumns - 1))
				r.right += 2;
			if (fPositions[i].hilited)
				draw.Draw3DRect(r, KXGEInButton);
			else
				draw.Draw3DRect(r, KXGEButton);

			DrawColumn(draw, r, i, fPositions[i].hilited);

			draw.ClipRect();
		}
	}
}

void
XGHeader::DrawColumn (XGDraw &draw, XRect &r, long ix, short hilite)
{
	char s[256];
	long descent = draw.GetFontDescent();
	long h = draw.GetFontAscent() + descent;

	try {
		fLabels.GetString(ix, s);
		draw.MoveTo(r.left + 8, r.bottom - ((r.bottom - r.top - h + 1) >> 1) - descent);
		if (hilite >= 0)
			draw.SetForeColor(KXGColorBlack);
		else
			draw.SetForeColor(KXGColorBtnShadow);
		draw.DrawString(s, -1);
	} catch (...) { }
}

long
XGHeader::NearBoundary (XPoint where)
{
	int i, b;

	for (i = 1; i < fColumns; i++) {
		b = fPositions[i].left;
		if (fPositions[i].left > fPositions[i - 1].left)
			if ((where.h > b - 3) && (where.h < b + 3))
				return(i);
	}
	return(0);
}

long
XGHeader::InColumn (XPoint where)
{
	XRect r;
	int i;

	r = GetContentRect();
	if (where.v < r.top || where.v > r.bottom)
		return(-1);

	for (i = fColumns; --i >= 0; ) {
		if (where.h >= fPositions[i].left)
			break;
	}
	return(i);
}

void
XGHeader::BroadcastChange (long column, long delta, bool shift, bool complete)
{
	XGHeaderChangeRecord change;
	XRect r, box;

	if (delta) {

		if (fView && !complete) {
			r = fView->GetContentRect();
			GetColumnBox(column + 1, &box);
			r.left = box.left;
			if (delta > 0)
				r.left -= delta;
			if (shift)
				GetColumnBox(fColumns - 1, &box);
			r.right = box.right;
			fView->ScrollView(delta, 0, &r, KXGColorWhite);

			GetColumnBox(column, &box);
			r.left = box.right - 5;
			r.right = box.right;
			if (delta > 0)
				r.left -= delta;
			fView->InvalView(&r);
			fView->UpdateView();
		}

		/*	This message can be used in two ways:
		 *	For "live" changes, check that complete == 0, and
		 *		if sizeDelta != 0, shift and resize one column
		 *		if sizeDelta == 0, shift the range of columns
		 *	For clean non-live changes, check that complete != 0, and
		 *		do the same thing as for live changes
		 */
		change.complete = complete;
		change.firstCol = column + 1;
		if (!shift) {
			change.lastCol = change.firstCol;
			change.sizeDelta = -delta;
		} else {
			change.lastCol = fColumns - 1;
			change.sizeDelta = 0;
		}
		change.posnDelta = delta;
		DoDispatch(KHeaderChange, GetViewID(), (void *)&change);

		/*	This message can be used in three ways:
		 *	For "live" changes, check that complete == 0, and
		 *		if sizeDelta < 0, not much to do
		 *		if sizeDelta > 0, redraw column intelligently
		 *	For clean non-live changes, check that complete != 0, and
		 *		do the same thing as for live changes
		 *	For easy, but ugly changes, check that complete != 0, and
		 *		invalidate the columns starting with firstCol
		 */
		change.firstCol = column;
		change.lastCol = column;
		change.sizeDelta = delta;
		change.posnDelta = 0;
		DoDispatch(KHeaderGrown, GetViewID(), (void *)&change);
	}
}

bool
XGHeader::DoMouseDown (XPoint where, short)
{
	int column;

	if (fResizable && (0 != (column = NearBoundary(where)))) {
		//fLastFocus = GetFocus();
		//SetFocus();

		fLastState = 1;
		fLastCol = column - 1;
		fLastWhere = where;
		fLastSlop = 0;
		fLastWidth = GetColumnWidth(fLastCol);
		SetCursorID(1024);
		return true;

	} else if (fClickable && ((column = InColumn(where)) >= 0)) {
		//fLastFocus = GetFocus();
		//SetFocus();

		fLastSlop = (GetColumnHilite(column) == 0);
		fLastState = 3;
		if (fLastSlop)
			fLastState = 2;
		fLastCol = column;
		fLastWhere = where;
		SetColumnHilite(fLastCol, -fLastSlop);
		DoMouseMove(where, KCapturedMouse);
		return true;

	}
	//fLastFocus = NULL;
	return false;
}

void
XGHeader::DoMouseMove (XPoint where, short flags)
{
	if (!(flags & KCapturedMouse)) {
		if (!fResizable) {
			XGView::DoMouseMove(where,flags);
		} else {
			if (NearBoundary(where))
				SetCursorID(1024);
			else
				SetCursorID(KNormalCursor);
		}
	} else {
		if (fResizable && (fLastState == 1)) {
			int diff = where.h - fLastWhere.h + fLastSlop;
			fLastSlop = diff - GrowColumnWidth(fLastCol, diff, (flags & KKeyShift) == 0);
			fLastWhere = where;
			if (fLastSlop < 0)
				SetCursorID(1025);
			else if (fLastSlop > 0)
				SetCursorID(1026);
			else
				SetCursorID(1024);

		} else if (fClickable) {
			bool inside = (fLastCol == InColumn(where));
			if (fLastState == 2) {
				if (fLastSlop != inside) {
					fLastSlop = inside;
					SetColumnHilite(fLastCol, -fLastSlop);
				}
			} else {
				if (fLastSlop != inside) {
					fLastSlop = inside;
					SetColumnHilite(fLastCol, 1 - (fLastSlop << 1));
				}
			}
		}
	}
}

void
XGHeader::DoMouseUp (XPoint where, short flags)
{
	if (!(flags & KCapturedMouse)) {
		XGView::DoMouseUp(where,flags);
	} else {
		if (fResizable && (fLastState == 1)) {
			BroadcastChange(fLastCol, GetColumnWidth(fLastCol) - fLastWidth, (flags & KKeyShift) == 0, true);
		} else if (fClickable) {
			if (GetColumnHilite(fLastCol)) {
				SetColumnHilite(fLastCol, 1);
				for (int i = 0; i < fColumns; i++) {
					if ((i != fLastCol) && GetColumnHilite(i))
						SetColumnHilite(i, 0);
				}
				DoDispatch(KHeaderSelect, GetViewID(), &fLastCol);
			}
		}
		fLastState = -1;
		fLastCol = -1;
		//if (fLastFocus)
		//	fLastFocus->SetFocus();
		//fLastFocus = NULL;

	}
}

void
XGHeader::RecalcAutoLocation (XGView *below, XGView *beside, XRect *r)
{
	XRect tr;

	if (!r) {
		r = &tr;
		tr.top = 0;
		tr.left = 0x8000;
		tr.bottom = tr.top + GetHeight();
		if (!fLeaveRoom)
			tr.right = 0x8000;
		else
			tr.right = -XGScrollBar::GetDefaultSize().h + 1;
	}
	XGView::RecalcAutoLocation(below, beside, r);
}

long
XGHeader::GetHeight (void)
{
	XGDraw draw(this);
	return(draw.GetFontHeight() + 4);
}

void
XGHeader::IInitView (void)
{
	int i;
	char s[64];

	fLastState = -1;
	fLastCol = -1;

	fView = NULL;

	fPositions.SetLength(fColumns);
	for (i = 0; i < fColumns; i++) {
		fPositions[i].left = 0;
		fPositions[i].minWidth = 0;
		fPositions[i].maxWidth = 1000;
		fPositions[i].hilited = 0;
		XGGetIndResString(fResID, i, s, sizeof(s));
		SetColumnLabel(i, s);
	}
}