/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki SEKIYAMA <sekiyama@yahoo.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

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



#include "v/VDraw.h"

extern "C" {
#include "task.h"
#include "pri_level.h"
#include "machine/v_object.h"
}


VPoint last_point = {0,0};
GtkWidget *last_point_widget = 0;
bool last_point_force_update = false;

short
gdk_state_to_modifiers(gint state)
{
	short ret = 0;
	if ( state & GDK_SHIFT_MASK )
		ret |= V_MODKEY_SHIFT;
	if ( state & GDK_LOCK_MASK )
		ret |= V_MODKEY_CAPS;
	if ( state & GDK_MOD1_MASK )
		ret |= V_MODKEY_ALT;
	if ( state & GDK_CONTROL_MASK )
		ret |= V_MODKEY_CTRL;
	if ( state & GDK_MOD2_MASK )
		ret |= V_MODKEY_META;
	return ret;
}

short
gdk_state_to_button(gint state)
{
	short ret = 0;
	if ( state & GDK_BUTTON1_MASK )
		ret |= V_BUTTON_1;
	if ( state & GDK_BUTTON2_MASK )
		ret |= V_BUTTON_3;
	if ( state & GDK_BUTTON3_MASK )
		ret |= V_BUTTON_2;
	if ( state & GDK_BUTTON4_MASK )
		ret |= V_BUTTON_4;
	if ( state & GDK_BUTTON5_MASK )
		ret |= V_BUTTON_5;
	return ret;
}

extern "C" void
redraw_darea_do(GtkWidget *widget, VImage *img, bool use_alpha)
{
	if ( use_alpha ) {
		GdkPixmap *pm = gdk_pixmap_new(widget->window, img->size.w, img->size.h, -1);
		gdk_draw_rectangle(pm, widget->style->bg_gc[GTK_STATE_NORMAL],
			true, 0, 0, img->size.w, img->size.h);
		gdk_pixbuf_render_to_drawable_alpha((GdkPixbuf*)img->info, pm,
			0, 0, 0, 0, img->size.w-1, img->size.h-1,
			GDK_PIXBUF_ALPHA_FULL, 128,
			GDK_RGB_DITHER_NONE, 0, 0);
		gdk_draw_pixmap(widget->window, widget->style->bg_gc[GTK_STATE_NORMAL], pm,
			0, 0, 0, 0, img->size.w-1, img->size.h-1);
		gdk_pixmap_unref(pm);
	}
	else {
		gdk_draw_rgb_32_image(widget->window,
			widget->style->fg_gc[GTK_STATE_NORMAL],
			0, 0, img->size.w, img->size.h,
			GDK_RGB_DITHER_NONE,
			gdk_pixbuf_get_pixels((GdkPixbuf*)img->info), img->w_border*4);
	}
}

V_CALLBACK_D(redraw_darea)
{
	GtkWidget *widget = GTK_WIDGET(user_arg);
	VDraw *vobj = (VDraw*)object;
	VImage *img = vobj->draw_start();
	if ( img && img->buf_32 ) {
		v_serialized_exec_sub(redraw_darea_do, widget, img, vobj->get_draw_use_alpha());
		vobj->draw_end();
	}
}

extern "C" gboolean
on_darea_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
{
	VDraw *vobj = (VDraw*)user_data;
	VImage *img = vobj->get_image();
	if ( img ) {
		if ( v_image_draw_start(img, 1) == 0 ) {
			redraw_darea_do(widget, img, vobj->get_draw_use_alpha());
			v_image_draw_end(img);
		}
		else
			vq_insert_callback(vobj, redraw_darea, widget, 0, 0);
		
		v_image_unref(img);
	}
	return TRUE;
}

extern "C" gboolean
on_darea_mouse_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
	VDraw* vobj = (VDraw*)user_data;
	VMouseEvent vme;
	switch ( event->type ) {
	  case GDK_BUTTON_PRESS:
		vme.type = V_EV_MOUSE_DOWN;
		break;
	  case GDK_BUTTON_RELEASE:
		vme.type = V_EV_MOUSE_UP;
		break;
	  case GDK_MOTION_NOTIFY:
		vme.type = V_EV_MOUSE_MOVE;
		break;
	  case GDK_ENTER_NOTIFY:
			vme.type = V_EV_MOUSE_ENTER;
		break;
	  case GDK_LEAVE_NOTIFY:
		vme.type = V_EV_MOUSE_LEAVE;
		break;
	  case GDK_2BUTTON_PRESS:
		vme.type = V_EV_MOUSE_DOUBLE_CLICK;
		break;
	  default:
		return false;
	}
	switch ( event->type ) {
	  case GDK_BUTTON_PRESS:
	  case GDK_BUTTON_RELEASE:
	  case GDK_2BUTTON_PRESS:
		vme.time = event->button.time;
		vme.point.x = (short)event->button.x;
		vme.point.y = (short)event->button.y;
		vme.button = 1 << (event->button.button - 1);
		vme.button = vme.button == 0x2 ? 0x4 : vme.button == 0x4 ? 0x2 : vme.button;
		vme.modifiers = gdk_state_to_modifiers(event->button.state);
		break;
	  case GDK_MOTION_NOTIFY:
		vme.time = event->motion.time;
		vme.point.x = (short)event->motion.x;
		vme.point.y = (short)event->motion.y;
		vme.button = gdk_state_to_button(event->motion.state);
		vme.modifiers = gdk_state_to_modifiers(event->motion.state);
		break;
	  case GDK_ENTER_NOTIFY:
	  case GDK_LEAVE_NOTIFY:
		vme.time = event->crossing.time;
		vme.point.x = (short)event->crossing.x;
		vme.point.y = (short)event->crossing.y;
		vme.button = gdk_state_to_button(event->crossing.state);;
		vme.modifiers = gdk_state_to_modifiers(event->crossing.state);
		break;
	  default:
		break;
	}
	vobj->mouse_event(&vme);
	
	return true;
}

extern "C" gint
on_darea_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer user_data)
{
	VDraw *draw = (VDraw*)user_data;
	draw->resize_event((VSize){event->width,event->height});
	return true;
}

GtkWidget *
v_draw_create_do(void *user_data)
{
	GtkWidget *info;
	info = gtk_drawing_area_new();
	g_object_ref(info);
	gtk_widget_set_double_buffered(info, FALSE);
	gtk_widget_add_events(info, GDK_ALL_EVENTS_MASK);
	
	g_signal_connect(info, "expose-event",
		GTK_SIGNAL_FUNC(on_darea_expose), user_data);
	g_signal_connect(info, "configure-event",
		GTK_SIGNAL_FUNC(on_darea_configure), user_data);
	g_signal_connect(info, "button_press_event",
		GTK_SIGNAL_FUNC(on_darea_mouse_event), user_data);
	g_signal_connect(info, "button_release_event",
		GTK_SIGNAL_FUNC(on_darea_mouse_event), user_data);
	g_signal_connect(info, "motion_notify_event",
		GTK_SIGNAL_FUNC(on_darea_mouse_event), user_data);
	g_signal_connect(info, "enter_notify_event",
		GTK_SIGNAL_FUNC(on_darea_mouse_event), user_data);
	g_signal_connect(info, "leave_notify_event",
		GTK_SIGNAL_FUNC(on_darea_mouse_event), user_data);
	return info;
}

VExError
VDraw::create_do(const VObjectStatus* s, int flags, 
			VObject * nmp,void * arg)
{

	mouse_event_handler = 0;
	resize_event_handler = 0;
	img = 0;
	img_locked = 0;
	info = v_serialized_exec_func(v_draw_create_do, (void*)this);

	return return_create_do(this,nmp,&sts,s,flags);
}

void
VDraw::destroy_do(VObject * nmp)
{
	if ( img_locked )
		er_panic("VDraw::destroy while img_lock");
	if ( img ) {
		v_image_unref(img);
		img = 0;
	}
	nmp->remove_child_do(this);
	v_serialized_exec_sub(gtk_widget_destroy, info);
	v_serialized_exec_sub(g_object_unref, info);
}

VDraw::~VDraw()
{
}


VExError
VDraw::get_status(VObjectStatus *s, int flags) const
{
	V_OP_START_EX
	VExError err = v_get_status_standard(s, &flags, info);
	VExError err2 = VObject::get_status(s,flags);
	if ( err2.code )
		err = merge_VExError_vstatus_type(err,err2);
	V_OP_END
	return err;
};

VExError
VDraw::set_status(const VObjectStatus *s, int flags)
{
	V_OP_START_EX
	VExError err = v_set_status_standard(s, flags, info);
	err = VObject::set_status(s,flags);
	if ( err.code ) {
		V_OP_END
		return err;
	}
	
	if ( flags & VSF_MIN_SIZE ) {
		int w = v_default_size.w, h = v_default_size.h;
		if ( s->min_size.w != v_default_size.w ) 
			w = s->min_size.w;
		else if ( img )
			w = img->size.w;
		if ( s->min_size.h != v_default_size.h )
			h = s->min_size.h;
		else if ( img )
			h = img->size.h;
		v_serialized_exec_sub(gtk_drawing_area_size, GTK_DRAWING_AREA(info),
			w<0?0:w, h<0?0:h);
		sts.min_size = (VSize){w,h};
		err.subcode1 &= ~VSF_MIN_SIZE;
	}
	if ( flags & VSF_SIZE ) {
		v_serialized_exec_sub(gtk_widget_set_size_request, info, (int)s->size.w, (int)s->size.h);
		err.subcode1 &= ~VSF_SIZE;
	}

	V_OP_END
	
	if ( flags & (VSF_ALIGN | VSF_PADDING ) ) {
		if ( sts.parent )
			sts.parent->child_status_changed(this, info);
		err.subcode1 &= ~(VSF_ALIGN | VSF_PADDING );
	}
	return err;
}

void
VDraw::redraw(VRect* rect) const
{
	_V_OP_START_VOID
	if ( rect )
		v_serialized_exec_sub(gtk_widget_queue_draw_area, info, 
			rect->l, rect->t, rect->r-rect->l, rect->b-rect->t);
	else
		v_serialized_exec_sub(gtk_widget_queue_draw, info);
	V_OP_END
}

VImage*
VDraw::draw_start(bool img_draw_start)
{
	_V_OP_START(NULL)
	if ( img == 0 || img_locked ) {
		V_OP_END
		return NULL;
	}
	img_locked = 1;
	if ( img_draw_start )
		v_image_draw_start(img, 0);
	return img;
}

void
VDraw::draw_end(bool img_draw_end)
{
	if ( ! img_locked )
		er_panic("illegal VDraw::draw_end");
	img_locked = 0;
	if ( img_draw_end )
		v_image_draw_end(img);
	V_OP_END
}

VError
VDraw::set_image(VImage* img, VSize min_size)
{
	V_OP_START
	VError err = V_ER_NO_ERR;
	if ( img_locked )
		err = V_ER_CANT_SET;
	else {
		VImage* old = this->img;
		if ( img ) {
			v_image_ref(img);
			bool size_f = false;
			if ( min_size.w != v_default_size.w && min_size.h != v_default_size.h ) {
				sts.min_size = min_size;
				size_f = true;
			}
			else if ( sts.min_size.w == v_default_size.w && sts.min_size.h == v_default_size.h ) {
				sts.min_size = img->size;
				size_f = true;
			}
			if ( size_f )
				v_serialized_exec_sub(gtk_drawing_area_size,
					GTK_DRAWING_AREA(info), sts.min_size.w, sts.min_size.h);
		}
		this->img = img;
		if ( old )
			v_image_unref(old);
	}
	V_OP_END
	return err;
}

bool
VDraw::mouse_event(VMouseEvent *event)
{	// called from OS on mouse event
	if (mouse_event_handler)
		vq_insert_callback(this, mouse_event_handler, user_arg_mouse, event, sizeof(*event));
	if ( event->type == V_EV_MOUSE_MOVE ) {
		if (sts.attr & mouse_move_wait) {
			last_point_widget = info;
			last_point = event->point;
		}
		else {
			// little hack : This will cause next pointer motion notify event
			// under GDK_POINTER_MOTION_HINT_MASK is set.
			// This enables avoiding too much events are put into event queue
			// when vobj->mouse_event() does heavy operation.
			gint x, y;
			gtk_widget_get_pointer(info, &x, &y);
		}
	}
	return false;
}

extern "C" void
mouse_move_event_done_do(VDraw *vobj, VInfo *info)
{
	gint x, y;
	GdkModifierType mod;
	gdk_window_get_pointer(info->window, &x, &y, &mod);
	if ( info != last_point_widget || last_point.x != x || last_point.y != y ) {
		last_point_force_update = true;
		VMouseEvent vme;
		vme.type = V_EV_MOUSE_MOVE;
		vme.time = gdk_event_get_time(0);
		vme.point = (VPoint){x,y};
		vme.button = gdk_state_to_button(mod);
		vme.modifiers = gdk_state_to_modifiers(mod);
		vobj->mouse_event(&vme);
	}
}

void
VDraw::mouse_move_event_done()
{
	if ( ! (sts.attr & mouse_move_wait) )
		return;
	if ( last_point_force_update ) {
		// ignore an event invoked in this function
		last_point_force_update = false;
		return;
	}
	
	v_serialized_exec_sub(mouse_move_event_done_do, this, info);
}
