/*
 * tkClock.c --
 *	This is a sample Tk widget for
 *	Practical Programming in Tcl and Tk
 *
 * Copyright (c) 1995 Prentice Hall
 * Copyright (c) 1995 Xerox Corporation
 */

/*
 * Include the standard Tk header files.
 */
#include "tkPort.h"
#include "tk.h"

/*
 * The Clock typedef is a data structure that defines one instance
 * of the widget.  There might be many instances in one interface.
 */
typedef struct {
	Tk_Window tkwin;	/* The window for the widget */
	Display *display;	/* X's handle on the display */
	Tcl_Interp *interp;	/* The interpreter of the widget */
	/*
	 * Clock-specific attributes.
	 */
	int borderWidth;	/* Size of 3-D border */
	int relief;		/* Style of 3-D border */
	Tk_3DBorder background;	/* Color info for border & background */
	XColor *foreground;	/* Color for the text */

	XColor *highlight;	/* Color info for the highlight */
	int highlightWidth;	/* Thickness of highlight rim */

	XFontStruct *fontPtr;	/* Font info for the text */
	char *format;		/* Format for the clock display */
	/*
	 * Graphic contexts and other support.
	 */
	GC highlightGC;		/* Graphics context for the highlight */
	GC textGC;		/* Graphics context for the text display */
	Tk_TimerToken token;	/* Handle on periodic callbacks */
	char *clock;		/* Pointer to the clock string */
	int numChars;		/* in the text */
	int textWidth;		/* in pixels */
	int textHeight;		/* in pixels */
	int flags;		/* Flags defined below */
} Clock;

/*
 * Flag bits.
 *	REDRAW_PENDING  - a call to ClockDisplay has been queued already.
 *	GOT_FOCUS	- the input focus is directed at the clock.
 *	TICKING		- periodic callback is installed.
 */
#define REDRAW_PENDING	0x1
#define GOT_FOCUS	0x2
#define TICKING		0x4

/*
 * These configuration specs are used to automatically process
 * arguments to the command that creates a Clock and to the
 * configure operation that changes a widget.  They define the
 * command line flag, its type, its resource name, its class name,
 * and its default value.  Tk_Offset is a macro that gets the
 * address of the structure member so the Clock struct can
 * be modified directly.
 */

static Tk_ConfigSpec configSpecs[] = {
	{TK_CONFIG_BORDER, "-background", "background", "Background",
		"light blue", Tk_Offset(Clock, background), TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-background", "background", "Background",
		"white", Tk_Offset(Clock, background), TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
		(char *) NULL, 0, 0},

	{TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
		(char *) NULL, 0, 0},
	{TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
		"2", Tk_Offset(Clock, borderWidth), 0},
	{TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
		"ridge", Tk_Offset(Clock, relief), 0},

	{TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
		"black", Tk_Offset(Clock, foreground), 0},
	{TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
		(char *) NULL, 0, 0},

	{TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
		"red", Tk_Offset(Clock, highlight),
		TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
		"black", Tk_Offset(Clock, highlight),
		TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
		"HighlightThickness",
		"2", Tk_Offset(Clock, highlightWidth),
		0},

	{TK_CONFIG_STRING, "-format", "format", "Format",
		"%H:%M:%S", Tk_Offset(Clock, format), 0},
	{TK_CONFIG_FONT, "-font", "font", "Font",
		"*courier-medium-r-normal-*-18-*", Tk_Offset(Clock, fontPtr),
		0},

	{TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
		(char *) NULL, 0, 0}
};

/*
 * Forward declarations.  _ANSI_ARGS_ is a macro that checks to
 * see if we are being compiled with an ANSI C compiler before
 * it puts the parameter types into the declaration.
 */
static int		ClockConfigure _ANSI_ARGS_((Tcl_Interp *interp,
			    Clock *clockPtr, int argc, char **argv,
			    int flags));
static void		ClockDestroy _ANSI_ARGS_((ClientData clientData));
static void		ClockDisplay _ANSI_ARGS_((ClientData clientData));
static void		ClockEventProc _ANSI_ARGS_((ClientData clientData,
			    XEvent *eventPtr));
static int		ClockInstanceCmd _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *, int argc, char **argv));
static void		ComputeGeometry _ANSI_ARGS_((Clock *clockPtr));

/*
 * ClockCmd --
 *	This is the command procedure for the "mywidget" Tcl command.
 *	It creates an instance of the widget.  As a side effect, a new
 *	Tcl command is created that operates on the new instance.
 *	This returns TCL_OK or TCL_ERROR if the widget could not be
 *	created.
 */
int
ClockCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				 * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
	Tk_Window main = (Tk_Window) clientData;
	Clock *clockPtr;
	Tk_Window tkwin;
    
	if (argc < 2) {
		Tcl_AppendResult(interp, "wrong # args:  should be \"",
			    argv[0], " pathName ?options?\"", (char *) NULL);
		return TCL_ERROR;
	}

	tkwin = Tk_CreateWindowFromPath(interp, main, argv[1], (char *) NULL);
	if (tkwin == NULL) {
		return TCL_ERROR;
	}
	Tk_SetClass(tkwin, "Clock");
    
	/*
	 * Allocate and initialize the widget record.
	 */

	clockPtr = (Clock *) ckalloc(sizeof(Clock));
	clockPtr->tkwin = tkwin;
	clockPtr->display = Tk_Display(tkwin);
	clockPtr->interp = interp;
	clockPtr->borderWidth = 0;
	clockPtr->highlightWidth = 0;
	clockPtr->relief = TK_RELIEF_FLAT;
	clockPtr->background = NULL;
	clockPtr->foreground = NULL;
	clockPtr->highlight = NULL;
	clockPtr->fontPtr = NULL;
	clockPtr->textGC = None;
	clockPtr->highlightGC = None;
	clockPtr->token = NULL;
	clockPtr->clock = NULL;
	clockPtr->numChars = 0;
	clockPtr->textWidth = 0;
	clockPtr->textHeight = 0;
	clockPtr->flags = 0;

	/*
	 * Register a handler for when the window is
	 * exposed or resized.
	 */
	Tk_CreateEventHandler(clockPtr->tkwin,
		ExposureMask|StructureNotifyMask|FocusChangeMask,
		ClockEventProc, (ClientData) clockPtr);
	/*
	 * Create a Tcl command that operates on the widget.
	 */
	Tcl_CreateCommand(interp, Tk_PathName(clockPtr->tkwin),
		ClockInstanceCmd,
		(ClientData) clockPtr, (void (*)()) NULL);
	/*
	 * Parse the command line arguments.
	 */
	if (ClockConfigure(interp, clockPtr, argc-2, argv+2, 0) != TCL_OK) {
		Tk_DestroyWindow(clockPtr->tkwin);
		return TCL_ERROR;
	}

	interp->result = Tk_PathName(clockPtr->tkwin);
	return TCL_OK;
}

/*
 * ClockInstanceCmd --
 *	This procedure implements the operations on an instance
 * 	of the widget.   It is a command procedure for the Tcl
 *	command that is the same as the name of the widget.
 *	This returns a standard Tcl result.
 */
static int
ClockInstanceCmd(clientData, interp, argc, argv)
	ClientData clientData;	/* A pointer to a Clock struct */
	Tcl_Interp *interp;	/* The interpreter */
	int argc;		/* The number of arguments */
	char *argv[];		/* The command line arguments */
{
	Clock *clockPtr = (Clock *)clientData;
	int result = TCL_OK;
	char c;
	int length;

	if (argc < 2) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			argv[0], " option ?arg arg ...?\"", (char *) NULL);
		return TCL_ERROR;
	}
	c = argv[1][0];
	length = strlen(argv[1]);
	if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
			&& (length >= 2)) {
		if (argc != 3) {
			Tcl_AppendResult(interp, "wrong # args: should be \"",
				argv[0], " cget option\"",
				(char *) NULL);
			return TCL_ERROR;
		}
		result = Tk_ConfigureValue(interp, clockPtr->tkwin, configSpecs,
			(char *) clockPtr, argv[2], 0);
	} else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
			&& (length >= 2)) {
		if (argc == 2) {
			/*
			 * Return info about all attributes.
			 */
			result = Tk_ConfigureInfo(interp, clockPtr->tkwin, configSpecs,
				(char *) clockPtr, (char *) NULL, 0);
		} else if (argc == 3) {
			/*
			 * Return info about one attribute, like cget.
			 */
			result = Tk_ConfigureInfo(interp, clockPtr->tkwin, configSpecs,
				(char *) clockPtr, argv[2], 0);
		} else {
			/*
			 * Change one or more attributes.
			 */
			result = ClockConfigure(interp, clockPtr, argc-2, argv+2,
				TK_CONFIG_ARGV_ONLY);
		}
	} else {
		Tcl_AppendResult(interp, "bad option \"", argv[1],
			"\":  must be cget, configure, position, or size",
			(char *) NULL);
		return TCL_ERROR;
	}
	return result;
}
/*
 * ClockConfigure --
 *	This processes command line arguments defined in the
 *	TkConfigSpecs array.  This is called when a widget is first
 *	created or when its configuration is changed via the
 *	configure operation.
 *	It returns a standard Tcl result.
 */
static int
ClockConfigure(interp, clockPtr, argc, argv, flags)
	Tcl_Interp *interp;	/* Needed for return values and errors */
	Clock *clockPtr;	/* The per-instance data structure */
	int argc;		/* Number of valid entries in argv */
	char *argv[];		/* The command line arguments */
	int flags;		/* Tk_ConfigureClock flags */
{
	XGCValues gcValues;
	GC newGC;

	/*
	 * Tk_ConfigureWidget does all the work of parsing the
	 * arguments and looking for things in the resource database.
	 */
	if (Tk_ConfigureWidget(interp, clockPtr->tkwin, configSpecs,
			argc, argv, (char *) clockPtr, flags) != TCL_OK) {
		return TCL_ERROR;
	}
	/*
	 * Give the widget a default background so it doesn't get a random
	 * background between the time it is initially displayed by the
	 * X server and we get a chance to paint it.
	 */
	Tk_SetWindowBackground(clockPtr->tkwin,
		Tk_3DBorderColor(clockPtr->background)->pixel);
	/*
	 * Set up the graphics contexts to display the widget.  The
	 * highlight has different colors than the main area of the widget,
	 * so it requires a different context.  These contexts are all used
	 * to draw off-screen pixmaps, so turn off exposure notifications.
	 */
	gcValues.graphics_exposures = False;
	gcValues.background = clockPtr->highlight->pixel;
	newGC = Tk_GetGC(clockPtr->tkwin,
		GCBackground|GCGraphicsExposures, &gcValues);
	if (clockPtr->highlightGC != None) {
	    Tk_FreeGC(clockPtr->display, clockPtr->highlightGC);
	}
	clockPtr->highlightGC = newGC;

	gcValues.background = Tk_3DBorderColor(clockPtr->background)->pixel;
	gcValues.foreground = clockPtr->foreground->pixel;
	gcValues.font = clockPtr->fontPtr->fid;
	newGC = Tk_GetGC(clockPtr->tkwin,
		GCBackground|GCForeground|GCFont|GCGraphicsExposures,
		&gcValues);
	if (clockPtr->textGC != None) {
	    Tk_FreeGC(clockPtr->display, clockPtr->textGC);
	}
	clockPtr->textGC = newGC;

	/*
	 * Determine how big the widget wants to be and ask for that
	 * much size.  The geometry manager might gives us more or less.
	 */
	ComputeGeometry(clockPtr);

	/*
	 * Set up a call to display ourself.
	 */
	if ((clockPtr->tkwin != NULL) && Tk_IsMapped(clockPtr->tkwin)
	    && !(clockPtr->flags & REDRAW_PENDING)) {
		Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr);
		clockPtr->flags |= REDRAW_PENDING;
	}
	return TCL_OK;
}
/*
 * ComputeGeometry --
 *	Determine how big the clock display will be based on its
 *	various configuration parameters.
 *	This calls Tk_GeometryRequest as a side effect.
 */
static void
ComputeGeometry(Clock *clockPtr)
{
	int width, height;
	struct tm *tmPtr;	/* Time info split into fields */
	struct timeval tv;	/* BSD-style time value */
	int offset = clockPtr->highlightWidth + clockPtr->borderWidth
		+ 2;	/* This should be padX attribute */
	char clock[1000];

	/*
	 * Get the time and format it to see how big it will be.
	 */
	gettimeofday(&tv, NULL);
	tmPtr = localtime(&tv.tv_sec);
	strftime(clock, 1000, clockPtr->format, tmPtr);
	if (clockPtr->clock != NULL) {
	    ckfree(clockPtr->clock);
	}
	clockPtr->clock = ckalloc(1+strlen(clock));
	clockPtr->numChars = strlen(clock);

	TkComputeTextGeometry(clockPtr->fontPtr, clock,
		clockPtr->numChars, 0, &clockPtr->textWidth,
		&clockPtr->textHeight);
	width = clockPtr->textWidth + 2*offset;
	height = clockPtr->textHeight + 2*offset;

	Tk_GeometryRequest(clockPtr->tkwin, width, height);
	Tk_SetInternalBorder(clockPtr->tkwin, offset);
}

/*
 * ClockDisplay --
 *	This is called to display the clock.
 */
static void
ClockDisplay(ClientData clientData)
{
	Clock *clockPtr = (Clock *)clientData;
	GC gc;
	Tk_Window tkwin = clockPtr->tkwin;
	Pixmap pixmap;
	int offset, x, y;
	struct tm *tmPtr;	/* Time info split into fields */
	struct timeval tv;	/* BSD-style time value */

	/*
	 * Make sure the button still exists
	 * and is mapped onto the display before painting.
	 */
	clockPtr->flags &= ~(REDRAW_PENDING|TICKING);
	if ((clockPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
		return;
	}
	/*
	 * Format the time into a string.
	 * localtime chops up the time into fields.
	 * strftime formats the fields into a string.
	 */
	gettimeofday(&tv, NULL);
	tmPtr = localtime(&tv.tv_sec);
	strftime(clockPtr->clock, clockPtr->numChars+1, clockPtr->format, tmPtr);
	/*
	 * To avoid flicker when the display is updated, the new
	 * image is painted in an offscreen pixmap and then copied
	 * onto the display in one operation.  Allocate the
	 * pixmap and paint its background.
	 */
	pixmap = Tk_GetPixmap(clockPtr->display, Tk_WindowId(tkwin),
		Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
	Tk_Fill3DRectangle(tkwin, pixmap, clockPtr->background,
		0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);

	/*
	 * Paint the text first.
	 */
	offset = clockPtr->highlightWidth + clockPtr->borderWidth;
	x = (Tk_Width(tkwin) - clockPtr->textWidth)/2;
	if (x < 0) {
	    x = 0;
	}
	y = (Tk_Height(tkwin) - clockPtr->textHeight)/2;
	if (y < 0) {
	    y = 0;
	}

	TkDisplayText(clockPtr->display, pixmap, clockPtr->fontPtr,
		clockPtr->clock, clockPtr->numChars,
		x, y, clockPtr->textWidth,
		TK_JUSTIFY_CENTER, -1, clockPtr->textGC);

	/*
	 * Display the borders, so they overright any of the
	 * text that extends to the edge of the display.
	 */
	if (clockPtr->relief != TK_RELIEF_FLAT) {
		Tk_Draw3DRectangle(tkwin, pixmap,
			clockPtr->background,
			clockPtr->highlightWidth, clockPtr->highlightWidth,
			Tk_Width(tkwin) - 2*clockPtr->highlightWidth,
			Tk_Height(tkwin) - 2*clockPtr->highlightWidth,
			clockPtr->borderWidth, clockPtr->relief);
	}
	if (clockPtr->highlightWidth != 0) {
		GC gc;
	
		if (clockPtr->flags & GOT_FOCUS) {
			gc = clockPtr->highlightGC;
		} else {
			gc = Tk_3DBorderGC(tkwin,
				clockPtr->background, TK_3D_FLAT_GC);
		}
		Tk_DrawFocusHighlight(tkwin, gc, clockPtr->highlightWidth, pixmap);
	}
    
	/*
	 * Copy the information from the off-screen pixmap onto the screen,
	 * then delete the pixmap.
	 */
    
	XCopyArea(clockPtr->display, pixmap, Tk_WindowId(tkwin),
	    clockPtr->textGC, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, 0);
	Tk_FreePixmap(clockPtr->display, pixmap);

	/*
	 * Queue another call to ourselves.  The rate at which this
	 * is done could be optimized.
	 */
	clockPtr->token = Tk_CreateTimerHandler(1000, ClockDisplay,
		(ClientData)clockPtr);
	clockPtr->flags |= TICKING;
}
/*
 * ClockEventProc --
 *	Handle expose and resize events.
 *	This routine is essentially the	same for all types of widgets.
 */
static void
ClockEventProc(ClientData clientData, XEvent *eventPtr)
{
	Clock *clockPtr = (Clock *) clientData;
	if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
		goto redraw;
	} else if (eventPtr->type == DestroyNotify) {
		Tcl_DeleteCommand(clockPtr->interp, Tk_PathName(clockPtr->tkwin));
		clockPtr->tkwin = NULL;
		if (clockPtr->flags & REDRAW_PENDING) {
			Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr);
			clockPtr->flags &= ~REDRAW_PENDING;
		}
		if (clockPtr->flags & TICKING) {
			Tk_DeleteTimerHandler(clockPtr->token);
			clockPtr->flags &= ~TICKING;
		}
		Tk_EventuallyFree((ClientData) clockPtr, ClockDestroy);
	} else if (eventPtr->type == FocusIn) {
		if (eventPtr->xfocus.detail != NotifyPointer) {
			clockPtr->flags |= GOT_FOCUS;
			if (clockPtr->highlightWidth > 0) {
				goto redraw;
			}
		}
	} else if (eventPtr->type == FocusOut) {
		if (eventPtr->xfocus.detail != NotifyPointer) {
			clockPtr->flags &= ~GOT_FOCUS;
			if (clockPtr->highlightWidth > 0) {
				goto redraw;
			}
		}
	}
	return;
    
	redraw:
	if ((clockPtr->tkwin != NULL) && !(clockPtr->flags & REDRAW_PENDING)) {
		Tk_DoWhenIdle(ClockDisplay, (ClientData) clockPtr);
		clockPtr->flags |= REDRAW_PENDING;
	}
}
/*
 * ClockDestroy --
 *	Clean up after a clock is destroyed.
 *	This frees all the resources associated with the clock.
 */
static void
ClockDestroy(clientData)
    ClientData clientData;		/* Info about entry widget. */
{
	register Clock *clockPtr = (Clock *) clientData;
    
	/*
	 * Free up all the stuff that requires special handling, then
	 * let Tk_FreeOptions handle all the standard option-related
	 * stuff.
	 */

	if (clockPtr->highlightGC != None) {
		Tk_FreeGC(clockPtr->display, clockPtr->highlightGC);
	}
	if (clockPtr->textGC != None) {
		Tk_FreeGC(clockPtr->display, clockPtr->textGC);
	}
	if (clockPtr->clock != NULL) {
		ckfree(clockPtr->clock);
	}
	if (clockPtr->flags & TICKING) {
		Tk_DeleteTimerHandler(clockPtr->token);
	}
	if (clockPtr->flags & REDRAW_PENDING) {
		Tk_CancelIdleCall(ClockDisplay, (ClientData) clockPtr);
	}
	/*
	 * This frees up colors and fonts and any allocated storage
	 * associated with the widget attributes.
	 */
	Tk_FreeOptions(configSpecs, (char *) clockPtr, clockPtr->display, 0);
	ckfree((char *) clockPtr);
}

