/* $Id$ */
/* Copyright (c) 2011-2016 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Keyboard */
/* All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#include <stdlib.h>
#include <string.h>
#include <gdk/gdkx.h>
#include <Desktop.h>
#include "common.h"
#include "key.h"


/* KeyboardKey */
/* private */
/* types */
typedef struct _KeyboardKeyModifier
{
	unsigned int modifier;
	unsigned int keysym;
	char * label;
} KeyboardKeyModifier;

struct _KeyboardKey
{
	GtkWidget * widget;
	GtkWidget * label;
	GtkWidget * popup;
	GtkWidget * button;
	KeyboardKeyModifier key;
	KeyboardKeyModifier * modifiers;
	size_t modifiers_cnt;
	KeyboardKeyModifier * current;
};


/* prototypes */
static void _keyboard_key_create_popup(KeyboardKey * key);

/* callbacks */
static gboolean _on_keyboard_key_button_press(GtkWidget * widget,
		GdkEventButton * event, gpointer data);
static gboolean _on_keyboard_key_button_release(GtkWidget * widget,
		GdkEventButton * event, gpointer data);


/* public */
/* functions */
/* keyboard_key_new */
KeyboardKey * keyboard_key_new(unsigned int keysym, char const * label)
{
	KeyboardKey * key;

	if((key = malloc(sizeof(*key))) == NULL)
		return NULL;
	if(keysym_is_modifier(keysym))
		key->widget = gtk_toggle_button_new();
	else
		key->widget = gtk_button_new();
	g_signal_connect(G_OBJECT(key->widget), "button-press-event",
			G_CALLBACK(_on_keyboard_key_button_press), key);
	g_signal_connect(G_OBJECT(key->widget), "button-release-event",
			G_CALLBACK(_on_keyboard_key_button_release), key);
	key->label = gtk_label_new(label);
	gtk_container_add(GTK_CONTAINER(key->widget), key->label);
	key->popup = NULL;
	key->button = NULL;
	key->key.modifier = 0;
	key->key.keysym = keysym;
	key->key.label = strdup(label);
	key->modifiers = NULL;
	key->modifiers_cnt = 0;
	key->current = &key->key;
	if(key->key.label == NULL)
	{
		keyboard_key_delete(key);
		return NULL;
	}
	return key;
}


/* keyboard_key_delete */
void keyboard_key_delete(KeyboardKey * key)
{
	size_t i;

	for(i = 0; i < key->modifiers_cnt; i++)
		free(key->modifiers[i].label);
	free(key->modifiers);
	free(key->key.label);
	free(key);
}


/* accessors */
/* keyboard_key_get_keysym */
unsigned int keyboard_key_get_keysym(KeyboardKey * key)
{
	return key->current->keysym;
}


/* keyboard_key_get_label_widget */
GtkWidget * keyboard_key_get_label_widget(KeyboardKey * key)
{
	return key->label;
}


/* keyboard_key_get_widget */
GtkWidget * keyboard_key_get_widget(KeyboardKey * key)
{
	return key->widget;
}


/* keyboard_key_set_background */
# if GTK_CHECK_VERSION(3, 0, 0)
void keyboard_key_set_background(KeyboardKey * key, GdkRGBA * color)
{
	_keyboard_key_create_popup(key);
	gtk_widget_override_background_color(key->widget, GTK_STATE_FLAG_NORMAL,
			color);
	gtk_widget_override_background_color(key->popup, GTK_STATE_FLAG_NORMAL,
			color);
	gtk_widget_override_background_color(key->button, GTK_STATE_FLAG_NORMAL,
			color);
}
# else
void keyboard_key_set_background(KeyboardKey * key, GdkColor * color)
{
	_keyboard_key_create_popup(key);
	gtk_widget_modify_bg(key->widget, GTK_STATE_NORMAL, color);
	gtk_widget_modify_bg(key->popup, GTK_STATE_NORMAL, color);
	gtk_widget_modify_bg(key->button, GTK_STATE_NORMAL, color);
}
#endif


/* keyboard_key_set_font */
void keyboard_key_set_font(KeyboardKey * key, PangoFontDescription * font)
{
	_keyboard_key_create_popup(key);
	gtk_widget_override_font(key->label, font);
	gtk_widget_override_font(gtk_bin_get_child(GTK_BIN(key->button)), font);
}


/* keyboard_key_set_foreground */
#if GTK_CHECK_VERSION(3, 0, 0)
void keyboard_key_set_foreground(KeyboardKey * key, GdkRGBA * color)
{
	_keyboard_key_create_popup(key);
	gtk_widget_override_color(key->label, GTK_STATE_FLAG_NORMAL, color);
	gtk_widget_override_color(key->button, GTK_STATE_FLAG_NORMAL, color);
}
#else
void keyboard_key_set_foreground(KeyboardKey * key, GdkColor * color)
{
	_keyboard_key_create_popup(key);
	gtk_widget_modify_fg(key->label, GTK_STATE_NORMAL, color);
	gtk_widget_modify_fg(key->button, GTK_STATE_NORMAL, color);
}
#endif


/* keyboard_key_set_modifier */
int keyboard_key_set_modifier(KeyboardKey * key, unsigned int modifier,
		unsigned int keysym, char const * label)
{
	char * p;
	KeyboardKeyModifier * q;

	if(label == NULL || (p = strdup(label)) == NULL)
		return -1;
	if(modifier == 0)
	{
		key->key.keysym = keysym;
		free(key->key.label);
		key->key.label = p;
		return 0;
	}
	if((q = realloc(key->modifiers, sizeof(*q) * (key->modifiers_cnt + 1)))
			== NULL)
	{
		free(p);
		return -1;
	}
	key->modifiers = q;
	q = &key->modifiers[key->modifiers_cnt++];
	q->modifier = modifier;
	q->keysym = keysym;
	q->label = p;
	return 0;
}


/* useful */
/* keyboard_key_apply_modifier */
void keyboard_key_apply_modifier(KeyboardKey * key, unsigned int modifier)
{
	char const * label = key->key.label;
	size_t i;

	key->current = &key->key;
	if(modifier != 0)
		for(i = 0; i < key->modifiers_cnt; i++)
			if(key->modifiers[i].modifier == modifier)
			{
				label = key->modifiers[i].label;
				key->current = &key->modifiers[i];
				break;
			}
	gtk_label_set_text(GTK_LABEL(key->label), label);
}


/* private */
/* functions */
/* keyboard_key_create_popup */
#if !GTK_CHECK_VERSION(3, 0, 0)
/* callbacks */
static void _create_popup_on_realize(gpointer data);
#endif

static void _keyboard_key_create_popup(KeyboardKey * key)
{
	if(key->popup != NULL)
		return;
	key->popup = gtk_window_new(GTK_WINDOW_POPUP);
#if !GTK_CHECK_VERSION(3, 0, 0)
	g_signal_connect(key->popup, "realize", G_CALLBACK(
				_create_popup_on_realize), NULL);
#endif
	key->button = gtk_button_new_with_label(gtk_label_get_text(GTK_LABEL(
					key->label)));
	gtk_button_set_alignment(GTK_BUTTON(key->button), 0.5, 0.1);
	gtk_container_add(GTK_CONTAINER(key->popup), key->button);
}

#if !GTK_CHECK_VERSION(3, 0, 0)
/* callbacks */
static void _create_popup_on_realize(gpointer data)
{
	GtkWidget * window = data;
	int width;
	int height;
	GdkBitmap * mask;
	GdkGC * gc;
	GdkColor black = { 0, 0, 0, 0 };
	GdkColor white = { 0xffffffff, 0xffff, 0xffff, 0xffff };

	/* XXX potential invalid assumption as to how the button is drawn */
	gtk_window_get_size(GTK_WINDOW(window), &width, &height);
	mask = gdk_pixmap_new(NULL, width, height, 1);
	gc = gdk_gc_new(mask);
	gdk_gc_set_foreground(gc, &white);
	gdk_draw_rectangle(mask, gc, TRUE, 0, 0, width, height);
	gdk_gc_set_foreground(gc, &black);
	/* left column */
	gdk_draw_rectangle(mask, gc, TRUE, 0, 0, 1, height);
	/* top row */
	gdk_draw_rectangle(mask, gc, TRUE, 0, 0, width, 1);
	/* right column */
	gdk_draw_rectangle(mask, gc, TRUE, width - 1, 0, 1, height);
	/* bottom row */
	gdk_draw_rectangle(mask, gc, TRUE, 0, height - 1, width, 1);
	/* top left corner */
	gdk_draw_rectangle(mask, gc, TRUE, 0, 0, 2, 2);
	/* top right corner */
	gdk_draw_rectangle(mask, gc, TRUE, width - 2, 0, 2, 2);
	/* bottom left corner */
	gdk_draw_rectangle(mask, gc, TRUE, 0, height - 2, 2, 2);
	/* bottom right corner */
	gdk_draw_rectangle(mask, gc, TRUE, width - 2, height - 2, 2, 2);
	gtk_widget_shape_combine_mask(window, mask, 0, 0);
	g_object_unref(gc);
	g_object_unref(mask);
}
#endif


/* callbacks */
/* on_keyboard_key_button_press */
static gboolean _on_keyboard_key_button_press(GtkWidget * widget,
		GdkEventButton * event, gpointer data)
{
	KeyboardKey * key = data;
	gint width;
	gint height;

#if GTK_CHECK_VERSION(2, 24, 0)
	width = gdk_window_get_width(event->window);
	height = gdk_window_get_height(event->window);
#else
	gdk_window_get_size(event->window, &width, &height);
#endif
	_keyboard_key_create_popup(key);
	gtk_widget_set_size_request(key->popup, width + 8, height * 2);
	gtk_window_move(GTK_WINDOW(key->popup), event->x_root - event->x - 4,
			event->y_root - event->y - height * 2);
	gtk_widget_show_all(key->popup);
	return FALSE;
}


/* on_keyboard_key_button_release */
static gboolean _on_keyboard_key_button_release(GtkWidget * widget,
		GdkEventButton * event, gpointer data)
{
	KeyboardKey * key = data;

	if(key->popup != NULL)
		gtk_widget_hide(key->popup);
	return FALSE;
}
