/* vim:set et sts=4: */
/* input-pad - The input pad
 * Copyright (C) 2010-2014 Takao Fujiwara <takao.fujiwara1@gmail.com>
 * Copyright (C) 2010-2014 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>

#include "input-pad-group.h"
#include "button-gtk.h"
#include "i18n.h"

#define TIMEOUT_INITIAL 500
#define TIMEOUT_REPEAT  300
#define INPUT_PAD_GTK_BUTTON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), INPUT_PAD_TYPE_GTK_BUTTON, InputPadGtkButtonPrivate))

enum {
    PRESSED_REPEAT,
    LAST_SIGNAL,
};

struct _InputPadGtkButtonPrivate {
    guint                       keycode;
    guint                       keysym;
    guint                     **keysyms;
    int                         keysym_group;
    guint                       state;
    gchar                      *label;
    gchar                      *rawtext;
    InputPadTableType           type;
    guint32                     timer;
    guint32                     unicode;
};

static guint                    signals[LAST_SIGNAL] = { 0 };
static GtkBuildableIface       *parent_buildable_iface;

static void input_pad_gtk_button_buildable_interface_init (GtkBuildableIface *iface);

#if GTK_CHECK_VERSION (2, 90, 0)
static void input_pad_gtk_button_destroy_real (GtkWidget *widget);
#else
static void input_pad_gtk_button_destroy_real (GtkObject *widget);
#endif

static gint input_pad_gtk_button_press_real (GtkWidget *widget, GdkEventButton *event);
static gint input_pad_gtk_button_release_real (GtkWidget *widget, GdkEventButton *event);

G_DEFINE_TYPE_WITH_CODE (InputPadGtkButton, input_pad_gtk_button,
                         GTK_TYPE_BUTTON,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                input_pad_gtk_button_buildable_interface_init))

static void
input_pad_gtk_button_init (InputPadGtkButton *button)
{
    InputPadGtkButtonPrivate *priv = INPUT_PAD_GTK_BUTTON_GET_PRIVATE (button);
    button->priv = priv;
}

static void
input_pad_gtk_button_buildable_interface_init (GtkBuildableIface *iface)
{
    parent_buildable_iface = g_type_interface_peek_parent (iface);
}

static void
input_pad_gtk_button_class_init (InputPadGtkButtonClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
#if !GTK_CHECK_VERSION (2, 90, 0)
    GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
#endif
    GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;

#if GTK_CHECK_VERSION (2, 90, 0)
    widget_class->destroy = input_pad_gtk_button_destroy_real;
#else
    object_class->destroy = input_pad_gtk_button_destroy_real;
#endif
    widget_class->button_press_event = input_pad_gtk_button_press_real;
    widget_class->button_release_event = input_pad_gtk_button_release_real;

    g_type_class_add_private (klass, sizeof (InputPadGtkButtonPrivate));

    signals[PRESSED_REPEAT] =
        g_signal_new (I_("pressed-repeat"),
                      G_TYPE_FROM_CLASS (gobject_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (InputPadGtkButtonClass, pressed_repeat),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE,
                      0);
}

static gint
button_timer_cb (InputPadGtkButton *button)
{
    g_return_val_if_fail (button->priv != NULL, FALSE);

    if (button->priv->timer == 0) {
        return FALSE;
    }

    button->priv->timer = gdk_threads_add_timeout (TIMEOUT_REPEAT,
                                                   (GSourceFunc) button_timer_cb,
                                                   (gpointer) button);

    g_signal_emit ((gpointer) button, signals[PRESSED_REPEAT], 0);
    return TRUE;
}

static void
start_timer (InputPadGtkButton *button)
{
    g_return_if_fail (button->priv != NULL);

    if (button->priv->timer != 0) {
        return;
    }
    button->priv->timer = gdk_threads_add_timeout (TIMEOUT_INITIAL,
                                                   (GSourceFunc) button_timer_cb,
                                                   (gpointer) button);
}

static void
end_timer (InputPadGtkButton *button)
{
    g_return_if_fail (button->priv != NULL);

    if (button->priv->timer == 0) {
        return;
    }
    g_source_remove (button->priv->timer);
    button->priv->timer = 0;
}

static void
#if GTK_CHECK_VERSION (2, 90, 0)
input_pad_gtk_button_destroy_real (GtkWidget *widget)
#else
input_pad_gtk_button_destroy_real (GtkObject *widget)
#endif
{
    InputPadGtkButton *ibutton;
    if (INPUT_PAD_IS_GTK_BUTTON (widget)) {
        ibutton = INPUT_PAD_GTK_BUTTON (widget);
        if (ibutton->priv) {
            end_timer (ibutton);
            g_free (ibutton->priv->rawtext);
            ibutton->priv->rawtext = NULL;
            g_free (ibutton->priv->label);
            ibutton->priv->label = NULL;
            ibutton->priv = NULL;
        }
    }
#if GTK_CHECK_VERSION (2, 90, 0)
    GTK_WIDGET_CLASS (input_pad_gtk_button_parent_class)->destroy (widget);
#else
    GTK_OBJECT_CLASS (input_pad_gtk_button_parent_class)->destroy (widget);
#endif
}

static gint
input_pad_gtk_button_press_real (GtkWidget      *widget,
                                 GdkEventButton *event)
{
    if (INPUT_PAD_IS_GTK_BUTTON (widget)) {
        start_timer (INPUT_PAD_GTK_BUTTON (widget));
    }
    return GTK_WIDGET_CLASS (input_pad_gtk_button_parent_class)->button_press_event (widget, event);
}

static gint
input_pad_gtk_button_release_real (GtkWidget      *widget,
                                   GdkEventButton *event)
{
    if (INPUT_PAD_IS_GTK_BUTTON (widget)) {
        end_timer (INPUT_PAD_GTK_BUTTON (widget));
    }
    return GTK_WIDGET_CLASS (input_pad_gtk_button_parent_class)->button_release_event (widget, event);
}

static int
context_render_string (cairo_t *cr, const gchar *label, int width, int height)
{
    PangoFontDescription *desc;
    PangoLayout *layout;
    int lwidth = 0;
    int lheight = 0;

    if (height > 14)
        desc = pango_font_description_from_string ("Monospace 10");
    else
        desc = pango_font_description_from_string ("Monospace 8");
    layout = pango_cairo_create_layout (cr);
    pango_layout_set_text (layout, label, -1);
    pango_layout_get_size (layout, &lwidth, &lheight);

#if 0
    g_print ("test %d %d %d %d\n",
             lwidth / PANGO_SCALE, lheight / PANGO_SCALE, width, height);
#endif

    cairo_move_to (cr,
                   (gdouble)(width - lwidth / PANGO_SCALE) / 2,
                   (gdouble)(height - lheight / PANGO_SCALE) / 2);
    cairo_set_source_rgba (cr, 0., 0., 0., 1.);
    pango_cairo_show_layout (cr, layout);
    g_object_unref (layout);
    pango_font_description_free (desc);

    return lwidth / PANGO_SCALE;
}

static GdkPixbuf *
create_pixbuf(const gchar *label, int icon_height)
{
    cairo_surface_t *image;
    cairo_t *cr;
    int width, height, lwidth;
    GdkPixbuf *pixbuf = NULL;

    if (icon_height <= 0)
        icon_height = DEFAULT_ICON_SIZE;

    image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
                                        icon_height, icon_height);
    cr = cairo_create (image);
    width = cairo_image_surface_get_width (image);
    height = cairo_image_surface_get_width (image);
    cairo_set_source_rgba (cr, 0., 0., 0., 0.);
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_paint (cr);
    cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
    lwidth = context_render_string (cr, label, width, height);

    /* If label is more than two chars. */
    if (lwidth > icon_height && lwidth < 1000) {
        cairo_destroy (cr);
        cairo_surface_destroy (image);

        image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
                                            lwidth, icon_height);
        cr = cairo_create (image);
        width = cairo_image_surface_get_width (image);
        height = cairo_image_surface_get_width (image);
        cairo_set_source_rgba (cr, 0., 0., 0., 0.);
        cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
        cairo_paint (cr);
        cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
        width = context_render_string (cr, label, width, icon_height);
    }

    pixbuf = gdk_pixbuf_get_from_surface (image, 0, 0, width, icon_height);
    cairo_destroy (cr);
    cairo_surface_destroy (image);
    return pixbuf;
}


GtkWidget *
input_pad_gtk_button_new_with_label (const gchar *label)
{
    return input_pad_gtk_button_new_with_label_size (label, 0);
}

GtkWidget *
input_pad_gtk_button_new_with_label_size (const gchar *label, int icon_size)
{
    GdkPixbuf *pixbuf = create_pixbuf(label, icon_size);
    GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf);
    GtkWidget *button;
    InputPadGtkButton *ibutton;

    g_object_unref (pixbuf);
    button = g_object_new (INPUT_PAD_TYPE_GTK_BUTTON, "image", image, NULL);
    ibutton = INPUT_PAD_GTK_BUTTON (button);
    ibutton->priv->label = g_strdup (label);
    return button;
}

GtkWidget *
input_pad_gtk_button_new_with_unicode (guint code)
{
    gchar buff[7];
    gchar buff2[35]; /* 7 x 5 e.g. 'a' -> '0x61 ' */
    gchar *tooltip;
    int i;
    GtkWidget *button;

    /* The displaying button is too long with '\t'. */
    if (code == '\t') {
        buff[0] = ' ';
        buff[1] = '\0';
        sprintf (buff2, "0x%02X ", (unsigned char) code);
    } else {
        buff[g_unichar_to_utf8 ((gunichar) code, buff)] = '\0';
        for (i = 0; buff[i] && i < 7; i++) {
            sprintf (buff2 + i * 5, "0x%02X ", (unsigned char) buff[i]);
        }
        if (buff[0] == '\0') {
            buff2[0] = '0'; buff2[0] = 'x'; buff2[1] = '0'; buff2[2] = '0';
            buff2[3] = '\0';
        }
    }

    button = input_pad_gtk_button_new_with_label (buff);
    if (code == '\t') {
        input_pad_gtk_button_set_keysym (INPUT_PAD_GTK_BUTTON (button),
                                         code);
    }
    tooltip = g_strdup_printf ("U+%04X\nUTF-8 %s", code, buff2);
    gtk_widget_set_tooltip_text (GTK_WIDGET (button), tooltip);
    g_free (tooltip);
    input_pad_gtk_button_set_table_type (INPUT_PAD_GTK_BUTTON (button),
                                         INPUT_PAD_TABLE_TYPE_CHARS);

    INPUT_PAD_GTK_BUTTON (button)->priv->unicode = code;
    return button;
}

guint
input_pad_gtk_button_get_unicode (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), 0x0);

    return button->priv->unicode;
}

void
input_pad_gtk_button_set_unicode (InputPadGtkButton *button, guint code)
{
    gchar buff[7];
    gchar buff2[35];
    gchar *tooltip;
    int i;

    if (code == '\t') {
        buff[0] = ' ';
        buff[1] = '\0';
        sprintf (buff2, "0x%02X ", (unsigned char) code);
    } else {
        buff[g_unichar_to_utf8 ((gunichar) code, buff)] = '\0';
        for (i = 0; buff[i] && i < 7; i++) {
            sprintf (buff2 + i * 5, "0x%02X ", (unsigned char) buff[i]);
        }
        if (buff[0] == '\0') {
            buff2[0] = '0'; buff2[0] = 'x'; buff2[1] = '0'; buff2[2] = '0';
            buff2[3] = '\0';
        }
    }

    input_pad_gtk_button_set_label (button, buff);
    if (code == '\t') {
        input_pad_gtk_button_set_keysym (button,
                                         code);
    }
    tooltip = g_strdup_printf ("U+%04X\nUTF-8 %s", code, buff2);
    gtk_widget_set_tooltip_text (GTK_WIDGET (button), tooltip);
    g_free (tooltip);
    input_pad_gtk_button_set_table_type (button,
                                         INPUT_PAD_TABLE_TYPE_CHARS);
    button->priv->unicode = code;
}

guint
input_pad_gtk_button_get_keycode (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), 0x0);

    return button->priv->keycode;
}

void
input_pad_gtk_button_set_keycode (InputPadGtkButton *button,
                                  guint              keycode)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->keycode = keycode;
}

guint
input_pad_gtk_button_get_keysym (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), 0x0);

    return button->priv->keysym;
}

void
input_pad_gtk_button_set_keysym (InputPadGtkButton *button,
                                 guint              keysym)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->keysym = keysym;
}

guint **
input_pad_gtk_button_get_all_keysyms (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), NULL);

    return button->priv->keysyms;
}

void
input_pad_gtk_button_set_all_keysyms (InputPadGtkButton         *button,
                                      guint                    **keysyms)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->keysyms = keysyms;
}

int
input_pad_gtk_button_get_keysym_group (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), 0);

    return button->priv->keysym_group;
}

void
input_pad_gtk_button_set_keysym_group (InputPadGtkButton         *button,
                                       int                        group)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->keysym_group = group;
}

guint
input_pad_gtk_button_get_state (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), 0x0);

    return button->priv->state;
}

void
input_pad_gtk_button_set_state (InputPadGtkButton *button,
                                guint              state)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->state = state;
}

InputPadTableType
input_pad_gtk_button_get_table_type (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), INPUT_PAD_TABLE_TYPE_NONE);

    return button->priv->type;
}

void
input_pad_gtk_button_set_table_type (InputPadGtkButton *button,
                                     InputPadTableType  type)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    button->priv->type = type;
}

const gchar *
input_pad_gtk_button_get_rawtext (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), NULL);

    return button->priv->rawtext;
}

void
input_pad_gtk_button_set_rawtext (InputPadGtkButton *button,
                                  const gchar       *rawtext)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    g_free (button->priv->rawtext);
    button->priv->rawtext = NULL;
    if (rawtext == NULL) {
        return;
    }

    button->priv->rawtext = g_strdup (rawtext);
}

const gchar *
input_pad_gtk_button_get_label (InputPadGtkButton *button)
{
    g_return_val_if_fail (button != NULL &&
                          INPUT_PAD_IS_GTK_BUTTON (button), NULL);

    return button->priv->label;
}

void
input_pad_gtk_button_set_label (InputPadGtkButton *button,
                                const gchar       *label)
{
    input_pad_gtk_button_set_label_size (button, label, 0);
}

void
input_pad_gtk_button_set_label_size (InputPadGtkButton *button,
                                     const gchar       *label,
                                     int                icon_size)
{
    g_return_if_fail (button != NULL &&
                      INPUT_PAD_IS_GTK_BUTTON (button));

    GdkPixbuf *pixbuf = create_pixbuf(label, icon_size);
    GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf);
    g_object_unref (pixbuf);
    gtk_button_set_image (GTK_BUTTON (button), image);
    g_free (button->priv->label);
    button->priv->label = g_strdup (label);
}
