/*	TListView.cpp
 *
 *		Background view
 */

/*  YAAF Constructor - Rapid application development for YAAF
 *  Copyright (C) 1998 William Edward Woody
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include "Inspector.h"
#include "LayoutViews.h"
#include "TResDocument.h"
#include <XScroll.h>
#include <XDialog.h>
#include <XMessages.h>
#include <XDraw.h>
#include <XPopup.h>

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

/*	TListView::TListView
 *
 *		Build me
 */

TListView::TListView(XGOwner *owner, XGArgStream &stream) : XGView(owner,stream)
{
	fSelect = -1;
	fViewDecl = NULL;
}

/*	TListView::~TListView
 *
 *		Delete me
 */

TListView::~TListView()
{
}

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

/*	TListView::DoDrawView
 *
 *		Draw me
 */

void TListView::DoDrawView(Rect)
{
	XGScrollView *v;
	long x,y,h;
	Rect r;
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));
	
	r = GetContentRect();
	v = dynamic_cast<XGScrollView *>(GetParent());
	h = draw.GetFontHeight();
	v->GetScrollValue(&x,&y);
	
	draw.EraseRect(r);
	
	if (fViewDecl) {
		DrawItems(draw,0,0,
					   5 - x * h, - y * h, 
					   fViewDecl->view);
	}
}

/*	TListView::DoSizeView
 *
 *		Resize view
 */

void TListView::DoSizeView()
{
	InvalView();
	RecalcScroll();
}

/*	TListView::DoMouseDown
 *
 *		Handle mouse down event by updating the selection, and dispatching
 *	a message indicating the selection has changed.
 */

bool TListView::DoMouseDown(Point where, short)
{
	XGScrollView *v;
	long x,y,h,dy;
	ViewItemDeclaration *vitem;
	
	if (!SetFocus()) return false;
	if (fViewDecl == NULL) return false;

	v = dynamic_cast<XGScrollView *>(GetParent());
	h = v->GetPixelStep();
	v->GetScrollValue(&x,&y);
	dy = y;
	y += where.v / h;
	
	x = CountItems(fViewDecl->view);
	if (y >= x) y = -1;
	
	if (fSelect != y) {
		fSelect = y;
		InvalView();
		if (y == -1) vitem = NULL;
		else vitem = FindByIndex(y);
		DoDispatch(KSelectViewItem,0,(void *)vitem);
	}
	
	if (fSelect == -1) return false;
	
	/*
	 *	Determine where I am
	 */
		
	fLine = GetContentRect();
	fLine.top = h * (fSelect - dy);
	fLine.bottom = fLine.top + h;
	fWhere = -1;
	fAfter = NULL;
	return true;
}

/*	TListView::DoMouseMove
 *
 *		Drag the selected item
 */

void TListView::DoMouseMove(Point where, short flags)
{
	ViewItemDeclaration *me,*parent;
	ViewItemDeclaration *tmp;
	long x,y,line,h;
	XGScrollView *v;
	long loc;
	Rect r;
	
	if (flags & KCapturedMouse) {
		/*
		 *	Initialize
		 */
		
		v = dynamic_cast<XGScrollView *>(GetParent());
		h = v->GetPixelStep();
		v->GetScrollValue(&x,&y);
		if (PtInRect(where,&fLine)) {
			// Fast don't do squat selection
			loc = -1;
		} else {
			line = y + where.v / h;

			me = FindByIndex(fSelect);
			parent = FindParent(me);
			
			/*
			 *	Scan siblings to find the selection areas
			 */
			
			if (parent == NULL) {
				loc = -1;
				tmp = fViewDecl->view;
			} else {
				loc = FindByItem(parent);
				tmp = parent->child;
			}
			fAfter = NULL;
			for (; tmp != NULL; tmp = tmp->sibling) {
				x = FindByItem(tmp);
				if (x > line) break;
				loc = x;
				fAfter = tmp;
			}
		}
				
		/*
		 *	Loc now contains the location of the item I'm moving
		 *	in back of
		 */
		
		if (fWhere != loc) {
			XGDraw draw(this);

			r = GetContentRect();
			if (loc != -1) {
				r.top = (loc + 1 - y) * h;
				r.bottom = r.top + 1;
				draw.InvertRect(r);
			}
			if (fWhere != -1) {
				r.top = (fWhere + 1 - y) * h;
				r.bottom = r.top + 1;
				draw.InvertRect(r);
			}
			
			fWhere = loc;
		}
	} else {
		XGView::DoMouseMove(where,flags);
	}
}

/*	TListView::DoMouseUp
 *
 *		Unselect
 */

void TListView::DoMouseUp(Point where, short flags)
{
	Rect r;
	long x,y,h;
	XGScrollView *v;
	ViewItemDeclaration *me,*parent;
	ViewItemDeclaration *tmp;
	TResDocument *doc;
	
	if (flags & KCapturedMouse) {
		/*
		 *	Loc now contains the location of the item I'm moving
		 *	in back of
		 */
		
		if (fWhere != -1) {
			/*
			 *	Erase the line
			 */
			
			XGDraw draw(this);
			v = dynamic_cast<XGScrollView *>(GetParent());
			h = v->GetPixelStep();
			v->GetScrollValue(&x,&y);

			r = GetContentRect();
			r.top = (fWhere + 1 - y) * h;
			r.bottom = r.top + 1;
			draw.InvertRect(r);

			/*
			 *	Move stuff around
			 */
			
			me = FindByIndex(fSelect);
			parent = FindParent(me);
			if (parent == NULL) return;
			
			/*
			 *	Remove old view
			 */
			
			if (parent->child == me) {
				parent->child = me->sibling;
			} else {
				for (tmp = parent->child; tmp != NULL; tmp = tmp->sibling) {
					if (tmp->sibling == me) {
						tmp->sibling = me->sibling;
						break;
					}
				}
			}
			
			/*
			 *	Insert after new view
			 */
			
			if (fAfter == NULL) {
				me->sibling = parent->child;
				parent->child = me;
			} else {
				me->sibling = fAfter->sibling;
				fAfter->sibling = me;
			}

			/*
			 *	Redraw the thing
			 */
			
			fSelect = FindByItem(me);
			doc = dynamic_cast<TResDocument *>(GetDocument());
			doc->MarkDirty();
			RecalcScroll();
			InvalView();
		}
	} else {
		XGView::DoMouseUp(where,flags);
	}
}

/************************************************************************/
/*																		*/
/*	Select Support														*/
/*																		*/
/************************************************************************/

/*	TListView::UpdateSelect
 *
 *		Update the current selection
 */

void TListView::UpdateSelect(ViewItemDeclaration *item)
{
	long i;
	
	i = FindByItem(item);
	if (i != fSelect) {
		fSelect = i;
		InvalView();
	}
}

/************************************************************************/
/*																		*/
/*	Edit Support														*/
/*																		*/
/************************************************************************/

static TResDocument	*GResDocument;
static bool GRoot;

/*	SelectTypeProc
 *
 *		Select the type dialog proc
 */

static long SelectTypeProc(XGDialog *dlog, void *ptr, long msg, long arg, void *)
{
	long i,len,x;
	ViewTypeRecord *t;
	XGPopControl *pop;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog:
					pop = dynamic_cast<XGPopControl *>(dlog->FindViewByID(1));
					pop->DeleteAll();
					
					x = 0;
					len = GResDocument->GetNumViewType();
					for (i = (GRoot ? 0 : 2); i < len; i++) {
						t = GResDocument->GetViewTypeByIndex(i);
						pop->Insert(x++,t->className);
					}
					pop->SetValue(0);		// XGWindow or XGView
					break;
				default:
					break;
			}
			break;
		
		case KXGControlClick:
			switch (arg) {
				case 100:
					pop = dynamic_cast<XGPopControl *>(dlog->FindViewByID(1));
					t = GResDocument->GetViewTypeByIndex(pop->GetValue() + (GRoot ? 0 : 2));
					*((long *)ptr) = t->viewType;
					dlog->EndDialog(1);
					return 0;
				case 101:
					dlog->EndDialog(0);
					return 0;
				default:
					break;
			}
			break;
		default:
			break;
	}
	return -1;
}

/*	TListView::AddNewView
 *
 *		Add a new view to the location specified
 */

void TListView::AddNewView(void)
{
	long len,i,j;
	long type;
	ViewItemDeclaration *parent;
	ViewItemDeclaration *me;
	ViewTypeRecord *trecord;
	short itype;
	XGStringList *arg;
	ViewItemDeclaration *end;
	
	/*
	 *	Figure out where I go
	 */
	
	parent = FindByIndex(fSelect);
	if (parent == NULL) {
		if (fViewDecl->view != NULL) return;
		GRoot = true;
	} else {
		GRoot = false;
	}
	
	/*
	 *	Run the dialog
	 */
	
	GResDocument = dynamic_cast<TResDocument *>(GetDocument());
	if (!XGDialog::ModalDialog(132,SelectTypeProc,(void *)&type)) return;
	
	/*
	 *	Create me
	 */
	
	me = new ViewItemDeclaration;
	me->viewType = type;
	me->child = NULL;
	me->data = new XGStringList;
	me->sibling = NULL;
	if (parent == NULL) {
		fViewDecl->view = me;
	} else {
		end = parent->child;
		if (end == NULL) {
			parent->child = me;
		} else {
			while (end->sibling != NULL) end = end->sibling;
			end->sibling = me;
		}
	}
	
	/*
	 *	Initialize the data for this thing
	 */
	
	trecord = GResDocument->GetViewTypeByType(type);
	len = trecord->LenDeclaration();
	arg = me->data;
	for (i = 0; i < len; i++) {
		itype = trecord->GetDeclItem(i);
		switch (itype) {
			case 0:				// integer
				arg->Insert(i,"0");
				break;
			case 1:				// boolean
				arg->Insert(i,"false");
				break;
			case 2:				// string
				arg->Insert(i,"Untitled");
				break;
		}
	}
	
	/*
	 *	Now scan for the defaults for this thing
	 */
	
	while (trecord) {
		for (i = 0; GPredefValues[i].viewValue != NULL; i++) {
			if (trecord->viewType == GPredefValues[i].viewType) break;
		}
		if (GPredefValues[i].viewValue == NULL) {
			trecord = trecord->childPtr;
		} else {
			PredefValue *v;
			
			v = GPredefValues[i].viewValue;
			for (j = 0; v[j].value != NULL; j++) {
				arg->Insert(j,v[j].value);			// insert defaults
			}
			break;
		}
	}
	
	/*
	 *	Update list
	 */
	
	GResDocument->MarkDirty();
	DoDispatch(KUpdateViewItem,0,NULL);
	RecalcScroll();
	InvalView();
}

/*	DeleteItem
 *
 *		Support routine; recurses and deletes ViewitemDeclarations
 */

static void DeleteItem(ViewItemDeclaration *me)
{
	ViewItemDeclaration *i;
	
	while (me->child) {
		i = me->child;
		me->child = me->child->sibling;
		DeleteItem(i);
	}
	
	delete me->data;
	delete me;
}

/*	TListView::DeleteView
 *
 *		Delete the specified view
 */

void TListView::DeleteView(void)
{
	ViewItemDeclaration *me;
	ViewItemDeclaration *par;
	ViewItemDeclaration *foo;
	
	me = FindByIndex(fSelect);
	if (me == NULL) return;

	/*
	 *	Find my parent
	 */

	par = FindParent(me);
	if (par == NULL) {
		/*
		 *	Delete from root
		 */
		
		fViewDecl->view = NULL;
	} else {
		/*
		 *	Delete from parent
		 */
		
		if (par->child == me) {
			par->child = me->sibling;
		} else {
			for (foo = par->child; foo != NULL; foo = foo->sibling) {
				if (foo->sibling == me) {
					foo->sibling = me->sibling;
					break;
				}
			}
		}
	}
	
	/*
	 *	Now delete me and my children
	 */
	
	DeleteItem(me);
	
	/*
	 *	And mark for update
	 */
	
	GResDocument = dynamic_cast<TResDocument *>(GetDocument());
	GResDocument->MarkDirty();
	fSelect = -1;
	DoDispatch(KSelectViewItem,0,NULL);
	DoDispatch(KUpdateViewItem,0,NULL);
	RecalcScroll();
	InvalView();
}

/************************************************************************/
/*																		*/
/*	Search Support														*/
/*																		*/
/************************************************************************/

static long GTarget;
static long GCount;
static ViewItemDeclaration *GItem;

/*	RecurseItem
 *
 *		Recurse the item
 */

static bool RecurseItem(ViewItemDeclaration *decl)
{
	if (decl == GItem) return true;
	GCount++;
	for (decl = decl->child; decl != NULL; decl = decl->sibling) {
		if (RecurseItem(decl)) return true;
	}
	return false;
}

/*	TListView::FindByItem
 *
 *		Given the item, find the index
 */

long TListView::FindByItem(ViewItemDeclaration *decl)
{
	if (fViewDecl->view == NULL) return -1;

	GCount = 0;
	GItem = decl;
	if (RecurseItem(fViewDecl->view)) return GCount;
	return -1;
}

/*	RecurseCount
 *
 *		Recurse down by count
 */

static bool RecurseCount(ViewItemDeclaration *decl)
{
	if (GTarget == GCount) {
		GItem = decl;
		return true;
	}
	GCount++;
	for (decl = decl->child; decl != NULL; decl = decl->sibling) {
		if (RecurseCount(decl)) return true;
	}
	return false;
}

/*	TListView::FindByIndex
 *
 *		Find the item by it's indx
 */

ViewItemDeclaration *TListView::FindByIndex(long count)
{
	if (fViewDecl->view == NULL) return NULL;

	GTarget = count;
	GCount = 0;
	GItem = NULL;
	if (RecurseCount(fViewDecl->view)) return GItem;
	return NULL;
}

/*	RecurseParent
 *
 *		Find the parent
 */

static ViewItemDeclaration *RecurseParent(ViewItemDeclaration *par)
{
	ViewItemDeclaration *i;
	ViewItemDeclaration *tmp;
	if (par == NULL) return NULL;
	
	for (i = par->child; i != NULL; i = i->sibling) {
		if (i == GItem) return par;
		if (NULL != (tmp = RecurseParent(i))) return tmp;
	}
	return NULL;
}

/*	TListView::FindParent
 *
 *		Find the parent of the specified view
 */

ViewItemDeclaration *TListView::FindParent(ViewItemDeclaration *me)
{
	GItem = me;
	return RecurseParent(fViewDecl->view);
}

/************************************************************************/
/*																		*/
/*	Internal Support													*/
/*																		*/
/************************************************************************/

/*	TListView::DrawLine
 *
 *		Draw the single line
 */

void TListView::DrawLine(XGDraw &draw, long count, short indent, 
						 short x, short y, ViewItemDeclaration *item)
{
	TResDocument *doc;
	ViewTypeRecord *type;
	char buffer[256];
	char n[6];
	Rect s;
	long val;
	
	doc = dynamic_cast<TResDocument *>(GetDocument());
	type = doc->GetViewTypeByType(item->viewType);
	item->data->GetString(0,buffer);
	val = atol(buffer);
	To4Char(n,item->viewType);
	sprintf(buffer,"'%s'%ld %s",n,val,type ? type->className : "(illegal)");
	
	s = GetContentRect();
	s.top = y;
	s.bottom = y + draw.GetFontHeight();
	if (count == fSelect) {
		draw.SetBackColor(KXGColorHilite);
		draw.SetForeColor(KXGColorHiliteText);
	}
	draw.EraseRect(s);
	draw.MoveTo(x + 16 * indent, y + draw.GetFontAscent());
	draw.DrawString(buffer);
	if (count == fSelect) {
		draw.SetBackColor(KXGColorWhite);
		draw.SetForeColor(KXGColorBlack);
	}
}

/*	TListView::DrawItems
 *
 *		Draw a group of items
 */

long TListView::DrawItems(XGDraw &draw, long cindex, short indent, 
						  short x, short y, ViewItemDeclaration *item)
{
	long count;
	ViewItemDeclaration *foo;
	short h;
	
	if (item == NULL) return 0;
	
	h = draw.GetFontHeight();
	count = 1;
	DrawLine(draw,cindex,indent,x,y,item);
	for (foo = item->child; foo != NULL; foo= foo->sibling) {
		count += DrawItems(draw,cindex+count,indent+1,x,y+count*h,foo);
	}
	return count;
}

/*	TListView::CountItems
 *
 *		Count the number of items inside of me
 */

long TListView::CountItems(ViewItemDeclaration *c)
{
	ViewItemDeclaration *foo;
	long count;
	
	if (c == NULL) return 0;
	count = 1;
	for (foo = c->child; foo != NULL; foo = foo->sibling) {
		count += CountItems(foo);
	}
	return count;
}

/*	TListView::RecalcScroll
 *
 *		Recalculate the scrollbar parameters
 */

void TListView::RecalcScroll(void)
{
	XGScrollView *v;
	long x,y,h;
	Rect r;
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(1));

	v = dynamic_cast<XGScrollView *>(GetParent());
	h = draw.GetFontHeight();
	v->SetPixelStep(h);
	
	r = GetContentRect();
	x = (r.right - r.left) / h;
	y = (r.bottom - r.top) / h;
	if (x < 1) x = 1;
	if (y < 1) y = 1;
	v->SetPageSize(x,y);
	
	if (fViewDecl == NULL) y = 0;
	else {
		y = CountItems(fViewDecl->view) - y;
		if (y < 0) y = 0;
	}
	v->SetMaxValue(1000,y);
}
