/*	TTreeView.cpp
 *
 *		The project view object; this is the thing that provides an 
 *	interface for my resource selection.
 */

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

  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>
#include <XDraw.h>
#include <XWindow.h>
#include <XMessages.h>
#include <XError.h>
#include <XScroll.h>
#include <XMisc.h>
#include <XDragDrop.h>
#include <stdlib.h>
#include <string.h>
#include "TTreeView.h"
#include <ctype.h>

/************************************************************************/
/*																		*/
/*	Class Declaration													*/
/*																		*/
/************************************************************************/

/*	TTreeView::TTreeView
 *
 *		Construct this view of my project
 */

TTreeView::TTreeView(XGView *parent, XGArgStream &stream) : 
	XGView(parent,stream), XGDropper(this)
{
	fUpdateFlag = false;
	fDrawFlag = true;
	fRoot = NULL;
	fSelection = NULL;
	fSelectedCount = 0;
	
	fUpIcon = stream.ReadInteger();
	fDownIcon = stream.ReadInteger();
	
	Recompute();
}

/*	TTreeView::~TTreeView
 *
 *		Delete this view
 */

TTreeView::~TTreeView()
{
}

/************************************************************************/
/*																		*/
/*	Draw the project list												*/
/*																		*/
/************************************************************************/

/*	TTreeView::DoDrawView
 *
 *		Draw the contents of this thing
 */

void TTreeView::DoDrawView(XRect)
{
	XRect r,s,t;
	long len;
	long x,y;
	long h;
	long i;
	short lh,lt;
	short it;
	TableDrawRecord tr,te;
	long b;
	short at;
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	
	fDrawFlag = true;
	fUpdateFlag = false;			// Clear the update flag as I'm updated
	
	/*
	 *	Get the drawing parameters so I know where to draw
	 */
	
	r = GetContentRect();
	GetScrollParams(&x,&y,&h);
	x *= h;
	
	len = (r.bottom - r.top + h - 1) / h;
	len += y;
	if (len > fLength) len = fLength;
	
	lh = draw.GetFontHeight();			// lh == line height
	lt = (h-1-lh)/2;					// lt == line top (of label)
	it = (h-17)/2;						// it == icon top (for 16x16 icon)
	at = (h-13)/2;						// at == arrow top
	
	/*
	 *	Iterate through drawing the whole mess
	 */
	
	draw.EraseRect(r);
	draw.SetForeColor(KXGColorBtnFace);	// a light gray value
	for (i = y; i < len; i++) {
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		if (1 || tr.inset > 0) {
			for (b = i-1; b >= y; b--) {
				fDrawList.GetMemory(b*sizeof(te),sizeof(te),&te);
				if (te.inset <= tr.inset) break;
			}
			
			t.left = 22 * (tr.inset) - x;
			t.right = t.left + 22;
			t.bottom = (i - y) * h;
			t.top = (b - y + 1) * h;
			
			if (b >= y) {
				if (te.inset == tr.inset) {
					if (te.entry->child == NULL) {
						t.top -= h;
					} else {
						t.top -= h - at - 12;
					}
				}
			}
			
			if (tr.inset > 0) {
				draw.MoveTo(t.left+8,t.top);
				if (tr.entry->child || tr.entry->fChildDefer) {
					/*
					 *	Has children; draw to icon
					 */
					
					draw.LineTo(t.left+8,t.bottom+at);
					draw.MoveTo(t.left+14,t.bottom+h/2);
				} else {
					/*
					 *	No children, draw to middle
					 */
					
					draw.LineTo(t.left+8,t.bottom+h/2);
				}
				draw.LineTo(t.left+22,t.bottom+h/2);
			} else {
				if (tr.entry->child || tr.entry->fChildDefer) {
					draw.MoveTo(t.left+14,t.bottom+h/2);
					draw.LineTo(t.left+22,t.bottom+h/2);
				} 
			}
			
			if (tr.entry->child || tr.entry->fChildDefer) {
				s = t;
				s.top = t.bottom + at;
				s.bottom = t.top + 12;
				s.left = t.left + 2;
				s.right = s.left + 12;
				DrawDropIcon(draw,s,tr.entry->fOpen);
			}
		}	
	}

	/*
	 *	Draw the text of these things.
	 */
	
	draw.SetForeColor(KXGColorBlack);	// text (black)
	for (i = y; i < len; i++) {
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		
		if (tr.entry->icon) {
			/*
			 *	Draw icon label
			 */

			s.left = 22 * (tr.inset+1) - x;
			s.right = s.left + 16;
			s.top = (i - y) * h + it;
			s.bottom = s.top + 16;
			DrawItemIcon(draw,s,tr.entry->icon);
					
			/*
			 *	Draw label
			 */
			
			r.top = (i - y) * h + lt;
			r.bottom = r.top + lh;
			r.left = 22 * (tr.inset+1) - x + 16;
			DrawItem(draw,r,IsSelected((uint32)tr.entry),(uint32)tr.entry);
		} else {
					
			/*
			 *	Draw label
			 */
			
			r.top = (i - y) * h + lt;
			r.bottom = r.top + lh;
			r.left = 22 * (tr.inset+1) - x;
			DrawItem(draw,r,IsSelected((uint32)tr.entry),(uint32)tr.entry);
		}
	}
	
	r = GetContentRect();
	r.top = (i - y) * h;
	if (r.top < r.bottom) draw.EraseRect(r);	// erase bottom of view
}

/*	TTreeView::DoSizeView
 *
 *		Handle resize
 */

void TTreeView::DoSizeView()
{
	EnableDrawing();	

	RecalcScroll();
}

/*	TTreeView::DoActivate
 *
 *		Handle activation/deactivation
 */

void TTreeView::DoActivate(bool flag)
{
	EnableDrawing();
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	for (uint32 item = GetFirstSelected(); item; item = GetNextSelected( item )) {
		RedrawItem(draw,(TableEntryRecord *)item);
	}

	XGView::DoActivate(flag);
}

/*	TTreeView::DoMouseDown
 *
 *		Handle selection/double click events
 */

bool TTreeView::DoMouseDown(XPoint where, short flags)
{
	XRect r,t;
	long len;
	long x,y;
	long h;
	long i;
	short lh,lt;
	short it;
	TableDrawRecord tr;
	short at;
	TreeEventRecord tevent;
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	if (!fDrawFlag) EnableDrawing();
	SetFocus();

	tevent.ev_flags = flags;
	
	/*
	 *	Get the drawing parameters so I know where these are
	 */
	
	r = GetContentRect();
	GetScrollParams(&x,&y,&h);
	x *= h;
	
	len = (r.bottom - r.top + h - 1) / h;
	len += y;
	if (len > fLength) len = fLength;
	
	lh = draw.GetFontHeight();			// lh == line height
	lt = (h-1-lh)/2;					// lt == line top (of label)
	it = (h-17)/2;						// it == icon top (for 16x16 icon)
	at = (h-13)/2;						// at == arrow top
	
	/*
	 *	Iterate through buttons
	 */
	
	for (i = y; i < len; i++) {
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		if (1 || tr.inset > 0) {
			
			if (tr.entry->child || tr.entry->fChildDefer) {
				t.top = (i - y) * h + at;
				t.left = 22 * (tr.inset) - x + 2;

				t.bottom = t.top + 12;
				t.right = t.left + 12;
				if (PtInRect(where,&t)) {
					/*
					 *	Clicked on open icon.
					 */
					
					if (tr.entry->child == NULL) {
						/*
						 *	Defer open--dispatch event to give the
						 *	app a chance to fill in the children
						 */
						
						tevent.child = (uint32)tr.entry;
						DoDispatch(KEventTreeOpen,GetViewID(),(void *)&tevent);
					}
					tr.entry->fOpen = !(tr.entry->fOpen);
					Update();
					return false;
				}
			}
		}	
	}

	/*
	 *	Iterate through text.
	 */
	
	for (i = y; i < len; i++) {
		char buffer[256];
		
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		GetItemData( (uint32)tr.entry, buffer, sizeof(buffer)-1 );
		
		r.top = (i - y) * h;
		r.bottom = r.top + h;
		r.left = 22 * (tr.inset+1) - x;
		r.right = r.left + draw.StringWidth(buffer) + 6;
		if (tr.entry->icon) r.right += 16;
		if (PtInRect(where,&r)) {
			/*
			 *	Clicked on an object
			 */
			
			fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
			tevent.child = (uint32)tr.entry;
			if (flags & KKeyShift) {
				SetSelected((uint32)tr.entry, !tr.entry->fSelected);
			} else {
				SetSelection((uint32)tr.entry);
			}
			if (flags & KDoubleClick) {
				/*
				 *	We double-clicked on this object
				 */
				
				DoDispatch(KEventTreeDoubleClick,GetViewID(),(void *)&tevent);
			} else {
				/*
				 *	We single-clicked here
				 */
				 
				DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
				
				if ((tr.entry->dragID) && XGDragger::Wait(this,where)) {
					/*
					 *	Should I drag?
					 */
					
					TreeDragRecord tdr;
					
					tdr.tree = this;
					tdr.dragID = (uint32)tr.entry;
					if (0 == DoDispatch(KEventTreeStartDrag,GetViewID(),(void *)&tdr)) {
						return false;
					}
					
					/*
					 *	Drag this one item
					 */
					 
					if ((tr.entry->dragSize.h != -1) && (tr.entry->dragSize.v != -1)) {
						r.left = where.h;
						r.top = where.v;
						r.right = r.left + tr.entry->dragSize.h;
						r.bottom = r.top + tr.entry->dragSize.v;
					}
					
					XGDragger drag(this,tr.entry->fInternDrag);
					
					/*char buffer[256];
					short len;
					len = fData.GetData(tr.entry->dragData,sizeof(buffer),buffer);
					
					drag.AddObject(1,tr.entry->dragID,buffer,len,&r);*/
					char buffer[256];
					int len = GetExtraData((uint32)tr.entry, buffer, sizeof(buffer));
					
					drag.AddObject(1,tr.entry->dragID, buffer, len, &r);
					drag.Track();
				}
			}
			
			return false;
		}
	}
	
	/*
	 *	If we get here, we didn't click on anything. Clear selection
	 */
	
	ClearSelection();
	tevent.child = NULL;
	DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
	return false;
}

/************************************************************************/
/*																		*/
/*	Drag and drop interface support										*/
/*																		*/
/************************************************************************/

/*	TTreeView::Accept
 *
 *		Does this accept this drag and drop message? This sends out
 *	a message upwards to give my owner a chance to answer on my behalf
 */

bool TTreeView::Accept(long, long, long flavor)
{
	TreeDropRecord d;
	
	fDropID = flavor;
	fDropParent = 0;
	fDropChild = 0;
	fDropPos.v = -1;
	
	d.tree = this;
	d.dropID = flavor;
	if (1 == DoDispatch(KEventTreeCanDrop,GetViewID(),(void *)&d)) return true;
	return false;
}

/*	TTreeView::Drop
 *
 *		Handle the drop message. This sends up the KEventTreeDropHere
 *	message with the last location that I received during drag/drop
 */

bool TTreeView::Drop(XPoint where, 
					 long modifiers, 
					 long /* itemRef */, 
					 long flavor,
					 void *dataptr,
					 long datalen)
{
	TreeDropRecord d;
	long retval;
	
	if (!FindDropPoint(where)) return false;		// don't do it!
	d.tree = this;
	d.dropID = flavor;
	d.parent = fDropParent;
	d.child = fDropChild;
	d.modifiers = modifiers;
	d.len = datalen;
	d.data = dataptr;
	
	retval = DoDispatch(KEventTreeDoDrop,GetViewID(),(void *)&d);
	InvalView();									// redraw me regardless
	
	if (1 == retval) return true;
	return false;
}

/*	TTreeView::Enter
 *
 *		The mouse has entered me; this tracks the current selection.
 */

void TTreeView::Enter(XPoint where, long mods, bool hilite)
{
	// First, draw the view hilite
	XGDropper::Enter(where,mods,hilite);
	
	// second, find where this is going and draw the appropriate hilite
	FindDropPoint(where);
}

/*	TTreeView::Leave
 *
 *		Leave the view; this unhilites
 */

void TTreeView::Leave()
{
	// Clear where the drop point is
	ClearDropPoint();
	
	// and clear the view hilite
	XGDropper::Leave();
}

/*	TTreeView::Track
 *
 *		Track the drag point.
 */

void TTreeView::Track(XPoint where, long)
{
	FindDropPoint(where);
}

/*	TTreeView::DrawDropPoint
 *
 *		Draw the drop point
 */

void TTreeView::DrawDropPoint(XGDraw &draw, XPoint where)
{
	XRect r;

	r = GetContentRect();
	r.left = where.h;
	r.top = where.v;
	r.bottom = r.top + 1;
	draw.InvertRect(r);
}

/*	TTreeView::ClearDropPoint
 *
 *		Unhilite the point where this dropper goes
 */

void TTreeView::ClearDropPoint(void)
{
	if (fDropPos.v != -1) {
		XGDraw draw(this);
		DrawDropPoint(draw,fDropPos);
		fDropPos.v = -1;
	}
}

/*	TTreeView::FindDropPoint
 *
 *		Find the parent and child where this thing will be dropped
 */

bool TTreeView::FindDropPoint(XPoint where)
{
	XRect r;
	long len;
	long x,y;
	long h;
	long i;
	short inset;
	TableDrawRecord tr;
	TreeDropRecord tevent;
	TableEntryRecord *parent;
	TableEntryRecord *child;
	XPoint pos;
	
	if (!fDrawFlag) EnableDrawing();

	/*
	 *	Set up for drawing
	 */
	
	XGDraw draw(this);
	
	/*
	 *	Get the drawing parameters so I know where to draw
	 */
	
	r = GetContentRect();
	GetScrollParams(&x,&y,&h);
	x *= h;
	
	len = (r.bottom - r.top + h - 1) / h;
	len += y;
	if (len > fLength) len = fLength;
	
	/*
	 *	Iterate through text.
	 */
	
	for (i = y; i < len; i++) {
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		
		r.top = (i - y) * h;
		r.bottom = r.top + h;
		r.left = 22 * tr.inset - x;
		
		/*
		 *	Determine relative position of drag point to the above
		 *	calculated rectangle
		 */
		
		if ((r.top + r.bottom)/2 > where.v) {
			break;
		}
	}
	
	/*
	 *	We have where I think the insertion point goes
	 */
	
	if (i == 0) {
		/*
		 *	This is being inserted into the root of this thing
		 */
		
		parent = NULL;
		child = NULL;
	} else {
		i--;
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		
		r.left = 22 * tr.inset - x;
		if (where.h > r.left + 30) {
			/*
			 *	Drop into this object
			 */
			
			parent = tr.entry;
			child = NULL;
		} else {
			/*
			 *	Drop after this object
			 */
			
			parent = tr.entry->parent;
			child = tr.entry;
		}
	}
	
	/*
	 *	Now figure out if I can drop here. If not, iterate upwards.
	 */
	
	tevent.tree = this;
	tevent.dropID = fDropID;
	tevent.len = 0;
	tevent.data = NULL;
	for (;;) {
		tevent.parent = (uint32)parent;
		tevent.child = (uint32)child;
		if (0 != DoDispatch(KEventTreeDropHere,GetViewID(),(void *)&tevent)) break;
		
		if (child) {
			/*
			 *	Can this be dropped into the parent anywhere?
			 */
			
			child = NULL;
		} else if (parent) {
			/*
			 *	If I can't be dropped inside, can I be dropped alongside?
			 */
			
			child = parent;
			parent = parent->parent;
		} else {
			DrawDropPoint(draw,fDropPos);
			fDropPos.h = -1;
			fDropPos.v = -1;
			return false;			// can't drop anywhere in this...
		}
	}

	/*
	 *	Now that I know where this is going, figure out where the
	 *	hilite should go.
	 */
	
	pos.h = -1;
	pos.v = -1;
	if (child == NULL) {
		if (parent == NULL) {
			/*
			 *	This can only be selected if we're at the top.
			 */
			
			pos.h = 0;
			pos.v = 0;
		} else {
			/*
			 *	Find the parent and insert below it
			 */
			
			for (i = 0; i < fLength; i++) {
				fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
				if (parent == tr.entry) break;
			}
			if ((i >= y-1) && (i < len)) {
				pos.v = (i - y + 1) * h;
				pos.h = 22 * (1 + tr.inset) - x;
			}
		}
	} else {
		/*
		 *	Find this child and insert right after.
		 */
		
		for (i = 0; i < fLength; i++) {
			fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
			if (child == tr.entry) break;
		}
		
		/*
		 *	Now find the item that is at my indent level right
		 *	after me.
		 */
		
		inset = tr.inset;
		for (i++; i < fLength; i++) {
			fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
			if (tr.inset <= inset) break;
		}
		
		if ((i >= y) && (i <= len)) {
			pos.v = (i - y) * h;
			pos.h = 22 * inset - x;
		}
	}
	
	if ((pos.h != fDropPos.h) || (pos.v != fDropPos.v)) {
		DrawDropPoint(draw,fDropPos);
		DrawDropPoint(draw,pos);
		fDropPos = pos;
	}
	
	fDropParent = (uint32)parent;
	fDropChild = (uint32)child;
				
	return true;
}

/************************************************************************/
/*																		*/
/*	Draw the project list												*/
/*																		*/
/************************************************************************/

/*	TTreeView::DisableDrawing
 *
 *		Disable the drawing code
 */

void TTreeView::DisableDrawing()
{
	if (fDrawFlag) {
		fDrawFlag = false;
		fUpdateFlag = false;
	}
}

/*	TTreeView::Update
 *
 *		Update drawing, or at least set a flag which indicates I need
 *	to be redrawn. This is an internal routine which is drawn each time
 *	I modify the list of things inside me
 */

void TTreeView::Update()
{
	if (fDrawFlag) {
		Recompute();
		InvalView();
		UpdateView();
	} else {
		fUpdateFlag = true;
	}
}

/*	TTreeView::EnableDrawing
 *
 *		Enable the drawing code
 */

void TTreeView::EnableDrawing()
{
	if (!fDrawFlag) {
		fDrawFlag = true;
		if (fUpdateFlag) {
			Recompute();
			InvalView();
			UpdateView();
		}
	}
}

/*	TTreeView::SetSelect
 *
 *		Set the current selection to the specified item, redrawing the
 *	entries as needed
 */

#if 0
void TTreeView::SetSelect(uint32 sel)
{
	TableEntryRecord *t;
	TableEntryRecord *s;
	
	t = (TableEntryRecord *)sel;
	if (fSelect != t) {
		if (fDrawFlag == false) {
			fSelect = t;
			Update();
		} else {
			XGDraw draw(this);
			draw.SetFont(XGFont::LoadFont(1));
			
			/*
			 *	We're drawing; change selection now.
			 */
			
			s = fSelect;
			fSelect = t;
			RedrawItem(draw,s);
			RedrawItem(draw,t);
		}
	}
}
#endif


void TTreeView::ClearSelection()
{
	uint32 item;
	while ((item = GetFirstSelected()) != 0) {
		SetSelected( item, false );
	}
}


void TTreeView::SetSelected( uint32 item, bool select )
{
	if (!item) return;
	TableEntryRecord *entry = (TableEntryRecord *)item;
	if (entry->fSelected == select) return;
	entry->fSelected = select;
	if (select) {
		entry->nextSelected = fSelection;
		if (fSelection) {
			fSelection->prevSelected = entry;
		}
		entry->prevSelected = 0;
		fSelection = entry;
		++fSelectedCount;
	} else {
		if (entry->nextSelected) {
			entry->nextSelected->prevSelected = entry->prevSelected;
		}
		if (entry->prevSelected) {
			entry->prevSelected->nextSelected = entry->nextSelected;
		} else {
			fSelection = entry->nextSelected;
		}
		--fSelectedCount;
	}
	
	if (fDrawFlag) {
		XGDraw draw(this);
		draw.SetFont(XGFont::LoadFont(1));
		RedrawItem( draw, entry );
	} else {
		fUpdateFlag = true;
	}
}


void TTreeView::DeselectChildren( TableEntryRecord *item )
{
	if (!item) return;
	TableEntryRecord *child = item->child;
	while (child) {
		if (child->fSelected) {
			SetSelected( (uint32)child, false );
		}
		if (child->child) {
			DeselectChildren( child );
		}
		child = child->next;
	}
}


/************************************************************************/
/*																		*/
/*	Events																*/
/*																		*/
/************************************************************************/

BEGINDISPATCH(TTreeView,XGView)
DISPATCHTABLE(KXGKeyEvent,TTreeView::DoKeyEvent)
ENDDISPATCH

/*	TTreeView::DoKeyEvent
 *
 *		Tree event
 */

long TTreeView::DoKeyEvent(long arg, void *)
{
	TreeEventRecord tevent;
	TableDrawRecord tr;
	short i;
	short mods;
	short key;
	uint32 item;
	
	key = GETLOWORD(arg);
	mods = GETHIWORD(arg);
	
	if (mods & KSpecialKey) {
		/*
		 *	Scan for the various arrow keys
		 */
		
		switch (key) {
			case KKeyUpCursor:
				if (fLength <= 0) return 0;
				for (i = 0; i < fLength; ++i) {
					fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
					if (tr.entry->fSelected) {
						--i;
						break;
					}
				}
				if (i >= fLength || i < 0) i = 0;
				fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
				if (!(mods & KKeyShift)) {
					ClearSelection();
				}
				SetSelected((uint32)tr.entry,true);
				tevent.child = (uint32)tr.entry;
				DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
				return 0;
			case KKeyDownCursor:
				if (fLength <= 0) return 0;
				for (i = fLength - 1; i >= 0; --i) {
					fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
					if (tr.entry->fSelected) {
						++i;
						break;
					}
				}
				if (i >= fLength || i < 0) i = fLength - 1;
				fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
				if (!(mods & KKeyShift)) {
					ClearSelection();
				}
				SetSelected((uint32)tr.entry,true);
				tevent.child = (uint32)tr.entry;
				DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
				return 0;
			case KKeyLeftCursor:
				if (fSelectedCount == 1) {
					item = 0;
					if (fSelection->fOpen) {
						item = (uint32)fSelection;
					} else {
						item = (uint32)(fSelection->parent);
					}
					
					if (item) {
						OpenItem( item, false );
						SetSelection( item );
						
						tevent.child = item;
						DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
					}
				}
				return 0;
			case KKeyRightCursor:
				if (fSelectedCount == 1) {
					item = (uint32)(fSelection->child);
					if (item) {
						OpenItem( (uint32)fSelection, true );
						SetSelection( item );
						
						tevent.child = item;
						DoDispatch(KEventTreeClick,GetViewID(),(void *)&tevent);
					}
				}
				return 0;
			default:
				return -1;
		}
	} else {
		/*
		 *	Scan for the regular keys
		 */
		
#if 0
		if (key == 0x0D) {
			/*
			 *	Enter key == double-click
			 */
			
			if (fSelect != NULL) {
				tevent.child = (uint32)fSelect;
				DoDispatch(KEventTreeDoubleClick,GetViewID(),(void *)&tevent);
			}
			return 0;
		} else if (key == ' ') {
			/*
			 *	Space key == open/close item
			 */
			
			if (fSelect) {
				OpenItem((uint32)fSelect,!(fSelect->fOpen));
				return 0;
			}
		}
#endif
		return -1;
	}
}


/************************************************************************/
/*																		*/
/*	Draw the project list												*/
/*																		*/
/************************************************************************/

/*	TTreeView::AppendItem
 *
 *		Append this item after the specified thing
 */

uint32 TTreeView::AppendItem(uint32 parent, uint32 after, const char *str, sint16 icon)
{
	TableEntryRecord *trec;
	TableEntryRecord *tmp;
	long len;
	uint16 h;
	
	trec = new TableEntryRecord;
	trec->child = NULL;
	trec->next = NULL;
	trec->parent = NULL;
	trec->owner = this;
	trec->fSelected = false;
	trec->fOpen = false;
	trec->fChildDefer = false;
	trec->icon = icon;
	trec->fInternDrag = false;
	trec->dragID = KDragFlavorTreeView;
	trec->dragData = 0;
	trec->dragSize.h = -1;
	trec->dragSize.v = -1;

	len = strlen(str)+1;
	h = fData.New(len);
	trec->strHandle = h;
	fData.SetData(h,len,str);
	
	trec->dataHandle = 0;
	
	if (after) {
		tmp = (TableEntryRecord *)after;
		trec->next = tmp->next;
		tmp->next = trec;
		trec->parent = tmp->parent;
	} else if (parent) {
		tmp = (TableEntryRecord *)parent;
		trec->next = tmp->child;
		tmp->child = trec;
		trec->parent = tmp;
	} else {
		trec->next = fRoot;
		fRoot = trec;
		trec->parent = NULL;
	}
	Update();
	
	return (uint32)trec;
}

/*	TTreeView::AppendDefer
 *
 *		Indicate this has children, but the children are unknown.
 */

void TTreeView::AppendDefer(uint32 item)
{
	TableEntryRecord *a;
	
	a = (TableEntryRecord *)item;
	Assert(a);
	a->fChildDefer = true;
	Update();
}

/*	TTreeView::OpenItem
 *
 *		Open this item.
 */

void TTreeView::OpenItem(uint32 item, bool open)
{
	TableEntryRecord *a;
	
	a = (TableEntryRecord *)item;
	Assert(a);
	if (a->fOpen == open) return;
	a->fOpen = open;
	if (!open) {
		DeselectChildren( a );
	}
	Update();
}

/*	TTreeView::IsItemOpen
 *
 *		Is this item open?
 */

bool TTreeView::IsItemOpen(uint32 item)
{
	TableEntryRecord *a;
	
	a = (TableEntryRecord *)item;
	Assert(a);
	return a->fOpen;
}

/*	TTreeView::DeleteItem
 *
 *		Delete this item.
 */

void TTreeView::DeleteItem(uint32 item)
{
	TableEntryRecord *foo;
	TableEntryRecord *me,*par;
	
	me = (TableEntryRecord *)item;
	par = me->parent;
	
	if (par == NULL) {
		if (fRoot == me) {
			fRoot = me->next;
		} else for (foo = fRoot; foo != NULL; foo = foo->next) {
			if (foo->next == me) {
				foo->next = me->next;
				break;
			}
		}
	} else {
		if (par->child == me) {
			par->child = me->next;
		} else for (foo = par->child; foo != NULL; foo = foo->next) {
			if (foo->next == me) {
				foo->next = me->next;
				break;
			}
		}
	}
	
	DeleteEntry(me);
	Update();
}

/*	TTreeView::DeleteAll
 *
 *		Delete the entire contents
 */

void TTreeView::DeleteAll()
{
	TableEntryRecord *te;
	
	while (fRoot) {
		te = fRoot;
		fRoot = fRoot->next;
		DeleteEntry(te);
	}
	Update();
}

/*	TTreeView::DeleteEntry
 *
 *		Delete the data that was allocated for this entry
 */

void TTreeView::DeleteEntry(TableEntryRecord *e)
{
	TableEntryRecord *foo;
	
	SetSelected( (uint32)e, false );
	while (e->child) {
		foo = e->child;
		e->child = foo->next;
		DeleteEntry(foo);
	}
	if (e->strHandle) {
		fData.Free(e->strHandle);
	}
	if (e->dataHandle) {
		fData.Free(e->dataHandle);
	}
	delete e;
}

/*	TTreeView::GetNumItems
 *
 *		Get the number of items inside of this
 */

short TTreeView::GetNumItems(uint32 item)
{
	short count;
	TableEntryRecord *a,*ptr;
	
	a = (TableEntryRecord *)item;
	if (a == NULL) {
		ptr = fRoot;
	} else {
		ptr = a->child;
	}
	for (count = 0; ptr != NULL; ptr = ptr->next) count++;
	return count;
}

/*	TTreeView::GetItemHandle
 *
 *		Get the specified item's handle
 */

uint32 TTreeView::GetItemHandle(uint32 item, short count)
{
	TableEntryRecord *a,*ptr;
	
	a = (TableEntryRecord *)item;
	if (a == NULL) {
		ptr = fRoot;
	} else {
		ptr = a->child;
	}
	for (; ptr != NULL; ptr = ptr->next) {
		if (count-- <= 0) break;
	}
	return (uint32)ptr;
}

/*	TTreeView::GetItemIcon
 *
 *		Get the specified item's data size
 */

sint16 TTreeView::GetItemIcon(uint32 item)
{
	TableEntryRecord *a;
	
	Assert(item);
	a = (TableEntryRecord *)item;
	return a->icon;
}

/*	TTreeView::GetItemParent
 *
 *		Get the specified item's parent
 */

uint32 TTreeView::GetItemParent(uint32 item)
{
	TableEntryRecord *a;
	
	Assert(item);
	a = (TableEntryRecord *)item;
	return (uint32)(a->parent);
}

/*	TTreeView::GetItemData
 *
 *		Get the specified item's data size
 */

void TTreeView::GetItemData(uint32 item, char *str, uint32 len)
{
	TableEntryRecord *a;
	
	Assert(item);
	Assert(len > 0);
	a = (TableEntryRecord *)item;
	
	fData.GetData(a->strHandle,len-1,str);
	str[len-1] = 0;
}

/*	TTreeView::SetItemData
 *
 *		Set the item's data string
 */

void TTreeView::SetItemData(uint32 item, char *str)
{
	TableEntryRecord *a;
	long nlen;
	
	Assert(item);
	a = (TableEntryRecord *)item;
	nlen = strlen(str) + 1;
	
	fData.SetSize(a->strHandle,nlen);
	fData.SetData(a->strHandle,nlen,str);
	
	Update();
}

/*	TTreeView::GetExtraData
 *
 *		Set the extra data associated with this item
 */

uint32 TTreeView::GetExtraData(uint32 item, void *data, uint32 len)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	
	if (a->dataHandle == 0) {
		return 0;
	}
	return fData.GetData(a->dataHandle,len,data);
}

/*	TTreeView::SetExtraData
 *
 *		Set the extra data associated with this item
 */

uint32 TTreeView::SetExtraData(uint32 item, void *data, uint32 len)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	
	if (a->dataHandle == 0) {
		a->dataHandle = fData.New(len);
	} else {
		fData.SetSize(a->dataHandle,len);
	}
	return fData.SetData(a->dataHandle,len,data);
}

/*	TTreeView::GetDragSource
 *
 *		This gets information about what will be sent through the
 *	drag & drop code when this item is dragged
 */

uint32 TTreeView::GetDragSource(uint32 item, long *dragid, void *data, uint32 len)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	
	*dragid = a->dragID;
	if (a->dragData == 0) {
		return 0;
	}
	return fData.GetData(a->dragData,len,data);
}

/*	TTreeView::SetDragSource
 *
 *		Set the drag source
 */

uint32 TTreeView::SetDragSource(uint32 item, 
								long dragid, 
								void *data, 
								uint32 len,
								bool intern)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	
	a->fInternDrag = intern;
	a->dragID = dragid;
	if (a->dragData == 0) {
		a->dragData = fData.New(len);
	} else {
		fData.SetSize(a->dragData,len);
	}
	return fData.SetData(a->dragData,len,data);
}

/*	TTreeView::GetDragSize
 *
 *		Get the drag size
 */

XPoint TTreeView::GetDragSize(uint32 item)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	return a->dragSize;
}

/*	TTreeView::SetDragSize
 *
 *		Set the drag size
 */

void TTreeView::SetDragSize(uint32 item, XPoint size)
{
	TableEntryRecord *a;

	Assert(item);
	a = (TableEntryRecord *)item;
	a->dragSize = size;
}


int TTreeView::CompareItems( const void *a, const void *b )
{
	Assert(a);
	Assert(b);
	TableEntryRecord *ta = *(TableEntryRecord **)a;
	TableEntryRecord *tb = *(TableEntryRecord **)b;
	Assert(ta->owner && ta->owner == tb->owner);
	Assert(ta->parent == tb->parent);
	TTreeView *owner = ta->owner;
	
	const char *ca = (const char *)owner->fData.GetDataPtr(ta->strHandle);
	const char *cb = (const char *)owner->fData.GetDataPtr(tb->strHandle);
	
	while (*ca && tolower(*ca) == tolower(*cb)) {
		++ca;
		++cb;
	}
	return tolower(*ca) - tolower(*cb);
}

void TTreeView::SortChildrenOf( uint32 parID )
{
	TableEntryRecord *trec;
	TableEntryRecord *step;
	TableEntryRecord **sort;
	int count;
	
	if (parID)
	{
		trec = ((TableEntryRecord *)parID)->child;
	}
	else
	{
		trec = fRoot;
	}
	
	count = 0;
	step = trec;
	while (step) {
		++count;
		step = step->next;
	}
	
	if (count < 2) return;
	
	sort = new TableEntryRecord *[count];
	count = 0;
	step = trec;
	while (step) {
		sort[count] = step;
		++count;
		step = step->next;
	}
	
	qsort( sort, count, sizeof(sort[0]), TTreeView::CompareItems );
	
	for (int i = 0; i < count-1; ++i)
	{
		sort[i]->next = sort[i+1];
	}
	sort[count-1]->next = 0;
	
	if (parID)
	{
		((TableEntryRecord *)parID)->child = sort[0];
	}
	else
	{
		fRoot = sort[0];
	}
	
	delete [] sort;
}


/************************************************************************/
/*																		*/
/*	Display Support														*/
/*																		*/
/************************************************************************/

/*	TTreeView::RedrawItem
 *
 *		Given a pointer to an object, this redraws the text in that
 *	item only. This gives me a chance to redraw the selection without
 *	redrawing the entire blasted thing.
 */

void TTreeView::RedrawItem(XGDraw &draw, TableEntryRecord *item)
{
	XRect r;
	long len;
	long x,y;
	long h;
	long i;
	short lh,lt;
	short it;
	TableDrawRecord tr;
	short at;
	
	if (item == NULL) return;
	
	/*
	 *	Get the drawing parameters so I know where to draw
	 */
	
	r = GetContentRect();
	GetScrollParams(&x,&y,&h);
	x *= h;
	
	len = (r.bottom - r.top + h - 1) / h;
	len += y;
	if (len > fLength) len = fLength;
	
	lh = draw.GetFontHeight();			// lh == line height
	lt = (h-1-lh)/2;					// lt == line top (of label)
	it = (h-17)/2;						// it == icon top (for 16x16 icon)
	at = (h-13)/2;						// at == arrow top
	
	/*
	 *	Draw the text of these things.
	 */
	
	draw.SetForeColor(KXGColorBlack);	// text (black)
	for (i = y; i < len; i++) {
		fDrawList.GetMemory(i*sizeof(tr),sizeof(tr),&tr);
		if (tr.entry == item) {
			if (tr.entry->icon) {						
				/*
				 *	Draw label
				 */
				
				r.top = (i - y) * h + lt;
				r.bottom = r.top + lh;
				r.left = 22 * (tr.inset+1) - x + 16;
			} else {
						
				/*
				 *	Draw label
				 */
				
				r.top = (i - y) * h + lt;
				r.bottom = r.top + lh;
				r.left = 22 * (tr.inset+1) - x;
			}
			draw.EraseRect(r);
			DrawItem(draw,r,IsSelected((uint32)tr.entry),(uint32)tr.entry);
			break;
		}
	}
}

/*	TTreeView::DrawItem
 *
 *		Draw an item. This draws within the rectangle
 */

void TTreeView::DrawItem(XGDraw &draw, XRect &r, bool hilite, uint32 data)
{
	char buffer[256];
	XRect s;
	
	/*
	 *	Get the string
	 */
	
	GetItemData(data,buffer,sizeof(buffer)-1);
	
	/*
	 *	Draw the string in the rectangle
	 */
	
	s = r;
	s.right = s.left + 6 + draw.StringWidth(buffer);
	s.left++;
	s.right--;
	if (hilite) {
		if (!IsActive()) {
			draw.SetForeColor(KXGColorHilite);
			draw.FrameRect(s);
			hilite = false;
			draw.SetForeColor(KXGColorBlack);
		} else {
			draw.SetForeColor(KXGColorHiliteText);
			draw.SetBackColor(KXGColorHilite);
			draw.EraseRect(s);
		}
	}
	

	draw.MoveTo(r.left+3,r.top + draw.GetFontAscent());
	draw.DrawString(buffer);

	if (hilite) {
		draw.SetForeColor(KXGColorBlack);
		draw.SetBackColor(KXGColorWhite);
	}
}

/*	TTreeView::DrawDropIcon
 *
 *		Draw the drop icon, either up or down. By default this
 *	draws the up/down flag icons
 */

void TTreeView::DrawDropIcon(XGDraw &draw, XRect &area, bool up)
{
	if (up) {
		if (fUpIcon) draw.DrawIcon(fUpIcon,area.left,area.top);
	} else {
		if (fDownIcon) draw.DrawIcon(fDownIcon,area.left,area.top);
	}
}

/*	TTreeView::DrawItemIcon
 *
 *		Draw the icon for this item.
 */

void TTreeView::DrawItemIcon(XGDraw &draw, XRect &area, sint16 icon)
{
	draw.DrawIcon(icon,area.left,area.top);
}

/************************************************************************/
/*																		*/
/*	Internal Updates													*/
/*																		*/
/************************************************************************/

/*	TTreeView::AppendEntry
 *
 *		Append a data entry with the table entry specified, and
 *	then descend recursively
 */

void TTreeView::AppendEntry(XGDraw &draw, short inset, TableEntryRecord *r)
{
	TableDrawRecord data;
	TableEntryRecord *te;
	long width;
	char buffer[256];
	
	fLength++;
	data.inset = inset;
	data.entry = r;
	fDrawList.Append(sizeof(data),&data);
	
	GetItemData((uint32)r,buffer,sizeof(buffer));
	width = 5 + (inset + 1) * 22 + draw.StringWidth(buffer);
	if (width > fWidth) fWidth = width;
	
	if (r->fOpen) {
		inset++;
		for (te = r->child; te != NULL; te = te->next) {
			AppendEntry(draw,inset,te);
		}
	}
}

/*	TTreeView::Recompute
 *
 *		Recompute the drop handle information
 */

void TTreeView::Recompute()
{
	TableEntryRecord *te;
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
		
	fWidth = 0;
	fLength = 0;
	fDrawList.SetSize(0);
	for (te = fRoot; te != NULL; te = te->next) {
		//te->fOpen = true;				/* All top level items are open. */ NOT ANYMORE!
		AppendEntry(draw,0,te);
	}
	RecalcScroll();
}

/*	TTreeView::RecalcScroll
 *
 *		Recalculate the scrollbar limits
 */

void TTreeView::RecalcScroll()
{
	short h;
	long len;
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	
	len = fDrawList.GetSize() / sizeof(TableDrawRecord);
	h = draw.GetFontHeight() + 1;
	if (h < 17) h = 17;
	
	SetScrollLimits(fWidth,h * len,h);
}
