/**
 * SECTION:SwamiguiBar
 * @short_description: Canvas item for displaying multiple pointers or ranges.
 * @see_also: #SwamiguiBarPtr
 *
 * Horizontal bar canvas item for displaying multiple pointers and/or ranges.
 * A #SwamiguiBar is composed of one or more #SwamiguiBarPtr items.
 */

/*
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * This program 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; version 2
 * of the License only.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA or point your web browser to http://www.gnu.org.
 */
#include <string.h>
#include <gtk/gtk.h>

#include "libswami/swami_priv.h"

#include "SwamiguiBar.h"
#include "i18n.h"

enum
{
    PROP_0,
    PROP_MIN_HEIGHT,	/* min height of pointers (top of stack) */
    PROP_MAX_HEIGHT	/* max height of pointers (bottom of stack) */
};

/* structure which defines an interface pointer */
typedef struct
{
    char *id;			/* id of this item */
    GnomeCanvasItem *barptr;	/* pointer canvas item */
    SwamiguiBar *bar;		/* parent bar object */
    gboolean mouse_sel;		/* TRUE if mouse selection is in progress */
} PtrInfo;

static void swamigui_bar_class_init(SwamiguiBarClass *klass);
static void swamigui_bar_set_property(GObject *object, guint property_id,
                                      const GValue *value,
                                      GParamSpec *pspec);
static void swamigui_bar_get_property(GObject *object, guint property_id,
                                      GValue *value, GParamSpec *pspec);
static void swamigui_bar_init(SwamiguiBar *bar);
static void swamigui_bar_finalize(GObject *object);

static gboolean swamigui_bar_cb_ptr_event(GnomeCanvasItem *item,
        GdkEvent *event, gpointer data);
static void swamigui_bar_update_ptr_heights(SwamiguiBar *bar);

static GObjectClass *parent_class = NULL;

GType
swamigui_bar_get_type(void)
{
    static GType obj_type = 0;

    if(!obj_type)
    {
        static const GTypeInfo obj_info =
        {
            sizeof(SwamiguiBarClass), NULL, NULL,
            (GClassInitFunc) swamigui_bar_class_init, NULL, NULL,
            sizeof(SwamiguiBar), 0,
            (GInstanceInitFunc) swamigui_bar_init,
        };

        obj_type = g_type_register_static(GNOME_TYPE_CANVAS_GROUP,
                                          "SwamiguiBar", &obj_info, 0);
    }

    return (obj_type);
}

static void
swamigui_bar_class_init(SwamiguiBarClass *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->set_property = swamigui_bar_set_property;
    obj_class->get_property = swamigui_bar_get_property;
    obj_class->finalize = swamigui_bar_finalize;

    g_object_class_install_property(obj_class, PROP_MIN_HEIGHT,
                                    g_param_spec_int("min-height", _("Min height"),
                                            _("Minimum height of pointers"),
                                            1, G_MAXINT, 16,
                                            G_PARAM_READWRITE));
    g_object_class_install_property(obj_class, PROP_MAX_HEIGHT,
                                    g_param_spec_int("max-height", _("Max height"),
                                            _("Maximum height of pointers"),
                                            1, G_MAXINT, 48,
                                            G_PARAM_READWRITE));
}

static void
swamigui_bar_set_property(GObject *object, guint property_id,
                          const GValue *value, GParamSpec *pspec)
{
    SwamiguiBar *bar = SWAMIGUI_BAR(object);

    switch(property_id)
    {
    case PROP_MIN_HEIGHT:
        bar->min_height = g_value_get_int(value);
        swamigui_bar_update_ptr_heights(bar);
        break;

    case PROP_MAX_HEIGHT:
        bar->max_height = g_value_get_int(value);
        swamigui_bar_update_ptr_heights(bar);
        break;

    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static void
swamigui_bar_get_property(GObject *object, guint property_id,
                          GValue *value, GParamSpec *pspec)
{
    SwamiguiBar *bar = SWAMIGUI_BAR(object);

    switch(property_id)
    {
    case PROP_MIN_HEIGHT:
        g_value_set_int(value, bar->min_height);
        break;

    case PROP_MAX_HEIGHT:
        g_value_set_int(value, bar->max_height);
        break;

    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static void
swamigui_bar_init(SwamiguiBar *bar)
{
    bar->min_height = 16;
    bar->max_height = 48;
    bar->ptrlist = NULL;
}

static void
swamigui_bar_finalize(GObject *object)
{
    SwamiguiBar *bar = SWAMIGUI_BAR(object);
    PtrInfo *info;
    GList *p;

    for(p = bar->ptrlist; p; p = g_list_delete_link(p, p))
    {
        info = (PtrInfo *)(p->data);
        g_free(info->id);
        g_free(p->data);
    }

    if(G_OBJECT_CLASS(parent_class)->finalize)
    {
        (* G_OBJECT_CLASS(parent_class)->finalize)(object);
    }
}

/* callback for SwamiguiBarPtr events */
static gboolean
swamigui_bar_cb_ptr_event(GnomeCanvasItem *item, GdkEvent *event,
                          gpointer data)
{
    PtrInfo *ptrinfo = (PtrInfo *)data;
    SwamiguiBar *bar = SWAMIGUI_BAR(ptrinfo->bar);
    GdkEventButton *bevent;

    switch(event->type)
    {
    case GDK_BUTTON_PRESS:
        bevent = (GdkEventButton *)event;

        if(bevent->button != 1)
        {
            break;
        }

        ptrinfo->mouse_sel = TRUE;

        /* make sure the item is raised to the top */
        swamigui_bar_raise_pointer_to_top(bar, ptrinfo->id);

        gnome_canvas_item_grab(item, GDK_POINTER_MOTION_MASK
                               | GDK_BUTTON_RELEASE_MASK, NULL,
                               bevent->time);
        return (TRUE);

    case GDK_BUTTON_RELEASE:
        if(!ptrinfo->mouse_sel)
        {
            break;    /* not selected? */
        }

        ptrinfo->mouse_sel = FALSE;
        gnome_canvas_item_ungrab(item, event->button.time);
        break;

    case GDK_MOTION_NOTIFY:
        if(!ptrinfo->mouse_sel)
        {
            break;    /* not selected? */
        }

        break;

    default:
        break;
    }

    return (FALSE);
}

/* update heights of all pointers based on current stacking order */
static void
swamigui_bar_update_ptr_heights(SwamiguiBar *bar)
{
    PtrInfo *info;
    int count, height_diff, min_height, max_height;
    double xform[6];
    GList *p;
    int height;
    int i;

    count = g_list_length(bar->ptrlist);

    if(count == 0)
    {
        return;
    }

    /* just in case they are swapped */
    min_height = MIN(bar->min_height, bar->max_height);
    max_height = MAX(bar->min_height, bar->max_height);
    height_diff = max_height - min_height;

    /* set height and y-position of pointers */
    for(i = 0, p = bar->ptrlist; i < count; i++, p = p->next)
    {
        info = (PtrInfo *)(p->data);

        /* set the height of the pointer between min-height and max-height */
        if(count != 1)
        {
            height = min_height + (height_diff * i) / (count - 1);
        }
        else
        {
            height = max_height;
        }

        g_object_set(info->barptr, "height", height, NULL);

        /* set the y pos of pointer so they line up on the bottom */
        gnome_canvas_item_i2w_affine(info->barptr, xform);
        xform[5] = max_height - height;
        gnome_canvas_item_affine_absolute(info->barptr, xform);
    }
}

/**
 * swamigui_bar_create_pointer:
 * @bar: Bar canvas item
 * @id: String identifier to use for this pointer
 * @first_property_name: Name of first #SwamiguiBarPtr property to assign to or
 *   %NULL to not set any pointer properties.
 * @...: First value to assign to @first_property_name followed by additional
 *   name/value pairs, terminated with a %NULL name argument.
 *
 * Creates a new #SwamiguiBarPtr, sets properties and adds it to a bar canvas
 * item.
 */
void
swamigui_bar_create_pointer(SwamiguiBar *bar, const char *id,
                            const char *first_property_name, ...)
{
    SwamiguiBarPtr *barptr;
    va_list args;

    g_return_if_fail(SWAMIGUI_IS_BAR(bar));
    g_return_if_fail(id != NULL);

    barptr = g_object_new(SWAMIGUI_TYPE_BAR_PTR, NULL);

    va_start(args, first_property_name);
    g_object_set_valist(G_OBJECT(barptr), first_property_name, args);
    va_end(args);

    swamigui_bar_add_pointer(bar, barptr, id);
}

/**
 * swamigui_bar_add_pointer:
 * @bar: Bar canvas item
 * @barptr: Existing bar pointer to add to bar canvas item.
 * @id: String identifier to use for this pointer
 *
 * Add an existing bar pointer to a bar canvas item.
 */
void
swamigui_bar_add_pointer(SwamiguiBar *bar, SwamiguiBarPtr *barptr,
                         const char *id)
{
    PtrInfo *info;

    g_return_if_fail(SWAMIGUI_IS_BAR(bar));
    g_return_if_fail(SWAMIGUI_IS_BAR_PTR(barptr));
    g_return_if_fail(id != NULL);

    info = g_slice_new(PtrInfo);
    info->id = g_strdup(id);
    info->barptr = GNOME_CANVAS_ITEM(barptr);
    info->bar = bar;
    info->mouse_sel = FALSE;

    bar->ptrlist = g_list_append(bar->ptrlist, info);

    gnome_canvas_item_reparent(GNOME_CANVAS_ITEM(barptr),
                               GNOME_CANVAS_GROUP(bar));
    gnome_canvas_item_lower_to_bottom(GNOME_CANVAS_ITEM(barptr));

    g_signal_connect(G_OBJECT(barptr),
                     "event", G_CALLBACK(swamigui_bar_cb_ptr_event), info);

    swamigui_bar_update_ptr_heights(bar);
}

/**
 * @bar: Bar canvas item
 * @id: String identifier of pointer to find
 *
 * Get a bar pointer object in a bar canvas item identified by its string ID.
 *
 * Returns: The bar pointer object with the given string @id or %NULL if not
 *   found.
 */
GnomeCanvasItem *
swamigui_bar_get_pointer(SwamiguiBar *bar, const char *id)
{
    PtrInfo *info;
    GList *p;

    g_return_val_if_fail(SWAMIGUI_IS_BAR(bar), NULL);
    g_return_val_if_fail(id != NULL, NULL);

    for(p = bar->ptrlist; p; p = p->next)
    {
        info = (PtrInfo *)(p->data);

        if(strcmp(info->id, id) == 0)
        {
            break;
        }
    }

    return (p ? info->barptr : NULL);
}

/**
 * swamigui_bar_set_pointer_position:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to set position of
 * @position: Position in pixels to set pointer to
 *
 * Set the position of a #SwamiguiBarPtr item in a bar canvas item.  Pointer
 * is centered on the given @position regardless of mode (position or range).
 */
void
swamigui_bar_set_pointer_position(SwamiguiBar *bar, const char *id,
                                  int position)
{
    GnomeCanvasItem *barptr;
    double xform[6];
    int width;

    barptr = swamigui_bar_get_pointer(bar, id);
    g_return_if_fail(barptr != NULL);

    g_object_get(barptr, "width", &width, NULL);

    position -= width / 2;

    gnome_canvas_item_i2w_affine(barptr, xform);
    xform[4] = position;
    gnome_canvas_item_affine_absolute(barptr, xform);
}

/**
 * swamigui_bar_set_pointer_range:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to set position of
 * @start: Position in pixels of start of range
 * @end: Position in pixels of end of range
 *
 * Set the range of a #SwamiguiBarPtr item in a bar canvas item.  Pointer is
 * set to the range defined by @start and @end.
 */
void
swamigui_bar_set_pointer_range(SwamiguiBar *bar, const char *id,
                               int start, int end)
{
    GnomeCanvasItem *barptr;
    double xform[6];
    int temp;

    barptr = swamigui_bar_get_pointer(bar, id);
    g_return_if_fail(barptr != NULL);

    if(start > end)	/* swap if backwards */
    {
        temp = start;
        start = end;
        end = temp;
    }

    g_object_set(barptr, "width", end - start + 1, NULL);

    gnome_canvas_item_i2w_affine(barptr, xform);
    xform[4] = start;
    gnome_canvas_item_affine_absolute(barptr, xform);
}

/**
 * swamigui_bar_get_pointer_order:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to get stacking order position of
 *
 * Get the stacking order of a pointer.
 *
 * Returns: Stacking order of pointer (0 = top, -1 if pointer not found).
 */
int
swamigui_bar_get_pointer_order(SwamiguiBar *bar, const char *id)
{
    PtrInfo *info;
    GList *p;
    int i;

    g_return_val_if_fail(SWAMIGUI_IS_BAR(bar), -1);
    g_return_val_if_fail(id != NULL, -1);

    for(p = bar->ptrlist, i = 0; p; p = p->next, i++)
    {
        info = (PtrInfo *)(p->data);

        if(strcmp(info->id, id) == 0)
        {
            break;
        }
    }

    return (p ? i : -1);
}

/**
 * swamigui_bar_set_pointer_order:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to set stacking order position of
 * @pos: Absolute position in stacking order (0 = top, -1 = bottom)
 *
 * Set the stacking order of a pointer to @pos.
 */
void
swamigui_bar_set_pointer_order(SwamiguiBar *bar, const char *id, int pos)
{
    PtrInfo *info;
    GList *p;
    int i;

    g_return_if_fail(SWAMIGUI_IS_BAR(bar));
    g_return_if_fail(id != NULL);

    for(p = bar->ptrlist, i = 0; p; p = p->next, i++)
    {
        info = (PtrInfo *)(p->data);

        if(strcmp(info->id, id) == 0)
        {
            break;
        }
    }

    g_return_if_fail(p != NULL);

    /* already at the requested position? */
    if(pos == i || (pos == -1 && !p->next))
    {
        return;
    }

    /* re-order the element */
    bar->ptrlist = g_list_delete_link(bar->ptrlist, p);
    bar->ptrlist = g_list_insert(bar->ptrlist, info, pos);

    /* set the stacking order of the canvas item */
    if(pos != -1)
    {
        /* raise to top and then lower to the given position */
        gnome_canvas_item_raise_to_top(info->barptr);
        gnome_canvas_item_lower(info->barptr, pos);
    }
    else
    {
        gnome_canvas_item_lower_to_bottom(info->barptr);
    }

    swamigui_bar_update_ptr_heights(bar);   /* update the pointer heights */
}

/**
 * swamigui_bar_raise_pointer_to_top:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to raise to top of stacking order
 *
 * Raise a pointer to the top of the stacking order.
 */
void
swamigui_bar_raise_pointer_to_top(SwamiguiBar *bar, const char *id)
{
    swamigui_bar_set_pointer_order(bar, id, 0);
}

/**
 * swamigui_bar_lower_pointer_to_bottom:
 * @bar: Bar canvas item
 * @id: String identifier of pointer to lower to bottom of stacking order
 *
 * Lower a pointer to the bottom of the stacking order.
 */
void
swamigui_bar_lower_pointer_to_bottom(SwamiguiBar *bar, const char *id)
{
    swamigui_bar_set_pointer_order(bar, id, -1);
}
