/* $Id: e2_gesture_dialog.c 2064 2010-03-12 13:15:36Z tpgww $

Copyright (C) 2008-2010 tooar <tooar@emelfm2.net>

This file is part of emelfm2.
emelfm2 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 3, or (at your option)
any later version.

emelfm2 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 emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses.
*/

/**
@file src/dialogs/e2_gesture_dialog.c
@brief dialog for showing/editing pointer (mouse) gestures
*/

/*
TODO
better initial size hints
*/

#include "e2_mousebinding.h"

#ifdef E2_MOUSECUSTOM
#ifdef E2_PTRGESTURES

#include "e2_dialog.h"
#include "stroke.h"
#include <string.h>

typedef struct _E2_GestureDialogRuntime
{
	GtkWidget *dialog;
	GtkWidget *from;	//the drawing-areas widget where the pointer is being tracked, logged when sequence ends
	GtkWidget *entry;
	GdkGC *gc;			//parameters for point/line drawing
	gchar *sequence;	//malloc()'d grid-sequence string generally from handle, free() not g_free() to clean
	gint prevx, prevy;	//px coordinates of previous reported position
	gboolean is_drag;	//TRUE while a button is pressed on 'from'
	Stroke *handle;		//for logging moves, getting stroke-strings
} E2_GestureDialogRuntime;

/**
@brief "configure-event" signal callback
Take any appropriate action(s) when @a widget changes size
@param widget where the signal was initiated
@param event pointer to event data
@param rt pointer to runtime data

@return
*/
static gboolean _e2_gesture_dialog_configured_cb (GtkWidget *widget,
	GdkEventConfigure *event, E2_GestureDialogRuntime *rt)
{
	/* this is the first callback when dialog is created, & also arrives later
	when re-sized */
	if (rt->handle == NULL)
	{
		printd (DEBUG, "drawing area first configured to %d X %d", event->width, event->height);
		rt->handle = stroke_new (event->width, event->height);
	}
	else
	{
		printd (DEBUG, "drawing area re-configured to %d X %d", event->width, event->height);
		stroke_scale (rt->handle, event->width, event->height);
	}
	return FALSE;
}
/**
@brief "realize" signal callback
Take any appripriate action(s) when @a widget is instantiated on a particular
display
@param widget where the signal was initiated
@param rt pointer to runtime data

@return
*/
static void _e2_gesture_dialog_realized_cb (GtkWidget *widget,
	E2_GestureDialogRuntime *rt)
{
	//this arrives second during dialog creation
	//widget's properties are available (gdkwindow,
	//(x, y, width, height) = same as for configured callback
	printd (DEBUG, "drawing area realised");
	//GDK things ? GdkWindow *window = event->window;
	if (rt->sequence != NULL)
	{
		//has already been verified at dialog start
		gpointer points_array;
		stroke_fake (rt->handle, rt->sequence);
		gint npoints = stroke_replay (rt->handle, &points_array);
		if (npoints > 0)
		{
			if (rt->gc == NULL)
			{
				GtkStyle *style = gtk_widget_get_style (widget);
				rt->gc = style->black_gc;
			}
			GdkWindow *window =
#ifdef USE_GTK2_14
				gtk_widget_get_window (widget); //CHECKME cache this ?
#else
				widget->window;
#endif
			printd (DEBUG, "draw stroke with %d points", npoints);
			gdk_draw_points (GDK_DRAWABLE (window), rt->gc, (GdkPoint *)points_array, npoints);
			gtk_entry_set_text (GTK_ENTRY (rt->entry), rt->sequence);
			free (points_array); //not g_free()
			stroke_clear (rt->handle);
		}
		else
		{
			free (rt->sequence);
			rt->sequence = NULL;
		}
	}
}
/**
@brief "expose-event" signal callback
@param widget where the signal was initiated
@param event pointer to event data
@param rt pointer to runtime data

@return
*/
static gboolean _e2_gesture_dialog_exposed_cb (GtkWidget *widget,
	GdkEventExpose *event, E2_GestureDialogRuntime *rt)
{
	//this arrives third during dialog creation, and also later
	printd (DEBUG, "drawing area exposed");
#ifdef USE_GTK2_18
	if (gtk_widget_get_visible (widget))
#else
	if (GTK_WIDGET_VISIBLE (widget))
#endif
	{
		gpointer points_array;
		if (rt->gc == NULL)
		{
			GtkStyle *style = gtk_widget_get_style (widget);
			rt->gc = style->black_gc;
		}
		gint npoints = stroke_get_count (rt->handle);

		if (npoints == 0)
		{
			if (rt->sequence != NULL)
			{
				printd (DEBUG, "recontruct stroke from runtime content");
				stroke_fake (rt->handle, rt->sequence);
				npoints = stroke_replay (rt->handle, &points_array);
			}
			else
			{
				const gchar *sequence = gtk_entry_get_text (GTK_ENTRY (rt->entry));
				if (*sequence != '\0')
				{
					printd (DEBUG, "recontruct stroke from entry content");
					stroke_fake (rt->handle, sequence);
					npoints = stroke_replay (rt->handle, &points_array);
				}
			}

			if (npoints > 0)
			{
				GdkPoint *pd;
				guint indx;
				float xfactor, yfactor;
				GtkAllocation alloc;
#ifdef USE_GTK2_18
				gtk_widget_get_allocation (widget, &alloc);
#else
				alloc = widget->allocation;
#endif
				stroke_get_scale (rt->handle, &alloc.x, &alloc.y);
				xfactor = alloc.width /  alloc.x;
				yfactor = alloc.height / alloc.y;

				for (pd = (GdkPoint *)points_array, indx = 0; indx < npoints; pd++, indx++)
				{
					pd->x *= xfactor;
					pd->y *= yfactor;
				}

				if (xfactor == 1. && yfactor == 1.)
					gdk_draw_points (GDK_DRAWABLE (event->window), rt->gc,
						(GdkPoint *)points_array, npoints);
				else
					gdk_draw_lines (GDK_DRAWABLE (event->window), rt->gc,
						(GdkPoint *)points_array, npoints);

				free (points_array); //not g_free()
				stroke_clear (rt->handle);
			}
		}
		else
		{
			npoints = stroke_replay (rt->handle, &points_array);
			printd (DEBUG, "exposure - redraw existing stroke with %d points", npoints);
			gdk_draw_points (GDK_DRAWABLE (event->window), rt->gc,
				(GdkPoint *)points_array, npoints);
			free (points_array); //not g_free()
		}
	}

	return FALSE;
}
/**
@brief "button-press-event" signal callback
@param widget where the signal was initiated
@param event pointer to event data
@param rt pointer to runtime data

@return
*/
static gboolean _e2_gesture_dialog_button_press_cb (GtkWidget *widget,
	GdkEventButton *event, E2_GestureDialogRuntime *rt)
{
	rt->is_drag = TRUE;
	rt->prevx = (gint) event->x;
	rt->prevy = (gint) event->y;
	return FALSE;
}
/**
@brief "button-release-event" signal callback
cleanup and grab sequence for later report if not cancelled by user
@param widget where the signal was initiated
@param event pointer to event data
@param rt pointer to runtime data

@return
*/
static gboolean _e2_gesture_dialog_button_release_cb (GtkWidget *widget,
	GdkEventButton *event, E2_GestureDialogRuntime *rt)
{
	rt->is_drag = FALSE;

	gchar *ascii;
	gint bins = stroke_translate (rt->handle, TRUE, TRUE, &ascii);
//	printd (DEBUG, "mouse gestures sequence %s", ascii);
	if (bins > 0)
	{
//		printd (DEBUG, "add mouse gestures sequence %s to runtime content", ascii);
		if (rt->sequence != NULL)
			free (rt->sequence); //not g_free, this was allocated by malloc
		rt->sequence = ascii;

		GdkWindow *win =
#ifdef USE_GTK2_14
			gtk_widget_get_window (rt->from);
#else
			rt->from->window;
#endif
		GdkRectangle rect;
		gdk_window_get_position (win, &rect.x, &rect.y);
		gdk_drawable_get_size (GDK_DRAWABLE (win), &rect.width, &rect.height);
		gdk_window_invalidate_rect (win, &rect, FALSE);
		gdk_window_process_updates (win, FALSE);

		gtk_entry_set_text (GTK_ENTRY (rt->entry), ascii);
	}

	return FALSE;
}
/**
@brief "motion-notify" signal callback
@param widget where the signal was initiated
@param event pointer to event data
@param rt pointer to runtime data

@return
*/
static gboolean _e2_gesture_dialog_moved_cb (GtkWidget *widget,
	GdkEventMotion *event, E2_GestureDialogRuntime *rt)
{
	if (rt->handle == NULL)
	{
		g_return_val_if_reached (FALSE);
/*		//start processing
		rt->handle = stroke_new (<size from widget allocation>);
		rt->prevx = event->x;
		rt->prevy = event->y;
*/
	}

	if (rt->is_drag)
	{
		if (rt->handle != NULL)
			stroke_record (rt->handle, event->x, event->y);
		if (rt->gc == NULL)
		{
			GtkStyle *style = gtk_widget_get_style (widget);
			rt->gc = style->black_gc;
		}
		GdkWindow *window =
#ifdef USE_GTK2_14
			gtk_widget_get_window (widget); //CHECKME cache this ?
#else
			widget->window;
#endif
		gdk_draw_line (GDK_DRAWABLE (window), rt->gc, rt->prevx, rt->prevy,
			event->x, event->y);
		rt->prevx = event->x;
		rt->prevy = event->y;
	}
	return FALSE;
}

/**
@brief setup stroke-input widget

@return the drawing area widget
*/
static GtkWidget *_e2_gesture_dialog_create_canvas (E2_GestureDialogRuntime *rt)
{
	GtkWidget *canvas = gtk_drawing_area_new ();
	//set min size corresponding to smallest count used by Strokes
	gtk_widget_set_size_request (canvas, 25, 25);
#ifdef USE_GTK2_18
	gtk_widget_set_can_focus (canvas, TRUE);
#else
	GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS);
#endif
	gtk_widget_add_events (canvas,
		GDK_EXPOSURE_MASK |
		GDK_POINTER_MOTION_MASK	| //or GDK_POINTER_MOTION_HINT_MASK ?
		GDK_BUTTON_PRESS_MASK |
		GDK_BUTTON_RELEASE_MASK |
		GDK_STRUCTURE_MASK
	);

	g_signal_connect (G_OBJECT (canvas), "realize",
		G_CALLBACK (_e2_gesture_dialog_realized_cb), rt);
	g_signal_connect (G_OBJECT (canvas), "configure-event",
		G_CALLBACK (_e2_gesture_dialog_configured_cb), rt);
	g_signal_connect (G_OBJECT (canvas), "expose-event",
		G_CALLBACK (_e2_gesture_dialog_exposed_cb), rt);
	g_signal_connect (G_OBJECT (canvas), "motion-notify-event",
		G_CALLBACK (_e2_gesture_dialog_moved_cb), rt);
	g_signal_connect (G_OBJECT (canvas), "button-press-event",
		G_CALLBACK (_e2_gesture_dialog_button_press_cb), rt);
	g_signal_connect (G_OBJECT (canvas), "button-release-event",
		G_CALLBACK (_e2_gesture_dialog_button_release_cb), rt);

	return canvas;
}

/**
@brief One of 2 "response" signal callbacks for the dialog
@param dialog the widget where the response was initiated
@param response enum refecting user's choice
@param rt pointer to dialog runtime data
See also _e2_option_tree_gesture_column_set_cb()
@return
*/
static void _e2_gesture_dialog_response_cb (GtkDialog *dialog, gint response,
	E2_GestureDialogRuntime *rt)
{
	switch (response)
	{
		case E2_RESPONSE_USER1: //clear
		{
			GdkWindow *win =
#ifdef USE_GTK2_14
				gtk_widget_get_window (rt->from);
#else
				rt->from->window;
#endif
			gdk_window_clear (win);
			gtk_entry_set_text (GTK_ENTRY (rt->entry), "");

			if (rt->sequence != NULL)
			{
				free (rt->sequence);
				rt->sequence = NULL;
			}
			stroke_clear (rt->handle);
		}
			break;
		case E2_RESPONSE_APPLY:
		{
			gchar *retval;
			const gchar *readable = gtk_entry_get_text (GTK_ENTRY (rt->entry));
			if (readable == NULL || *readable == '\0')
			{
				retval = g_strdup ("");
			}
			else
			{
				retval = g_strdup (readable);
				g_strstrip (retval);
				//check that string is ok
				gchar *valid = stroke_verify_sequence (retval);
				if (valid == NULL)
				{
					g_free (retval);
					//stay alive by blocking other response
					g_signal_stop_emission_by_name ((gpointer)dialog, "response");
					break;
				}
				if (strcmp (valid, retval))
				{
					g_free (retval);
					//arrange to show the amended stoke
					if (rt->sequence != NULL)
						free (rt->sequence);
					rt->sequence = valid;
					GdkWindow *win =
#ifdef USE_GTK2_14
						gtk_widget_get_window (rt->from);
#else
						rt->from->window;
#endif
					GdkRectangle rect;
					gdk_window_get_position (win, &rect.x, &rect.y);
					gdk_drawable_get_size (GDK_DRAWABLE (win), &rect.width, &rect.height);
					gdk_window_invalidate_rect (win, &rect, FALSE);
					gdk_window_process_updates (win, FALSE);
					gtk_entry_set_text (GTK_ENTRY (rt->entry), valid);
					g_signal_stop_emission_by_name ((gpointer)dialog, "response"); //stay alive by blocking other response
					break;
				}
				free (valid);
			}
			//send back the results
			g_object_set_data_full (G_OBJECT (dialog), "sequence", retval, g_free);
		}
		default:
			if (rt->sequence != NULL)
				free (rt->sequence);
			if (rt->handle != NULL)
				stroke_destroy (rt->handle);
			DEALLOCATE (E2_GestureDialogRuntime, rt);
			//dialog is destroyed in other response callback
			break;
	}
}
/**
@brief "activate" signal callback for the dialog's entry
@param entry the widget where the response was initiated
@param rt pointer to dialog runtime data

@return
*/
static void _e2_gesture_dialog_activated_cb (GtkEntry *entry, E2_GestureDialogRuntime *rt)
{
	g_signal_emit_by_name (G_OBJECT (rt->dialog), "response", E2_RESPONSE_APPLY);
}

/**
@brief create gesture-input dialog

@param parent the main config-dialog widget
@param initial_sequence string with series of comma-separated grid-numbers, may be empty or NULL

@return the dialog widget, or NULL if error occurred
*/
GtkWidget *e2_gesture_dialog_create (GtkWidget *parent,
	const gchar *initial_sequence)
{
	//init runtime object (0's to ensure edit-dialog things are NULL)
	E2_GestureDialogRuntime *rt = ALLOCATE0 (E2_GestureDialogRuntime);
	CHECKALLOCATEDWARN (rt, return NULL;)

	rt->dialog = e2_dialog_create (NULL, NULL, _("gesture"),
		(ResponseFunc)_e2_gesture_dialog_response_cb, rt);

	GtkWidget *dialog_vbox =
#ifdef USE_GTK2_14
		gtk_dialog_get_content_area (GTK_DIALOG (rt->dialog));
#else
		GTK_DIALOG (rt->dialog)->vbox;
#endif
	rt->from = _e2_gesture_dialog_create_canvas (rt);
	e2_widget_set_safetip (rt->from, _("Draw gesture"));
	gtk_box_pack_start (GTK_BOX (dialog_vbox), rt->from,
		TRUE, TRUE, E2_PADDING_XSMALL);

	if (G_LIKELY (initial_sequence != NULL && *initial_sequence != '\0'))
	{
		gchar *valid = stroke_verify_sequence (initial_sequence);
		if (valid != NULL)
		{
			g_strstrip (valid);
			rt->sequence = valid;
		}
	}

	//don't use e2_widget_add_entry(), that expands and does other useless things
	rt->entry = gtk_entry_new ();
	gtk_box_pack_start (GTK_BOX (dialog_vbox), rt->entry, FALSE, FALSE,
		E2_PADDING_XSMALL);
	if (rt->sequence != NULL)
		gtk_entry_set_text (GTK_ENTRY (rt->entry), rt->sequence);
	e2_widget_set_safetip (rt->entry, _("Comma-separated grid-positions, each 1-9"));
	g_signal_connect (G_OBJECT (rt->entry), "activate",
		G_CALLBACK (_e2_gesture_dialog_activated_cb), rt);

//	gtk_window_set_type_hint (GTK_WINDOW (rt->dialog), GDK_WINDOW_TYPE_HINT_NORMAL);
	GdkGeometry params =
	{
		50, //min_width;
		50, //min_height;
		0, //max_width;
		0, //max_height;
		0, //base_width;
		0, //base_height;
		5, //width_inc;
		5, //height_inc;
		0.9, //min_aspect; CHECKME values ?
		1.0, //max_aspect;
		0  //win_gravity;
	};

	gtk_window_set_geometry_hints (GTK_WINDOW (rt->dialog), rt->from, &params,
		GDK_HINT_MIN_SIZE | GDK_HINT_ASPECT | GDK_HINT_RESIZE_INC);

	//don't want typical treatment of buttons or gestures
	e2_mousebinding_disrol (rt->dialog, NULL);
	e2_mousegesture_disrol (rt->dialog, NULL);
/*CHECKME
	//or of keys
	e2_keybinding_unregister (NULL, rt->dialog);
*/
	e2_dialog_set_negative_response (rt->dialog, E2_RESPONSE_NOTOALL);

	E2_Button local_btn =
	{
		_("Clea_r"), GTK_STOCK_CLEAR, NULL, 0, 0, E2_RESPONSE_USER1
	};

	e2_dialog_show (rt->dialog, parent, 0,
		&local_btn, &E2_BUTTON_CANCEL, &E2_BUTTON_APPLY, NULL);

	return rt->dialog;
}

#endif //def E2_PTRGESTURES
#endif //def E2_MOUSECUSTOM
