/* 
 * tkImgCmp.c --
 *
 *	This procedure implements images of type "compound" for Tix.
 *
 * Copyright (c) 1996, Expert Interface Technologies
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

#include <tixPort.h>
#include <tixInt.h>
#include <tixDef.h>

/*
 * ToDo: 
 *	- lineconfig and itemconfig command
 */

/*
 * The following data structure represents the master for a bitmap
 * image:
 */
typedef struct CmpMaster {
    Tk_ImageMaster tkMaster;	/* Tk's token for image master.  NULL means
				 * the image is being deleted. */
    Tcl_Interp *interp;		/* Interpreter for application that is
				 * using image. */
    Tcl_Command imageCmd;	/* Token for image command (used to delete
				 * it when the image goes away).  NULL means
				 * the image command has already been
				 * deleted. */
    Display * display;		/* Display of the the window associated with
				 * this image. We need to keep it
				 * because Tk_Display(CmpMaster.tkwin) may
				 * be invalid. */
    Tk_Window tkwin;		/* default options are taken from this window.
				 * If undefined, will use the main window
				 * of this application */
    int width, height;		/* Dimensions of image. */
    int padX, padY;
    struct CmpLine * lineHead;
    struct CmpLine * lineTail;

    /* Thde default options, etc */
    int borderWidth;		/* Width of 3-D borders. */
    Tk_3DBorder background;	/* Used for drawing background. */
    int relief;			/* Indicates whether window as a whole is
				 * raised, sunken, or flat. */

    TixFont font;		/* Information about text font.*/
    XColor *foreground;		/* Color for drawing text and bitmaps */
    GC gc;			/* default GC for drawing text. */

    int showBackground;		/* whether the background should be drawn */
    unsigned int changing;	/* is this image going to call Tk_ImageChanged
				 * in an idle event? */
    unsigned int isDeleted;
} CmpMaster;

#define TYPE_TEXT	0
#define TYPE_SPACE	1
#define TYPE_IMAGE	2
#define TYPE_BITMAP	3
#define TYPE_WIDGET	4

typedef struct CmpLine {
    struct CmpMaster *masterPtr;
    struct CmpLine * next;
    struct CmpItem * itemHead;
    struct CmpItem * itemTail;
    int padX, padY;
    Tk_Anchor anchor;
    int width, height;		/* Dimensions of this line. */

} CmpLine;

/* abstract type */

#define COMMON_MEMBERS \
    struct CmpLine * line; \
    struct CmpItem * next; \
    Tk_Anchor anchor; \
    char type; \
    int width; \
    int height; \
    int padX, padY

typedef struct CmpItem {
    COMMON_MEMBERS;
} CmpItem;

typedef struct CmpBitmapItem {
    COMMON_MEMBERS;

    Pixmap bitmap;
    XColor *foreground;
    XColor *background;
    GC gc;			/* GC for drawing the bitmap. */
} CmpBitmapItem;

typedef struct CmpImageItem {
    COMMON_MEMBERS;

    Tk_Image image;
    char * imageString;
} CmpImageItem;

typedef struct CmpSpaceItem {
    COMMON_MEMBERS;

} CmpSpaceItem;

typedef struct CmpTextItem {
    COMMON_MEMBERS;

    char * text;
    int numChars;
    Tk_Justify justify;		/* Justification to use for multi-line text. */
    int wrapLength;
    int underline;		/* Index of character to underline.  < 0 means
				 * don't underline anything. */
    XColor *foreground;
    TixFont font;		/* Information about text font, or NULL. */
    GC gc;			/* GC for drawing the bitmap. */
} CmpTextItem;

typedef union CmpItemPtr {
    CmpItem	  * item;
    CmpBitmapItem * bitmap;
    CmpImageItem  * image;
    CmpSpaceItem  * space;
    CmpTextItem  * text;
} CmpItemPtr;

/*
 * The type record for bitmap images:
 */
static int		ImgCmpCreate _ANSI_ARGS_((Tcl_Interp *interp,
			    char *name, int objc, Tcl_Obj *CONST *objv,
			    Tk_ImageType *typePtr, Tk_ImageMaster master,
			    ClientData *clientDataPtr));
static ClientData	ImgCmpGet _ANSI_ARGS_((Tk_Window tkwin,
			    ClientData clientData));
static void		ImgCmpDisplay _ANSI_ARGS_((ClientData clientData,
			    Display *display, Drawable drawable, 
			    int imageX, int imageY, int width, int height,
			    int drawableX, int drawableY));
static void		ImgCmpFree _ANSI_ARGS_((ClientData clientData,
			    Display *display));
static void		ImgCmpDelete _ANSI_ARGS_((ClientData clientData));

Tk_ImageType tixCompoundImageType = {
    "compound",			/* name */
    ImgCmpCreate,		/* createProc */
    ImgCmpGet,			/* getProc */
    ImgCmpDisplay,		/* displayProc */
    ImgCmpFree,			/* freeProc */
    ImgCmpDelete,		/* deleteProc */
    (Tk_ImageType *) NULL	/* nextPtr */
};

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BORDER, "-background", "background", "Background",
       DEF_CMPIMAGE_BG_COLOR, Tk_Offset(CmpMaster, background),
       TK_CONFIG_COLOR_ONLY},

    {TK_CONFIG_BORDER, "-background", "background", "Background",
       DEF_CMPIMAGE_BG_MONO, Tk_Offset(CmpMaster, background),
       TK_CONFIG_MONO_ONLY},

    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", (char *) NULL,
       "0", Tk_Offset(CmpMaster, borderWidth), 0},

    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_FONT, "-font", "font", "Font",
       DEF_CMPIMAGE_FONT, Tk_Offset(CmpMaster, font), 0},

    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
       DEF_CMPIMAGE_FG_COLOR, Tk_Offset(CmpMaster, foreground),
       TK_CONFIG_COLOR_ONLY},

    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
       DEF_CMPIMAGE_FG_MONO, Tk_Offset(CmpMaster, foreground),
       TK_CONFIG_MONO_ONLY},

    {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
	"0", Tk_Offset(CmpMaster, padX), 0},

    {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
	"0", Tk_Offset(CmpMaster, padY), 0},

    {TK_CONFIG_RELIEF, "-relief", (char *) NULL, (char *) NULL,
	"flat", Tk_Offset(CmpMaster, relief), 0},

    {TK_CONFIG_BOOLEAN, "-showbackground", (char *) NULL, (char *) NULL,
	"0", Tk_Offset(CmpMaster, showBackground), 0},

    {TK_CONFIG_WINDOW, "-window", (char *) NULL, (char *) NULL,
	(char *) NULL, Tk_Offset(CmpMaster, tkwin), TK_CONFIG_NULL_OK},

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

static Tk_ConfigSpec lineConfigSpecs[] = {
    {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
       "c", Tk_Offset(CmpLine, anchor), 0},
    {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpLine, padX), 0},
    {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpLine, padY), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

static Tk_ConfigSpec bitmapConfigSpecs[] = {
    {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
       "c", Tk_Offset(CmpBitmapItem, anchor), 0},

    {TK_CONFIG_COLOR, "-background", "background", "Background",
       "", Tk_Offset(CmpBitmapItem, background),
       TK_CONFIG_NULL_OK},

    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
       "",  Tk_Offset(CmpBitmapItem, bitmap), TK_CONFIG_NULL_OK},

    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
       "", Tk_Offset(CmpBitmapItem, foreground),
       TK_CONFIG_NULL_OK},

    {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpBitmapItem, padX), 0},

    {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpBitmapItem, padY), 0},

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

static Tk_ConfigSpec imageConfigSpecs[] = {
    {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
       "c", Tk_Offset(CmpImageItem, anchor), 0},
    {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL,
       (char *) NULL, Tk_Offset(CmpImageItem, imageString), TK_CONFIG_NULL_OK},
    {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpImageItem, padX), 0},
    {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpImageItem, padY), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
        (char *) NULL, 0, 0}
};

static Tk_ConfigSpec spaceConfigSpecs[] = {
    {TK_CONFIG_PIXELS, "-height", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpSpaceItem, height), 0},
    {TK_CONFIG_PIXELS, "-width", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpSpaceItem, width), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

static Tk_ConfigSpec textConfigSpecs[] = {
    {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
       "c", Tk_Offset(CmpTextItem, anchor), 0},

    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
       (char *) NULL, 0, 0},

    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
       "", Tk_Offset(CmpTextItem, font), TK_CONFIG_NULL_OK},

    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
       "", Tk_Offset(CmpTextItem, foreground),
       TK_CONFIG_NULL_OK},

    {TK_CONFIG_JUSTIFY, "-justify", (char *) NULL, (char *) NULL,
       "left", Tk_Offset(CmpTextItem, justify), 0},

    {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpTextItem, padX), 0},

    {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpTextItem, padY), 0},

    {TK_CONFIG_STRING, "-text", (char *) NULL, (char *) NULL,
       "",  Tk_Offset(CmpTextItem, text), TK_CONFIG_NULL_OK},

    {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
       "-1", Tk_Offset(CmpTextItem, underline), 0},

    {TK_CONFIG_PIXELS, "-wraplength", (char *) NULL, (char *) NULL,
       "0", Tk_Offset(CmpTextItem, wrapLength), 0},

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

/*
 * Prototypes for procedures used only locally in this file:
 */

static int		ImgCmpCmd _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, int argc, char **argv));
static void		ImgCmpCmdDeletedProc _ANSI_ARGS_((
			    ClientData clientData));
static int		ImgCmpConfigureMaster _ANSI_ARGS_((
			    CmpMaster *masterPtr, int argc, char **argv,
			    int flags));
CmpBitmapItem *		AddNewBitmap _ANSI_ARGS_((CmpMaster *masterPtr,
			    CmpLine *line,
			    int argc, char **argv));
CmpImageItem * 		AddNewImage _ANSI_ARGS_((CmpMaster *masterPtr,
			    CmpLine *line,
			    int argc, char **argv));
CmpSpaceItem * 		AddNewSpace _ANSI_ARGS_((CmpMaster *masterPtr,
			    CmpLine *line,
			    int argc, char **argv));
CmpTextItem * 		AddNewText _ANSI_ARGS_((CmpMaster *masterPtr,
			    CmpLine *line,
			    int argc, char **argv));
CmpLine* 		AddNewLine _ANSI_ARGS_((CmpMaster *masterPtr,
			    int argc, char **argv));
static void		CalculateMasterSize _ANSI_ARGS_((
			    ClientData clientData));
static void		ChangeImageWhenIdle _ANSI_ARGS_((
			    CmpMaster *masterPtr));
static void		ImageProc _ANSI_ARGS_((ClientData clientData,
			    int x, int y, int width, int height,
			    int imgWidth, int imgHeight));
static void 		FreeLine _ANSI_ARGS_((CmpLine * lPtr));
static void 		FreeItem _ANSI_ARGS_((CmpItemPtr p));
static void		CmpEventProc _ANSI_ARGS_((ClientData clientData,
			    XEvent *eventPtr));

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpCreate --
 *
 *	This procedure is called by the Tk image code to create "test"
 *	images.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	The data structure for a new image is allocated.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
static int
ImgCmpCreate(interp, name, objc, objv, typePtr, master, clientDataPtr)
    Tcl_Interp *interp;		/* Interpreter for application containing
				 * image. */
    char *name;			/* Name to use for image. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST *objv;	/* Arguments for options (doesn't
				 * include image name or type). */
    Tk_ImageType *typePtr;	/* Pointer to our type record (not used). */
    Tk_ImageMaster master;	/* Token for image, to be used by us in
				 * later callbacks. */
    ClientData *clientDataPtr;	/* Store manager's token for image here;
				 * it will be returned in later callbacks. */
{
    CmpMaster *masterPtr;
    char **argv;
    int i, length;

    argv = (char **) Tcl_Alloc((objc + 1) * sizeof(char *));
    for (i = 0; i < objc; i++) {
	argv[i] = Tcl_GetStringFromObj(objv[i], &length);
    }
    argv[objc] = NULL;

    masterPtr = (CmpMaster *) ckalloc(sizeof(CmpMaster));
    masterPtr->tkMaster = master;
    masterPtr->interp = interp;
    masterPtr->imageCmd = Tcl_CreateCommand(interp, name, ImgCmpCmd,
	(ClientData)masterPtr, ImgCmpCmdDeletedProc);
    masterPtr->tkwin = NULL;
    masterPtr->display = NULL;
    masterPtr->width = 0;
    masterPtr->height = 0;
    masterPtr->padX = 0;
    masterPtr->padY = 0;
    masterPtr->lineHead = NULL;
    masterPtr->lineTail = NULL;
    masterPtr->borderWidth = 0;
    masterPtr->background = NULL;
    masterPtr->relief = 0;
    masterPtr->font = NULL;
    masterPtr->foreground = NULL;
    masterPtr->gc = None;
    masterPtr->showBackground = 0;
    masterPtr->changing = 0;
    masterPtr->isDeleted = 0;
	
    if (ImgCmpConfigureMaster(masterPtr, objc, argv, 0) != TCL_OK) {
	ImgCmpDelete((ClientData) masterPtr);
	Tcl_Free((char *) argv);
	return TCL_ERROR;
    }
    *clientDataPtr = (ClientData) masterPtr;
    Tcl_Free((char *) argv);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpConfigureMaster --
 *
 *	This procedure is called when a bitmap image is created or
 *	reconfigured.  It process configuration options and resets
 *	any instances of the image.
 *
 * Results:
 *	A standard Tcl return value.  If TCL_ERROR is returned then
 *	an error message is left in masterPtr->interp->result.
 *
 * Side effects:
 *	Existing instances of the image will be redisplayed to match
 *	the new configuration options.
 *
 *----------------------------------------------------------------------
 */
static int
ImgCmpConfigureMaster(masterPtr, argc, argv, flags)
    CmpMaster *masterPtr;	/* Pointer to data structure describing
				 * overall bitmap image to (reconfigure). */
    int argc;			/* Number of entries in argv. */
    char **argv;		/* Pairs of configuration options for image. */
    int flags;			/* Flags to pass to Tk_ConfigureWidget,
				 * such as TK_CONFIG_ARGV_ONLY. */
{
    XGCValues gcValues;
    GC newGC;
    int i;

    if (argc %2) {
	Tcl_AppendResult(masterPtr->interp, "value missing for option \"",
	    argv[argc-1], "\"", NULL);
	return TCL_ERROR;
    }
    for (i=0; i<argc; i+=2) {
	size_t length = strlen(argv[i]);
	if (strncmp(argv[i], "-window", length) == 0) {
	    masterPtr->tkwin = Tk_NameToWindow(masterPtr->interp, argv[i+1],
		Tk_MainWindow(masterPtr->interp));
	    if (masterPtr->tkwin == NULL) {
		return TCL_ERROR;
	    }
	}
    }
    if (masterPtr->tkwin == NULL) {
	Tcl_AppendResult(masterPtr->interp, 
	    "no value given for -window option.", NULL);
	return TCL_ERROR;
    }
    masterPtr->display = Tk_Display(masterPtr->tkwin);

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	    configSpecs, argc, argv, (char *) masterPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }

    Tk_CreateEventHandler(masterPtr->tkwin,
	StructureNotifyMask, CmpEventProc, (ClientData)masterPtr);
    /*
     * Get the default GC for text and bitmaps
     */
    gcValues.foreground = masterPtr->foreground->pixel;
    gcValues.background = Tk_3DBorderColor(masterPtr->background)->pixel;
    gcValues.font = TixFontId(masterPtr->font);
    gcValues.graphics_exposures = False;
    newGC = Tk_GetGC(masterPtr->tkwin,
	 GCBackground|GCForeground|GCFont|GCGraphicsExposures,
	 &gcValues);
    if (masterPtr->gc != None) {
	Tk_FreeGC(Tk_Display(masterPtr->tkwin), masterPtr->gc);
    }
    masterPtr->gc = newGC;

    ChangeImageWhenIdle(masterPtr);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * ImgCmpCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to an image managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */
static int
ImgCmpCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Information about button widget. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpMaster *masterPtr = (CmpMaster *) clientData;
    int c, code;
    size_t length;

    if (argc < 2) {
	sprintf(interp->result,
	    "wrong # args: should be \"%.50s option ?arg arg ...?\"",
	    argv[0]);
	return TCL_ERROR;
    }
    c = argv[1][0];
    length = strlen(argv[1]);
    if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)) {
	if (argc < 3) {
	    return Tix_ArgcError(interp, argc, argv, 2, 
		"type ?option value? ...");
	}
	c = argv[2][0];
	length = strlen(argv[2]);

	if ((c == 'l') && (strncmp(argv[2], "line", length) == 0)) {
	    CmpLine * newLine;

	    newLine = AddNewLine(masterPtr, argc-3, argv+3);
	    if (newLine == NULL) {
		return TCL_ERROR;
	    }
	}
	else {
	    CmpItemPtr p;

	    if (masterPtr->lineTail == 0) {
		if (AddNewLine(masterPtr, 0, 0) == NULL) {
		    return TCL_ERROR;
		}
	    }
	    if ((c == 'b') && (strncmp(argv[2], "bitmap", length) == 0)) {
		p.bitmap = AddNewBitmap(masterPtr, masterPtr->lineTail, 
		    argc-3, argv+3);
		if (p.bitmap == NULL) {
		    return TCL_ERROR;
		}
	    }
	    else if ((c == 'i') && (strncmp(argv[2], "image", length) == 0)) {
		p.image = AddNewImage(masterPtr, masterPtr->lineTail, 
		    argc-3, argv+3);
		if (p.image == NULL) {
		    return TCL_ERROR;
		}
	    }
	    else if ((c == 's') && (strncmp(argv[2], "space", length) == 0)) {
		p.space = AddNewSpace(masterPtr, masterPtr->lineTail, 
		    argc-3, argv+3);
		if (p.space == NULL) {
		    return TCL_ERROR;
		}
	    }
	    else if ((c == 't') && (strncmp(argv[2], "text", length) == 0)) {
		p.text = AddNewText(masterPtr, masterPtr->lineTail, 
		    argc-3, argv+3);
		if (p.text == NULL) {
		    return TCL_ERROR;
		}
	    }
	    else {
		Tcl_AppendResult(interp, "unknown option \"",
		    argv[2], "\", must be bitmap, image, line, ",
		    "space, text or widget", NULL);
		return TCL_ERROR;
	    }

	    /* append to the end of the line */
	    if (masterPtr->lineTail->itemHead == NULL) {
		masterPtr->lineTail->itemHead =  p.item;
		masterPtr->lineTail->itemTail = p.item;
	    } else {
		masterPtr->lineTail->itemTail->next = p.item;
		masterPtr->lineTail->itemTail = p.item;
	    }
	}
	ChangeImageWhenIdle(masterPtr);
	return TCL_OK;
    } else 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;
	}
	return Tk_ConfigureValue(interp, Tk_MainWindow(interp), configSpecs,
		(char *) masterPtr, argv[2], 0);
    } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
	    && (length >= 2)) {
	if (argc == 2) {
	    code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp),
		configSpecs, (char *) masterPtr, (char *) NULL, 0);
	} else if (argc == 3) {
	    code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp),
		configSpecs, (char *) masterPtr, argv[2], 0);
	} else {
	    int i;
	    for (i=2; i<argc-2; i++) {
		length = strlen(argv[i]);
		if (strncmp(argv[i], "-window", length) == 0) {
		    Tcl_AppendResult(interp, "The -window option cannot ",
			"be changed.", (char *) NULL);
		    return TCL_ERROR;
		}
	    }
	    code = ImgCmpConfigureMaster(masterPtr, argc-2, argv+2,
		TK_CONFIG_ARGV_ONLY);
	}
	return code;
    } else if ((c == 'i') && (strncmp(argv[1], "itemconfigure", length)== 0)) {
	Tcl_AppendResult(interp, "unimplemented", NULL);
	return TCL_ERROR;
    } else if ((c == 'l') && (strncmp(argv[1], "lineconfigure", length)== 0)) {
	Tcl_AppendResult(interp, "unimplemented", NULL);
	return TCL_ERROR;
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
	    "\": must be cget or configure", (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*----------------------------------------------------------------------
 *
 *
 *----------------------------------------------------------------------
 */
CmpLine * 
AddNewLine(masterPtr, argc, argv)
    CmpMaster *masterPtr;
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpLine * lPtr = (CmpLine *)ckalloc(sizeof(CmpLine));

    lPtr->masterPtr = masterPtr;
    lPtr->next = NULL;
    lPtr->itemHead = NULL;
    lPtr->itemTail = NULL;
    lPtr->padX   = 0;
    lPtr->padY   = 0;
    lPtr->width  = 1;
    lPtr->height = 1;

    lPtr->anchor = TK_ANCHOR_CENTER;

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	    lineConfigSpecs, argc, argv, (char *) lPtr, 
	    TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	FreeLine(lPtr);
	return NULL;
    }

    /*
     * Append to the end of the master's lines
     */
    if (masterPtr->lineHead == NULL) {
	masterPtr->lineHead = masterPtr->lineTail = lPtr;
    } else {
	masterPtr->lineTail->next = lPtr;
	masterPtr->lineTail = lPtr;
    }

    return lPtr;
}

/*----------------------------------------------------------------------
 *
 *
 *----------------------------------------------------------------------
 */
CmpBitmapItem *
AddNewBitmap(masterPtr, line, argc, argv)
    CmpMaster *masterPtr;
    CmpLine *line;
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpItemPtr p;
    XGCValues gcValues;

    p.bitmap = (CmpBitmapItem*) ckalloc(sizeof(CmpBitmapItem));
    p.bitmap->line = line;
    p.bitmap->next = NULL;
    p.bitmap->anchor = TK_ANCHOR_CENTER;
    p.bitmap->type = TYPE_BITMAP;
    p.bitmap->padX = 0;
    p.bitmap->padY = 0;
    p.bitmap->width = 0;
    p.bitmap->height = 0;
   
    p.bitmap->bitmap = None;
    p.bitmap->foreground = NULL;
    p.bitmap->background = NULL;
    p.bitmap->gc = None;

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	    bitmapConfigSpecs, argc, argv, (char *) p.bitmap, 
	    TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	goto error;
    }

    /* Get the GC for the bitmap */
    if (p.bitmap->background) {
	gcValues.background = p.bitmap->background->pixel;
    } else {
	gcValues.background = Tk_3DBorderColor(masterPtr->background)->pixel;
    }
    if (p.bitmap->foreground) {
	gcValues.foreground = p.bitmap->foreground->pixel;
    } else {
	gcValues.foreground = masterPtr->foreground->pixel;
    }
    gcValues.graphics_exposures = False;
    p.bitmap->gc = Tk_GetGC(masterPtr->tkwin,
	 GCBackground|GCForeground|GCGraphicsExposures,
	 &gcValues);

    return p.bitmap;

  error:

    FreeItem(p);
    return NULL;
}

/*----------------------------------------------------------------------
 *
 *
 *----------------------------------------------------------------------
 */
CmpImageItem *
AddNewImage(masterPtr, line, argc, argv)
    CmpMaster *masterPtr;
    CmpLine *line;
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpItemPtr p;


    p.image = (CmpImageItem*) ckalloc(sizeof(CmpImageItem));
    p.image->line = line;
    p.image->next = NULL;
    p.image->anchor = TK_ANCHOR_CENTER;
    p.image->type = TYPE_IMAGE;
    p.image->padX = 0;
    p.image->padY = 0;
    p.image->width = 0;
    p.image->height = 0;
   
    p.image->imageString = NULL;
    p.image->image = NULL;

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	    imageConfigSpecs, argc, argv, (char *) p.image, 
	    TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	goto error;
    }

    /* Get the image */
    if (p.image->imageString != NULL) {
	p.image->image = Tk_GetImage(masterPtr->interp, masterPtr->tkwin,
	    p.image->imageString, ImageProc, (ClientData)p.image);
	if (p.image->image == NULL) {
	    goto error;
	}
    }

    return p.image;

  error:

    FreeItem(p);
    return NULL;
}

/*----------------------------------------------------------------------
 *
 *
 *----------------------------------------------------------------------
 */
CmpSpaceItem *
AddNewSpace(masterPtr, line, argc, argv)
    CmpMaster *masterPtr;
    CmpLine *line;
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpItemPtr p;

    p.space = (CmpSpaceItem*) ckalloc(sizeof(CmpSpaceItem));
    p.space->line = line;
    p.space->next = NULL;
    p.space->anchor = TK_ANCHOR_CENTER;
    p.space->type = TYPE_SPACE;
    p.space->padX = 0;
    p.space->padY = 0;
    p.space->width = 0;
    p.space->height = 0;

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	spaceConfigSpecs, argc, argv, (char *)p.space, 
	TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	goto error;
    }

    return p.space;

  error:

    FreeItem(p);
    return NULL;
}

/*----------------------------------------------------------------------
 *
 *
 *----------------------------------------------------------------------
 */
CmpTextItem *
AddNewText(masterPtr, line, argc, argv)
    CmpMaster *masterPtr;
    CmpLine *line;
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    CmpItemPtr p;
    XGCValues gcValues;

    p.text = (CmpTextItem*) ckalloc(sizeof(CmpTextItem));
    p.text->line = line;
    p.text->next = NULL;
    p.text->anchor = TK_ANCHOR_CENTER;
    p.text->type = TYPE_TEXT;
    p.text->padX = 0;
    p.text->padY = 0;
    p.text->width = 0;
    p.text->height = 0;
   
    p.text->text = NULL;
    p.text->numChars = 0;
    p.text->justify = TK_JUSTIFY_CENTER;
    p.text->underline = -1;
    p.text->wrapLength = 0;

    p.text->foreground = NULL;
    p.text->font = NULL;
    p.text->gc = None;

    if (Tk_ConfigureWidget(masterPtr->interp, masterPtr->tkwin,
	textConfigSpecs, argc, argv, (char *) p.text, 
	TK_CONFIG_ARGV_ONLY) != TCL_OK) {

	goto error;
    }

    /* Get the GC for the text */
    if (p.text->foreground) {
	gcValues.foreground = p.text->foreground->pixel;
    } else {
	gcValues.foreground = masterPtr->foreground->pixel;
    }
    if (p.text->font) {
	gcValues.font = TixFontId(p.text->font);
    } else {
	gcValues.font = TixFontId(masterPtr->font);
    }
    gcValues.graphics_exposures = False;
    p.text->gc = Tk_GetGC(masterPtr->tkwin,
	 GCFont|GCForeground|GCGraphicsExposures,
	 &gcValues);

    return p.text;

  error:

    FreeItem(p);
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpGet --
 *
 *	This procedure is called for each use of a bitmap image in a
 *	widget.
 *
 * Results:
 *	The return value is a token for the instance, which is passed
 *	back to us in calls to ImgCmpDisplay and ImgCmpFree.
 *
 * Side effects:
 *	A data structure is set up for the instance (or, an existing
 *	instance is re-used for the new one).
 *
 *----------------------------------------------------------------------
 */
static ClientData
ImgCmpGet(tkwin, masterData)
    Tk_Window tkwin;		/* Window in which the instance will be
				 * used. */
    ClientData masterData;	/* Pointer to our master structure for the
				 * image. */
{
    CmpMaster *masterPtr = (CmpMaster *)masterData;
    
    if (tkwin == masterPtr->tkwin) {
	return masterData;
    }

    Tcl_AppendResult(masterPtr->interp, 
	"Image \"",
	Tk_NameOfImage(masterPtr->tkMaster),
	"\" can only be assigned to window \"",
	Tk_PathName(masterPtr->tkwin), "\"", NULL);
    Tcl_AddErrorInfo(masterPtr->interp, "\n    (while configuring image \"");
    Tcl_AddErrorInfo(masterPtr->interp, Tk_NameOfImage(masterPtr->tkMaster));
    Tcl_AddErrorInfo(masterPtr->interp, "\")");
    Tk_BackgroundError(masterPtr->interp);

    return NULL;
}

static void
CalculateMasterSize(clientData)
    ClientData clientData;
{
    CmpMaster *masterPtr = (CmpMaster *)clientData;
    CmpLine *lPtr;
    CmpItemPtr p;

    masterPtr->width  = 0;
    masterPtr->height = 0;

    for (lPtr = masterPtr->lineHead; lPtr; lPtr=lPtr->next) {
	lPtr->width  = 0;
	lPtr->height = 0;
	for (p.item = lPtr->itemHead; p.item; p.item=p.item->next) {

	    switch (p.item->type) {
	      case TYPE_IMAGE:
		Tk_SizeOfImage(p.image->image, 
		    &p.image->width, &p.image->height);
		break;

	      case TYPE_SPACE:
		/* Do nothing */
		break;

	      case TYPE_TEXT:
		{
		    TixFont font;

		    if (p.text->text == NULL) {
			break;
		    }
		  
		    if (p.text->font) {
			font = p.text->font;
		    } else {
			font = masterPtr->font;
		    }
		    
		    p.text->numChars = strlen(p.text->text);
		    TixComputeTextGeometry(font, p.text->text,
			p.text->numChars,
			p.text->wrapLength,
			&p.text->width, &p.text->height);
	        }
		break;
		
	      case TYPE_BITMAP:
		Tk_SizeOfBitmap(Tk_Display(masterPtr->tkwin),
		    p.bitmap->bitmap, &p.bitmap->width,
		    &p.bitmap->height);
		break;

	      case TYPE_WIDGET:

		
		break;
	    }
	    p.item->width  += 2*p.item->padX;
	    p.item->height += 2*p.item->padY;

	    lPtr->width += p.item->width;
	    if (lPtr->height < p.item->height) {
		lPtr->height = p.item->height;
	    }
	}
	lPtr->width  += 2*lPtr->padX;
	lPtr->height += 2*lPtr->padY;

	if (masterPtr->width < lPtr->width) {
	    masterPtr->width = lPtr->width;
	}
	masterPtr->height += lPtr->height;
    }
    masterPtr->width  += 2*masterPtr->padX + 2*masterPtr->borderWidth;
    masterPtr->height += 2*masterPtr->padY + 2*masterPtr->borderWidth;

    Tk_ImageChanged(masterPtr->tkMaster, 0, 0, masterPtr->width,
	masterPtr->height, masterPtr->width, masterPtr->height);
    masterPtr->changing = 0;
}

static void
ChangeImageWhenIdle(masterPtr)
    CmpMaster *masterPtr;
{
    if (!masterPtr->changing) {
	masterPtr->changing = 1;
	Tk_DoWhenIdle(CalculateMasterSize, (ClientData)masterPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpDisplay --
 *
 *	This procedure is invoked to draw a bitmap image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A portion of the image gets rendered in a pixmap or window.
 *
 *----------------------------------------------------------------------
 */
static void
ImgCmpDisplay(clientData, display, drawable, imageX, imageY, width,
	height, drawableX, drawableY)
    ClientData clientData;	/* Pointer to CmpInstance structure for
				 * for instance to be displayed. */
    Display *display;		/* Display on which to draw image. */
    Drawable drawable;		/* Pixmap or window in which to draw image. */
    int imageX, imageY;		/* Upper-left corner of region within image
				 * to draw. */
    int width, height;		/* Dimensions of region within image to draw.*/
    int drawableX, drawableY;	/* Coordinates within drawable that
				 * correspond to imageX and imageY. */
{
    CmpMaster * masterPtr = (CmpMaster*)clientData;
    CmpLine *lPtr;
    CmpItemPtr p;
    int dx, dy, extraX;

    if (masterPtr == NULL) {
	/* attempting to draw into a invalid window (can only be drawn into
	 * the original window set by the -window option */
	return;
    }

    if (masterPtr->showBackground) {
	Tk_Fill3DRectangle(masterPtr->tkwin, drawable, 
	    masterPtr->background,
	    drawableX + masterPtr->padX - imageX,
	    drawableY + masterPtr->padY - imageY,
	    masterPtr->width  - 2*masterPtr->padX,
	    masterPtr->height - 2*masterPtr->padY,
	    masterPtr->borderWidth, masterPtr->relief);
    }

    /* ToDo: Set the clipping region according to the imageX,Y, and 
     * width, height */
    dy = drawableY + masterPtr->padY + masterPtr->borderWidth - imageY;

    for (lPtr = masterPtr->lineHead; lPtr; lPtr=lPtr->next) {
	dx = drawableX + masterPtr->padX - imageX;
	dx += lPtr->padX;
	dy += lPtr->padY;

	extraX = masterPtr->width - 2*masterPtr->padX - lPtr->width;
	switch (lPtr->anchor) {
	  case TK_ANCHOR_SW: case TK_ANCHOR_W: case TK_ANCHOR_NW:
	    extraX = 0;
	    break;
	  case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
	    extraX /= 2;
	    break;
	  case TK_ANCHOR_SE: case TK_ANCHOR_E: case TK_ANCHOR_NE:
	    break;
	}
	dx += extraX;

	for (p.item = lPtr->itemHead; p.item; p.item=p.item->next) {
	    int extraY;
	    dx += p.item->padX;

	    extraY = lPtr->height - 2*lPtr->padY - p.item->height;
	    switch (p.item->anchor) {
	      case TK_ANCHOR_SW: case TK_ANCHOR_S: case TK_ANCHOR_SE:
		break;
	      case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
		extraY /= 2;
		break;
	      case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
		extraY = 0;
		break;
	    }

	    switch (p.item->type) {
	      case TYPE_IMAGE:
		Tk_RedrawImage(p.image->image, 0, 0, 
		    p.image->width  - 2*p.item->padX,
		    p.image->height - 2*p.item->padY,
		    drawable, dx, dy+extraY);
		break;

	      case TYPE_SPACE:
		/* Do nothing */
		break;

	      case TYPE_TEXT:
		{
		    TixFont font;

		    if (p.text->text == NULL) {
			break;
		    }
		  
		    if (p.text->font) {
			font = p.text->font;
		    } else {
			font = masterPtr->font;
		    }

		    TixDisplayText(Tk_Display(masterPtr->tkwin), drawable,
			font, p.text->text, p.text->numChars,
			dx, dy+extraY,	    
			p.text->width - 2*p.item->padX,
			p.text->justify,
			p.text->underline,
			p.text->gc);
	        }
		break;
		
	      case TYPE_BITMAP:
		XCopyPlane(Tk_Display(masterPtr->tkwin), p.bitmap->bitmap,
		    drawable, p.bitmap->gc, 0, 0,
		    p.bitmap->width  - 2*p.item->padX,
		    p.bitmap->height - 2*p.item->padY,
		    dx, dy+extraY, 1);

		break;

	      case TYPE_WIDGET:

		
		break;
	    }
	    dx += p.item->width - p.item->padX;
	}
	dy += lPtr->height - lPtr->padY;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpFree --
 *
 *	This procedure is called when a widget ceases to use a
 *	particular instance of an image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Internal data structures get cleaned up.
 *
 *----------------------------------------------------------------------
 */

static void
ImgCmpFree(clientData, display)
    ClientData clientData;	/* Pointer to CmpInstance structure for
				 * for instance to be displayed. */
    Display *display;		/* Display containing window that used image.*/
{
    /*
     * Since one compound image can only be used in one window, when that 
     * window is deleted, this image is now useless and should be deleted as
     * well
     */
}

static void FreeLine(lPtr)
    CmpLine * lPtr;
{
    Tk_FreeOptions(lineConfigSpecs, (char *)lPtr,
	Tk_Display(lPtr->masterPtr->tkwin), 0);
    ckfree((char *) lPtr);
}

static void FreeItem(p)
    CmpItemPtr p;
{
    switch (p.item->type) {
      case TYPE_IMAGE:
	if (p.image->image) {
	    Tk_FreeImage(p.image->image);
	}
	Tk_FreeOptions(imageConfigSpecs, (char *)p.image, 
	    Tk_Display(p.item->line->masterPtr->tkwin), 0);
	break;

      case TYPE_SPACE:
	Tk_FreeOptions(spaceConfigSpecs, (char *)p.space, 
	    Tk_Display(p.item->line->masterPtr->tkwin), 0);
	break;

      case TYPE_TEXT:
	if (p.text->gc != None) {
	    Tk_FreeGC(Tk_Display(p.text->line->masterPtr->tkwin),
		p.text->gc);
	}
	Tk_FreeOptions(textConfigSpecs, (char *)p.text, 
	    Tk_Display(p.item->line->masterPtr->tkwin), 0);
	break;

      case TYPE_BITMAP:
	if (p.bitmap->gc != None) {
	    Tk_FreeGC(Tk_Display(p.bitmap->line->masterPtr->tkwin),
		p.bitmap->gc);
	}
	Tk_FreeOptions(bitmapConfigSpecs, (char *)p.bitmap, 
	    Tk_Display(p.item->line->masterPtr->tkwin), 0);
	break;

      case TYPE_WIDGET:
	break;
    }
    ckfree((char *) p.item);
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpDelete --
 *
 *	This procedure is called by the image code to delete the
 *	master structure for an image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources associated with the image get freed.
 *
 *----------------------------------------------------------------------
 */
static void
ImgCmpDelete(masterData)
    ClientData masterData;	/* Pointer to CmpMaster structure for
				 * image.  Must not have any more instances. */
{
    CmpMaster *masterPtr = (CmpMaster *) masterData;
    CmpLine * lPtr;
    CmpItemPtr p;

    if (masterPtr->tkwin == NULL) {
	goto done;
    }

    Tk_Preserve((ClientData) masterPtr);

    Tk_DeleteEventHandler(masterPtr->tkwin,
	StructureNotifyMask, CmpEventProc, (ClientData)masterPtr);

    if (masterPtr->isDeleted) {
	Tk_Release((ClientData) masterPtr);
	return;
    }
    masterPtr->isDeleted = 1;

    for (lPtr=masterPtr->lineHead; lPtr;) {
	CmpLine * toDelete = lPtr;
	lPtr = lPtr->next;

	for (p.item=toDelete->itemHead; p.item;) {
	    CmpItemPtr toDelete;

	    toDelete.item = p.item;
	    p.item=p.item->next;

	    FreeItem(toDelete);
	}
	FreeLine(toDelete);
    }

    if (masterPtr->changing) {
	Tk_CancelIdleCall(CalculateMasterSize, (ClientData)masterPtr);
    }
    masterPtr->tkMaster = NULL;
    if (masterPtr->imageCmd != NULL) {
	char * cmd = Tcl_GetCommandName(masterPtr->interp,masterPtr->imageCmd);
	masterPtr->imageCmd = NULL;
	Tcl_DeleteCommand(masterPtr->interp, cmd);
    }
    if (masterPtr->gc != None) {
	Tk_FreeGC(masterPtr->display, masterPtr->gc);
    }

    Tk_FreeOptions(configSpecs, (char *) masterPtr, masterPtr->display, 0);
    Tk_Release((ClientData) masterPtr);

  done:
    ckfree((char *) masterPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ImgCmpCmdDeletedProc --
 *
 *	This procedure is invoked when the image command for an image
 *	is deleted.  It deletes the image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The image is deleted.
 *
 *----------------------------------------------------------------------
 */
static void
ImgCmpCmdDeletedProc(clientData)
    ClientData clientData;	/* Pointer to CmpMaster structure for
				 * image. */
{
    CmpMaster *masterPtr = (CmpMaster *) clientData;

    Tk_Preserve((ClientData) masterPtr);
    if (masterPtr->isDeleted == 1) {
	Tk_Release((ClientData) masterPtr);
	return;
    } else {
	if (masterPtr->tkMaster != NULL) {
	    if (Tk_MainWindow(masterPtr->interp) != NULL) {
		Tk_DeleteImage(masterPtr->interp, 
		    Tk_NameOfImage(masterPtr->tkMaster));
	    }
	}
	Tk_Release((ClientData) masterPtr);
    }
}
/*
 *----------------------------------------------------------------------
 *
 * ImageProc --
 *
 *	This procedure is invoked by the image code whenever the manager
 *	for an image does something that affects the size of contents
 *	of an image displayed in a button.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for the HList to get redisplayed.
 *
 *----------------------------------------------------------------------
 */
static void
ImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
    ClientData clientData;		/* Pointer to widget record. */
    int x, y;				/* Upper left pixel (within image)
					 * that must be redisplayed. */
    int width, height;			/* Dimensions of area to redisplay
					 * (may be <= 0). */
    int imgWidth, imgHeight;		/* New dimensions of image. */
{
    CmpItemPtr p;

    p.image = (CmpImageItem *)clientData;

    ChangeImageWhenIdle(p.item->line->masterPtr);
}

/*
 *--------------------------------------------------------------
 *
 * CmpEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on the window that employs this compound image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

static void
CmpEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    CmpMaster *masterPtr = (CmpMaster *)clientData;
    char * cmd;

    if (eventPtr->type == DestroyNotify) {
	if (masterPtr->imageCmd != NULL) {
	    cmd = Tcl_GetCommandName(masterPtr->interp,masterPtr->imageCmd);
	    masterPtr->imageCmd = NULL;
	    Tcl_DeleteCommand(masterPtr->interp, cmd);
	}
    }
}
