#include <gtk/gtk.h>
#include "guiutils.h"
#include "statictip.h"


typedef struct _StaticTipData		StaticTipData;


/*
 *	Static Tip Data:
 */
struct _StaticTipData {

	GtkWidget	*toplevel;	/* GtkWindow */
	gchar		*text;
	GdkTextBounds	text_bounds;

};
#define STATIC_TIP_DATA(p)	((StaticTipData *)(p))
#define STATIC_TIP_DATA_KEY	"static_tip_data"


static void StaticTipDataDestroyCB(gpointer data);
static gint StaticTipPaintCB(StaticTipData *t);
static void StaticTipDraw(StaticTipData *t);

static StaticTipData *StaticTipDataNew(void);
static void StaticTipDataDelete(StaticTipData *t);

void StaticTipSet(
	GtkWidget *w, const gchar *text,  
	StaticTipAlign x_align, StaticTipAlign y_align, 
	gint x, gint y
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


#define STATIC_TIP_BORDER_MINOR		4


/*
 *	Static Tip Data destroy callback.
 */
static void StaticTipDataDestroyCB(gpointer data)
{
	StaticTipDataDelete(STATIC_TIP_DATA(data));
}

/*
 *	Static Tip paint signal callback.
 */
static gint StaticTipPaintCB(StaticTipData *t)
{
	StaticTipDraw(t);
	return(FALSE);
}

/*
 *	Redraws the Static Tip.
 */
static void StaticTipDraw(StaticTipData *t)
{
	const gint border_minor = STATIC_TIP_BORDER_MINOR;
	const gchar *text;
	GdkFont *font;
	GdkWindow *window;
	GtkStateType state;
	GtkStyle *style;
	GtkWidget *w = (t != NULL) ? t->toplevel : NULL;
	if(w == NULL)
	    return;

	window = w->window;
	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	if((window == NULL) || (style == NULL))
	    return;

	/* Draw background */
	gtk_paint_flat_box(
	    style, window, state, GTK_SHADOW_OUT,
	    NULL, w, "tooltip",
	    0, 0, -1, -1
	);

	/* Draw text */
	font = style->font;
	text = t->text;
	if((font != NULL) && (text != NULL))
	{
	    const GdkTextBounds *b = &t->text_bounds;
	    gint	x = border_minor,
			y = border_minor;
	    gtk_paint_string(
		style, window, state, NULL,
		w, "tooltip",
		x + b->lbearing,
		y + font->ascent,
		text
	    );
	}
}


/*
 *	Creates a new Static Tip Data.
 */
static StaticTipData *StaticTipDataNew(void)
{
	GtkWidget *w;
	StaticTipData *t = STATIC_TIP_DATA(g_malloc0(
	    sizeof(StaticTipData)
	));
	if(t == NULL)
	    return(NULL);

	t->toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_app_paintable(w, TRUE);
	gtk_window_set_policy(GTK_WINDOW(w), FALSE, FALSE, TRUE);
	gtk_widget_set_name(w, "gtk-tooltips");
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(StaticTipPaintCB), (GtkObject *)t
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "draw",
	    GTK_SIGNAL_FUNC(StaticTipPaintCB), (GtkObject *)t
	);
	gtk_widget_realize(w);

	return(t);
}

/*
 *	Deletes the Static Tip Data.
 */
static void StaticTipDataDelete(StaticTipData *t)
{
	if(t == NULL)
	    return;

	GTK_WIDGET_DESTROY(t->toplevel)
	g_free(t->text);

	g_free(t);
}


/*
 *	Sets the Static Tip for the specified widget to the specified
 *	text and displays it.
 *
 *	If text is NULL then the Static Tip is deleted for the specified
 *	widget.
 */
void StaticTipSet(
	GtkWidget *w, const gchar *text,  
	StaticTipAlign x_align, StaticTipAlign y_align, 
	gint x, gint y
)
{
	const gint border_minor = STATIC_TIP_BORDER_MINOR;
	gint x2, y2, width, height;
	GdkFont *font;
	GdkTextBounds *b;
	GdkWindow *window;
	GtkAllocation *allocation;
	GtkStyle *style;
	GtkWidget *toplevel;
	StaticTipData *t;

	if(w == NULL)
	    return;

	/* Remove static tip from widget? */
	if(text == NULL)
	{
	    gtk_object_remove_data(GTK_OBJECT(w), STATIC_TIP_DATA_KEY);
	    return;
	}

	/* Get Static Tip Data, create it as needed */
	t = gtk_object_get_data(GTK_OBJECT(w), STATIC_TIP_DATA_KEY);
	if(t == NULL)
	{
	    t = StaticTipDataNew();
	    if(t == NULL)
		return;

	    gtk_object_set_data_full(
		GTK_OBJECT(w), STATIC_TIP_DATA_KEY,
		t, StaticTipDataDestroyCB
	    );
	}

	toplevel = t->toplevel;
	style = gtk_widget_get_style(toplevel);
	font = style->font;

	/* Set text */
	g_free(t->text);
	t->text = STRDUP(text);

	/* Get the specified widget's window and geometry */
	window = w->window;
	allocation = &w->allocation;


	/* Calculate size based on the specified text */
	b = &t->text_bounds;
	gdk_string_bounds(font, t->text, b);
	width = b->width + (2 * border_minor);
	height = ((font != NULL) ? (font->ascent + font->descent) : 0) +
	    (2 * border_minor);

	/* Calculate x position */
	switch(x_align)
	{
	  case STATIC_TIP_ALIGN_POINTER_MIN:
	    gdk_window_get_pointer(NULL, &x, NULL, NULL);
	    x -= width + border_minor;
	    break;
	  case STATIC_TIP_ALIGN_POINTER_CENTER:
	    gdk_window_get_pointer(NULL, &x, NULL, NULL);
	    x -= width / 2;
	    break;
	  case STATIC_TIP_ALIGN_POINTER_MAX:
	    gdk_window_get_pointer(NULL, &x, NULL, NULL);
	    x += width + border_minor;
	    break;

	  case STATIC_TIP_ALIGN_WIDGET_MIN:
	    gdk_window_get_origin(window, &x, NULL);
	    x -= width + border_minor;
	    if(GTK_WIDGET_NO_WINDOW(w))
		x += allocation->x;
	    break;
	  case STATIC_TIP_ALIGN_WIDGET_CENTER:
	    gdk_window_get_origin(window, &x, NULL);
	    x += (allocation->width / 2) - (width / 2);
	    if(GTK_WIDGET_NO_WINDOW(w))
		x += allocation->x;
	    break;
	  case STATIC_TIP_ALIGN_WIDGET_MAX:
	    gdk_window_get_origin(window, &x, NULL);
	    x += allocation->width + border_minor;
	    if(GTK_WIDGET_NO_WINDOW(w))
		x += allocation->x;
	    break;

	  case STATIC_TIP_ALIGN_WIDGET_VALUE:
	    gdk_window_get_origin(window, &x2, NULL);
	    x += x2;
	    if(GTK_WIDGET_NO_WINDOW(w))
		x += allocation->x;
	    break;
	  case STATIC_TIP_ALIGN_ROOT_VALUE:
	    break;
	}

	/* Calculate y position */
	switch(y_align)
	{
	  case STATIC_TIP_ALIGN_POINTER_MIN:
	    gdk_window_get_pointer(NULL, NULL, &y, NULL);
	    y -= height + border_minor;
	    break;
	  case STATIC_TIP_ALIGN_POINTER_CENTER:
	    gdk_window_get_pointer(NULL, NULL, &y, NULL);
	    y -= height / 2;
	    break;
	  case STATIC_TIP_ALIGN_POINTER_MAX:
	    gdk_window_get_pointer(NULL, NULL, &y, NULL);
	    y += height + border_minor;
	    break;

	  case STATIC_TIP_ALIGN_WIDGET_MIN:
	    gdk_window_get_origin(window, NULL, &y);
	    y -= height + border_minor;
	    if(GTK_WIDGET_NO_WINDOW(w))
		y += allocation->y;
	    break;
	  case STATIC_TIP_ALIGN_WIDGET_CENTER:
	    gdk_window_get_origin(window, NULL, &y);
	    y += (allocation->height / 2) - (height / 2);
	    if(GTK_WIDGET_NO_WINDOW(w))
		y += allocation->y;
	    break;
	  case STATIC_TIP_ALIGN_WIDGET_MAX:
	    gdk_window_get_origin(window, NULL, &y);
	    y += allocation->height + border_minor;
	    if(GTK_WIDGET_NO_WINDOW(w))
		y += allocation->y;
	    break;

	  case STATIC_TIP_ALIGN_WIDGET_VALUE:
	    gdk_window_get_origin(window, NULL, &y2);
	    y += y2;
	    if(GTK_WIDGET_NO_WINDOW(w))
		y += allocation->y;
	    break;
	  case STATIC_TIP_ALIGN_ROOT_VALUE:
	    break;
	}


	/* Set new geometry and map toplevel as needed */
	if(GTK_WIDGET_MAPPED(toplevel))
	{
	    gtk_widget_set_usize(toplevel, width, height);
	    gtk_widget_set_uposition(toplevel, x, y);
	    gtk_widget_queue_resize(toplevel);
	}
	else
	{
	    gtk_widget_set_usize(toplevel, width, height);
	    gtk_widget_popup(toplevel, x, y);
	}

	gtk_widget_queue_draw(toplevel);
}
