/* -*- 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.
 */

/*
 * The following source code is part of the Microline Widget Library.
 * The Microline widget library is made available to Mozilla developers
 * under the Netscape Public License (NPL) by Neuron Data.  To learn
 * more about Neuron Data, please visit the Neuron Data Home Page at
 * http://www.neurondata.com.
 */

#include "TreeP.h"

#include <stdio.h>

static void Initialize(Widget req, Widget newW, ArgList args, Cardinal *nargs);
static void Destroy(Widget w);
static Boolean SetValues(Widget curW, Widget, Widget newW, 
	ArgList args, Cardinal *nargs);
static int _PreLayout(XmLGridWidget g, int isVert);
static int _CellAction(XmLGridCell cell, Widget w,
	XmLGridCallbackStruct *cbs);
static void DrawIconCell(XmLGridCell cell, Widget w,
	int row, XRectangle *clipRect, XmLGridDrawStruct *ds);
static void DrawConnectingLine(Display *dpy, Window win, GC gc,
	XRectangle *clipRect, int offFlag, int x1, int y1, int x2, int y2);
static void BtnPress(Widget w, XtPointer closure, XEvent *event,
	Boolean *ctd);
static void Activate(Widget w, XtPointer clientData, XtPointer callData);
static void SwitchRowState(XmLTreeWidget t, int row, XEvent *event);
static XmLGridRow _RowNew(Widget tree);
static void _GetRowValueMask(XmLGridWidget g, char *s, long *mask);
static void _GetRowValue(XmLGridWidget g, XmLGridRow r,
	XtArgVal value, long mask);
static int _SetRowValues(XmLGridWidget g, XmLGridRow r, long mask);
static int _SetCellValuesResize(XmLGridWidget g, XmLGridRow row,
	XmLGridColumn col, XmLGridCell cell, long mask);

static void GetManagerForeground(Widget w, int, XrmValue *value);
static void CreateDefaultPixmaps(XmLTreeWidget t);
static XmLTreeWidget WidgetToTree(Widget w, char *funcname);

static XtResource resources[] =
	{
		{
		XmNcollapseCallback, XmCCallback,
		XmRCallback, sizeof(XtCallbackList),
		XtOffset(XmLTreeWidget, tree.collapseCallback),
		XmRImmediate, (XtPointer)0,
		},
		{
		XmNconnectingLineColor, XmCConnectingLineColor,
		XmRPixel, sizeof(Pixel),
		XtOffset(XmLTreeWidget, tree.lineColor),
		XmRCallProc, (XtPointer)GetManagerForeground,
		},
		{
		XmNexpandCallback, XmCCallback,
		XmRCallback, sizeof(XtCallbackList),
		XtOffset(XmLTreeWidget, tree.expandCallback),
		XmRImmediate, (XtPointer)0,
		},
		{
		XmNlevelSpacing, XmCLevelSpacing,
		XmRDimension, sizeof(Dimension),
		XtOffset(XmLTreeWidget, tree.levelSpacing),
		XmRImmediate, (XtPointer)11,
		},
		{
		XmNplusMinusColor, XmCPlusMinusColor,
		XmRPixel, sizeof(Pixel),
		XtOffset(XmLTreeWidget, tree.pmColor),
		XmRCallProc, (XtPointer)GetManagerForeground,
		},
		/* Row Resources */
		{
		XmNrowExpands, XmCRowExpands,
		XmRBoolean, sizeof(Boolean),
		XtOffset(XmLTreeWidget, tree.rowExpands),
		XmRImmediate, (XtPointer)False,
		},
		{
		XmNrowIsExpanded, XmCRowIsExpanded,
		XmRBoolean, sizeof(Boolean),
		XtOffset(XmLTreeWidget, tree.rowIsExpanded),
		XmRImmediate, (XtPointer)True,
		},
		{
		XmNrowLevel, XmCRowLevel,
		XmRInt, sizeof(int),
		XtOffset(XmLTreeWidget, tree.rowLevel),
		XmRImmediate, (XtPointer)0,
		},

        /* XmNignorePixmaps.  Causes the tree to NOT render any pixmaps */
		{
		XmNignorePixmaps, XmCIgnorePixmaps,
		XmRBoolean, sizeof(Boolean),
		XtOffset(XmLTreeWidget, tree.ignorePixmaps),
		XmRImmediate, (XtPointer) False,
		},
	};

XmLTreeClassRec xmlTreeClassRec =
	{
		{ /* core_class */
		(WidgetClass)&xmlGridClassRec,            /* superclass         */
		"XmLTree",                                /* class_name         */
		sizeof(XmLTreeRec),                       /* widget_size        */
		(XtProc)NULL,                             /* class_init         */
		0,                                        /* class_part_init    */
		FALSE,                                    /* class_inited       */
		(XtInitProc)Initialize,                   /* initialize         */
		0,                                        /* initialize_hook    */
		XtInheritRealize,                         /* realize            */
		NULL,                                     /* actions            */
		0,                                        /* num_actions        */
		resources,                                /* resources          */
		XtNumber(resources),                      /* num_resources      */
		NULLQUARK,                                /* xrm_class          */
		TRUE,                                     /* compress_motion    */
		XtExposeCompressMaximal,                  /* compress_exposure  */
		TRUE,                                     /* compress_enterleav */
		TRUE,                                     /* visible_interest   */
		(XtWidgetProc)Destroy,                    /* destroy            */
		XtInheritResize,                          /* resize             */
		XtInheritExpose,                          /* expose             */
		(XtSetValuesFunc)SetValues,               /* set_values         */
		0,                                        /* set_values_hook    */
		XtInheritSetValuesAlmost,                 /* set_values_almost  */
		0,                                        /* get_values_hook    */
		0,                                        /* accept_focus       */
		XtVersion,                                /* version            */
		0,                                        /* callback_private   */
		XtInheritTranslations,                    /* tm_table           */
		0,                                        /* query_geometry     */
		0,                                        /* display_accelerato */
		0,                                        /* extension          */
		},
		{ /* composite_class */
		XtInheritGeometryManager,                 /* geometry_manager   */
		XtInheritChangeManaged,                   /* change_managed     */
		XtInheritInsertChild,                     /* insert_child       */
		XtInheritDeleteChild,                     /* delete_child       */
		0,                                        /* extension          */
		},
		{ /* constraint_class */
		0,	                                      /* subresources       */
		0,                                        /* subresource_count  */
		sizeof(XmLTreeConstraintRec),             /* constraint_size    */
		0,                                        /* initialize         */
		0,                                        /* destroy            */
		0,                                        /* set_values         */
		0,                                        /* extension          */
		},
		{ /* manager_class */
		XtInheritTranslations,                    /* translations       */
		0,                                        /* syn resources      */
		0,                                        /* num syn_resources  */
		0,                                        /* get_cont_resources */
		0,                                        /* num_get_cont_resou */
		XmInheritParentProcess,                   /* parent_process     */
		0,                                        /* extension          */
		},
		{ /* grid_class */
		0,                                        /* initial rows       */
		1,                                        /* initial cols       */
		_PreLayout,                               /* post layout        */
		sizeof(struct _XmLTreeRowRec),            /* row rec size       */
		_RowNew,                                  /* row new            */
		XmInheritGridRowFree,                     /* row free           */
		_GetRowValueMask,                         /* get row value mask */
		_GetRowValue,                             /* get row value      */
		_SetRowValues,                            /* set row values     */
		sizeof(struct _XmLGridColumnRec),         /* column rec size    */
		XmInheritGridColumnNew,                   /* column new         */
		XmInheritGridColumnFree,                  /* column free        */
		XmInheritGridGetColumnValueMask,          /* get col value mask */
		XmInheritGridGetColumnValue,              /* get column value   */
		XmInheritGridSetColumnValues,             /* set column values  */
		_SetCellValuesResize,                     /* set cell vl resize */
		_CellAction,                              /* cell action        */
		},
		{ /* tree_class */
		0,                                        /* unused             */
		}
	};

WidgetClass xmlTreeWidgetClass = (WidgetClass)&xmlTreeClassRec;

static void
Initialize(Widget reqW,
	   Widget newW,
	   ArgList args,
	   Cardinal *narg)
	{
	XmLTreeWidget t;
 
	t = (XmLTreeWidget)newW;
	if (t->core.width <= 0) 
		t->core.width = 100;
	if (t->core.height <= 0) 
		t->core.height = 100;
	t->tree.defaultPixmapsCreated = 0;
	t->tree.linesData = 0;
	t->tree.linesSize = 0;
	t->tree.recalcTreeWidth = 0;
	if (t->grid.rowCount)
		{
		XmLWarning(newW, "Initialize() - can't set XmNrows");
		XmLGridDeleteAllRows(newW, XmCONTENT);
		}
	XtAddCallback(newW, XmNactivateCallback, Activate, NULL);
	XtAddEventHandler(newW, ButtonPressMask,
		True, (XtEventHandler)BtnPress, (XtPointer)0);

	XtVaSetValues(newW,
		XmNcellDefaults, True,
		XmNcolumn, 0,
		XmNcellType, XmICON_CELL,
		NULL);
	}

static void
Destroy(Widget w)
	{
	XmLTreeWidget t;
	Display *dpy;
	XWindowAttributes attr;

	t = (XmLTreeWidget)w;
	dpy = XtDisplay(t);
	if (t->tree.linesData)
		free((char *)t->tree.linesData);
	if (t->tree.defaultPixmapsCreated)
		{
		XGetWindowAttributes(dpy, XtWindow(w), &attr);
		XFreePixmap(dpy, t->tree.filePixmask);
		XFreePixmap(dpy, t->tree.folderPixmask);
		XFreePixmap(dpy, t->tree.folderOpenPixmask);
		XFreePixmap(dpy, t->tree.filePixmask);
		XFreePixmap(dpy, t->tree.folderPixmask);
		XFreePixmap(dpy, t->tree.folderOpenPixmask);
		XFreeColors(dpy, attr.colormap, t->tree.pixColors, 4, 0L);
		}
	}

static Boolean
SetValues(Widget curW,
	  Widget reqW,
	  Widget newW,
	  ArgList args,
	  Cardinal *nargs)
{
	XmLTreeWidget t, cur;
	XmLGridColumn col;
	Boolean needsResize, needsRedraw;
 
	t = (XmLTreeWidget)newW;
	cur = (XmLTreeWidget)curW;
	needsResize = False;
	needsRedraw = False;

#define NE(value) (t->value != cur->value)
	if (NE(grid.rowCount))
		XmLWarning(newW, "SetValues() - can't set XmNrows");
	if (NE(tree.pmColor) || NE(tree.lineColor))
		needsRedraw = True;
	if (NE(tree.levelSpacing) ||
		t->tree.recalcTreeWidth)
		{
		col = XmLGridGetColumn(newW, XmCONTENT, 0);
		if (col)
			col->grid.widthInPixelsValid = 0;
		t->tree.recalcTreeWidth = 0;
		needsResize = True;
		needsRedraw = True;
		}
#undef NE
	if (needsResize)
		_XmLGridLayout((XmLGridWidget)t);
	if (needsRedraw)
		XmLGridRedrawAll((Widget)t);
	return False;
}

static int
_PreLayout(XmLGridWidget g,
           int isVert)
	{
	XmLTreeWidget t;
	XmLTreeRow row;
	Widget w;
	int i, j, size, maxLevel, hideLevel, lineWidth;
	char *thisLine, *prevLine;

	t = (XmLTreeWidget)g;
	w = (Widget)g;
	if (!t->grid.vertVisChangedHint)
		return 0; /* ?? */
	t->grid.vertVisChangedHint = 0;

	/* top down calculation of hidden states and maxLevel */
	hideLevel = -1;
	maxLevel = 0;
	t->grid.layoutFrozen = True;
	for (i = 0; i < t->grid.rowCount; i++)
		{
		row = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, i);
		if (row->tree.level > maxLevel)
			maxLevel = row->tree.level;

		if (hideLevel != -1 && row->tree.level > hideLevel)
			{
			if (row->grid.height)
				XtVaSetValues(w,
					XmNrow, i,
					XmNrowHeight, 0,
					NULL);
			}
		else
			{
			if (row->tree.expands == True && row->tree.isExpanded == False)
				hideLevel = row->tree.level;
			else
				hideLevel = -1;
			if (!row->grid.height)
				XtVaSetValues(w,
					XmNrow, i,
					XmNrowHeight, 1,
					NULL);
			}
		}
	t->grid.layoutFrozen = False;
	t->tree.linesMaxLevel = maxLevel;
	if (!t->grid.rowCount)
		return 0;

	/* bottom up calculation of connecting lines */
	lineWidth = maxLevel + 1;
	size = lineWidth * t->grid.rowCount;
	if (t->tree.linesSize < size)
		{
		if (t->tree.linesData)
			free((char *)t->tree.linesData);
		t->tree.linesSize = size;
		t->tree.linesData = (char *)malloc(size);
		}
	prevLine = 0;
	thisLine = &t->tree.linesData[size - lineWidth];
	for (i = t->grid.rowCount - 1; i >= 0; i--)
		{
		row = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, i);
		if (!row->grid.height)
			{
			thisLine -= lineWidth;
			continue;
			}
		for (j = 0; j < row->tree.level - 1; j++)
			{
			if (prevLine && (prevLine[j] == 'L' || prevLine[j] == 'I' ||
				prevLine[j] == 'E'))
				thisLine[j] = 'I';
			else
				thisLine[j] = ' ';
			}	
		if (row->tree.level)
			{
			if (prevLine && (prevLine[j] == 'L' || prevLine[j] == 'I' ||
				prevLine[j] == 'E'))
				thisLine[j++] = 'E';
			else
				thisLine[j++] = 'L';
			}
		thisLine[j++] = 'O';
		for (; j < lineWidth; j++)
			thisLine[j] = ' ';
		prevLine = thisLine;
		thisLine -= lineWidth;
		}
	if (prevLine)
		{
		for (i = 0; i < lineWidth; i++)
			{
			if (prevLine[i] == 'L')
				prevLine[i] = '-';
			else if (prevLine[i] == 'E')
				prevLine[i] = 'P';
			}
		}

	/* if we are in VertLayout(), the horizontal size may need */
	/* recomputing because of the row hides. */
	if (isVert)
		return 1;

	/* if we are in HorizLayout(), the vertical recomputation */
	/* will be happening regardless, since row changes (vertical) */
	/* are why we are here */
	return 0;
	}

static int
_CellAction(XmLGridCell cell,
            Widget w,
            XmLGridCallbackStruct *cbs)
	{
	XmLTreeWidget t;
	XmLTreeRow row;
	XmLGridColumn col;
	XmLGridWidgetClass sc;
	XmLGridCellActionProc cellActionProc;
	XmLGridCellRefValues *cellValues;
	XmLGridCellIcon *icon;
	/*	XRectangle *rect, cRect;*/
	int ret, h, isTreeCell;


	Dimension default_icon_width = 16;
	Dimension default_icon_height = 16;

	t = (XmLTreeWidget)w;
	if (cbs->rowType == XmCONTENT &&
		cbs->columnType == XmCONTENT &&
		cbs->column == 0)
		isTreeCell = 1;
	else
		isTreeCell = 0;
	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	cellActionProc = sc->grid_class.cellActionProc;
	ret = 0;

	/* Check for ignore pixmaps */
	if (t->tree.ignorePixmaps)
	{
		default_icon_width = 0;
		default_icon_height = 0;
	}

	switch (cbs->reason)
		{
		case XmCR_CELL_DRAW:
			if (isTreeCell)
				DrawIconCell(cell, w, cbs->row, cbs->clipRect, cbs->drawInfo);
			else
				ret = cellActionProc(cell, w, cbs);
			break;
		case XmCR_PREF_HEIGHT:
			ret = cellActionProc(cell, w, cbs);
			if (isTreeCell)
				{
				cellValues = cell->cell.refValues;
				if (cellValues->type != XmICON_CELL)
					return 0;
				icon = (XmLGridCellIcon *)cell->cell.value;

				h = 4 + default_icon_height + cellValues->topMargin + cellValues->bottomMargin;

				if (icon && icon->pix.pixmap == XmUNSPECIFIED_PIXMAP &&
					ret < h)
					ret = h;
				}
			break;
		case XmCR_PREF_WIDTH:
			if (isTreeCell)
				{
				cellValues = cell->cell.refValues;
				if (cellValues->type != XmICON_CELL)
					return 0;
				icon = (XmLGridCellIcon *)cell->cell.value;
				col = (XmLGridColumn)cbs->object;
				row = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, cbs->row);
				if (row->tree.stringWidthValid == False)
					{
					if (icon && icon->string)
						row->tree.stringWidth =
							XmStringWidth(cellValues->fontList, icon->string);
					else
						row->tree.stringWidth = 0;
					row->tree.stringWidthValid = True;
					}
				ret = 4 + cellValues->leftMargin + t->tree.levelSpacing * 2 *
					row->tree.level + t->grid.iconSpacing + row->tree.stringWidth +
					cellValues->rightMargin;
				if (!icon)
 					ret += default_icon_width;
				else if (icon->pix.pixmap == XmUNSPECIFIED_PIXMAP)
 					ret += default_icon_width;
				else
					ret += icon->pix.width;
				if (!row->grid.height)
					ret = 0;
				}
			else
				ret = cellActionProc(cell, w, cbs);
			break;
		default:
			ret = cellActionProc(cell, w, cbs);
			break;
		}
	return ret;
	}

static void
DrawIconCell(XmLGridCell cell,
             Widget w,
             int row,
             XRectangle *clipRect,
             XmLGridDrawStruct *ds)
	{
	XmLTreeWidget t;
	XmLTreeRow rowp;
	XmLGridCellRefValues *cellValues;
	XmLGridCellIcon *icon;
	XRectangle *cellRect, rect;
	Display *dpy;
	Window win;
	char *thisLine;
	int i, clipSet, pixWidth, pixHeight;
	int xoff, xoff2, midy, oddFlag, x1, y1, x2, y2;
	Pixmap pixmap, pixmask;

	t = (XmLTreeWidget)w;
	rowp = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, row);
	dpy = XtDisplay(w);
	win = XtWindow(w);
	cellValues = cell->cell.refValues;
	if (cellValues->type != XmICON_CELL)
		return;
	icon = (XmLGridCellIcon *)cell->cell.value;
	if (!icon)
		return;
	cellRect = ds->cellRect;
	if (!t->tree.linesData)
		{
		XmLWarning(w, "DrawIconCell() - no lines data calculated");
		return;
		}

	/* draw background */
	XSetForeground(dpy, ds->gc, cell->cell.refValues->background);
	XFillRectangle(dpy, win, ds->gc, clipRect->x, clipRect->y,
		clipRect->width, clipRect->height);

	if (t->grid.singleColScrollMode)
		oddFlag = t->grid.singleColScrollPos & 1;
	else
		oddFlag = 0;

	pixWidth = 0;
	xoff = t->tree.levelSpacing;
	xoff2 = xoff * 2;
	y1 = cellRect->y;
	y2 = cellRect->y + cellRect->height - 1;
	midy = cellRect->y + cellRect->height / 2;
	if (midy & 1)
		midy += 1;

	/* draw connecting lines and pixmap */
	XSetForeground(dpy, ds->gc, t->tree.lineColor);
	thisLine = &t->tree.linesData[row * (t->tree.linesMaxLevel + 1)];
	for (i = 0; i <= t->tree.linesMaxLevel; i++)
		{
		x1 = cellRect->x + (xoff2 * i);
		if (x1 >= clipRect->x + (int)clipRect->width)
			continue;
		switch (thisLine[i])
			{
			case 'O':
              if (!t->tree.ignorePixmaps)
              {
				rect.x = x1;
				rect.y = cellRect->y;
				rect.width = cellRect->width;
				rect.height = cellRect->height;
				if (icon->pix.pixmap != XmUNSPECIFIED_PIXMAP)
					{
					pixmap = icon->pix.pixmap;
					pixmask = icon->pix.pixmask;
					pixWidth = icon->pix.width;
					pixHeight = icon->pix.height;
					}
				else
					{
					if (!t->tree.defaultPixmapsCreated)
						CreateDefaultPixmaps(t);
					if (rowp->tree.expands && rowp->tree.isExpanded)
						{
						pixmap = t->tree.folderOpenPixmap;
						pixmask = t->tree.folderOpenPixmask;
						}
					else if (rowp->tree.expands)
						{
						pixmap = t->tree.folderPixmap;
						pixmask = t->tree.folderPixmask;
						}
					else
						{
						pixmap = t->tree.filePixmap;
						pixmask = t->tree.filePixmask;
						}
					pixWidth = 16;
					pixHeight = 16;
					}
				
				XmLPixmapDraw(w, pixmap, pixmask, pixWidth, pixHeight,
							  XmALIGNMENT_BOTTOM_LEFT, ds->gc, &rect, clipRect);
              } /* !t->tree.ignorePixmaps */
				break;
			case 'I':
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, y1, x1 + xoff, y2);
				break;
			case 'E':
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, y1, x1 + xoff, y2);
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, midy, x1 + xoff2, midy);
				break;
			case 'L':
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, y1, x1 + xoff, midy);
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, midy, x1 + xoff2, midy);
				break;
			case 'P':
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, midy, x1 + xoff, y2);
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, midy, x1 + xoff2, midy);
				break;
			case '-':
				DrawConnectingLine(dpy, win, ds->gc, clipRect, oddFlag,
					x1 + xoff, midy, x1 + xoff2, midy);
				break;
			}
		}

	clipSet = 0;

	/* draw expand/collapse graphic */
	rect.x = cellRect->x + (rowp->tree.level - 1) * xoff2 + xoff - 5;
	rect.y = midy - 5;
	rect.width = 11;
	rect.height = 11;
	i = XmLRectIntersect(&rect, clipRect);
	if (rowp->tree.expands && rowp->tree.level && i != XmLRectOutside)
		{
		if (i == XmLRectPartial)
			{
			clipSet = 1;
			XSetClipRectangles(dpy, ds->gc, 0, 0, clipRect, 1, Unsorted);
			}
		x1 = rect.x;
		x2 = rect.x + rect.width - 1;
		y1 = rect.y;
		y2 = rect.y + rect.height - 1;
		XSetForeground(dpy, ds->gc, cellValues->background);
		XFillRectangle(dpy, win, ds->gc, x1, y1, 11, 11);
		XSetForeground(dpy, ds->gc, t->tree.lineColor);
		XDrawLine(dpy, win, ds->gc, x1 + 2, y1 + 1, x2 - 2, y1 + 1);
		XDrawLine(dpy, win, ds->gc, x2 - 1, y1 + 2, x2 - 1, y2 - 2);
		XDrawLine(dpy, win, ds->gc, x1 + 2, y2 - 1, x2 - 2, y2 - 1);
		XDrawLine(dpy, win, ds->gc, x1 + 1, y1 + 2, x1 + 1, y2 - 2);
		XSetForeground(dpy, ds->gc, t->tree.pmColor);
		if (!rowp->tree.isExpanded)
			XDrawLine(dpy, win, ds->gc, x1 + 5, y1 + 3, x1 + 5, y1 + 7);
		XDrawLine(dpy, win, ds->gc, x1 + 3, y1 + 5, x1 + 7, y1 + 5);
		}

	/* draw select background and highlight */
	i = rowp->tree.level * xoff2 + pixWidth + t->grid.iconSpacing;
	rect.x = cellRect->x + i;
	rect.y = cellRect->y;
	rect.height = cellRect->height;
	rect.width = 0;
	if (t->grid.colCount == 1 && rowp->tree.stringWidthValid)
		rect.width = rowp->tree.stringWidth + 4;
	else if ((int)cellRect->width > i)
		rect.width = cellRect->width - i;
	i = XmLRectIntersect(&rect, clipRect);
	if (i != XmLRectOutside && ds->drawSelected)
		{
		if (i == XmLRectPartial && !clipSet)
			{
			clipSet = 1;
			XSetClipRectangles(dpy, ds->gc, 0, 0, clipRect, 1, Unsorted);
			}
		XSetForeground(dpy, ds->gc, ds->selectBackground);
		XFillRectangle(dpy, win, ds->gc, rect.x, rect.y,
			rect.width, rect.height);
		}
	if (ds->drawFocusType != XmDRAW_FOCUS_NONE &&
		t->grid.highlightThickness >= 2)
		{
		if (i == XmLRectPartial && !clipSet)
			{
			clipSet = 1;
			XSetClipRectangles(dpy, ds->gc, 0, 0, clipRect, 1, Unsorted);
			}
		XSetForeground(dpy, ds->gc, t->manager.highlight_color);
		x1 = rect.x;
		x2 = rect.x + rect.width - 1;
		y1 = rect.y;
		y2 = rect.y + rect.height - 1;
		XDrawLine(dpy, win, ds->gc, x1, y1, x2, y1);
		if (ds->drawFocusType == XmDRAW_FOCUS_CELL ||
			ds->drawFocusType == XmDRAW_FOCUS_RIGHT)
			XDrawLine(dpy, win, ds->gc, x2, y1, x2, y2);
		XDrawLine(dpy, win, ds->gc, x1, y2, x2, y2);
		XDrawLine(dpy, win, ds->gc, x1, y1, x1, y2);
		y1 += 1;
		y2 -= 1;
		XDrawLine(dpy, win, ds->gc, x1, y2, x2, y2);
		XDrawLine(dpy, win, ds->gc, x1, y1, x2, y1);
		x1 += 1;
		x2 -= 1;
		XDrawLine(dpy, win, ds->gc, x1, y1, x1, y2);
		if (ds->drawFocusType == XmDRAW_FOCUS_CELL ||
			ds->drawFocusType == XmDRAW_FOCUS_RIGHT)
			XDrawLine(dpy, win, ds->gc, x2, y1, x2, y2);
		}

	/* draw string */
	if (icon->string)
		{
		if (ds->drawSelected == True)
			XSetForeground(dpy, ds->gc, ds->selectForeground);
		else
			XSetForeground(dpy, ds->gc, cellValues->foreground);
		XmLStringDraw(w, icon->string, ds->stringDirection,
			cellValues->fontList, XmALIGNMENT_LEFT,
			ds->gc, &rect, clipRect);
		}

	if (clipSet)
		XSetClipMask(dpy, ds->gc, None);
	}

static void
DrawConnectingLine(Display *dpy,
                    Window win,
                    GC gc,
                    XRectangle *clipRect,
                    int oddFlag,
                    int x1,
                    int y1,
                    int x2,
                    int y2)
	{
	int i, x, y;
	XPoint points[100];

	i = 0;
	for (x = x1; x <= x2; x++)
		for (y = y1; y <= y2; y++)
			{
			if ((((x + oddFlag) & 1) == (y & 1)) ||
				x < clipRect->x ||
				x >= (clipRect->x + (int)clipRect->width) ||
				y < clipRect->y ||
				y >= (clipRect->y + (int)clipRect->height))
				continue;
			points[i].x = x;
			points[i].y = y;
			if (++i < 100)
				continue;
			XDrawPoints(dpy, win, gc, points, i, CoordModeOrigin);
			i = 0;
			}
	if (i)
		XDrawPoints(dpy, win, gc, points, i, CoordModeOrigin);
	}

static void
BtnPress(Widget w,
         XtPointer closure,
         XEvent *event,
         Boolean *ctd)
	{
	XmLTreeWidget t;
	XmLTreeRow rowp;
	unsigned char rowType, colType;
	int row, col, x1, y1, x2, y2, xoff;
	XRectangle rect;
	XButtonEvent *be;
	static int lastRow = -1;
	static Time lastSelectTime = 0;

	t = (XmLTreeWidget)w;
	if (event->type != ButtonPress)
		return;
	be = (XButtonEvent *)event;
	if (be->button != Button1 || be->state & ControlMask ||
		be->state & ShiftMask)
		return;
	if (XmLGridXYToRowColumn(w, be->x, be->y, &rowType, &row,
		&colType, &col) == -1)
		return;
	rowp = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, row);
	if (rowType != XmCONTENT || colType != XmCONTENT || col != 0)
		return;
	if (XmLGridRowColumnToXY(w, rowType, row, colType, col,
		False, &rect) == -1)
		return;
	if ((be->time - lastSelectTime) < XtGetMultiClickTime(XtDisplay(w)) &&
		lastRow == row)
		{
		/* activate callback will be handling expand/collapse */
		lastSelectTime = be->time;
		return;
		}
	lastSelectTime = be->time;
	lastRow = row;
	xoff = t->tree.levelSpacing;
	x1 = rect.x + (rowp->tree.level - 1) * xoff * 2 + xoff - 6;
	x2 = x1 + 13;
	y1 = rect.y + rect.height / 2 - 6;
	y2 = y1 + 13;
	if (be->x > x2 || be->x < x1 || be->y > y2 || be->y < y1)
		return;
	SwitchRowState(t, row, event);
	}

static void
Activate(Widget w,
         XtPointer clientData,
         XtPointer callData)
	{
	XmLTreeWidget t;
	XmLGridCallbackStruct *cbs;

	t = (XmLTreeWidget)w;
	cbs = (XmLGridCallbackStruct *)callData;
	if (cbs->rowType != XmCONTENT)
	if (t->grid.selectionPolicy == XmSELECT_CELL &&
		(cbs->columnType != XmCONTENT || cbs->column != 0))
		return;
	SwitchRowState(t, cbs->row, cbs->event);
	}

static void
SwitchRowState(XmLTreeWidget t,
               int row,
               XEvent *event)
	{
	Widget w;
	XmLTreeRow rowp;
	XmLGridCallbackStruct cbs;

	w = (Widget)t;
	rowp = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, row);
	if (rowp->tree.expands == False) 
		return;
	cbs.event = event;
	cbs.columnType = XmCONTENT;
	cbs.column = 0;
	cbs.rowType = XmCONTENT;
	cbs.row = row;
	if (rowp->tree.isExpanded == True)
		{
		XtVaSetValues(w,
			XmNrow, row,
			XmNrowIsExpanded, False,
			NULL);
		cbs.reason = XmCR_COLLAPSE_ROW;
		XtCallCallbackList(w, t->tree.collapseCallback, (XtPointer)&cbs);
		}
	else
		{
		XtVaSetValues(w,
			XmNrow, row,
			XmNrowIsExpanded, True,
			NULL);
		cbs.reason = XmCR_EXPAND_ROW;
		XtCallCallbackList(w, t->tree.expandCallback, (XtPointer)&cbs);
		}
	}

/* Only to be called through Grid class */
static XmLGridRow
_RowNew(Widget tree)
	{
	XmLGridWidgetClass sc;
	XmLTreeRow row;

	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	row = (XmLTreeRow)sc->grid_class.rowNewProc(tree);
	row->tree.level = 0;
	row->tree.expands = False;
	row->tree.isExpanded = True;
	row->tree.hasSiblings = False;
	row->tree.stringWidth = 0;
	row->tree.stringWidthValid = False;
	return (XmLGridRow)row;
	}

/* Only to be called through Grid class */
static void
_GetRowValueMask(XmLGridWidget g,
                 char *s,
                 long *mask)
        {
	XmLGridWidgetClass sc;
	static XrmQuark qLevel, qExpands, qIsExpanded;
	static int quarksValid = 0;
	XrmQuark q;
 
	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	sc->grid_class.getRowValueMaskProc(g, s, mask);
	if (!quarksValid)
		{
		qLevel = XrmStringToQuark(XmNrowLevel);
		qExpands = XrmStringToQuark(XmNrowExpands);
		qIsExpanded = XrmStringToQuark(XmNrowIsExpanded);
		quarksValid = 1;
		}
	q = XrmStringToQuark(s);
	if (q == qLevel)
		*mask |= XmLTreeRowLevel;
	else if (q == qExpands)
		*mask |= XmLTreeRowExpands;
	else if (q == qIsExpanded)
		*mask |= XmLTreeRowIsExpanded;
	}

/* Only to be called through Grid class */
static void
_GetRowValue(XmLGridWidget g,
             XmLGridRow r,
             XtArgVal value,
             long mask)
	{
	XmLGridWidgetClass sc;
	XmLTreeRow row;

	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	sc->grid_class.getRowValueProc(g, r, value, mask);
	row = (XmLTreeRow)r;
	switch (mask)
		{
		case XmLTreeRowLevel:
			*((int *)value) = row->tree.level;
			break;
		case XmLTreeRowExpands:
			*((Boolean *)value) = row->tree.expands;
			break;
		case XmLTreeRowIsExpanded:
			*((Boolean *)value) = row->tree.isExpanded;
			break;
		}
	}

/* Only to be called through Grid class */
static int
_SetRowValues(XmLGridWidget g,
              XmLGridRow r,
              long mask)
	{
	XmLGridWidgetClass sc;
	int needsResize;
	XmLTreeRow row;
	XmLTreeWidget t;

	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	needsResize = sc->grid_class.setRowValuesProc(g, r, mask);
	t = (XmLTreeWidget)g;
	row = (XmLTreeRow)r;
	if ((mask & XmLGridRowHeight) && needsResize)
		t->tree.recalcTreeWidth = 1;
	if (mask & XmLTreeRowLevel)
		{
		row->tree.level = t->tree.rowLevel;
		t->tree.recalcTreeWidth = 1;
		t->grid.vertVisChangedHint = 1;
		needsResize = 1;
		}
	if (mask & XmLTreeRowExpands)
		{
		row->tree.expands = t->tree.rowExpands;
		t->grid.vertVisChangedHint = 1;
		needsResize = 1;
		}
	if (mask & XmLTreeRowIsExpanded)
		{
		row->tree.isExpanded = t->tree.rowIsExpanded;
		t->grid.vertVisChangedHint = 1;
		needsResize = 1;
		}
	return needsResize;
	}

/* Only to be called through Grid class */
static int
_SetCellValuesResize(XmLGridWidget g,
		      XmLGridRow row,
		      XmLGridColumn col,
		      XmLGridCell cell,
  		      long mask)
	{
	XmLGridWidgetClass sc;
	int needsResize;

	sc = (XmLGridWidgetClass)xmlTreeWidgetClass->core_class.superclass;
	needsResize = 0;
	if (col->grid.pos == g->grid.headingColCount &&
		row->grid.pos >= g->grid.headingRowCount &&
		row->grid.pos < g->grid.headingRowCount + g->grid.rowCount)
		{
		if (mask & XmLGridCellFontList)
			{
			row->grid.heightInPixelsValid = 0;
			((XmLTreeRow)row)->tree.stringWidthValid = False;
			col->grid.widthInPixelsValid = 0;
			needsResize = 1;
			}
		if (mask & XmLGridCellString)
			{
			((XmLTreeRow)row)->tree.stringWidthValid = False;
			col->grid.widthInPixelsValid = 0;
			needsResize = 1;
			}
		}
	if (sc->grid_class.setCellValuesResizeProc(g, row, col, cell, mask))
		needsResize = 1;
	return needsResize;
	}

/*
  Utility
*/

static void
GetManagerForeground(Widget w,
                      int offset,
                      XrmValue *value)
	{
	XmLTreeWidget t;

	t = (XmLTreeWidget)w;
	value->addr = (caddr_t)&t->manager.foreground;
	}

static void
CreateDefaultPixmaps(XmLTreeWidget t)
	{
	Display *dpy;
	Window win;
	XWindowAttributes attr;
	XColor color;
	Pixmap pixmap;
	Pixel pixel;
	XImage *image;
	int i, x, y;
	enum { white = 0, black = 1, yellow = 2, gray = 3 };
	static unsigned short colors[4][3] =
		{
			{ 65535, 65535, 65535 },
			{ 0,         0,     0 },
			{ 57344, 57344,     0 },
			{ 32768, 32768, 32768 },
		};
	static unsigned char fileMask_bits[] =
		{
		0xf8, 0x0f, 0xfc, 0x1f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f,
		0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f,
		0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xf8, 0x7f
		};
	static unsigned char folderMask_bits[] =
		{
		0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xfc, 0x3f, 0xfe, 0x7f,
		0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff,
		0xfe, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8, 0x7f
		};
	static unsigned char folderOpenMask_bits[] =
		{
		0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x0f, 0xfc, 0x3f, 0xfe, 0x7f,
		0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff,
		0xfc, 0xff, 0xfc, 0xff, 0xf8, 0xff, 0xf0, 0x7f
		};
	static char icons[3][16][16] =
		{
			{
			"   GGGGGGGGG    ",
			"  GWWWWWWWWKK   ",
			"  GWWWWWWWWKWK  ",
			"  GWWWWWWWWKKKK ",
			"  GWWWWWWWWWWGK ",
			"  GWGGGGGGGWWGK ",
			"  GWWKKKKKKKWGK ",
			"  GWWWWWWWWWWGK ",
			"  GWGGGGGGGWWGK ",
			"  GWWKKKKKKKWGK ",
			"  GWWWWWWWWWWGK ",
			"  GWGGGGGGGWWGK ",
			"  GWWKKKKKKKWGK ",
			"  GWWWWWWWWWWGK ",
			"  GGGGGGGGGGGGK ",
			"   KKKKKKKKKKKK ",
			},
			{
			"                ",
			"                ",
			"     GGGGGG     ",
			"    GYYYYYYG    ",
			"  GGYYYYYYYYGG  ",
			" GWWWWWWWWWWWYG ",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GWYYYYYYYYYYYGK",
			" GYYYYYYYYYYYYGK",
			"  GGGGGGGGGGGGKK",
			"   KKKKKKKKKKKK ",
			},
			{
			"                ",
			"                ",
			"     GGGGGG     ",
			"    GYYYYYYG    ",
			"  GGYYYYYYYYGG  ",
			" GYYYYYYYYYYYYG ",
			" GYYYYYYYYYYYYGK",
			"GGGGGGGGGGGYYYGK",
			"GWWWWWWWWWYKYYGK",
			"GWYYYYYYYYYKYYGK",
			" GYYYYYYYYYYKYGK",
			" GYYYYYYYYYYKYGK",
			"  GYYYYYYYYYYKGK",
			"  GYYYYYYYYYYKGK",
			"   GGGGGGGGGGGKK",
			"    KKKKKKKKKKK ",
			},
		};

	dpy = XtDisplay(t);
	win = XtWindow(t);
	XGetWindowAttributes(dpy, win, &attr);
	t->tree.filePixmask = XCreatePixmapFromBitmapData(dpy, win,
		(char *)fileMask_bits, 16, 16, 1L, 0L, 1);
	t->tree.folderPixmask = XCreatePixmapFromBitmapData(dpy, win,
		(char *)folderMask_bits, 16, 16, 1L, 0L, 1);
	t->tree.folderOpenPixmask = XCreatePixmapFromBitmapData(dpy, win,
		(char *)folderOpenMask_bits, 16, 16, 1L, 0L, 1);
	for (i = 0; i < 4; i++)
		{
		color.red = colors[i][0];
		color.green = colors[i][1];
		color.blue = colors[i][2];
		color.flags = DoRed | DoGreen | DoBlue;
		if (XAllocColor(dpy, attr.colormap, &color))
			t->tree.pixColors[i] = color.pixel;
		else
			{
			color.flags = 0;
			XAllocColor(dpy, attr.colormap, &color);
			t->tree.pixColors[i] = color.pixel;
			}
		}
	image = XCreateImage(dpy, attr.visual, attr.depth, ZPixmap, 0,
		NULL, 16, 16, XBitmapPad(dpy), 0);
	if (!image)
		XmLWarning((Widget)t,
			"CreateDefaultPixmaps() - can't allocate image");
	else
		image->data = (char *)malloc(image->bytes_per_line * 16);
	for (i = 0; i < 3; i++)
		{
		pixmap = XCreatePixmap(dpy, win, 16, 16, attr.depth);
		for (x = 0; x < 16; x++)
			for (y = 0; y < 16; y++)
				{
				switch (icons[i][y][x])
					{
					case ' ':
						pixel = t->core.background_pixel;
						break;
					case 'W':
						pixel = t->tree.pixColors[white];
						break;
					case 'K':
						pixel = t->tree.pixColors[black];
						break;
					case 'Y':
						pixel = t->tree.pixColors[yellow];
						break;
					case 'G':
						pixel = t->tree.pixColors[gray];
						break;
					}
				XPutPixel(image, x, y, pixel);
				}
		if (image)
			XPutImage(dpy, pixmap, t->grid.gc, image, 0, 0, 0, 0, 16, 16);
		if (i == 0)
			t->tree.filePixmap = pixmap;
		else if (i == 1)
			t->tree.folderPixmap = pixmap;
		else
			t->tree.folderOpenPixmap = pixmap;
		}
	if (image)
		XDestroyImage(image);
	t->tree.defaultPixmapsCreated = 1;
	}

static XmLTreeWidget
WidgetToTree(Widget w,
             char *funcname)
	{
	char buf[256];

	if (!XmLIsTree(w))
		{
		sprintf(buf, "%s - widget not an XmLTree", funcname);
		XmLWarning(w, buf);
		return 0;
		}
	return (XmLTreeWidget)w;
	}

/*
   Public Functions
*/

Widget
XmLCreateTree(Widget parent,
              char *name,
              ArgList arglist,
              Cardinal argcount)
	{
	return XtCreateWidget(name, xmlTreeWidgetClass, parent,
		arglist, argcount);
	}

void
XmLTreeAddRow(Widget w,
              int level,
              Boolean expands,
              Boolean isExpanded,
              int position,
              Pixmap pixmap,
              Pixmap pixmask,
              XmString string)
	{
	XmLTreeRowDefinition row;

	row.level = level;
	row.expands = expands;
	row.isExpanded = isExpanded;
	row.pixmap = pixmap;
	row.pixmask = pixmask;
	row.string = string;
	XmLTreeAddRows(w, &row, 1, position);
	}

void 
XmLTreeAddRows(Widget w,
                XmLTreeRowDefinition *rows,
                int count,
                int position)
	{
	XmLTreeWidget t;
	XmLTreeRow row;
	int i, level;
	unsigned char layoutFrozen;

	t = WidgetToTree(w, "XmLTreeAddRows()");
	if (!t || count <= 0)
		return;
	if (position < 0 || position > t->grid.rowCount)
		position = t->grid.rowCount;
	layoutFrozen = t->grid.layoutFrozen;
	if (layoutFrozen == False)
		XtVaSetValues(w,
			XmNlayoutFrozen, True,
			NULL);
	XmLGridAddRows(w, XmCONTENT, position, count);
	for (i = 0; i < count; i++)
		{
		row = (XmLTreeRow)XmLGridGetRow(w, XmCONTENT, position + i);
		if (!row)
			continue;
		level = rows[i].level;
		if (level < 0)
			level = 0;
		row->tree.level = level;
		row->tree.expands = rows[i].expands;
		row->tree.isExpanded = rows[i].isExpanded;

		XtVaSetValues(w,
			XmNrow, position + i,
			XmNcolumn, 0,
			XmNcellString, rows[i].string,
			XmNcellPixmap, rows[i].pixmap,
			XmNcellPixmapMask, rows[i].pixmask,
			NULL);
		}
	if (layoutFrozen == False)
		XtVaSetValues(w,
			XmNlayoutFrozen, False,
			NULL);
	}
