#ifndef lint
static char *rcsid = "$Header: Flip.c,v 1.7 90/10/26 14:24:59 klee Exp Locker: klee $";
#endif lint
/*
 *			  COPYRIGHT 1990
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
 * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
 * SET FORTH ABOVE.
 *
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting documenta-
 * tion, and that the name of Digital Equipment Corporation not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.
 */
#include <stdio.h>
#include <X11/IntrinsicP.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <Xm/XmP.h>
#include <Xm/Xm.h>
#include "FlipP.h"
#include "Flip.h"

static void Initialize(), Redisplay(), SetSize(), Destroy();
static Boolean SetValues();
static void changeDelay(), setFlipGC(), startTimer(), drawFrame();
static void Forward(), Backward();

static char defaultTranslations[] =
    "<Btn1Down>: forward()	\n\
     <Btn3Down>: backward()	";
static XtActionsRec actions[] = {
    { "forward", Forward },
    { "backward", Backward },
};

static XtResource resources[] = {
  {XkNdirection, XkCDirection, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.direction), XmRImmediate, (caddr_t)1 },
  {XkNskip, XkCSkip, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.skip), XmRImmediate, (caddr_t)1 },
  {XkNdelay, XkCDelay, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.delay), XmRImmediate, (caddr_t)XkSTOP },
  {XkNframe, XkCFrame, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.frame), XmRImmediate, (caddr_t)0 },
  {XkNmaxFrames, XkCMaxFrames, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.maxFrames), XmRImmediate, (caddr_t)0 },
  {XkNborderLeft, XkCBorderLeft, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.borderLeft), XmRImmediate, (caddr_t)0 },
  {XkNborderTop, XkCBorderTop, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.borderTop), XmRImmediate, (caddr_t)0 },
  {XkNforeground, XmCForeground, XmRPixel, sizeof(Pixel),
    XtOffset(XkFlipWidget, primitive.foreground), XmRString, "Black" },
  {XkNflipBackground, XkCFlipBackground, XmRPixel, sizeof(Pixel),
    XtOffset(XkFlipWidget, flip.flipBackground), XmRString, "White" },
  {XkNsample, XkCSample, XmRInt, sizeof(int),
    XtOffset(XkFlipWidget, flip.sample), XmRImmediate, (caddr_t)1 },
  {XkNflipFrames, XkCFlipFrames, XmRPixmap, sizeof(Pixmap),
    XtOffset(XkFlipWidget, flip.flipFrames), XmRPixmap, NULL },
  {XkNfilePrefix, XkCFilePrefix, XmRString, sizeof(String),
    XtOffset(XkFlipWidget, flip.filePrefix), XmRString, NULL },
  {XkNclipmask, XkCClipmask, XmRString, sizeof(String),
    XtOffset(XkFlipWidget, flip.clipmask), XmRString, NULL },
  {XkNgrowShadow, XkCGrowShadow, XmRBoolean, sizeof(Boolean),
    XtOffset(XkFlipWidget, flip.growShadow), XmRString, "FALSE" },
 };

XkFlipClassRec  xkFlipClassRec = {
    /* CoreClassPart */
  {
    (WidgetClass) &xmPrimitiveClassRec,	/* superclass            */
    "XkFlip",				/* class_name            */
    sizeof(XkFlipRec),			/* widget_size           */
    NULL,				/* class_initialize      */
    NULL,				/* class_part_initialize */
    FALSE,				/* class_inited          */
    Initialize,				/* initialize            */
    NULL,				/* initialize_hook       */
    XtInheritRealize,			/* realize               */
    actions,				/* actions               */
    XtNumber(actions),			/* num_actions           */
    resources,				/* resources             */
    XtNumber(resources),		/* num_resources         */
    NULLQUARK,				/* xrm_class             */
    TRUE,				/* compress_motion       */
    TRUE,				/* compress_exposure     */
    TRUE,				/* compress_enterleave   */
    TRUE,				/* visible_interest      */
    Destroy,				/* destroy               */
    SetSize,				/* resize                */
    Redisplay,				/* expose                */
    SetValues,				/* set_values            */
    NULL,				/* set_values_hook       */
    XtInheritSetValuesAlmost,		/* set_values_almost     */
    NULL,				/* get_values_hook       */
    NULL,				/* accept_focus          */
    XtVersion,				/* version               */
    NULL,				/* callback private      */
    defaultTranslations,		/* tm_table              */
    NULL,				/* query_geometry        */
    NULL,				/* display_accelerator   */
    NULL,				/* extension             */
  },
     /* Primitive class fields */
  {
    NULL,				/* border_highlight */
    NULL,				/* border_unhighlight */
    NULL,				/* translations */
    NULL,				/* arm_and_activate */
    NULL,				/* get resources */
    0,					/* num get resources */
    NULL,				/* extension */
  },
     /* Flip class fields */
  {
    NULL,				/* ignore */
  },
};

WidgetClass xkFlipWidgetClass = (WidgetClass) &xkFlipClassRec;

static void Initialize(req, new)
    XkFlipWidget req, new;
{
    /* make sure there are some bitmaps */
    if ((!(new->flip.filePrefix)) && (!(new->flip.flipFrames))) {
        XtWarning("no data files or bitmaps specified");
	new->flip.maxFrames = 0;
    }

    /* check frame count */
    if (new->flip.maxFrames == 0)
        XtWarning("invalid frame count: 0");

    /* read in files */
    if (new->flip.filePrefix) {
        Display *dpy = XtDisplay(new);
        Window win = DefaultRootWindow(dpy);
        unsigned int nbit, frame;
	unsigned int width, height;

        new->flip.flipFrames = (Pixmap *)XtCalloc(new->flip.maxFrames, sizeof(Pixmap));
        if (new->flip.flipFrames == NULL) {
            XtWarning("could not allocate bitmaps");
	    new->flip.maxFrames = 0;
            return;
        }

        for (nbit = frame = 0;
            frame < new->flip.maxFrames;
            nbit += new->flip.sample, frame++) {
                char buf[1024];		/* file names */
                int xhot, yhot;
                Pixmap bitmap;
                int status;

                sprintf(buf, "%s%d", new->flip.filePrefix, nbit);
                status = XReadBitmapFile(dpy, win, buf, &width, &height,
                    &bitmap, &xhot, &yhot);
                if (status == BitmapSuccess) {
		    new->flip.flipFrames[frame] = bitmap;
                } else {
		    new->flip.maxFrames = frame;
                    if (frame == 0)
                        XtWarning("could not find any bitmap files");
		    else
                        XtWarning("found fewer bitmap files than requested");
		    break;
		}
        }
	/* set frame size */
	new->flip.frameWidth = width;
	new->flip.frameHeight = height;
    }

    /* clipmask */
    if (new->flip.clipmask) {
        Display *dpy = XtDisplay(new);
        Window win = DefaultRootWindow(dpy);
        Pixmap bitmap;
        int xhot, yhot, width, height;
        int status;
        extern void setClipmaskGC();

        status = XReadBitmapFile(dpy, win, new->flip.clipmask, &width, &height,
            &bitmap, &xhot, &yhot);
        if (status != BitmapSuccess) {
            XtWarning("could not find clipmask file");
	    new->flip.clipmask = FALSE;
        } else {
            new->flip.clipmaskBitmap = bitmap;
            new->flip.clipmaskResized = bitmap;
            new->flip.clipmaskWidth = width;
            new->flip.clipmaskHeight = height;
        }

        if ((width != new->flip.frameWidth)
	    || (height != new->flip.frameHeight))
		XtWarning("clipmask size different from bitmap size");
        setClipmaskGC(new);
    }

    /* set edge */
    new->flip.edgeLeft = new->flip.borderLeft
	+ new->primitive.shadow_thickness
	+ new->primitive.highlight_thickness;
    new->flip.edgeTop = new->flip.borderTop
	+ new->primitive.shadow_thickness
	+ new->primitive.highlight_thickness;

    /* if window size not set, use frame size */
    {
	unsigned int newWidth = new->flip.frameWidth + 2 * new->flip.edgeLeft;
	unsigned int newHeight = new->flip.frameHeight + 2 * new->flip.edgeTop;
	if (new->core.width < newWidth)
	    new->core.width = newWidth;
	if (new->core.height < newHeight);
	    new->core.height = newHeight;
    }

    /* GC: if there is a clipmask, GC set in SetSize() */
    if (!(new->flip.clipmask))
        setFlipGC(new);

    SetSize(new);
}

static void Destroy(w)
    XkFlipWidget w;
{
    if (w->flip.maxFrames) {
        int frame;
        for (frame = 0; frame < w->flip.maxFrames; frame++)
            if (w->flip.flipFrames[frame])
                XFreePixmap(XtDisplay(w), w->flip.flipFrames[frame]);
        XtFree(w->flip.flipFrames);
    }
    XtReleaseGC(w, w->flip.flipGC);
    if (w->flip.clipmaskResized)
	XFreePixmap(XtDisplay(w), w->flip.clipmaskResized);
    if (w->flip.clipmaskGC)
	XFreeGC(XtDisplay(w), w->flip.clipmaskGC);
}

static void SetSize(w)
    XkFlipWidget w;
{
    int width = w->core.width;
    int height = w->core.height;

    /* center width and height */
    w->flip.edgeLeft = (width - w->flip.frameWidth) / 2;
    w->flip.edgeTop = (height - w->flip.frameHeight) / 2;
    if (w->flip.growShadow) {
	if (w->flip.edgeLeft > w->flip.edgeTop) {
	    int shadow = w->flip.edgeTop - w->primitive.highlight_thickness;
	    w->primitive.shadow_thickness = (shadow > 0) ? shadow : 0;
	} else {
	    int shadow = w->flip.edgeLeft - w->primitive.highlight_thickness;
	    w->primitive.shadow_thickness = (shadow > 0) ? shadow : 0;
	}
    }
    w->flip.borderLeft = w->flip.edgeLeft 
	- w->primitive.shadow_thickness 
	- w->primitive.highlight_thickness;
    w->flip.borderTop = w->flip.edgeTop 
	- w->primitive.shadow_thickness 
	- w->primitive.highlight_thickness;

    /* resize clipmask bitmap */
    if (w->flip.clipmaskBitmap && w->flip.clipmaskGC) {
        Display *dpy = XtDisplay(w);
        /* free old bitmap and create new one */
        if (w->flip.clipmaskResized != w->flip.clipmaskBitmap)
            XFreePixmap(dpy, w->flip.clipmaskResized);
        w->flip.clipmaskResized = XCreatePixmap(dpy, DefaultRootWindow(dpy),
            width, height, 1);
        XCopyPlane(dpy, w->flip.clipmaskBitmap, w->flip.clipmaskResized,
            w->flip.clipmaskGC, 0, 0,
            w->flip.clipmaskWidth, w->flip.clipmaskHeight,
            w->flip.edgeLeft, w->flip.edgeTop, 1);
        setFlipGC(w);
    }
} 

static void Redisplay(w, event, region)
    XkFlipWidget  w;
    XEvent       *event;
    Region        region;
{
    if (w->core.visible) {
	if (w->primitive.shadow_thickness > 0)
	    _XmDrawShadow (XtDisplay(w), XtWindow(w),
		w->primitive.top_shadow_GC,
		w->primitive.bottom_shadow_GC,
		w->primitive.shadow_thickness,
		w->primitive.highlight_thickness,
		w->primitive.highlight_thickness,
		w->core.width - 2 * w->primitive.highlight_thickness,
		w->core.height - 2 * w->primitive.highlight_thickness);
	drawFrame(w);
    }
} 

static void drawFrame(w)
    XkFlipWidget w;
{
    if (w->core.visible) {

	/* wrap frame count */
	if (w->flip.maxFrames == 0)
	    return;
	else if (w->flip.frame < 0)
	    w->flip.frame = w->flip.maxFrames - 1;
	else if (w->flip.frame >= w->flip.maxFrames)
	    w->flip.frame = 0;

	/* draw */
	XCopyPlane(XtDisplay(w),		/* display */
	    w->flip.flipFrames[w->flip.frame],	/* src bitmap */
	    XtWindow(w),			/* dest window */
	    w->flip.flipGC,			/* GC */
	    0, 0,				/* src_x, src_y */
	    w->flip.frameWidth,			/* width */
	    w->flip.frameHeight,		/* height */
	    w->flip.edgeLeft,			/* dst_x */
	    w->flip.edgeTop,			/* dst_y */
	    1);					/* depth */
    }
}

static Boolean SetValues(current, req, new)
    XkFlipWidget current, req, new;
{
    Boolean redraw = FALSE;

    /* Make sure the new skip values are reasonable.  */
    if (new->flip.skip < 0) {
        XtWarning("skip must be non-negative");
        new->flip.skip = 0;
    } else if (new->flip.skip >= new->flip.maxFrames) {
        XtWarning("skipping too many frames");
        new->flip.skip = new->flip.maxFrames / 2;
    }

    /* new frame */
    if (new->flip.frame != current->flip.frame)
        redraw = TRUE;     

    /* new drawing GC */
    if ((new->primitive.foreground != current->primitive.foreground) ||
        (new->flip.flipBackground != current->flip.flipBackground)) {
            setFlipGC(new);
            redraw = TRUE;     
    }

    /* if we're stopped, start, else delay just changes */
    if ((new->flip.delay != XkSTOP) && (current->flip.delay == XkSTOP)) {
        if (new->flip.delay < 0)
            XtWarning("delay must be positive");
        else
            startTimer(new, NULL);
    }

    return(redraw); 
} 

static void setFlipGC(w)
    XkFlipWidget w;
{
    XGCValues xgcv;
    XtGCMask mask = GCForeground | GCBackground | GCGraphicsExposures;

    xgcv.graphics_exposures = False;
    xgcv.foreground = w->primitive.foreground;
    xgcv.background = w->flip.flipBackground;
    if (w->flip.clipmaskResized) {
        xgcv.clip_mask = w->flip.clipmaskResized;
        mask |= GCClipMask;
    }
    XtReleaseGC(w, w->flip.flipGC);
    w->flip.flipGC = XtGetGC(w, mask, &xgcv);
}

static void setClipmaskGC(w)
    XkFlipWidget w;
{
    XGCValues xgcv;
    unsigned long mask = GCForeground | GCBackground | GCGraphicsExposures;

    xgcv.graphics_exposures = False;
    xgcv.foreground = 1;
    xgcv.background = 0;
    if (w->flip.clipmaskBitmap)
	w->flip.clipmaskGC = XCreateGC(XtDisplay(w), w->flip.clipmaskBitmap,
	   mask, &xgcv);
}

static void startTimer(w, id)
    XkFlipWidget w;
    XtIntervalId *id;
{
    if (w->flip.delay != XkSTOP) {
        w->flip.frame += (w->flip.skip * w->flip.direction);
	drawFrame(w);
        XtAddTimeOut(w->flip.delay, startTimer, (caddr_t)w);
    }
}

static void Forward(w, event, params, num_params)
    XkFlipWidget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    /* advance frame IF stopped */
    if (w->flip.delay == XkSTOP) {
        w->flip.frame++;
	drawFrame(w);
    }
}

static void Backward(w, event, params, num_params)
    XkFlipWidget w;
    XEvent *event;
    String *params;
    Cardinal *num_params;
{
    /* reverse frame IF stopped */
    if (w->flip.delay == XkSTOP) {
        w->flip.frame--;
	drawFrame(w);
    }
}
