/*
 * $Header: /home/dmarkle/xtrkcad-fork-cvs/xtrkcad/app/wlib/gtklib/gtkdraw-cairo.c,v 1.11 2009-10-03 17:34:37 dspagnol Exp $
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <unistd.h>
#include <string.h>
#include <math.h>

#include "gtkint.h"
#include "gdk/gdkkeysyms.h"


#define CENTERMARK_LENGTH (6)

static long drawVerbose = 0;

struct wDrawBitMap_t {
		int w;
		int h;
		int x;
		int y;
		const char * bits;
		GdkPixmap * pixmap;
		GdkBitmap * mask;
		};

//struct wDraw_t {
		//WOBJ_COMMON
		//void * context;
		//wDrawActionCallBack_p action;
		//wDrawRedrawCallBack_p redraw;

		//GdkPixmap * pixmap;
		//GdkPixmap * pixmapBackup;

		//double dpi;

		//GdkGC * gc;
		//wDrawWidth lineWidth;
		//wDrawOpts opts;
		//wPos_t maxW;
		//wPos_t maxH;
		//unsigned long lastColor;
		//wBool_t lastColorInverted;
		//const char * helpStr;

		//wPos_t lastX;
		//wPos_t lastY;

		//wBool_t delayUpdate;
		//};

struct wDraw_t psPrint_d;

/*****************************************************************************
 *
 * MACROS
 *
 */

#define LBORDER (22)
#define RBORDER (6)
#define TBORDER (6)
#define BBORDER (20)

#define INMAPX(D,X)	(X)
#define INMAPY(D,Y)	(((D)->h-1) - (Y))
#define OUTMAPX(D,X)	(X)
#define OUTMAPY(D,Y)	(((D)->h-1) - (Y))


/*******************************************************************************
 *
 * Basic Drawing Functions
 *
*******************************************************************************/



static GdkGC * selectGC(
		wDraw_p bd,
		wDrawWidth width,
		wDrawLineType_e lineType,
		wDrawColor color,
		wDrawOpts opts )
{
	if(width < 0.0)
	{
		width = - width;
	}

	if(opts & wDrawOptTemp)
	{
		if(bd->lastColor != color || !bd->lastColorInverted)
		{
			gdk_gc_set_foreground( bd->gc, gtkGetColor(color,FALSE) );
			bd->lastColor = color;
			bd->lastColorInverted = TRUE;
		}
		gdk_gc_set_function( bd->gc, GDK_XOR );
		gdk_gc_set_line_attributes( bd->gc, width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER );
	}
	else
	{
		if(bd->lastColor != color || bd->lastColorInverted)
		{
			gdk_gc_set_foreground( bd->gc, gtkGetColor(color,TRUE) );
			bd->lastColor = color;
			bd->lastColorInverted = FALSE;
		}
		gdk_gc_set_function( bd->gc, GDK_COPY );
		if (lineType==wDrawLineDash)
		{
			gdk_gc_set_line_attributes( bd->gc, width, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER );
		}
		else
		{
			gdk_gc_set_line_attributes( bd->gc, width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER );
		}

		gdk_gc_set_function(bd->gc, GDK_NOOP);
	}
	return bd->gc;
}

static cairo_t* gtkDrawCreateCairoContext(
		wDraw_p bd,
		wDrawWidth width,
		wDrawLineType_e lineType,
		wDrawColor color,
		wDrawOpts opts )
{
	cairo_t* cairo = gdk_cairo_create(bd->pixmap);

	width = width ? abs(width) : 1;
	cairo_set_line_width(cairo, width);

	cairo_set_line_cap(cairo, CAIRO_LINE_CAP_BUTT);
	cairo_set_line_join(cairo, CAIRO_LINE_JOIN_MITER);

	switch(lineType)
	{
		case wDrawLineSolid:
		{
			cairo_set_dash(cairo, 0, 0, 0);
			break;
		}
		case wDrawLineDash:
		{
			double dashes[] = { 5, 5 };
			cairo_set_dash(cairo, dashes, 2, 0);
			break;
		}
	}

	if(opts & wDrawOptTemp)
	{
		cairo_set_source_rgba(cairo, 0, 0, 0, 0);
	}
	else
	{
		GdkColor* const gcolor = gtkGetColor(color, TRUE);
		cairo_set_source_rgb(cairo, gcolor->red / 65535.0, gcolor->green / 65535.0, gcolor->blue / 65535.0);

		cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
	}

	return cairo;
}

static cairo_t* gtkDrawDestroyCairoContext(cairo_t *cairo) {
	cairo_destroy(cairo);
}

EXPORT void wDrawDelayUpdate(
		wDraw_p bd,
		wBool_t delay )
{
	GdkRectangle update_rect;

	if ( (!delay) && bd->delayUpdate ) {
		update_rect.x = 0;
		update_rect.y = 0;
		update_rect.width = bd->w;
		update_rect.height = bd->h;
		gtk_widget_draw( bd->widget, &update_rect );
	}
	bd->delayUpdate = delay;
}


EXPORT void wDrawLine(
		wDraw_p bd,
		wPos_t x0, wPos_t y0,
		wPos_t x1, wPos_t y1,
		wDrawWidth width,
		wDrawLineType_e lineType,
		wDrawColor color,
		wDrawOpts opts )
{
	GdkGC * gc;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		psPrintLine( x0, y0, x1, y1, width, lineType, color, opts );
		return;
	}
	gc = selectGC( bd, width, lineType, color, opts );
	x0 = INMAPX(bd,x0);
	y0 = INMAPY(bd,y0);
	x1 = INMAPX(bd,x1);
	y1 = INMAPY(bd,y1);
	gdk_draw_line( bd->pixmap, gc, x0, y0, x1, y1 );

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, width, lineType, color, opts);
	cairo_move_to(cairo, x0 + 0.5, y0 + 0.5);
	cairo_line_to(cairo, x1 + 0.5, y1 + 0.5);
	cairo_stroke(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL ) return;
	width /= 2;
	if (x0 < x1) {
		update_rect.x = x0-1-width;
		update_rect.width = x1-x0+2+width+width;
	} else {
		update_rect.x = x1-1-width;
		update_rect.width = x0-x1+2+width+width;
	}
	if (y0 < y1) {
		update_rect.y = y0-1-width;
		update_rect.height = y1-y0+2+width+width;
	} else {
		update_rect.y = y1-1-width;
		update_rect.height = y0-y1+2+width+width;
	}
	gtk_widget_draw( bd->widget, &update_rect );
}

/**
 * Draw an arc around a specified center
 *
 * \param bd IN ?
 * \param x0, y0 IN  center of arc
 * \param r IN radius
 * \param angle0, angle1 IN start and end angle
 * \param drawCenter draw marking for center
 * \param width line width
 * \param lineType
 * \param color color
 * \param opts ?
 */


EXPORT void wDrawArc(
		wDraw_p bd,
		wPos_t x0, wPos_t y0,
		wPos_t r,
		wAngle_t angle0,
		wAngle_t angle1,
		int drawCenter,
		wDrawWidth width,
		wDrawLineType_e lineType,
		wDrawColor color,
		wDrawOpts opts )
{
	int x, y, w, h;
	GdkGC * gc;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		psPrintArc( x0, y0, r, angle0, angle1, drawCenter, width, lineType, color, opts );
		return;
	}
	gc = selectGC( bd, width, lineType, color, opts );
	if (r < 6.0/75.0) return;
	x = INMAPX(bd,x0-r);
	y = INMAPY(bd,y0+r);
	w = 2*r;
	h = 2*r;

	// remove the old arc
	gdk_draw_arc( bd->pixmap, gc, FALSE, x, y, w, h, (int)((-angle0 + 90)*64.0), (int)(-angle1*64.0) );

	// and its center point
	if (drawCenter) {
		x = INMAPX(bd,x0);
		y = INMAPY(bd,y0);
		gdk_draw_line( bd->pixmap, gc, x - ( CENTERMARK_LENGTH/2), y, x + ( CENTERMARK_LENGTH/2), y );
		gdk_draw_line( bd->pixmap, gc, x, y - ( CENTERMARK_LENGTH/2), x, y + ( CENTERMARK_LENGTH/2));
	}

	// now create the new arc
	cairo_t* cairo = gtkDrawCreateCairoContext(bd, width, lineType, color, opts);
	cairo_new_path(cairo);

	// its center point marker
	if(drawCenter)
	{
		// draw a small crosshair to mark the center of the curve
		cairo_move_to(cairo,  INMAPX(bd, x0 - (CENTERMARK_LENGTH / 2)), INMAPY(bd, y0 ));
		cairo_line_to(cairo, INMAPX(bd, x0 + (CENTERMARK_LENGTH / 2)), INMAPY(bd, y0 ));
		cairo_move_to(cairo, INMAPX(bd, x0), INMAPY(bd, y0 - (CENTERMARK_LENGTH / 2 )));
		cairo_line_to(cairo, INMAPX(bd, x0) , INMAPY(bd, y0  + (CENTERMARK_LENGTH / 2)));
		cairo_new_sub_path( cairo );
	}

	// draw the curve itself
	cairo_arc_negative(cairo, INMAPX(bd, x0), INMAPY(bd, y0), r, (angle0 - 90 + angle1) * (M_PI / 180.0), (angle0 - 90) * (M_PI / 180.0));
	cairo_stroke(cairo);

	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	width /= 2;
	update_rect.x = x-1-width;
	update_rect.y = y-1-width;
	update_rect.width = w+2+width+width;
	update_rect.height = h+2+width+width;
	gtk_widget_draw( bd->widget, &update_rect );

}

EXPORT void wDrawPoint(
		wDraw_p bd,
		wPos_t x0, wPos_t y0,
		wDrawColor color,
		wDrawOpts opts )
{
	GdkGC * gc;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		/*psPrintArc( x0, y0, r, angle0, angle1, drawCenter, width, lineType, color, opts );*/
		return;
	}
	gc = selectGC( bd, 0, wDrawLineSolid, color, opts );
	gdk_draw_point( bd->pixmap, gc, INMAPX(bd, x0 ), INMAPY(bd, y0 ) );

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, color, opts);
	cairo_new_path(cairo);
	cairo_arc(cairo, INMAPX(bd, x0), INMAPY(bd, y0), 0.75, 0, 2 * M_PI);
	cairo_stroke(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	update_rect.x = INMAPX(bd, x0 )-1;
	update_rect.y = INMAPY(bd, y0 )-1;
	update_rect.width = 2;
	update_rect.height = 2;
	gtk_widget_draw( bd->widget, &update_rect );
}

/*******************************************************************************
 *
 * Strings
 *
 ******************************************************************************/

EXPORT void wDrawString(
		wDraw_p bd,
		wPos_t x, wPos_t y,
		wAngle_t a,
		const char * s,
		wFont_p fp,
		wFontSize_t fs,
		wDrawColor color,
		wDrawOpts opts )
{
	PangoLayout *layout;
	GdkRectangle update_rect;
	int w;
	int h;
	gint ascent;
	gint descent;
	double angle = -M_PI * a / 180.0;

	if ( bd == &psPrint_d ) {
		psPrintString( x, y, a, (char *) s, fp, fs, color, opts );
		return;
	}

	x = INMAPX(bd,x);
	y = INMAPY(bd,y);

	/* draw text */
	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, color, opts);

	cairo_save( cairo );
	cairo_translate( cairo, x, y );
	cairo_rotate( cairo, angle );

	layout = gtkFontCreatePangoLayout(bd->widget, cairo, fp, fs, s,
									  (int *) &w, (int *) &h,
									  (int *) &ascent, (int *) &descent);

	/* cairo does not support the old method of text removal by overwrite; force always write here and
           refresh on cancel event */
	GdkColor* const gcolor = gtkGetColor(color, TRUE);
	cairo_set_source_rgb(cairo, gcolor->red / 65535.0, gcolor->green / 65535.0, gcolor->blue / 65535.0);

	cairo_move_to( cairo, 0, -ascent );

	pango_cairo_show_layout(cairo, layout);
	gtkFontDestroyPangoLayout(layout);
	cairo_restore( cairo );
	gtkDrawDestroyCairoContext(cairo);

	if (bd->delayUpdate || bd->widget == NULL) return;

	/* recalculate the area to be updated
	 * for simplicity sake I added plain text height ascent and descent,
	 * mathematically correct would be to use the trigonometrical functions as well
	 */
	update_rect.x      = (gint) x - ascent - descent - 1;
	update_rect.y      = (gint) y - (gint) ascent - 1;
	update_rect.width  = (gint) (w * cos( angle ) + 2 + ascent + descent);
	update_rect.height = (gint) (w * sin( angle ) + ascent + descent + 2 );
	gtk_widget_draw(bd->widget, &update_rect);
}

EXPORT void wDrawGetTextSize(
		wPos_t *w,
		wPos_t *h,
		wPos_t *d,
		wDraw_p bd,
		const char * s,
		wFont_p fp,
		wFontSize_t fs )
{
	int textWidth;
	int textHeight;
	int ascent;
	int descent;

	*w = 0;
	*h = 0;

	gtkFontDestroyPangoLayout(
		gtkFontCreatePangoLayout(bd->widget, NULL, fp, fs, s,
								 &textWidth, (int *) &textHeight,
								 (int *) &ascent, (int *) &descent));

	*w = (wPos_t) textWidth;
	*h = (wPos_t) ascent;
	*d = (wPos_t) descent;

	if (debugWindow >= 3)
		fprintf(stderr, "text metrics: w=%d, h=%d, d=%d\n", *w, *h, *d);
}


/*******************************************************************************
 *
 * Basic Drawing Functions
 *
*******************************************************************************/

EXPORT void wDrawFilledRectangle(
		wDraw_p bd,
		wPos_t x,
		wPos_t y,
		wPos_t w,
		wPos_t h,
		wDrawColor color,
		wDrawOpts opt )
{
	GdkGC * gc;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		psPrintFillRectangle( x, y, w, h, color, opt );
		return;
	}

	gc = selectGC( bd, 0, wDrawLineSolid, color, opt );
	x = INMAPX(bd,x);
	y = INMAPY(bd,y)-h;
	gdk_draw_rectangle( bd->pixmap, gc, TRUE, x, y, w, h );

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, color, opt);

	cairo_move_to(cairo, x, y);
	cairo_rel_line_to(cairo, w, 0);
	cairo_rel_line_to(cairo, 0, h);
	cairo_rel_line_to(cairo, -w, 0);
	cairo_fill(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	update_rect.x = x-1;
	update_rect.y = y-1;
	update_rect.width = w+2;
	update_rect.height = h+2;
	gtk_widget_draw( bd->widget, &update_rect );
}

EXPORT void wDrawFilledPolygon(
		wDraw_p bd,
		wPos_t p[][2],
		int cnt,
		wDrawColor color,
		wDrawOpts opt )
{
	GdkGC * gc;
	static int maxCnt = 0;
	static GdkPoint *points;
	int i;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		psPrintFillPolygon( p, cnt, color, opt );
		return;
	}

	if (cnt > maxCnt) {
		if (points == NULL)
			points = (GdkPoint*)malloc( cnt*sizeof *points );
		else
			points = (GdkPoint*)realloc( points, cnt*sizeof *points );
		if (points == NULL)
			abort();
		maxCnt = cnt;
	}

	update_rect.x = bd->w;
	update_rect.y = bd->h;
	update_rect.width = 0;
	update_rect.height = 0;
	for (i=0; i<cnt; i++) {
		points[i].x = INMAPX(bd,p[i][0]);
		points[i].y = INMAPY(bd,p[i][1]);
		if (update_rect.x > points[i].x)
			update_rect.x = points[i].x;
		if (update_rect.width < points[i].x)
			update_rect.width = points[i].x;
		if (update_rect.y > points[i].y)
			update_rect.y = points[i].y;
		if (update_rect.height < points[i].y)
			update_rect.height = points[i].y;
	}
	update_rect.x -= 1;
	update_rect.y -= 1;
	update_rect.width -= update_rect.x-2;
	update_rect.height -= update_rect.y-2;
	gc = selectGC( bd, 0, wDrawLineSolid, color, opt );
	gdk_draw_polygon( bd->pixmap, gc, TRUE,	points, cnt );

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, color, opt);
	for(i = 0; i < cnt; ++i)
	{
		if(i)
			cairo_line_to(cairo, points[i].x, points[i].y);
		else
			cairo_move_to(cairo, points[i].x, points[i].y);
	}
	cairo_fill(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	gtk_widget_draw( bd->widget, &update_rect );
}

EXPORT void wDrawFilledCircle(
		wDraw_p bd,
		wPos_t x0,
		wPos_t y0,
		wPos_t r,
		wDrawColor color,
		wDrawOpts opt )
{
	GdkGC * gc;
	int x, y, w, h;
	GdkRectangle update_rect;

	if ( bd == &psPrint_d ) {
		psPrintFillCircle( x0, y0, r, color, opt );
		return;
	}

	gc = selectGC( bd, 0, wDrawLineSolid, color, opt );
	x = INMAPX(bd,x0-r);
	y = INMAPY(bd,y0+r);
	w = 2*r;
	h = 2*r;
	gdk_draw_arc( bd->pixmap, gc, TRUE, x, y, w, h, 0, 360*64 );

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, color, opt);
	cairo_arc(cairo, INMAPX(bd, x0), INMAPY(bd, y0), r, 0, 2 * M_PI);
	cairo_fill(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	update_rect.x = x-1;
	update_rect.y = y-1;
	update_rect.width = w+2;
	update_rect.height = h+2;
	gtk_widget_draw( bd->widget, &update_rect );

}


EXPORT void wDrawClear(
		wDraw_p bd )
{
	GdkGC * gc;
	GdkRectangle update_rect;

	gc = selectGC( bd, 0, wDrawLineSolid, wDrawColorWhite, 0 );
	gdk_draw_rectangle(bd->pixmap, gc, TRUE, 0, 0, bd->w, bd->h);

	cairo_t* cairo = gtkDrawCreateCairoContext(bd, 0, wDrawLineSolid, wDrawColorWhite, 0);
	cairo_move_to(cairo, 0, 0);
	cairo_rel_line_to(cairo, bd->w, 0);
	cairo_rel_line_to(cairo, 0, bd->h);
	cairo_rel_line_to(cairo, -bd->w, 0);
	cairo_fill(cairo);
	gtkDrawDestroyCairoContext(cairo);

	if ( bd->delayUpdate || bd->widget == NULL) return;
	update_rect.x = 0;
	update_rect.y = 0;
	update_rect.width = bd->w;
	update_rect.height = bd->h;
	gtk_widget_draw( bd->widget, &update_rect );
}

EXPORT void * wDrawGetContext(
		wDraw_p bd )
{
	return bd->context;
}

/*******************************************************************************
 *
 * Bit Maps
 *
*******************************************************************************/


EXPORT wDrawBitMap_p wDrawBitMapCreate(
		wDraw_p bd,
		int w,
		int h,
		int x,
		int y,
		const char * fbits )
{
	wDrawBitMap_p bm;

	bm = (wDrawBitMap_p)malloc( sizeof *bm );
	bm->w = w;
	bm->h = h;
	/*bm->pixmap = gtkMakeIcon( NULL, fbits, w, h, wDrawColorBlack, &bm->mask );*/
	bm->bits = fbits;
	bm->x = x;
	bm->y = y;
	return bm;
}


EXPORT void wDrawBitMap(
		wDraw_p bd,
		wDrawBitMap_p bm,
		wPos_t x, wPos_t y,
		wDrawColor color,
		wDrawOpts opts )
{
	GdkGC * gc;
	GdkRectangle update_rect;
	int i, j, wb;
	wPos_t xx, yy;
	wControl_p b;
	GdkDrawable * gdk_window;

	x = INMAPX( bd, x-bm->x );
	y = INMAPY( bd, y-bm->y )-bm->h;
	wb = (bm->w+7)/8;
	gc = selectGC( bd, 0, wDrawLineSolid, color, opts );

	for ( i=0; i<bm->w; i++ )
		for ( j=0; j<bm->h; j++ )
			if ( bm->bits[ j*wb+(i>>3) ] & (1<<(i&07)) ) {
				xx = x+i;
				yy = y+j;
				if ( 0 <= xx && xx < bd->w &&
					 0 <= yy && yy < bd->h ) {
					gdk_window = bd->pixmap;
					b = (wControl_p)bd;
				} else if ( (opts&wDrawOptNoClip) != 0 ) {
					xx += bd->realX;
					yy += bd->realY;
					b = gtkGetControlFromPos( bd->parent, xx, yy );
					if ( b ) {
						if ( b->type == B_DRAW )
							gdk_window = ((wDraw_p)b)->pixmap;
						else
							gdk_window = b->widget->window;
						xx -= b->realX;
						yy -= b->realY;
					} else {
						gdk_window = bd->parent->widget->window;
					}
				} else {
					continue;
				}
/*printf( "gdk_draw_point( %ld, gc, %d, %d )\n", (long)gdk_window, xx, yy );*/
				gdk_draw_point( gdk_window, gc, xx, yy );
				if ( b && b->type == B_DRAW ) {
					update_rect.x = xx-1;
					update_rect.y = yy-1;
					update_rect.width = 3;
					update_rect.height = 3;
					gtk_widget_draw( b->widget, &update_rect );
				}
			}
#ifdef LATER
	gdk_draw_pixmap(bd->pixmap, gc,
		bm->pixmap,
		0, 0,
		x, y,
		bm->w, bm->h );
#endif
	if ( bd->delayUpdate || bd->widget == NULL) return;

	update_rect.x = x;
	update_rect.y = y;
	update_rect.width = bm->w;
	update_rect.height = bm->h;
	gtk_widget_draw( bd->widget, &update_rect );
}


/*******************************************************************************
 *
 * Event Handlers
 *
*******************************************************************************/



EXPORT void wDrawSaveImage(
		wDraw_p bd )
{
	if ( bd->pixmapBackup ) {
		gdk_pixmap_unref( bd->pixmapBackup );
	}
	bd->pixmapBackup = gdk_pixmap_new( bd->widget->window, bd->w, bd->h, -1 );

	selectGC( bd, 0, wDrawLineSolid, bd->lastColor, 0 );
	gdk_gc_set_function(bd->gc, GDK_COPY);

	gdk_draw_pixmap( bd->pixmapBackup, bd->gc,
				bd->pixmap,
				0, 0,
				0, 0,
				bd->w, bd->h );
}


EXPORT void wDrawRestoreImage(
		wDraw_p bd )
{
	GdkRectangle update_rect;
	if ( bd->pixmapBackup ) {

		selectGC( bd, 0, wDrawLineSolid, bd->lastColor, 0 );
		gdk_gc_set_function(bd->gc, GDK_COPY);

		gdk_draw_pixmap( bd->pixmap, bd->gc,
				bd->pixmapBackup,
				0, 0,
				0, 0,
				bd->w, bd->h );

		if ( bd->delayUpdate || bd->widget == NULL ) return;
		update_rect.x = 0;
		update_rect.y = 0;
		update_rect.width = bd->w;
		update_rect.height = bd->h;
		gtk_widget_draw( bd->widget, &update_rect );
	}
}


EXPORT void wDrawSetSize(
		wDraw_p bd,
		wPos_t w,
		wPos_t h )
{
	wBool_t repaint;
	if (bd == NULL) {
		fprintf(stderr,"resizeDraw: no client data\n");
		return;
	}

	/* Negative values crashes the program */
	if (w < 0 || h < 0)
		return;

	repaint = (w != bd->w || h != bd->h);
	bd->w = w;
	bd->h = h;
	gtk_widget_set_size_request( bd->widget, w, h );
	if (repaint)
	{
		if (bd->pixmap)
			gdk_pixmap_unref( bd->pixmap );
		bd->pixmap = gdk_pixmap_new( bd->widget->window, w, h, -1 );

		wDrawClear( bd );
		/*bd->redraw( bd, bd->context, w, h );*/
	}
	/*wRedraw( bd );*/
}


EXPORT void wDrawGetSize(
		wDraw_p bd,
		wPos_t *w,
		wPos_t *h )
{
	if (bd->widget)
		gtkControlGetSize( (wControl_p)bd );
	*w = bd->w-2;
	*h = bd->h-2;
}

/**
 * Return the resolution of a device in dpi
 *
 * \param d IN the device
 * \return    the resolution in dpi
 */

EXPORT double wDrawGetDPI(
		wDraw_p d )
{
	//if (d == &psPrint_d)
		//return 1440.0;
	//else
		return d->dpi;
}


EXPORT double wDrawGetMaxRadius(
		wDraw_p d )
{
	if (d == &psPrint_d)
		return 10e9;
	else
		return 32767.0;
}


EXPORT void wDrawClip(
		wDraw_p d,
		wPos_t x,
		wPos_t y,
		wPos_t w,
		wPos_t h )
{
	GdkRectangle rect;
	rect.width = w;
	rect.height = h;
	rect.x = INMAPX( d, x );
	rect.y = INMAPY( d, y ) - rect.height;
	gdk_gc_set_clip_rectangle( d->gc, &rect );

}


static gint draw_expose_event(
		GtkWidget *widget,
		GdkEventExpose *event,
		wDraw_p bd)
{
	gdk_draw_pixmap(widget->window,
		widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		bd->pixmap,
		event->area.x, event->area.y,
		event->area.x, event->area.y,
		event->area.width, event->area.height);
	return FALSE;
}


static gint draw_configure_event(
		GtkWidget *widget,
		GdkEventConfigure *event,
		wDraw_p bd)
{
	return FALSE;
}

static const char * actionNames[] = { "None", "Move", "LDown", "LDrag", "LUp", "RDown", "RDrag", "RUp", "Text", "ExtKey", "WUp", "WDown" };

/**
 * Handler for scroll events, ie mouse wheel activity
 */

static gint draw_scroll_event(
		GtkWidget *widget,
		GdkEventScroll *event,
		wDraw_p bd)
{
	wAction_t action;

	switch( event->direction ) {
	case GDK_SCROLL_UP:
		action = wActionWheelUp;
		break;
	case GDK_SCROLL_DOWN:
		action = wActionWheelDown;
		break;
	default:
		action = 0;
		break;
	}

	if (action != 0) {
		if (drawVerbose >= 2)
			printf( "%s[%dx%d]\n", actionNames[action], bd->lastX, bd->lastY );
		bd->action( bd, bd->context, action, bd->lastX, bd->lastY );
	}

	return TRUE;
}



static gint draw_leave_event(
		GtkWidget *widget,
		GdkEvent * event )
{
	gtkHelpHideBalloon();
	return FALSE;
}


/**
 * Handler for mouse button clicks.
 */

static gint draw_button_event(
		GtkWidget *widget,
		GdkEventButton *event,
		wDraw_p bd )
{
	wAction_t action = 0;
	if (bd->action == NULL)
		return TRUE;

	bd->lastX = OUTMAPX(bd, event->x);
	bd->lastY = OUTMAPY(bd, event->y);

	switch ( event->button ) {
	case 1: /* left mouse button */
		action = event->type==GDK_BUTTON_PRESS?wActionLDown:wActionLUp;
		/*bd->action( bd, bd->context, event->type==GDK_BUTTON_PRESS?wActionLDown:wActionLUp, bd->lastX, bd->lastY );*/
		break;
	case 3: /* right mouse button */
		action = event->type==GDK_BUTTON_PRESS?wActionRDown:wActionRUp;
		/*bd->action( bd, bd->context, event->type==GDK_BUTTON_PRESS?wActionRDown:wActionRUp, bd->lastX, bd->lastY );*/
		break;
	}
	if (action != 0) {
		if (drawVerbose >= 2)
			printf( "%s[%dx%d]\n", actionNames[action], bd->lastX, bd->lastY );
		bd->action( bd, bd->context, action, bd->lastX, bd->lastY );
	}
	gtk_widget_grab_focus( bd->widget );
	return TRUE;
}

static gint draw_motion_event(
		GtkWidget *widget,
		GdkEventMotion *event,
		wDraw_p bd )
{
	int x, y;
	GdkModifierType state;
	wAction_t action;

	if (bd->action == NULL)
		return TRUE;

	if (event->is_hint) {
		gdk_window_get_pointer (event->window, &x, &y, &state);
	} else {
		x = event->x;
		y = event->y;
		state = event->state;
	}

	if (state & GDK_BUTTON1_MASK) {
		action = wActionLDrag;
	} else if (state & GDK_BUTTON3_MASK) {
		action = wActionRDrag;
	} else {
		action = wActionMove;
	}
	bd->lastX = OUTMAPX(bd, x);
	bd->lastY = OUTMAPY(bd, y);
	if (drawVerbose >= 2)
		printf( "%lx: %s[%dx%d] %s\n", (long)bd, actionNames[action], bd->lastX, bd->lastY, event->is_hint?"<Hint>":"<>" );
	bd->action( bd, bd->context, action, bd->lastX, bd->lastY );
	gtk_widget_grab_focus( bd->widget );
	return TRUE;
}


static gint draw_char_event(
		GtkWidget * widget,
		GdkEventKey *event,
		wDraw_p bd )
{
	guint key = event->keyval;
	wAccelKey_e extKey = wAccelKey_None;
	switch (key) {
	case GDK_Escape:	key = 0x1B; break;
	case GDK_Return:	key = 0x0D; break;
	case GDK_Linefeed:	key = 0x0A; break;
	case GDK_Tab:	key = 0x09; break;
	case GDK_BackSpace:	key = 0x08; break;
	case GDK_Delete:    extKey = wAccelKey_Del; break;
	case GDK_Insert:    extKey = wAccelKey_Ins; break;
	case GDK_Home:      extKey = wAccelKey_Home; break;
	case GDK_End:       extKey = wAccelKey_End; break;
	case GDK_Page_Up:   extKey = wAccelKey_Pgup; break;
	case GDK_Page_Down: extKey = wAccelKey_Pgdn; break;
	case GDK_Up:        extKey = wAccelKey_Up; break;
	case GDK_Down:      extKey = wAccelKey_Down; break;
	case GDK_Right:     extKey = wAccelKey_Right; break;
	case GDK_Left:      extKey = wAccelKey_Left; break;
	case GDK_F1:        extKey = wAccelKey_F1; break;
	case GDK_F2:        extKey = wAccelKey_F2; break;
	case GDK_F3:        extKey = wAccelKey_F3; break;
	case GDK_F4:        extKey = wAccelKey_F4; break;
	case GDK_F5:        extKey = wAccelKey_F5; break;
	case GDK_F6:        extKey = wAccelKey_F6; break;
	case GDK_F7:        extKey = wAccelKey_F7; break;
	case GDK_F8:        extKey = wAccelKey_F8; break;
	case GDK_F9:        extKey = wAccelKey_F9; break;
	case GDK_F10:       extKey = wAccelKey_F10; break;
	case GDK_F11:       extKey = wAccelKey_F11; break;
	case GDK_F12:       extKey = wAccelKey_F12; break;
	default: ;
	}

	if (extKey != wAccelKey_None) {
		if ( gtkFindAccelKey( event ) == NULL ) {
			bd->action( bd, bd->context, wActionExtKey + ((int)extKey<<8), bd->lastX, bd->lastY );
		}
		return TRUE;
	} else if (key <= 0xFF && (event->state&(GDK_CONTROL_MASK|GDK_MOD1_MASK)) == 0 && bd->action) {
		bd->action( bd, bd->context, wActionText+(key<<8), bd->lastX, bd->lastY );
		return TRUE;
	} else {
		return FALSE;
	}
}


/*******************************************************************************
 *
 * Create
 *
*******************************************************************************/



int XW = 0;
int XH = 0;
int xw, xh, cw, ch;

EXPORT wDraw_p wDrawCreate(
		wWin_p	parent,
		wPos_t	x,
		wPos_t	y,
		const char 	* helpStr,
		long	option,
		wPos_t	width,
		wPos_t	height,
		void	* context,
		wDrawRedrawCallBack_p redraw,
		wDrawActionCallBack_p action )
{
	wDraw_p bd;

	bd = (wDraw_p)gtkAlloc( parent,  B_DRAW, x, y, NULL, sizeof *bd, NULL );
	bd->option = option;
	bd->context = context;
	bd->redraw = redraw;
	bd->action = action;
	gtkComputePos( (wControl_p)bd );

	bd->widget = gtk_drawing_area_new();
	gtk_drawing_area_size( GTK_DRAWING_AREA(bd->widget), width, height );
	gtk_widget_set_size_request( GTK_WIDGET(bd->widget), width, height );
	gtk_signal_connect (GTK_OBJECT (bd->widget), "expose_event",
						   (GtkSignalFunc) draw_expose_event, bd);
	gtk_signal_connect (GTK_OBJECT(bd->widget),"configure_event",
						   (GtkSignalFunc) draw_configure_event, bd);
	gtk_signal_connect (GTK_OBJECT (bd->widget), "motion_notify_event",
						   (GtkSignalFunc) draw_motion_event, bd);
	gtk_signal_connect (GTK_OBJECT (bd->widget), "button_press_event",
						   (GtkSignalFunc) draw_button_event, bd);
	gtk_signal_connect (GTK_OBJECT (bd->widget), "button_release_event",
						   (GtkSignalFunc) draw_button_event, bd);
	gtk_signal_connect (GTK_OBJECT (bd->widget), "scroll_event",
						   (GtkSignalFunc) draw_scroll_event, bd);
	gtk_signal_connect_after (GTK_OBJECT (bd->widget), "key_press_event",
						   (GtkSignalFunc) draw_char_event, bd);
	gtk_signal_connect (GTK_OBJECT (bd->widget), "leave_notify_event",
						   (GtkSignalFunc) draw_leave_event, bd);
	GTK_WIDGET_SET_FLAGS(GTK_WIDGET(bd->widget), GTK_CAN_FOCUS);
	gtk_widget_set_events (bd->widget, GDK_EXPOSURE_MASK
							  | GDK_LEAVE_NOTIFY_MASK
							  | GDK_BUTTON_PRESS_MASK
							  | GDK_BUTTON_RELEASE_MASK
/*							  | GDK_SCROLL_MASK */
							  | GDK_POINTER_MOTION_MASK
							  | GDK_POINTER_MOTION_HINT_MASK
							  | GDK_KEY_PRESS_MASK
							  | GDK_KEY_RELEASE_MASK );
	bd->lastColor = -1;
	bd->dpi = 75;
	bd->maxW = bd->w = width;
	bd->maxH = bd->h = height;

	gtk_fixed_put( GTK_FIXED(parent->widget), bd->widget, bd->realX, bd->realY );
	gtkControlGetSize( (wControl_p)bd );
	gtk_widget_realize( bd->widget );
	bd->pixmap = gdk_pixmap_new( bd->widget->window, width, height, -1 );
	bd->gc = gdk_gc_new( parent->gtkwin->window );
	gdk_gc_copy( bd->gc, parent->gtkwin->style->base_gc[GTK_STATE_NORMAL] );
{
	GdkCursor * cursor;
	cursor = gdk_cursor_new ( GDK_TCROSS );
	gdk_window_set_cursor ( bd->widget->window, cursor);
	gdk_cursor_destroy (cursor);
}
#ifdef LATER
	if (labelStr)
		bd->labelW = gtkAddLabel( (wControl_p)bd, labelStr );
#endif
	gtk_widget_show( bd->widget );
	gtkAddButton( (wControl_p)bd );
	gtkAddHelpString( bd->widget, helpStr );

	return bd;
}

/*******************************************************************************
 *
 * BitMaps
 *
*******************************************************************************/

wDraw_p wBitMapCreate(          wPos_t w, wPos_t h, int arg )
{
	wDraw_p bd;

	bd = (wDraw_p)gtkAlloc( gtkMainW,  B_DRAW, 0, 0, NULL, sizeof *bd, NULL );

	bd->lastColor = -1;
	bd->dpi = 75;
	bd->maxW = bd->w = w;
	bd->maxH = bd->h = h;

	bd->pixmap = gdk_pixmap_new( gtkMainW->widget->window, w, h, -1 );
	if ( bd->pixmap == NULL ) {
		wNoticeEx( NT_ERROR, "CreateBitMap: pixmap_new failed", "Ok", NULL );
		return FALSE;
	}
	bd->gc = gdk_gc_new( gtkMainW->gtkwin->window );
	if ( bd->gc == NULL ) {
		wNoticeEx( NT_ERROR, "CreateBitMap: gc_new failed", "Ok", NULL );
		return FALSE;
	}
	gdk_gc_copy( bd->gc, gtkMainW->gtkwin->style->base_gc[GTK_STATE_NORMAL] );

	wDrawClear( bd );
	return bd;
}


wBool_t wBitMapDelete(          wDraw_p d )
{
	gdk_pixmap_unref( d->pixmap );
	d->pixmap = NULL;
	return TRUE;
}