/*

Copyright 1989, 1998  The Open Group

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from The Open Group.

*/

/*
 * Author:  Davor Matic, MIT X Consortium
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>

#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xos.h>
#include <X11/Xaw/XawInit.h>

#include "CutPaste.h"
#include "ScaleP.h"

#ifdef HAVE_LRINT
#define myrint(x) lrint(x)
#else
#define myrint(x) floor(x + 0.5)
#endif

#define streq(a,b) (strcmp( (a), (b) ) == 0)
#ifndef min
#define min(x, y) ((x) > (y) ? (y) : (x))
#endif
#ifndef max
#define max(x, y) ((x) < (y) ? (y) : (x))
#endif

#define DefaultBufferSize 1024
#define DefaultScaleFactor NULL

#define Offset(field) XtOffsetOf(ScaleRec, scale.field)

static XtResource resources[] = {
{XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
     Offset(foreground_pixel), XtRString, (XtPointer) XtDefaultForeground},
{XtNgravity, XtCGravity, XtRGravity, sizeof(XtGravity),
     Offset(gravity), XtRImmediate, (XtPointer) "ForgetGravity"},
{XtNinternalWidth, XtCWidth, XtRDimension,  sizeof(Dimension),
     Offset(internal_width), XtRImmediate, (XtPointer) 2},
{XtNinternalHeight, XtCHeight, XtRDimension, sizeof(Dimension),
     Offset(internal_height), XtRImmediate, (XtPointer) 2},
{XtNresize, XtCResize, XtRBoolean, sizeof(Boolean),
     Offset(resize), XtRImmediate, (XtPointer) True},
{XtNautoscale, XtCAutoscale, XtRBoolean, sizeof(Boolean),
     Offset(autoscale), XtRImmediate, (XtPointer) True},
{XtNproportional, XtCProportional, XtRBoolean, sizeof(Boolean),
     Offset(proportional), XtRImmediate, (XtPointer) True},
{XtNscaleX, XtCScaleFactor, XtRString, sizeof(String),
     Offset(scale_x_str), XtRImmediate, (XtPointer) DefaultScaleFactor},
{XtNscaleY, XtCScaleFactor, XtRString, sizeof(String),
     Offset(scale_y_str), XtRImmediate, (XtPointer) DefaultScaleFactor},
{XtNaspectRatio, XtCAspectRatio, XtRString, sizeof(String),
     Offset(aspect_ratio_str), XtRImmediate, (XtPointer) "1.0"},
{XtNprecision, XtCPrecision, XtRString, sizeof(String),
     Offset(precision_str), XtRImmediate, (XtPointer) "0.001"},
{XtNimage, XtCImage, XtRImage, sizeof(XImage*),
     Offset(image), XtRImmediate, (XtPointer) NULL},
{XtNpasteBuffer, XtCPasteBuffer, XtRBoolean, sizeof(Boolean),
     Offset(paste_buffer), XtRImmediate, (XtPointer) False},
{XtNbufferSize, XtCBufferSize, XtRCardinal, sizeof(Cardinal),
     Offset(buffer_size), XtRImmediate, (XtPointer) DefaultBufferSize},
{XtNuserData, XtCuserData, XtRuserData, sizeof(XtPointer),
     Offset(userData), XtRImmediate, (XtPointer) NULL},
{ XtNvisual, XtCvisual, XtRVisual, sizeof(Visual*),
     Offset(visual), XtRImmediate, CopyFromParent}
};

#undef Offset

static void ClassInitialize ( void );
static void GetGC ( ScaleWidget sw );
static void GetInitialScaleValues ( ScaleWidget sw );
static void GetRectangleBuffer ( ScaleWidget sw, Cardinal buffer_size );
static void Initialize ( Widget request, Widget new, ArgList args,
			 Cardinal *num_args );
static void BuildTable ( ScaleWidget sw );
static void FlushRectangles ( ScaleWidget sw, Drawable drawable, GC gc );
static void FillRectangle ( ScaleWidget sw, Drawable drawable, GC gc,
			    Position x, Position y,
			    Dimension width, Dimension height );
static void ScaleImage ( ScaleWidget sw, Drawable drawable,
			 Position img_x, Position img_y,
			 Position dst_x, Position dst_y,
			 Dimension img_width, Dimension img_height );
static int FindPixel ( ScaleWidget sw, Position x, Position y,
		       Position *img_x, Position *img_y, Pixel *img_pixel );
static void Redisplay ( Widget w, XEvent *event, Region region );
static void TryResize ( ScaleWidget sw );
static void Precision ( ScaleWidget sw );
static void Proportional ( ScaleWidget sw );
static void GetScaledSize ( ScaleWidget sw );
static void GetScaleValues ( ScaleWidget sw );
static void Unscale ( ScaleWidget sw );
static void Autoscale ( ScaleWidget sw );
static void PositionImage ( ScaleWidget sw );
static void Resize ( Widget w );
static void Realize ( Widget wid, Mask *vmask, XSetWindowAttributes *attr );
static void Destroy ( Widget w );
static Boolean SetValues ( Widget current, Widget request, Widget new,
			   ArgList args, Cardinal *num_args );



static XtActionsRec actions[] =
{
{"unscale", SWUnscale},
{"autoscale", SWAutoscale},
{"initial-size", SWInitialSize},
{"paste", RequestSelection},
{"cut", GrabSelection}
};

static char translations[] =
"\
 <Key>u:           unscale()\n\
 <Key>a:           autoscale()\n\
 <Key>i:           initial-size()\n\
";

ScaleClassRec scaleClassRec = {
{   /* core fields */
    /* superclass		*/	(WidgetClass) &simpleClassRec,
    /* class_name		*/	"Scale",
    /* widget_size		*/	sizeof(ScaleRec),
    /* class_initialize		*/	ClassInitialize,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	Initialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	Realize,
    /* actions			*/	actions,
    /* num_actions		*/	XtNumber(actions),
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/      XtExposeCompressMaximal|
                                        XtExposeGraphicsExposeMerged,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	TRUE,
    /* destroy			*/	Destroy,
    /* resize			*/	Resize,
    /* expose			*/	Redisplay,
    /* set_values		*/	SetValues,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/      translations,
    /* query_geometry		*/	XtInheritQueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },
  {
    /* change_sensitive		*/	XtInheritChangeSensitive,
  }
};

WidgetClass scaleWidgetClass = (WidgetClass) &scaleClassRec;



/*
 * Private Procedures
 */


static void
ClassInitialize(void)
{
}



static void
GetGC(ScaleWidget sw)
{
    XGCValues values;

    values.foreground = sw->scale.foreground_pixel;
    values.background = sw->core.background_pixel;
    values.function = GXcopy;

    sw->scale.gc = XtGetGC((Widget) sw,
			   GCForeground |
			   GCBackground |
			   GCFunction,
			   &values);
}




static void
GetInitialScaleValues(ScaleWidget sw)
{
    if (sw->scale.proportional) {
	sw->scale.scale_x = sw->scale.scale_y =
	    ((sw->scale.aspect_ratio > 1.0) ?
	     sw->scale.aspect_ratio : 1.0 / sw->scale.aspect_ratio) *
		 (sw->scale.precision > 1.0 ?
		  sw->scale.precision : 1.0);
	Proportional(sw); /* need to cut them down to proper values */
    }
    else
	sw->scale.scale_x = sw->scale.scale_y = 1.0;
}



static void
GetRectangleBuffer(ScaleWidget sw, Cardinal buffer_size)
    /*
     * This procedure will realloc a new rectangles buffer.
     * If the new buffer size is less than nrectangles, some
     * information will be lost.
     */
{
    if (buffer_size == 0) {
	buffer_size = DefaultBufferSize;
	XtWarning("buffer size has to be a positive number greater than zero");
    }
    sw->scale.rectangles = (XRectangle *)
	XtRealloc((char *) sw->scale.rectangles,
		  buffer_size * sizeof(XRectangle));
    sw->scale.buffer_size = buffer_size;
}



/* ARGSUSED */
static void
Initialize(Widget request, Widget new, ArgList args, Cardinal *num_args)
{
    ScaleWidget new_sw = (ScaleWidget) new;

    new_sw->scale.table.x = (Position *) NULL;
    new_sw->scale.table.y = (Position *) NULL;
    new_sw->scale.table.width = (Dimension *) NULL;
    new_sw->scale.table.height = (Dimension *) NULL;

    new_sw->scale.nrectangles = 0;
    new_sw->scale.rectangles = (XRectangle *) NULL;

    GetRectangleBuffer(new_sw, new_sw->scale.buffer_size);

    GetGC(new_sw);

    if (new_sw->scale.image != NULL) {
	if (new_sw->core.width == 0)
	    new_sw->core.width =
		new_sw->scale.image->width + 2 * new_sw->scale.internal_width;
	if (new_sw->core.height == 0)
	    new_sw->core.height =
		new_sw->scale.image->height + 2 *new_sw->scale.internal_height;
    }
    else {
	if (new_sw->core.width == 0)
	    new_sw->core.width = 1 + 2 * new_sw->scale.internal_width;
	if (new_sw->core.height == 0)
	    new_sw->core.height = 1 + 2 * new_sw->scale.internal_height;
	new_sw->scale.image = XCreateImage(XtDisplay(new),
				       DefaultVisual(XtDisplay(new),
					        DefaultScreen(XtDisplay(new))),
				       1, XYBitmap, 0,
				       XtCalloc(1, sizeof(char)),
				       1, 1, 8, 0);
    }

    if ((new_sw->scale.aspect_ratio =
	 atof(new_sw->scale.aspect_ratio_str)) < 0.0) {
	new_sw->scale.aspect_ratio = 1.0;
	XtWarning("AspectRatio has to be a positive number. (forced to 1.0)");
    }

    if ((new_sw->scale.precision =
         atof(new_sw->scale.precision_str)) < 0.0) {
	new_sw->scale.precision = 0.001;
	XtWarning("Precision has to be a positive number. (forced to 0.001)");
    }

    if (new_sw->scale.scale_x_str == DefaultScaleFactor
	||
	new_sw->scale.scale_y_str == DefaultScaleFactor)
	GetInitialScaleValues(new_sw);
    else {
	if ((new_sw->scale.scale_x =
	     atof(new_sw->scale.scale_x_str)) < 0.0) {
	    new_sw->scale.scale_x = 1.0;
	    XtWarning("ScaleValue has to be a positive number. (forced to 1.0)");
	}
	if ((new_sw->scale.scale_y =
	     atof(new_sw->scale.scale_y_str)) < 0.0) {
	    new_sw->scale.scale_y = 1.0;
	    XtWarning("ScaleValue has to be a positive number. (forced to 1.0)");
	}
    }
}



static void
BuildTable(ScaleWidget sw)
    /*
     * This procedure builds scaling table for image in the scale struct
     * Requires image, scale_x and scale_y to be set properly
     */
{
    Position x, y;

    XtFree((char *) sw->scale.table.x);
    XtFree((char *) sw->scale.table.y);
    XtFree((char *) sw->scale.table.width);
    XtFree((char *) sw->scale.table.height);
    sw->scale.table.x =
	(Position *) XtMalloc(sizeof(Position) * sw->scale.image->width);
    sw->scale.table.y =
	(Position *) XtMalloc(sizeof(Position) * sw->scale.image->height);
    sw->scale.table.width =
	(Dimension *) XtMalloc(sizeof(Dimension) * sw->scale.image->width);
    sw->scale.table.height =
	(Dimension *) XtMalloc(sizeof(Dimension) * sw->scale.image->height);

    /* Build the scaling table */
    for (x = 0; x < sw->scale.image->width; x++) {
	sw->scale.table.x[(int) x] = (Position) myrint(sw->scale.scale_x * x);
	sw->scale.table.width[(int) x] = (Dimension)
	    myrint(sw->scale.scale_x *(x + 1)) - myrint(sw->scale.scale_x * x);
    }
    for (y = 0; y < sw->scale.image->height; y++) {
	sw->scale.table.y[(int) y] = (Position) myrint(sw->scale.scale_y * y);
	sw->scale.table.height[(int) y] = (Dimension)
	    myrint(sw->scale.scale_y *(y + 1)) - myrint(sw->scale.scale_y * y);
    }
}



static void
FlushRectangles(ScaleWidget sw, Drawable drawable, GC gc)
{
    XFillRectangles(XtDisplay(sw), drawable, gc,
		    sw->scale.rectangles, sw->scale.nrectangles);

    sw->scale.nrectangles = 0;
}



static void
FillRectangle(ScaleWidget sw, Drawable drawable, GC gc,
	      Position x, Position y, Dimension width, Dimension height)
{

    if (sw->scale.nrectangles == sw->scale.buffer_size)
	FlushRectangles(sw, drawable, gc);

    sw->scale.rectangles[(int) sw->scale.nrectangles].x = x;
    sw->scale.rectangles[(int) sw->scale.nrectangles].y = y;
    sw->scale.rectangles[(int) sw->scale.nrectangles].width = width;
    sw->scale.rectangles[(int) sw->scale.nrectangles].height = height;

    ++sw->scale.nrectangles;
}



static void
ScaleImage(ScaleWidget sw, Drawable drawable, Position img_x, Position img_y,
	   Position dst_x, Position dst_y,
	   Dimension img_width, Dimension img_height)
    /*
     * This procedure scales image into the specified drawable
     * It assumes scaling table was already built
     */
{
    GC gc;
    XGCValues gcv;
    Position x, y;
    Pixel pixel;

    /* Clip the img coordinates */
    img_x = min(max(img_x, 0), (Position) sw->scale.image->width - 1);
    img_y = min(max(img_y, 0), (Position) sw->scale.image->height - 1);
    img_width =
      min(img_width, (Dimension)(sw->scale.image->width - (Dimension)img_x));
    img_height =
      min(img_height, (Dimension)(sw->scale.image->height - (Dimension)img_y));

    if (sw->scale.scale_x == 1.0 && sw->scale.scale_y == 1.0)
	XPutImage(XtDisplay(sw), drawable, sw->scale.gc, sw->scale.image,
		  img_x, img_y, dst_x, dst_y,
		  img_width, img_height);
    else {
	dst_x = dst_x - sw->scale.table.x[(int) img_x];
	dst_y = dst_y - sw->scale.table.y[(int) img_y];

	gc = XCreateGC(XtDisplay(sw), drawable, 0, NULL);

	gcv.function = GXcopy;
	XChangeGC(XtDisplay(sw), gc, GCFunction, &gcv);

	/* make sure gc knows the right background */
	gcv.background = sw->core.background_pixel;
	XChangeGC(XtDisplay(sw), gc, GCBackground, &gcv);

	/* Set the background of drawable.  If the most frequent color
	   is the background color it can speed up scaling process. */
	gcv.foreground = gcv.background;
	XChangeGC(XtDisplay(sw), gc, GCForeground, &gcv);
	XFillRectangle(XtDisplay(sw), drawable, gc,
		       sw->scale.table.x[(int) img_x] + dst_x,
		       sw->scale.table.y[(int) img_y] + dst_y,
		       sw->scale.table.x[(int) img_x + img_width - 1] -
		       sw->scale.table.x[(int) img_x],
		       sw->scale.table.y[(int) img_y + img_height - 1] -
		       sw->scale.table.y[(int) img_y]);

	if (sw->scale.image->format == XYBitmap) {
	    for (x = img_x; x < img_x + (Position)img_width; x++)
		for (y = img_y; y < img_y + (Position)img_height; y++) {
		    pixel = XGetPixel(sw->scale.image, x, y);
		    if (pixel) /* Do not draw background */
			FillRectangle(sw, drawable, sw->scale.gc,
				      sw->scale.table.x[(int) x] + dst_x,
				      sw->scale.table.y[(int) y] + dst_y,
				      sw->scale.table.width[(int) x],
				      sw->scale.table.height[(int) y]);
		}
	    FlushRectangles(sw, drawable, sw->scale.gc);
	}
	else {
	    for (x = img_x; x < img_x + (Position)img_width; x++)
		for (y = img_y; y < img_y + (Position)img_height; y++) {
		    pixel = XGetPixel(sw->scale.image, x, y);
		    if (pixel != gcv.background) { /* Do not draw background */
			if (gcv.foreground != pixel) { /* Change fg to pixel */
			    gcv.foreground = pixel;
			    XChangeGC(XtDisplay(sw), gc, GCForeground, &gcv);
			}
			XFillRectangle(XtDisplay(sw), drawable, gc,
				       sw->scale.table.x[(int) x] + dst_x,
				       sw->scale.table.y[(int) y] + dst_y,
				       sw->scale.table.width[(int) x],
				       sw->scale.table.height[(int) y]);
		    }
		}
	}
	XFreeGC(XtDisplay(sw), gc);
    }
}



static int
FindPixel(ScaleWidget sw, Position x, Position y,
	  Position *img_x, Position *img_y, Pixel *img_pixel)
          /* (x,y) == (0,0) where image starts in sw window*/
{
    if (*img_x < 0 || *img_x >= sw->scale.image->width
	||
	*img_y < 0 || *img_y >= sw->scale.image->height)
	return (-1);

    if (sw->scale.table.x[(int) *img_x] >= x) {
	--*img_x;
	return FindPixel(sw, x, y, img_x, img_y, img_pixel);
    }
    if (sw->scale.table.x[(int) *img_x] +
	(Position)sw->scale.table.width[(int) *img_x] < x) {
	++*img_x;
	return FindPixel(sw, x, y, img_x, img_y, img_pixel);
    }
    if (sw->scale.table.y[(int) *img_y] >= y) {
	--*img_y;
	return FindPixel(sw, x, y, img_x, img_y, img_pixel);
    }
    if (sw->scale.table.y[(int) *img_y] +
	(Position)sw->scale.table.height[(int) *img_y] < y) {
	++*img_y;
	return FindPixel(sw, x, y, img_x, img_y, img_pixel);
    }

    *img_pixel = XGetPixel(sw->scale.image, *img_x, *img_y);

    return (0);
}



int
SWGetImagePixel(Widget w, Position x, Position y,
		Position *img_x, Position *img_y, Pixel *img_pixel)
{
    ScaleWidget sw = (ScaleWidget) w;

    x -= sw->scale.x;
    y -= sw->scale.y;

    *img_x = (Position) floor(x / sw->scale.scale_x);
    *img_y = (Position) floor(y / sw->scale.scale_y);

    return FindPixel(sw, x, y, img_x, img_y, img_pixel);
}



/* ARGSUSED */
static void
Redisplay(Widget w, XEvent *event, Region region)
{
    ScaleWidget sw = (ScaleWidget) w;
    Position  x, y, img_x, img_y;
    Dimension width, height;

    if (event->type == Expose) {

	if (event->xexpose.x < sw->scale.x) {
	    x = 0;
	    width = event->xexpose.width -
		(sw->scale.x - event->xexpose.x);
	}
	else {
	    x = event->xexpose.x - sw->scale.x;
	    width = event->xexpose.width;
	}

	if (event->xexpose.y < sw->scale.y) {
	    y = 0;
	    height = event->xexpose.height -
		(sw->scale.y - event->xexpose.y);
	}
	else {
	    y = event->xexpose.y - sw->scale.y;
	    height = event->xexpose.height;
	}

	img_x = min(max((Position) floor((float) x
					 / (float) sw->scale.scale_x), 0),
		    (Position) sw->scale.image->width - 1);

	img_y = min(max((Position) floor((float) y
					 / (float) sw->scale.scale_y), 0),
		    (Position) sw->scale.image->height - 1);

	if (sw->core.visible) {
	    ScaleImage(sw, XtWindow(w),
		       img_x, img_y,
		       sw->scale.x + sw->scale.table.x[(int) img_x],
		       sw->scale.y + sw->scale.table.y[(int) img_y],
		       (Dimension) ceil((float) width
					/ sw->scale.scale_x) + 1,
		       (Dimension) ceil((float) height
					/ sw->scale.scale_y) + 1);
	}
    }
}



static void
TryResize(ScaleWidget sw)
{
    Dimension width, height;
    XtGeometryResult result;

    width = (Dimension)
	floor(sw->scale.image->width * sw->scale.scale_x)
	    + 2 * sw->scale.internal_width;
    height = (Dimension)
	floor(sw->scale.image->height * sw->scale.scale_y)
	    + 2 * sw->scale.internal_height;

    while ((result =
/* SUPPRESS 530 */XtMakeResizeRequest((Widget)sw,width,height,&width,&height))
	   == XtGeometryAlmost);

    if (result != XtGeometryNo) {
	sw->core.width = width;
	sw->core.height = height;
    }
}



static void
Precision(ScaleWidget sw)
{
    if (sw->scale.scale_x != 1.0)
	sw->scale.scale_x = floor(sw->scale.scale_x / sw->scale.precision)
	    		    * sw->scale.precision;

    if (sw->scale.scale_y != 1.0)
	sw->scale.scale_y = floor(sw->scale.scale_y / sw->scale.precision)
			    * sw->scale.precision;
}



static void
Proportional(ScaleWidget sw)
{
    float scale_x, scale_y;

    scale_x = sw->scale.scale_y / sw->scale.aspect_ratio;
    scale_y = sw->scale.scale_x * sw->scale.aspect_ratio;

    if (scale_x <= sw->scale.scale_x && scale_y <= sw->scale.scale_y) {
	if (scale_x > scale_y)
	    sw->scale.scale_x = scale_x;
	else
	    sw->scale.scale_y = scale_y;
    }
    else if (scale_x <= sw->scale.scale_x)
	sw->scale.scale_x = scale_x;
    else if (scale_y <= sw->scale.scale_y)
	sw->scale.scale_y = scale_y;
    else {
	float x_ratio, y_ratio;

	x_ratio = scale_x / sw->scale.scale_x;
	y_ratio = scale_y / sw->scale.scale_y;

	if (x_ratio < y_ratio)
	    sw->scale.scale_y /= x_ratio;
	else
	    sw->scale.scale_x /= y_ratio;
    }

    if (fabs(sw->scale.scale_x / sw->scale.scale_y * sw->scale.aspect_ratio
	    - 1.0) > sw->scale.precision)
	XtWarning("can not preserve aspect ratio");
}



static void
GetScaledSize(ScaleWidget sw)
{
    sw->scale.width = (Dimension)
	max(myrint(sw->scale.scale_x * sw->scale.image->width), 1);
    sw->scale.height = (Dimension)
	max(myrint(sw->scale.scale_y * sw->scale.image->height), 1);
}



static void
GetScaleValues(ScaleWidget sw)
{
    /*
     * Make sure to subtract internal width and height.
     */

    sw->scale.scale_x =
	(float) max((int)(sw->core.width - 2 * sw->scale.internal_width), 1)
	    / (float) sw->scale.image->width;

    sw->scale.scale_y =
	(float) max((int)(sw->core.height - 2 * sw->scale.internal_height), 1)
	    / (float) sw->scale.image->height;
}



static void
Unscale(ScaleWidget sw)
{
    sw->scale.scale_x = sw->scale.scale_y = 1.0;

    GetScaledSize(sw);

    BuildTable(sw);
}



static void
Autoscale(ScaleWidget sw)
{
    GetScaleValues(sw);

    if (sw->scale.proportional) Proportional(sw);

    Precision(sw);

    GetScaledSize(sw);

    BuildTable(sw);
}



static void
PositionImage(ScaleWidget sw)
{
    /*
     * Set as if for ForgegGravity (that is center the image)
     */
    sw->scale.x = (Position)
	(sw->core.width - sw->scale.width) / 2;
    sw->scale.y = (Position)
	(sw->core.height - sw->scale.height) / 2;

/*****
    if (sw->scale.gravity & WestGravity) {
    }
    if (sw->scale.gravity & EastGravity) {
    }
    if (sw->scale.gravity & NorthGravity) {
    }
    if (sw->scale.gravity & SouthGravity) {
    }
*****/
}



static void
Resize(Widget w)
{
    ScaleWidget sw = (ScaleWidget) w;

    if (sw->scale.autoscale) Autoscale(sw);

    PositionImage(sw);
}



static void
Realize(Widget wid, Mask *vmask, XSetWindowAttributes *attr)
{
  ScaleWidget sw = (ScaleWidget) wid;
  XtCreateWindow(wid, (unsigned int) InputOutput,
		 (Visual *) sw->scale.visual, *vmask, attr);
}



static void
Destroy(Widget w)
{
    ScaleWidget sw = (ScaleWidget) w;

    XtFree((char *) sw->scale.table.x);
    XtFree((char *) sw->scale.table.y);
    XtFree((char *) sw->scale.table.width);
    XtFree((char *) sw->scale.table.height);

    XtFree((char *) sw->scale.rectangles);

    XtReleaseGC(w, sw->scale.gc);

    XDestroyImage(sw->scale.image);
}



/* ARGSUSED */
static Boolean
SetValues(Widget current, Widget request, Widget new,
	  ArgList args, Cardinal *num_args)
{
    ScaleWidget cur_sw = (ScaleWidget) current;
    /* ScaleWidget req_sw = (ScaleWidget) request; */
    ScaleWidget new_sw = (ScaleWidget) new;
    Boolean redisplay = False;
    Cardinal i;

    for (i = 0; i < *num_args; i++) {
	if (streq(XtNbackground, args[i].name)) {
	    XSetBackground(XtDisplay(new), new_sw->scale.gc,
			   new_sw->core.background_pixel);
	}
	if (streq(XtNforeground, args[i].name)) {
	    XSetForeground(XtDisplay(new), new_sw->scale.gc,
			   new_sw->scale.foreground_pixel);
	}
	if (streq(XtNimage, args[i].name)) {
	    XDestroyImage(cur_sw->scale.image);
	    if (new_sw->scale.image == NULL)
		new_sw->scale.image = XCreateImage(XtDisplay(new),
						DefaultVisual(XtDisplay(new),
						DefaultScreen(XtDisplay(new))),
						   1, XYBitmap, 0,
						   XtCalloc(1, sizeof(char)),
						   1, 1, 8, 0);
	    else
		new_sw->scale.image =
		    XSubImage(new_sw->scale.image,
			      0, 0,
			      new_sw->scale.image->width,
			      new_sw->scale.image->height);

	    if (new_sw->scale.resize)
		TryResize(new_sw);
	    if (new_sw->scale.autoscale)
		Autoscale(new_sw);
	    else {
		GetScaledSize(new_sw);
		BuildTable(new_sw);
	    }
	    PositionImage(new_sw);
	    redisplay = True;
	}

	if (streq(XtNuserData, args[i].name))
	  new_sw->scale.userData = (XtPointer)args[i].value;

	if (streq(XtNbufferSize, args[i].name)) {
	    if (new_sw->scale.buffer_size != cur_sw->scale.buffer_size) {
		GetRectangleBuffer(new_sw, new_sw->scale.buffer_size);
	    }
	}

	if (streq(XtNaspectRatio, args[i].name)) {
	    if ((new_sw->scale.aspect_ratio =
		 atof(new_sw->scale.aspect_ratio_str)) < 0.0) {
		new_sw->scale.aspect_ratio = cur_sw->scale.aspect_ratio;
		XtWarning("AspectRatio has to be a positive number.");
	    }
	    else if (new_sw->scale.aspect_ratio != cur_sw->scale.aspect_ratio){
		if (new_sw->scale.proportional) {
		    Proportional(new_sw);
		    Precision(new_sw);
		    GetScaledSize(new_sw);
		    BuildTable(new_sw);
		    PositionImage(new_sw);
		    redisplay = True;
		}
	    }
	}

	if (streq(XtNproportional, args[i].name)) {
	    if (new_sw->scale.proportional != cur_sw->scale.proportional) {
		if (new_sw->scale.proportional) Proportional(new_sw);
		Precision(new_sw);
		GetScaledSize(new_sw);
		BuildTable(new_sw);
		PositionImage(new_sw);
		redisplay = True;
	    }
	}

	if (streq(XtNscaleX, args[i].name)
	    ||
	    streq(XtNscaleY, args[i].name)) {
	    if (new_sw->scale.scale_x_str == DefaultScaleFactor
		||
		new_sw->scale.scale_y_str == DefaultScaleFactor)
		GetInitialScaleValues(new_sw);
	    else {
		if ((new_sw->scale.scale_x =
		     atof(new_sw->scale.scale_x_str)) < 0.0) {
		    new_sw->scale.scale_x = cur_sw->scale.scale_x;
		    XtWarning("ScaleValue has to be a positive number.");
		}
		if ((new_sw->scale.scale_y =
		     atof(new_sw->scale.scale_y_str)) < 0.0) {
		    new_sw->scale.scale_y = cur_sw->scale.scale_y;
		    XtWarning("ScaleValue has to be a positive number.");
		}
	    }
	    if (new_sw->scale.scale_x != cur_sw->scale.scale_x
		||
		new_sw->scale.scale_y != cur_sw->scale.scale_y) {

		/*?*?*?*?*?*?*?*?*?*?*?*?*?*?**?*?*?*?*?*?*?*?*?***?*/
		fprintf(stderr, "================>>%f %f\n",
			new_sw->scale.scale_x, new_sw->scale.scale_y);

		if (new_sw->scale.proportional) Proportional(new_sw);
		Precision(new_sw);
		if (new_sw->scale.resize)
		    TryResize(new_sw);
		GetScaledSize(new_sw);
		BuildTable(new_sw);
		PositionImage(new_sw);
		redisplay = True;
	    }
	}

	if (streq(XtNprecision, args[i].name)) {
	    if ((new_sw->scale.precision =
		 atof(new_sw->scale.precision_str)) < 0.0) {
		new_sw->scale.precision = cur_sw->scale.precision;
		XtWarning("Precision has to be a positive number.");
	    }
	    if (new_sw->scale.precision != cur_sw->scale.precision) {
		if (new_sw->scale.proportional) Proportional(new_sw);
		Precision(new_sw);
		GetScaledSize(new_sw);
		BuildTable(new_sw);
		PositionImage(new_sw);
		redisplay = True;
	    }
	}
    }
    return(redisplay);
}



void
SWUnscale(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    ScaleWidget sw = (ScaleWidget) w;

    Unscale(sw);
    PositionImage(sw);
    XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);
}



void
SWAutoscale(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    ScaleWidget sw = (ScaleWidget) w;

    Autoscale(sw);
    PositionImage(sw);
    XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);
}



void
SWInitialSize(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    ScaleWidget sw = (ScaleWidget) w;

    GetInitialScaleValues(sw);

    if (sw->scale.proportional) Proportional(sw);
    Precision(sw);
    if (sw->scale.resize)
	TryResize(sw);
    GetScaledSize(sw);
    BuildTable(sw);
    PositionImage(sw);
    XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);
}



void
SWSetImage(Widget w, XImage *image)
{
    int n;
    Arg wargs[2];

    n = 0;
    XtSetArg(wargs[n], XtNimage, (XtArgVal) image); n++;
    XtSetValues(w, wargs, n);
}




void
RequestSelection(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    SWRequestSelection(w, event->xbutton.time);
}



void
GrabSelection(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    SWGrabSelection(w, event->xbutton.time);
}



Pixmap
SWGetPixmap(Widget w)
{
    ScaleWidget sw = (ScaleWidget) w;
    Pixmap pixmap;

    pixmap = XCreatePixmap(XtDisplay(w), XtWindow(w),
			   sw->scale.width,
			   sw->scale.height,
			   sw->scale.image->depth);
    ScaleImage(sw, pixmap,
	       0, 0, 0, 0,
	       (Dimension) sw->scale.image->width,
	       (Dimension) sw->scale.image->height);

    return(pixmap);
}
