/***************************************************************************

  CDraw.cpp

  (c) 2004-2006 - Daniel Campos Fernández <dcamposf@gmail.com>

  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, 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., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __CDRAW_CPP

#include "CWindow.h"
#include "CDrawingArea.h"
#include "CPicture.h"
#include "CImage.h"
#include "CFont.h"
#include "CDraw.h"
#include "gdesktop.h"

typedef
	struct {
		gDraw *dr;
		CFONT *font;
		}
	GB_DRAW_EXTRA;

#define EXTRA(d) ((GB_DRAW_EXTRA *)(d->extra))
#define DR(d) (EXTRA(d)->dr)
 
DRAW_INTERFACE DRAW EXPORT;

void DRAW_init()
{
	GB.GetInterface("gb.draw", DRAW_INTERFACE_VERSION, &DRAW);
}

static int begin(GB_DRAW *d)
{
	gDraw *dr;
	
	EXTRA(d)->dr = dr = new gDraw();
	EXTRA(d)->font = NULL;
	
	/*if (GB.Is(d->device, CLASS_Window))
		dr->connect(((CWINDOW *)d->device)->ob.widget);
	else */
	if (GB.Is(d->device, CLASS_DrawingArea))
	{
		gDrawingArea *da = (gDrawingArea *)((CDRAWINGAREA *)d->device)->ob.widget;
		if (da->cached() || da->inDrawEvent())
			dr->connect(((CDRAWINGAREA *)d->device)->ob.widget);
		else
		{
			GB.Error("Cannot draw outside of Draw event handler");
			return TRUE;
		}
	}
	else if (GB.Is(d->device, CLASS_Picture))
	{
		gPicture *pic = ((CPICTURE*)d->device)->picture;
		if (pic->isVoid())
		{
			GB.Error("Bad picture");
			return TRUE;
		}
		dr->connect(pic);
	}
		
	d->width = dr->width();
	d->height = dr->height();
	d->resolution = dr->resolution();
	return FALSE;
}

static void end(GB_DRAW *d)
{
	gDraw *dr = DR(d);
	GB.Unref(POINTER(&(EXTRA(d)->font)));
	EXTRA(d)->font = NULL;
	delete dr;
}

static void save(GB_DRAW *d)
{
	DR(d)->save();
}

static void restore(GB_DRAW *d)
{
	DR(d)->restore();
}

static int get_background(GB_DRAW *d)
{
	return DR(d)->background();
}

static void set_background(GB_DRAW *d, int col)
{
	DR(d)->setBackground(col);
}

static int get_foreground(GB_DRAW *d)
{
	return DR(d)->foreground();
}

static void set_foreground(GB_DRAW *d, int col)
{
	DR(d)->setForeground(col);
}

static void apply_font(gFont *font, void *object = 0)
{
	GB_DRAW *d = DRAW.GetCurrent();
	if (!d)
		return;
		
  DR(d)->setFont(font);
}

static GB_FONT get_font(GB_DRAW *d)
{
	if (!EXTRA(d)->font)
	{
		EXTRA(d)->font = CFONT_create(DR(d)->font()->copy(), apply_font);
		GB.Ref(EXTRA(d)->font);
	}
		
	return (GB_FONT)EXTRA(d)->font;
}

static void set_font(GB_DRAW *d, GB_FONT font)
{
	if (font)
		apply_font(((CFONT*)font)->font);
}

static int is_inverted(GB_DRAW *d)
{
	return DR(d)->invert();
}

static void set_inverted(GB_DRAW *d, int inverted)
{
	DR(d)->setInvert(inverted);
}

static int is_transparent(GB_DRAW *d)
{
	return DR(d)->isTransparent();
}

static void set_transparent(GB_DRAW *d, int transparent)
{
	DR(d)->setTransparent(transparent);
}

static void get_picture_info(GB_DRAW *d, GB_PICTURE picture, GB_PICTURE_INFO *info)
{
	gPicture *pic = ((CPICTURE *)picture)->picture;
	
	info->width = pic->width();
	info->height = pic->height();
}

static int get_line_width(GB_DRAW *d)
{
	return DR(d)->lineWidth();
}

static void set_line_width(GB_DRAW *d, int width)
{
	DR(d)->setLineWidth(width);
}

static int get_line_style(GB_DRAW *d)
{
	return DR(d)->lineStyle();
}

static void set_line_style(GB_DRAW *d, int style)
{
	DR(d)->setLineStyle(style);
}

static int get_fill_color(GB_DRAW *d)
{
	return DR(d)->fillColor();
}

static void set_fill_color(GB_DRAW *d, int color)
{
	DR(d)->setFillColor(color);
}

static int get_fill_style(GB_DRAW *d)
{
	return DR(d)->fillStyle();
}

static void set_fill_style(GB_DRAW *d, int style)
{
	DR(d)->setFillStyle(style);
}

static void get_fill_origin(GB_DRAW *d, int *x, int *y)
{
	if (x) *x = DR(d)->fillX();
	if (y) *y = DR(d)->fillY();
}

static void set_fill_origin(GB_DRAW *d, int x, int y)
{
	DR(d)->setFillX(x);
	DR(d)->setFillY(y);
}

static void draw_rect(GB_DRAW *d, int x, int y, int w, int h)
{
	DR(d)->rect(x, y, w, h);
}

static void draw_ellipse(GB_DRAW *d, int x, int y, int w, int h, double start, double end)
{
	DR(d)->ellipse(x, y, w, h, start, end);
}

static void draw_arc(GB_DRAW *d, int x, int y, int w, int h, double start, double end)
{
	DR(d)->arc(x, y, w, h, start, end);
}

static void draw_line(GB_DRAW *d, int x1, int y1, int x2, int y2)
{
	DR(d)->line(x1, y1, x2, y2);
}

static void draw_point(GB_DRAW *d, int x, int y)
{
	DR(d)->point(x, y);
}

static void draw_picture(GB_DRAW *d, GB_PICTURE picture, int x, int y, int w, int h, int sx, int sy, int sw, int sh)
{
	gPicture *pic = ((CPICTURE *)picture)->picture;
	DR(d)->picture(pic, x, y, w, h, sx, sy, sw, sh);
}

static void draw_image(GB_DRAW *d, GB_IMAGE image, int x, int y, int w, int h, int sx, int sy, int sw, int sh)
{
	gPicture *pic = CIMAGE_get((CIMAGE *)image);
	DR(d)->picture(pic, x, y, w, h, sx, sy, sw, sh);
}

static void draw_tiled_picture(GB_DRAW *d, GB_PICTURE picture, int x, int y, int w, int h)
{
	gPicture *pic = ((CPICTURE *)picture)->picture;
	DR(d)->tiledPicture(pic, x, y, w, h);
}

static void draw_text(GB_DRAW *d, char *text, int len, int x, int y, int w, int h, int align)
{
	if (align == GB_DRAW_ALIGN_DEFAULT)
		align = ALIGN_TOP_NORMAL;
		
	DR(d)->text(text, len, x, y, w, h, align);
}

static void text_size(GB_DRAW *d, char *text, int len, int *w, int *h)
{
	if (w) *w = DR(d)->textWidth(text, len);
	if (h) *h = DR(d)->textHeight(text, len);
}

static void draw_rich_text(GB_DRAW *d, char *text, int len, int x, int y, int w, int h, int align)
{
	if (align == GB_DRAW_ALIGN_DEFAULT)
		align = ALIGN_TOP_NORMAL;
		
	DR(d)->richText(text, len, x, y, w, h, align);
}

static void rich_text_size(GB_DRAW *d, char *text, int len, int sw, int *w, int *h)
{
	DR(d)->richTextSize(text, len, sw, w, h);
}

static void draw_polyline(GB_DRAW *d, int count, int *points)
{
	DR(d)->polyline(points, count);
}

static void draw_polygon(GB_DRAW *d, int count, int *points)
{
	DR(d)->polygon(points, count);
}

static void get_clipping(GB_DRAW *d, int *x, int *y, int *w, int *h)
{
	if (x) *x = DR(d)->clipX();
	if (y) *y = DR(d)->clipY();
	if (w) *w = DR(d)->clipWidth();
	if (h) *h = DR(d)->clipHeight();
}

static void set_clipping(GB_DRAW *d, int x, int y, int w, int h)
{
	DR(d)->setClip(x, y, w, h);
}

int is_clipping_enabled(GB_DRAW *d)
{
	return DR(d)->clipEnabled();
}

static void set_clipping_enabled(GB_DRAW *d, int enable)
{
	DR(d)->setClipEnabled(enable);
}

static GtkStateType get_state(int state)
{
	if (state & GB_DRAW_STATE_DISABLED)
		return GTK_STATE_INSENSITIVE;
	if (state & GB_DRAW_STATE_ACTIVE)
		return GTK_STATE_ACTIVE;
	if (state & GB_DRAW_STATE_HOVER)
		return GTK_STATE_PRELIGHT;
	
	return GTK_STATE_NORMAL;
}

static GdkRectangle *get_area(GB_DRAW *d)
{
	static GdkRectangle area;
	
	if (!DR(d)->clipEnabled())
		return NULL;
	
	get_clipping(d, &area.x, &area.y, &area.width, &area.height);
	return &area;
}

/*static GtkWidget *get_widget(GB_DRAW *d)
{
	if (!GB.Is(d->device, CLASS_DrawingArea))
		return NULL;
	
	return ((gDrawingArea *)((CDRAWINGAREA *)d->device)->ob.widget)->border;
}*/

static void paint_focus(GB_DRAW *d, GtkStyle *style, int x, int y, int w, int h, GtkStateType state, const char *kind)
{
	gtk_paint_focus(style, DR(d)->drawable(),
		state, 
		get_area(d), DR(d)->widget(), kind, 
		x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_focus(style, DR(d)->mask(),
			state, 
			get_area(d), DR(d)->widget(), kind, 
			x, y, w, h);
}

static void style_arrow(GB_DRAW *d, int x, int y, int w, int h, int type, int state)
{
	GtkArrowType arrow;
	GtkStyle *style = DR(d)->style();
	
	switch (type)
	{
		case ALIGN_NORMAL: arrow = GB.System.IsRightToLeft() ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT; break;
		case ALIGN_LEFT: arrow = GTK_ARROW_LEFT; break;
		case ALIGN_RIGHT: arrow = GTK_ARROW_RIGHT; break;
		case ALIGN_TOP: arrow = GTK_ARROW_UP; break;
		case ALIGN_BOTTOM: arrow = GTK_ARROW_DOWN; break;
		default:
			return;
	}
	
	gtk_paint_arrow(style, DR(d)->drawable(), get_state(state), 
		GTK_SHADOW_NONE, get_area(d), DR(d)->widget(), NULL, 
		arrow, TRUE, x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_arrow(style, DR(d)->mask(), get_state(state), 
			GTK_SHADOW_NONE, get_area(d), DR(d)->widget(), NULL, 
			arrow, TRUE, x, y, w, h);
}

static void style_check(GB_DRAW *d, int x, int y, int w, int h, int value, int state)
{
	GtkShadowType shadow;
	GtkStateType st = get_state(state | (value ? GB_DRAW_STATE_ACTIVE : 0));
	GtkStyle *style = DR(d)->style("GtkCheckButton", GTK_TYPE_CHECK_BUTTON);
	
	switch (value)
	{
		case -1: shadow = GTK_SHADOW_IN; break;
		case 1: shadow = GTK_SHADOW_ETCHED_IN; break;
		default: shadow = GTK_SHADOW_OUT; break;
	}

	gtk_paint_check(style, DR(d)->drawable(),
		st, shadow, get_area(d), NULL, "checkbutton", 
		x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_check(style, DR(d)->mask(),
			st, shadow, get_area(d), NULL, "checkbutton", 
			x, y, w, h);

	if (state & GB_DRAW_STATE_FOCUS)
		paint_focus(d, style, x, y, w, h, st, "checkbutton");
}

static void style_option(GB_DRAW *d, int x, int y, int w, int h, int value, int state)
{
	GtkShadowType shadow;
	GtkStateType st = get_state(state | (value ? GB_DRAW_STATE_ACTIVE : 0));
	GtkStyle *style = DR(d)->style("GtkRadioButton", GTK_TYPE_RADIO_BUTTON);
	
	shadow = value ? GTK_SHADOW_IN : GTK_SHADOW_OUT;

	gtk_paint_option(style, DR(d)->drawable(),
		st, shadow, get_area(d), NULL, "radiobutton", 
		x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_option(style, DR(d)->mask(),
			st, shadow, get_area(d), NULL, "radiobutton", 
			x, y, w, h);
			
	if (state & GB_DRAW_STATE_FOCUS)
		paint_focus(d, style, x, y, w, h, st, "radiobutton");
}

static void style_separator(GB_DRAW *d, int x, int y, int w, int h, int vertical, int state)
{
	GtkStateType st = get_state(state);
	GtkStyle *style = DR(d)->style();
	
	if (vertical)
	{
		gtk_paint_vline(style, DR(d)->drawable(),
			st, get_area(d), NULL, NULL, 
			y, y + h - 1, x + (w / 2));
		if (DR(d)->mask())
			gtk_paint_vline(style, DR(d)->mask(),
				st, get_area(d), NULL, NULL, 
				y, y + h - 1, x + (w / 2));
	}
	else
	{
		gtk_paint_hline(style, DR(d)->drawable(),
			st, get_area(d), NULL, NULL, 
			x, x + w - 1, y + (h / 2));
		if (DR(d)->mask())
			gtk_paint_hline(style, DR(d)->mask(),
				st, get_area(d), NULL, NULL, 
				x, x + w - 1, y + (h / 2));
	}
}

static void style_button(GB_DRAW *d, int x, int y, int w, int h, int value, int state, int flat)
{
	GtkStateType st = get_state(state | (value ? GB_DRAW_STATE_ACTIVE : 0));
	GtkStyle *style = DR(d)->style("GtkButton", GTK_TYPE_BUTTON);
	int xf, yf, wf, hf;
	GtkBorder *default_border, *default_outside_border, *inner_border;
	int focus_width, focus_pad, df;
	gboolean interior_focus;
	
	gtk_style_get(style, GTK_TYPE_BUTTON,
		"default-border", &default_border,
		"default-outside-border", &default_outside_border,
		"inner-border", &inner_border,
		"focus-line-width", &focus_width,
		"focus-padding", &focus_pad,
		"interior-focus", &interior_focus,
		(char *)NULL); 

	/*if (default_outside_border)
	{
		x += default_outside_border->left;
		y += default_outside_border->top;
		w -= default_outside_border->left + default_outside_border->right;
		h -= default_outside_border->top + default_outside_border->bottom;
	}*/
	
	if (default_border)
	{
		x += default_border->left;
		y += default_border->top;
		w -= default_border->left + default_border->right;
		h -= default_border->top + default_border->bottom;
	}

	if (inner_border) gtk_border_free(inner_border);
	if (default_outside_border) gtk_border_free(default_outside_border);
	if (default_border) gtk_border_free(default_border);
	
	xf = x;
	yf = y;
	wf = w;
	hf = h;
		
	if (interior_focus)
	{
		df = focus_pad + style->xthickness;
		xf += df;
		wf -= df * 2;
		df = focus_pad + style->ythickness;
		yf += df;
		hf -= df * 2;
	}
	else if (state & GB_DRAW_STATE_FOCUS)
	{
		df = focus_pad + focus_width;
		
		x += df;
		w -= df * 2;
		y += df;
		h -= df * 2;
	}
	
	if (flat && (state & GB_DRAW_STATE_HOVER) == 0)
	{
		/*gtk_paint_flat_box(style, DR(d)->drawable(),
			st, value ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
			get_area(d), DR(d)->widget(), "button",
			x, y, w, h);
		if (DR(d)->mask())
			gtk_paint_flat_box(style, DR(d)->mask(),
				st, value ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
				get_area(d), DR(d)->widget(), "button",
				x, y, w, h);*/
	}
	else
	{
		gtk_paint_box(style, DR(d)->drawable(),
			st, value ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
			get_area(d), DR(d)->widget(), "button",
			x, y, w, h);
		if (DR(d)->mask())
			gtk_paint_box(style, DR(d)->mask(),
				st, value ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
				get_area(d), DR(d)->widget(), "button",
				x, y, w, h);
	}

	if (state & GB_DRAW_STATE_FOCUS)
	{
		paint_focus(d, style, xf, yf, wf, hf, st, "button");
	}
}
			
static void style_panel(GB_DRAW *d, int x, int y, int w, int h, int border, int state)
{
	GtkShadowType shadow;
	GtkStateType st = get_state(state);
	GtkStyle *style = DR(d)->style();

	switch (border)
	{
		case BORDER_SUNKEN: shadow = GTK_SHADOW_IN; break;
		case BORDER_RAISED: shadow = GTK_SHADOW_OUT; break;
		case BORDER_ETCHED: shadow = GTK_SHADOW_ETCHED_IN; break;
		default: shadow = GTK_SHADOW_NONE;
	}
	
	gtk_paint_shadow(style, DR(d)->drawable(), 
		st, shadow, get_area(d), NULL, NULL, 
		x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_shadow(style, DR(d)->mask(), 
			st, shadow, get_area(d), NULL, NULL, 
			x, y, w, h);
	
	if (border == BORDER_PLAIN)
	{
		GdkGC *gc;
		GdkGCValues values;
		uint col;
		
		col = IMAGE.MergeColor(gDesktop::bgColor(), gDesktop::fgColor(), 0.5);
		col = IMAGE.LighterColor(col);

		fill_gdk_color(&values.foreground, col, gdk_drawable_get_colormap(DR(d)->drawable()));
		gc = gtk_gc_get(gdk_drawable_get_depth(DR(d)->drawable()), gdk_drawable_get_colormap(DR(d)->drawable()), &values, GDK_GC_FOREGROUND);
		gdk_draw_rectangle(DR(d)->drawable(), gc, FALSE, x, y, w - 1, h - 1); 
		gtk_gc_release(gc);
		
		if (DR(d)->mask())
		{
			fill_gdk_color(&values.foreground, col, gdk_drawable_get_colormap(DR(d)->mask()));
			gc = gtk_gc_get(gdk_drawable_get_depth(DR(d)->mask()), gdk_drawable_get_colormap(DR(d)->mask()), &values, GDK_GC_FOREGROUND);
			gdk_draw_rectangle(DR(d)->mask(), gc, FALSE, x, y, w - 1, h - 1); 
			gtk_gc_release(gc);
		}
	}

	if (state & GB_DRAW_STATE_FOCUS)
		paint_focus(d, style, x, y, w, h, st, "button");
}
			
static void style_handle(GB_DRAW *d, int x, int y, int w, int h, int vertical, int state)
{
	GtkStateType st = get_state(state);
	GtkStyle *style = DR(d)->style();

	gtk_paint_handle(style, DR(d)->drawable(), st,
		GTK_SHADOW_NONE, get_area(d), NULL, NULL,
		x, y, w, h,
		(!vertical) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
	if (DR(d)->mask())
		gtk_paint_handle(style, DR(d)->mask(), st, 
			GTK_SHADOW_NONE, get_area(d), NULL, NULL,
			x, y, w, h,
			(!vertical) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
}

static void style_box(GB_DRAW *d, int x, int y, int w, int h, int state)
{
	//static GtkWidget *widget = NULL;
	
	GtkStateType st = get_state(state);
	GtkStyle *style = DR(d)->style("GtkEntry", GTK_TYPE_ENTRY);

	//if (!widget)
		//widget = gtk_entry_new();
	
	gtk_paint_shadow(style, DR(d)->drawable(), st,
		GTK_SHADOW_IN, get_area(d), NULL, "entry", x, y, w, h);
	if (DR(d)->mask())
		gtk_paint_shadow(style, DR(d)->mask(), st,
			GTK_SHADOW_IN, get_area(d), NULL, "entry", x, y, w, h);

	if (state & GB_DRAW_STATE_FOCUS)
		paint_focus(d, style, x, y, w, h, st, "entry");
}

			
GB_DRAW_DESC DRAW_Interface = {
	sizeof(GB_DRAW_EXTRA),
	begin,
	end,
	save,
	restore,
	get_background,
	set_background,
	get_foreground,
	set_foreground,
	get_font,
	set_font,
	is_inverted,
	set_inverted,
	is_transparent,
	set_transparent,
	get_picture_info,
	{
		get_line_width,
		set_line_width,
		get_line_style,
		set_line_style
	},
	{
		get_fill_color,
		set_fill_color,
		get_fill_style,
		set_fill_style,
		get_fill_origin,
		set_fill_origin
	},
	{
		draw_rect,
		draw_ellipse,
		draw_arc,
		draw_line,
		draw_point,
		draw_picture,
		draw_image,
		draw_tiled_picture,
		draw_text,
		text_size,
		draw_polyline,
		draw_polygon,
		draw_rich_text,
		rich_text_size
	},
	{
		get_clipping,
		set_clipping,
		is_clipping_enabled,
		set_clipping_enabled
	},
	{
		style_arrow,
		style_check,
		style_option,
		style_separator,
		style_button,
		style_panel,
		style_handle,
		style_box
	}
};

void DRAW_begin(void *device)
{
	DRAW.Begin(device);
}

void DRAW_end()
{
	DRAW.End();
}

gDraw *DRAW_get_current()
{
	GB_DRAW *current = DRAW.GetCurrent(); 
	return current ? DR(current) : NULL;	
}

void *DRAW_get_drawable(void *dr)
{
	if (!dr) return NULL;
	return (void*)((gDraw*)dr)->drawable();
}

void *DRAW_get_style(void *dr)
{
	if (!dr) return NULL;
	return (void*)((gDraw*)dr)->style();
}

int DRAW_get_state(void *dr)
{
	if (!dr) return GTK_STATE_NORMAL;
	return ((gDraw*)dr)->state();
}

int DRAW_get_shadow(void *dr)
{
	if (!dr) return GTK_SHADOW_NONE;
	return ((gDraw*)dr)->shadow();
}

void DRAW_set_state(void *dr,int vl)
{
	if (!dr) return;
	((gDraw*)dr)->setState(vl);
}

void DRAW_set_shadow(void *dr,int vl)
{
	if (!dr) return;
	((gDraw*)dr)->setShadow(vl);
}




