/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Microline Widget Library, originally made available under the NPL by Neuron Data <http://www.neurondata.com>.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * In addition, as a special exception to the GNU GPL, the copyright holders
 * give permission to link the code of this program with the Motif and Open
 * Motif libraries (or with modified versions of these that use the same
 * license), and distribute linked combinations including the two. You
 * must obey the GNU General Public License in all respects for all of
 * the code used other than linking with Motif/Open Motif. If you modify
 * this file, you may extend this exception to your version of the file,
 * but you are not obligated to do so. If you do not wish to do so,
 * delete this exception statement from your version.
 *
 * ***** END LICENSE BLOCK ***** */

#include "ProgressP.h"
#include <stdio.h>
#include <sys/time.h>

static void ClassInitialize(void);
static void Initialize(Widget , Widget, ArgList, Cardinal *);
static void Resize(Widget);
static void Destroy(Widget);
static void Realize(Widget w, XtValueMask *valueMask,
	XSetWindowAttributes *attr);
static void Redisplay(Widget, XEvent *, Region);
static Boolean SetValues(Widget, Widget, Widget, ArgList, Cardinal *);
static void CopyFontList(XmLProgressWidget p);
static void TimeStr(char *, int);
static void DrawBarMeter(XmLProgressWidget p, XRectangle *rect);
static void DrawBoxesMeter(XmLProgressWidget p, XRectangle *rect);
static void DrawString(XmLProgressWidget, XmString, int, int,
	int, XRectangle *, XRectangle *);
static Boolean CvtStringToMeterStyle(Display *dpy, XrmValuePtr args,
	Cardinal *numArgs, XrmValuePtr fromVal, XrmValuePtr toVal,
	XtPointer *data);

static XtResource resources[] = 
	{
		{
		XmNcompleteValue, XmCCompleteValue,
		XtRInt, sizeof(int),
		XtOffset(XmLProgressWidget, progress.completeValue),
		XtRImmediate, (caddr_t)100
		},
		{
		XmNnumBoxes, XmCNumBoxes,
		XtRInt, sizeof(int),
		XtOffset(XmLProgressWidget, progress.numBoxes),
		XtRImmediate, (caddr_t)10
		},
		{
		XmNvalue, XmCValue,
		XtRInt, sizeof(int),
		XtOffset(XmLProgressWidget, progress.value),
		XtRImmediate, (caddr_t)0
		},
		{
		XmNfontList, XmCFontList,
		XmRFontList, sizeof(XmFontList),
		XtOffset(XmLProgressWidget, progress.fontList),
		XmRImmediate, (XtPointer)0,
		},
		{
		XmNmeterStyle, XmCMeterStyle,
		XmRMeterStyle, sizeof(unsigned char),
		XtOffset(XmLProgressWidget, progress.meterStyle),
		XmRImmediate, (XtPointer)XmMETER_BAR,
		},
		{
		XmNshowTime, XmCShowTime,
		XmRBoolean, sizeof(Boolean),
		XtOffset(XmLProgressWidget, progress.showTime),
		XmRImmediate, (XtPointer)False
		},
		{
		XmNshowPercentage, XmCShowPercentage,
		XmRBoolean, sizeof(Boolean),
		XtOffset(XmLProgressWidget, progress.showPercentage),
		XmRImmediate, (XtPointer)True
		}
	};

XmLProgressClassRec xmlProgressClassRec = 
	{
		{ /* Core */
		(WidgetClass)&xmPrimitiveClassRec,      /* superclass */
		"XmLProgress",                          /* class_name */
		sizeof(XmLProgressRec),                 /* widget_size */
		ClassInitialize,                        /* class_initialize */
		NULL,                                   /* class_part_initialize */
		FALSE,                                  /* class_inited */
		(XtInitProc)Initialize,                 /* initialize */
		NULL,                                   /* initialize_hook */
		(XtRealizeProc)Realize,                 /* realize */
		NULL,                                   /* actions */
		0,                                      /* num_actions */
		resources,                              /* resources */
		XtNumber(resources),                    /* num_resources */
		NULLQUARK,                              /* xrm_class */
		TRUE,                                   /* compress_motion */
		FALSE,                                  /* compress_exposure */
		TRUE,                                   /* compress_enterleave */
		TRUE,                                   /* visible_interest */
		(XtWidgetProc)Destroy,                  /* destroy */
		(XtWidgetProc)Resize,                   /* resize */
		(XtExposeProc)Redisplay,                /* expose */
		(XtSetValuesFunc)SetValues,             /* set_values */
		NULL,                                   /* set_values_hook */
		XtInheritSetValuesAlmost,               /* set_values_almost */
		NULL,                                   /* get_values_hook */
		NULL,                                   /* accept_focus */
		XtVersion,                              /* version */
		NULL,                                   /* callback_private */
		XtInheritTranslations,                  /* tm_table */
		NULL,                                   /* query_geometry */
		NULL,                                   /* display_accelerator */
		NULL,                                   /* extension */
		},
		{ /* Primitive */
		(XtWidgetProc)_XtInherit,               /* border_highlight */
		(XtWidgetProc)_XtInherit,               /* border_unhighlight */
		XtInheritTranslations,                  /* translations */
		NULL,                                   /* arm_and_activate */
		NULL,                                   /* syn_resources */
		0,                                      /* num_syn_resources */
		NULL,                                   /* extension */
		},
		{ /* Progress */
		0,                                      /* unused */
		}
	};

WidgetClass xmlProgressWidgetClass = (WidgetClass)&xmlProgressClassRec;

static void 
ClassInitialize(void)
	{
	XmLInitialize();

	XtSetTypeConverter(XmRString, XmRMeterStyle, CvtStringToMeterStyle,
		0, 0, XtCacheNone, 0);
	}

static void
Initialize(Widget reqW,
	   Widget newW,
	   ArgList args,
	   Cardinal *narg)
	{
	XmLProgressWidget p;

	p = (XmLProgressWidget)newW;

	if (!p->core.width)
		p->core.width = 200;
	if (!p->core.height)
		p->core.height = 30;

	p->progress.gc = 0;
	p->progress.startTime = time(0);
	CopyFontList(p);
	if (p->progress.completeValue < 1)
		{
		XmLWarning(newW, "Initialize() - complete value can't be < 1");
		p->progress.completeValue = 1;
		}
	if (p->progress.numBoxes < 1)
		{
		XmLWarning(newW, "Initialize() - number of boxes can't be < 1");
		p->progress.numBoxes = 1;
		}
	if (p->progress.value < 0)
		{
		XmLWarning(newW, "Initialize() - value can't be < 0");
		p->progress.value = 0;
		}
	if (p->progress.value > p->progress.completeValue)
		{
		XmLWarning(newW, "Initialize() - value can't be > completeValue");
		p->progress.value = p->progress.completeValue;
		}
	XtVaSetValues(newW, XmNtraversalOn, False, NULL);
	}

static void
Resize(Widget w)
	{
	Display *dpy;
	Window win;

	if (!XtIsRealized(w))
		return;
	dpy = XtDisplay(w);
	win = XtWindow(w);
	XClearArea(dpy, win, 0, 0, 0, 0, True);
	}

static void
Destroy(Widget w)
	{
	Display *dpy;
	XmLProgressWidget p;

	p = (XmLProgressWidget)w;
	dpy = XtDisplay(w);
	if (p->progress.gc)
		{
		XFreeGC(dpy, p->progress.gc);
		XFreeFont(dpy, p->progress.fallbackFont);
		}
	XmFontListFree(p->progress.fontList);
	}

static void
Realize(Widget w,
	XtValueMask *valueMask,
	XSetWindowAttributes *attr)
	{
	XmLProgressWidget p;
	Display *dpy;
	WidgetClass superClass;
	XtRealizeProc realize;
	XGCValues values;
	/*	XtGCMask mask;*/

	p = (XmLProgressWidget)w;
	dpy = XtDisplay(p);
	superClass = xmlProgressWidgetClass->core_class.superclass;
	realize = superClass->core_class.realize;
	(*realize)(w, valueMask, attr);

	if (!p->progress.gc)
		{
		p->progress.fallbackFont = XLoadQueryFont(dpy, "fixed");
		values.font = p->progress.fallbackFont->fid;
		p->progress.gc = XCreateGC(dpy, XtWindow(p), GCFont, &values);
		}
	}

static void
Redisplay(Widget w,
	  XEvent *event,
	  Region region)
	{
	XmLProgressWidget p;
	Display *dpy;
	Window win;
	XRectangle rect;
	int st;

	if (!XtIsRealized(w) || !w->core.visible)
		return;

	p = (XmLProgressWidget)w;
	dpy = XtDisplay(w);
	win = XtWindow(w);
	st = p->primitive.shadow_thickness;
	rect.x = st;
	rect.y = st;
	rect.width = p->core.width - st * 2;
	rect.height = p->core.height - st * 2;

	if (p->progress.meterStyle == XmMETER_BAR)
		DrawBarMeter(p, &rect);
	else if (p->progress.meterStyle == XmMETER_BOXES)
		DrawBoxesMeter(p, &rect);

#ifdef MOTIF11
	_XmDrawShadow(dpy, win,
		p->primitive.bottom_shadow_GC,
		p->primitive.top_shadow_GC,
		p->primitive.shadow_thickness,
		0, 0, p->core.width, p->core.height);
#else
	_XmDrawShadows(dpy, win,
		p->primitive.top_shadow_GC,
		p->primitive.bottom_shadow_GC,
		0, 0, p->core.width, p->core.height,
		p->primitive.shadow_thickness,
		XmSHADOW_IN);
#endif
	}

static void
DrawBoxesMeter(XmLProgressWidget p,
	       XRectangle *rect)
	{
	Display *dpy;
	Window win;
	int i, j, st, nb, x1, x2;

	dpy = XtDisplay(p);
	win = XtWindow(p);
	st = p->primitive.shadow_thickness;
	nb = p->progress.numBoxes;
	if (nb * st * 2 > (int)rect->width)
		return;

	if (p->progress.completeValue)
		j = (int)((long)nb * (long)p->progress.value /
			(long)p->progress.completeValue);
	else
		j = 0;
	x2 = 0;
	for (i = 0; i < nb; i++)
		{
		if (i < j)
			XSetForeground(dpy, p->progress.gc, p->primitive.foreground); 
		else
			XSetForeground(dpy, p->progress.gc, p->core.background_pixel);
		x1 = x2;
		if (i == nb - 1)
			x2 = rect->width;
		else
			x2 = ((int)rect->width * (i + 1)) / nb;
		XFillRectangle(dpy, win, p->progress.gc,
			rect->x + x1 + st, rect->y + st,
			x2 - x1 - st * 2, rect->height - st * 2);
#ifdef MOTIF11
		_XmDrawShadow(dpy, win,
			p->primitive.bottom_shadow_GC,
			p->primitive.top_shadow_GC,
			p->primitive.shadow_thickness,
			rect->x + x1, rect->y,
			x2 - x1, rect->height);
#else
		_XmDrawShadows(dpy, win,
			p->primitive.top_shadow_GC,
			p->primitive.bottom_shadow_GC,
			rect->x + x1, rect->y,
			x2 - x1, rect->height,
			p->primitive.shadow_thickness,
			XmSHADOW_IN);
#endif
		}
	}

static void
DrawBarMeter(XmLProgressWidget p,
	     XRectangle *rect)
	{
	Display *dpy;
	Window win;
	int timeLeft, timeSoFar;
	time_t currentTime;
	XmString str;
	Dimension strWidth, strHeight;
	XRectangle lRect, rRect;
	int percent;
	char c[10];
	long l;

	dpy = XtDisplay(p);
	win = XtWindow(p);

	/* Left Rect */
	if (p->progress.completeValue)
		l = (long)rect->width * (long)p->progress.value /
			(long)p->progress.completeValue;
	else
		l = 0;
	lRect.x = rect->x;
	lRect.y = rect->y;
	lRect.width = (Dimension)l;
	lRect.height = rect->height;
	XSetForeground(dpy, p->progress.gc, p->primitive.foreground); 
	XFillRectangle(dpy, win, p->progress.gc, lRect.x, lRect.y,
		lRect.width, lRect.height);

	/* Right Rect */
	rRect.x = rect->x + (int)l;
	rRect.y = rect->y;
	rRect.width = rect->width - (Dimension)l;
	rRect.height = rect->height;
	XSetForeground(dpy, p->progress.gc, p->core.background_pixel);
	XFillRectangle(dpy, win, p->progress.gc, rRect.x, rRect.y,
		rRect.width, rRect.height);

	if (p->progress.completeValue)
		percent = (int)(((long)p->progress.value * 100) /
			(long)p->progress.completeValue);
	else
		percent = 0;

	/* percent complete */
	sprintf(c, "%d%c", percent, '%');
	str = XmStringCreateSimple(c);
	XmStringExtent(p->progress.fontList, str, &strWidth, &strHeight);
	if (p->progress.showPercentage)
		DrawString(p, str, rect->x + rect->width / 2 - (int)strWidth / 2,
			rect->y + rect->height / 2 - (int)strHeight / 2, strWidth,
			&lRect, &rRect);
	XmStringFree(str);

	/* Left Time */
	currentTime = time(0);
	timeSoFar = (int)(currentTime - p->progress.startTime);
	if (p->progress.showTime && p->progress.value &&
		p->progress.value != p->progress.completeValue && timeSoFar)
		{
		TimeStr(c, timeSoFar);
		str = XmStringCreateSimple(c);
		XmStringExtent(p->progress.fontList, str,
			&strWidth, &strHeight);
		DrawString(p, str, rect->x + 5, rect->y + rect->height / 2 -
			(int)strHeight / 2, strWidth, &lRect, &rRect);
		XmStringFree(str);
		}

	/* Right Time */
	timeLeft = 0;
	if (percent)
		timeLeft = (timeSoFar * 100 / percent) - timeSoFar;
	if (p->progress.showTime && percent && percent != 100 && timeLeft)
		{
		TimeStr(c, timeLeft);
		str = XmStringCreateSimple(c);
		XmStringExtent(p->progress.fontList, str,
			&strWidth, &strHeight);
		DrawString(p, str, rect->x + rect->width - strWidth - 5,
			rect->y + rect->height / 2 - (int)strHeight / 2,
			strWidth, &lRect, &rRect);
		XmStringFree(str);
		}
	}

static void
DrawString(XmLProgressWidget p,
	   XmString str,
	   int x,
	   int y,
	   int strWidth,
	   XRectangle *lRect,
	   XRectangle *rRect)
	{
	Display *dpy;
	Window win;

	dpy = XtDisplay(p);
	win = XtWindow(p);
	if (lRect->width && lRect->height)
		{
		XSetForeground(dpy, p->progress.gc, p->core.background_pixel);
		XSetClipRectangles(dpy, p->progress.gc, 0, 0, lRect, 1, Unsorted);
		XmStringDraw(dpy, win, p->progress.fontList, str,
			p->progress.gc, x, y, strWidth, XmALIGNMENT_BEGINNING,
			XmSTRING_DIRECTION_L_TO_R, 0);
		XSetClipMask(dpy, p->progress.gc, None);
		}
	if (rRect->width && rRect->height)
		{
		XSetForeground(dpy, p->progress.gc, p->primitive.foreground);
		XSetClipRectangles(dpy, p->progress.gc, 0, 0, rRect, 1, Unsorted);
		XmStringDraw(dpy, win, p->progress.fontList, str,
			p->progress.gc, x, y, strWidth, XmALIGNMENT_BEGINNING,
			XmSTRING_DIRECTION_L_TO_R, 0);
		XSetClipMask(dpy, p->progress.gc, None);
		}
	}

static void
TimeStr(char *c,
	int seconds)
	{
	int h, m, s;

	s = seconds;
	m = s / 60;
	s -= m * 60;
	h = m / 60;
	m -= h * 60;
	if (h > 99)
		h = 99;
	if (h > 0 && m < 10)
		sprintf(c, "%d:0%d hr", h, m);
	else if (h > 0)
		sprintf(c, "%d:%d hr", h, m);
	else if (m > 0 && s < 10)
		sprintf(c, "%d:0%d min", m, s);
	else if (m > 0)
		sprintf(c, "%d:%d min", m, s);
	else
		sprintf(c, "%d sec", s);
	}

static Boolean
SetValues(Widget curW,
	  Widget reqW,
	  Widget newW,
	  ArgList args,
	  Cardinal *narg)
	{
	XmLProgressWidget cur, p;
	XtAppContext app;

	cur = (XmLProgressWidget)curW;
	p = (XmLProgressWidget)newW;
	app = XtWidgetToApplicationContext(curW);
	if (p->progress.value == 0)
		p->progress.startTime = time(0);
	if (p->progress.completeValue < 1)
		{
		XmLWarning(newW, "SetValues() - complete value can't be < 1");
		p->progress.completeValue = 1;
		}
	if (p->progress.numBoxes < 1)
		{
		XmLWarning(newW, "SetValues() - number of boxes can't be < 1");
		p->progress.numBoxes = 1;
		}
	if (p->progress.value < 0)
		{
		XmLWarning(newW, "SetValues() - value can't be < 0");
		p->progress.value = 0;
		}
	if (p->progress.value > p->progress.completeValue)
		{
		XmLWarning(newW, "SetValues() - value can't be > completeValue");
		p->progress.value = p->progress.completeValue;
		}
	if (p->progress.fontList != cur->progress.fontList)
		{
		XmFontListFree(cur->progress.fontList);
		CopyFontList(p);
		}
	/* display changes immediately since we may be not get back
	   to XNextEvent if the calling application is computing */
	if (p->core.background_pixel != cur->core.background_pixel ||
		p->primitive.foreground != cur->primitive.foreground || 
		p->progress.value != cur->progress.value ||
		p->progress.completeValue != cur->progress.completeValue ||
		p->progress.fontList != cur->progress.fontList ||
		p->progress.showTime != cur->progress.showTime ||
		p->progress.showPercentage != cur->progress.showPercentage ||
		p->progress.meterStyle != cur->progress.meterStyle ||
		p->progress.numBoxes != cur->progress.numBoxes ||
		p->primitive.shadow_thickness != cur->primitive.shadow_thickness)
		{
		Redisplay(newW, 0, 0);
		XFlush(XtDisplay(newW));
		XmUpdateDisplay(newW);
		}
	return FALSE;
	}

static void
CopyFontList(XmLProgressWidget p)
	{ 
	if (!p->progress.fontList)
		p->progress.fontList = XmLFontListCopyDefault((Widget)p);
	else
		p->progress.fontList = XmFontListCopy(p->progress.fontList);
	if (!p->progress.fontList)
		XmLWarning((Widget)p, "- fatal error - font list NULL");
	}

static Boolean
CvtStringToMeterStyle(Display *dpy,
		      XrmValuePtr args,
		      Cardinal *narg,
		      XrmValuePtr fromVal,
		      XrmValuePtr toVal,
		      XtPointer *data)
	{
	static XmLStringToUCharMap map[] =
		{
		{ "METER_BAR", XmMETER_BAR },
		{ "METER_BOXES", XmMETER_BOXES },
		{ 0, 0 },
		};

	return XmLCvtStringToUChar(dpy, "XmRMeterStyle", map, fromVal, toVal);
	}

/*
   Public Functions
*/

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


