/*
 * SwamiguiSampleEditor.c - Sample editor widget
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>

#include <libinstpatch/libinstpatch.h>

#include <libswami/swami_priv.h> /* log_if_fail() */

#include "SwamiguiControl.h"
#include "SwamiguiRoot.h"
#include "SwamiguiSampleEditor.h"
#include "SwamiguiSampleCanvas.h"
#include "SwamiguiLoopFinder.h"
#include "icons.h"
#include "util.h"
#include "i18n.h"

/* number of markers that are built in (don't get removed) */
#define BUILTIN_MARKER_COUNT	3

/* value stored in editor->sel_state for current active selection state */
typedef enum
{
    SEL_INACTIVE,		/* no selection is in progress */
    SEL_MAYBE,		/* a mouse click occurred by not moved enough yet */
    SEL_START,		/* actively changing selection from start edge */
    SEL_END		/* actively changing selection from end edge */
} SelState;

typedef struct
{
    IpatchSampleData *sample;
    gboolean right_chan;	/* set to TRUE for stereo to use right channel */
    GnomeCanvasItem *sample_view;
    GnomeCanvasItem *loop_view;
    GnomeCanvasItem *sample_center_line; /* sample view horizonal center line */
    GnomeCanvasItem *loop_center_line; /* loop view horizontal center line */
} TrackInfo;

typedef struct
{
    guint flags;			/* SwamiguiSampleEditorMarkerFlags */
    gboolean visible;		/* TRUE if visible, FALSE otherwise */
    SwamiControl *start_ctrl;	/* marker start control */
    SwamiControl *end_ctrl;	/* marker end control (range only) */
    GnomeCanvasItem *start_line;	/* marker start line canvas item */
    GnomeCanvasItem *end_line;	/* marker end line canvas item (range only) */
    GnomeCanvasItem *range_box;	/* marker range box (range only) */
    guint start_pos;		/* sample start position of marker */
    guint end_pos;		/* sample end position of marker (range only) */
    SwamiguiSampleEditor *editor;	/* so we can pass MarkerInfo to callbacks */
} MarkerInfo;

/* min zoom value for sample and loop canvas (samples/pixel) */
#define SAMPLE_CANVAS_MIN_ZOOM  0.02
#define LOOP_CANVAS_MIN_ZOOM	0.02

#define LOOP_CANVAS_DEF_ZOOM	0.2	/* default zoom for loop canvas */

/* default colors */
#define DEFAULT_CENTER_LINE_COLOR	0x7F00FFFF
#define DEFAULT_MARKER_BORDER_COLOR	0xBBBBBBFF
#define DEFAULT_SNAP_LINE_COLOR		0xFF0000FF
#define DEFAULT_LOOP_LINE_COLOR		0x777777FF

/* default marker colors */
guint default_marker_colors[] =
{
    GNOME_CANVAS_COLOR(0xC2, 0xFF, 0xB6),	/* selection marker */
    GNOME_CANVAS_COLOR(0xB5, 0x10, 0xFF),	/* loop finder start marker */
    GNOME_CANVAS_COLOR(0xFF, 0x10, 0x70),	/* loop finder end marker */
    GNOME_CANVAS_COLOR(0x21, 0xED, 0x3D),	/* loop (handler defined) */
    GNOME_CANVAS_COLOR(0xFF, 0xEE, 0x10),
    GNOME_CANVAS_COLOR(0xFF, 0x30, 0x10),
    GNOME_CANVAS_COLOR(0x10, 0xC4, 0xFF),
    GNOME_CANVAS_COLOR(0x10, 0xFF, 0x7B)
};

/* default height of marker bar */
#define DEFAULT_MARKER_BAR_HEIGHT	24

/* colors are in RRGGBBAA */
#define MARKER_DEFAULT_COLOR  0x00FF00FF /* interactive marker color */
#define MARKER_NONIACTV_COLOR 0x666666FF /* non-interactive marker color */

#define MARKER_DEFAULT_WIDTH  1	/* width of markers (in pixels) */
#define MARKER_CLICK_DISTANCE 3	/* click area surrounding markers (in pixels) */

enum
{
    PROP_0,
    PROP_ITEM_SELECTION,
    PROP_BORDER_WIDTH,
    PROP_MARKER_BAR_HEIGHT
};

static void
swamigui_sample_editor_class_init(SwamiguiSampleEditorClass *klass);
static void swamigui_sample_editor_panel_iface_init(SwamiguiPanelIface *panel_iface);
static gboolean
swamigui_sample_editor_panel_iface_check_selection(IpatchList *selection,
        GType *selection_types);
static void swamigui_sample_editor_set_property(GObject *object,
        guint property_id,
        const GValue *value,
        GParamSpec *pspec);
static void swamigui_sample_editor_get_property(GObject *object,
        guint property_id,
        GValue *value,
        GParamSpec *pspec);
static void swamigui_sample_editor_finalize(GObject *object);
static void swamigui_sample_editor_init(SwamiguiSampleEditor *editor);

//#define sample_edition
/* Disabled until sample editing operations are implemented by #define sample_edition */
#ifdef sample_edition
static void swamigui_sample_editor_cb_cut_sample(GtkToggleToolButton *button,
        gpointer user_data);
static void swamigui_sample_editor_cb_crop_sample(GtkToggleToolButton *button,
        gpointer user_data);
static void swamigui_sample_editor_cb_copy_new_sample(GtkToggleToolButton *button,
        gpointer user_data);
static IpatchSampleData *get_selection_sample_data(SwamiguiSampleEditor *editor);
#endif

static void swamigui_sample_editor_cb_loop_finder(GtkToggleToolButton *button,
        gpointer user_data);
static void editor_cb_pane_size_allocate(GtkWidget *pane,
        GtkAllocation *allocation,
        gpointer user_data);
static void
swamigui_sample_editor_cb_canvas_size_allocate(GtkWidget *widget,
        GtkAllocation *allocation,
        gpointer user_data);
static void swamigui_sample_editor_update_sizes(SwamiguiSampleEditor *editor);
static void
swamigui_sample_editor_update_canvas_size(SwamiguiSampleEditor *editor,
        GnomeCanvas *canvas);
static void swamigui_sample_editor_cb_scroll(GtkAdjustment *adj,
        gpointer user_data);
static gboolean
swamigui_sample_editor_cb_sample_canvas_event(GnomeCanvas *canvas,
        GdkEvent *event, gpointer data);
static gboolean
swamigui_sample_editor_cb_loop_canvas_event(GnomeCanvas *canvas,
        GdkEvent *event, gpointer data);
static MarkerInfo *
pos_is_marker(SwamiguiSampleEditor *editor, int xpos, int ypos,
              int *marker_edge, gboolean *is_onbox);
static void
swamigui_sample_editor_sample_mod_update(SwamiguiCanvasMod *mod, double xzoom,
        double yzoom, double xscroll,
        double yscroll, double xpos,
        double ypos, gpointer user_data);
static void
swamigui_sample_editor_sample_mod_snap(SwamiguiCanvasMod *mod, guint actions,
                                       double xsnap, double ysnap,
                                       gpointer user_data);
static void
swamigui_sample_editor_loop_mod_update(SwamiguiCanvasMod *mod, double xzoom,
                                       double yzoom, double xscroll,
                                       double yscroll, double xpos,
                                       double ypos, gpointer user_data);
static void
swamigui_sample_editor_loop_mod_snap(SwamiguiCanvasMod *mod, guint actions,
                                     double xsnap, double ysnap,
                                     gpointer user_data);
static gboolean
swamigui_sample_editor_real_set_selection(SwamiguiSampleEditor *editor,
        IpatchList *items);
static void
swamigui_sample_editor_deactivate_handler(SwamiguiSampleEditor *editor);
static void
swamigui_sample_editor_remove_track_item(SwamiguiSampleEditor *editor,
        GList *p, gboolean destroy);
static void
swamigui_sample_editor_set_marker_info(SwamiguiSampleEditor *editor,
                                       MarkerInfo *marker_info,
                                       guint start, guint end);
static void
swamigui_sample_editor_remove_marker_item(SwamiguiSampleEditor *editor,
        GList *p, gboolean destroy);

static void start_marker_control_get_value_func(SwamiControl *control,
        GValue *value);
static void start_marker_control_set_value_func(SwamiControl *control,
        SwamiControlEvent *event,
        const GValue *value);
static void end_marker_control_get_value_func(SwamiControl *control,
        GValue *value);
static void end_marker_control_set_value_func(SwamiControl *control,
        SwamiControlEvent *event,
        const GValue *value);
static void marker_control_update(MarkerInfo *marker_info);
static void marker_control_update_all(SwamiguiSampleEditor *editor);
static gboolean
swamigui_sample_editor_default_handler(SwamiguiSampleEditor *editor);
static gboolean
swamigui_sample_editor_default_handler_check_func(IpatchList *selection,
        GType *selection_types);
static void swamigui_sample_editor_loopsel_ctrl_get(SwamiControl *control,
        GValue *value);
static void swamigui_sample_editor_loopsel_ctrl_set(SwamiControl *control,
        SwamiControlEvent *event,
        const GValue *value);
static void swamigui_sample_editor_loopsel_cb_changed(GtkComboBox *widget,
        gpointer user_data);


static GObjectClass *parent_class = NULL;

/* list of SwamiguiSampleEditorHandlers */
static GList *sample_editor_handlers = NULL;

/* list of SwamiguiPanelCheckFunc corresponding to sample_editor_handlers */
static GList *sample_editor_check_funcs = NULL;

/* loop type info for IpatchSampleLoopType enum GtkComboBox */
struct
{
    int loop_type;
    char *icon;
    char *label;
    char *tooltip;
} loop_type_info[] =
{
    {
        IPATCH_SAMPLE_LOOP_NONE, SWAMIGUI_STOCK_LOOP_NONE,
        N_("None"), N_("No looping")
    },
    {
        IPATCH_SAMPLE_LOOP_STANDARD, SWAMIGUI_STOCK_LOOP_STANDARD,
        N_("Standard"), N_("Standard continuous loop")
    },
    {
        IPATCH_SAMPLE_LOOP_RELEASE, SWAMIGUI_STOCK_LOOP_RELEASE,
        N_("Release"), N_("Loop until key is released then play to end of sample")
    },
    {
        IPATCH_SAMPLE_LOOP_PINGPONG, SWAMIGUI_STOCK_LOOP_STANDARD,	/* FIXME */
        N_("Ping Pong"), N_("Alternate playing loop forwards and backwards")
    }
};

/* columns from GtkListStore model for loop selector GtkComboBox */
enum
{
    LOOPSEL_COL_LOOP_TYPE,
    LOOPSEL_COL_ICON,
    LOOPSEL_COL_LABEL,
    LOOPSEL_COL_TOOLTIP,
    LOOPSEL_COL_COUNT
};

/*--- Initialization / deinitialization -------------------------------------*/

/***
 * Initialization
 */
void _swamigui_sample_editor_init(void)
{
    /* list of SwamiguiSampleEditorHandlers */
    sample_editor_handlers = NULL;

    /* list of SwamiguiPanelCheckFunc corresponding to sample_editor_handlers */
    sample_editor_check_funcs = NULL;
}

/***
 * Deinitialization: free memory
 */
void _swamigui_sample_editor_deinit(void)
{
    g_list_free(sample_editor_handlers);
    g_list_free(sample_editor_check_funcs);
}

/*----- Swamigui Sample Editor dialog functions -----------------------------*/

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

    if(!obj_type)
    {
        static const GTypeInfo obj_info =
        {
            sizeof(SwamiguiSampleEditorClass), NULL, NULL,
            (GClassInitFunc) swamigui_sample_editor_class_init, NULL, NULL,
            sizeof(SwamiguiSampleEditor), 0,
            (GInstanceInitFunc) swamigui_sample_editor_init,
        };

        static const GInterfaceInfo panel_info =
        { (GInterfaceInitFunc)swamigui_sample_editor_panel_iface_init, NULL, NULL };

        obj_type = g_type_register_static(GTK_TYPE_HBOX,
                                          "SwamiguiSampleEditor", &obj_info, 0);
        g_type_add_interface_static(obj_type, SWAMIGUI_TYPE_PANEL, &panel_info);

        /* register default sample editor handler */
        swamigui_sample_editor_register_handler(swamigui_sample_editor_default_handler,
                                                swamigui_sample_editor_default_handler_check_func);
    }

    return (obj_type);
}

static void
swamigui_sample_editor_class_init(SwamiguiSampleEditorClass *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->set_property = swamigui_sample_editor_set_property;
    obj_class->get_property = swamigui_sample_editor_get_property;
    obj_class->finalize = swamigui_sample_editor_finalize;

    g_object_class_override_property(obj_class, PROP_ITEM_SELECTION, "item-selection");

    g_object_class_install_property(obj_class, PROP_MARKER_BAR_HEIGHT,
                                    g_param_spec_int("marker-bar-height", _("Marker bar height"),
                                            _("Height of marker and loop meter bar"),
                                            0, 100, DEFAULT_MARKER_BAR_HEIGHT,
                                            G_PARAM_READWRITE));
}

static void
swamigui_sample_editor_panel_iface_init(SwamiguiPanelIface *panel_iface)
{
    panel_iface->label = _("Sample Editor");
    panel_iface->blurb = _("Sample data and loop editor");
    panel_iface->stockid = SWAMIGUI_STOCK_SAMPLE_VIEWER;
    panel_iface->check_selection = swamigui_sample_editor_panel_iface_check_selection;
}

static gboolean
swamigui_sample_editor_panel_iface_check_selection(IpatchList *selection,
        GType *selection_types)
{
    GList *p;

    /* loop over handler check functions */
    for(p = sample_editor_check_funcs; p; p = p->next)
    {
        if(((SwamiguiPanelCheckFunc)(p->data))(selection, selection_types))
        {
            return (TRUE);
        }
    }

    return (FALSE);
}

static void
swamigui_sample_editor_set_property(GObject *object, guint property_id,
                                    const GValue *value, GParamSpec *pspec)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(object);
    IpatchList *list;
    int i;

    switch(property_id)
    {
    case PROP_ITEM_SELECTION:
        list = g_value_get_object(value);
        swamigui_sample_editor_real_set_selection(editor, list);
        break;

    case PROP_MARKER_BAR_HEIGHT:
        i = g_value_get_int(value);

        if(i != editor->marker_bar_height)
        {
            editor->marker_bar_height = i;
            swamigui_sample_editor_update_sizes(editor);
        }

        break;

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

static void
swamigui_sample_editor_get_property(GObject *object, guint property_id,
                                    GValue *value, GParamSpec *pspec)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(object);

    switch(property_id)
    {
    case PROP_ITEM_SELECTION:
        g_value_set_object(value, editor->selection);
        break;

    case PROP_MARKER_BAR_HEIGHT:
        g_value_set_int(value, editor->marker_bar_height);
        break;

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

static void
swamigui_sample_editor_finalize(GObject *object)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(object);
    GList *p, *temp;

    /* remove all markers (don't destroy GtkObjects though) */
    p = editor->markers;

    while(p)
    {
        temp = p;
        p = g_list_next(p);
        swamigui_sample_editor_remove_marker_item(editor, temp, FALSE);
    }

    /* remove all tracks (don't destroy GtkObjects though) */
    p = editor->tracks;

    while(p)
    {
        temp = p;
        p = g_list_next(p);
        swamigui_sample_editor_remove_track_item(editor, temp, FALSE);
    }

    if(editor->selection)
    {
        g_object_unref(editor->selection);
    }

    if(editor->loop_start_hub)
    {
        /* disconnect and unref control */
        swami_control_disconnect_unref(editor->loop_start_hub);
    }

    if(editor->loop_end_hub)
    {
        /* disconnect and unref control */
        swami_control_disconnect_unref(editor->loop_end_hub);
    }

    if(editor->loopsel_ctrl)
    {
        /* disconnect and unref control */
        swami_control_disconnect_unref(editor->loopsel_ctrl);
    }

    if(editor->loopsel_store)
    {
        g_object_unref(editor->loopsel_store);
    }

    editor->selection = NULL;
    editor->loop_start_hub = NULL;
    editor->loop_end_hub = NULL;
    editor->loopsel_ctrl = NULL;
    editor->loopsel_store = NULL;

    /* remove ref, necessary since pane is added/removed from container */
    g_object_unref(editor->loop_finder_pane);

    /* unref SwamiguiCanvasMod objects */
    g_object_unref(editor->sample_mod);
    g_object_unref(editor->loop_mod);

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

static void
swamigui_sample_editor_init(SwamiguiSampleEditor *editor)
{
    GtkWidget *vbox, *hbox;
    GtkWidget *frame;
    GtkWidget *pane;
    GtkWidget *image;
    GtkToolItem *item;
    GtkStyle *style;
    GtkCellRenderer *renderer;
    GtkTooltips *tips;
    SwamiLoopFinder *loop_finder;
    SwamiControl *marker_start, *marker_end;
    int id;

    editor->selection = NULL;
    editor->marker_bar_height = DEFAULT_MARKER_BAR_HEIGHT;
    editor->sel_marker = -1;
    editor->zoom_all = TRUE;
    editor->sel_state = SEL_INACTIVE;
    editor->center_line_color = DEFAULT_CENTER_LINE_COLOR;
    editor->marker_border_color = DEFAULT_MARKER_BORDER_COLOR;
    editor->snap_line_color = DEFAULT_SNAP_LINE_COLOR;
    editor->loop_line_color = DEFAULT_LOOP_LINE_COLOR;
    editor->loop_zoom = LOOP_CANVAS_DEF_ZOOM;

    /* create zoom/scroll modulator for sample canvas */
    editor->sample_mod = swamigui_canvas_mod_new();

    /* attach to "update" and "snap" signals */
    g_signal_connect(editor->sample_mod, "update",
                     G_CALLBACK(swamigui_sample_editor_sample_mod_update),
                     editor);
    g_signal_connect(editor->sample_mod, "snap",
                     G_CALLBACK(swamigui_sample_editor_sample_mod_snap),
                     editor);

    /* create zoom modulator for loop canvas */
    editor->loop_mod = swamigui_canvas_mod_new();

    /* attach to "update" and "snap" signals */
    g_signal_connect(editor->loop_mod, "update",
                     G_CALLBACK(swamigui_sample_editor_loop_mod_update),
                     editor);
    g_signal_connect(editor->loop_mod, "snap",
                     G_CALLBACK(swamigui_sample_editor_loop_mod_snap),
                     editor);

    /* create control hubs to control sample loop views for all tracks */
    /* editor retains holds references on controls */
    editor->loop_start_hub = SWAMI_CONTROL(swami_control_hub_new());   /* ++ ref */
    swamigui_control_set_queue(editor->loop_start_hub);

    editor->loop_end_hub = SWAMI_CONTROL(swami_control_hub_new());   /* ++ ref */
    swamigui_control_set_queue(editor->loop_end_hub);

    tips = gtk_tooltips_new();

    /* create loop finder widget and pane, only gets packed when active */
    editor->loop_finder_gui = SWAMIGUI_LOOP_FINDER(swamigui_loop_finder_new());
    editor->loop_finder_pane = gtk_hpaned_new();
    gtk_paned_pack1(GTK_PANED(editor->loop_finder_pane),
                    GTK_WIDGET(editor->loop_finder_gui), FALSE, TRUE);
    gtk_widget_show_all(editor->loop_finder_pane);

    /* when loop_finder_pane is created its reference is floating so we need to
       convert this reference into an ordinary reference. This ensure that the
       object will be correctly freed on SwamiguiSampleEditor finalization.
    */
    g_object_ref_sink(editor->loop_finder_pane);

    /* create main vbox for tool bar and sample canvases and pack in editor hbox */
    editor->mainvbox = gtk_vbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(editor), editor->mainvbox, TRUE, TRUE, 0);

    /* create toolbox area horizontal box */
    editor->toolbar = gtk_toolbar_new();

    editor->loopsel_store	/* ++ ref - editor holds reference to loopsel_store */
        = gtk_list_store_new(LOOPSEL_COL_COUNT, G_TYPE_INT, G_TYPE_STRING,
                             G_TYPE_STRING, G_TYPE_STRING);

    editor->loopsel = gtk_combo_box_new_with_model
                      (GTK_TREE_MODEL(editor->loopsel_store));
    gtk_widget_set_sensitive(editor->loopsel, FALSE);

    item = gtk_tool_item_new();
    gtk_container_add(GTK_CONTAINER(item), editor->loopsel);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar), item, -1);

    /* create the control for the loop type selector */
    /* ++ ref - editor holds reference to it */
    editor->loopsel_ctrl = SWAMI_CONTROL(swami_control_func_new());

    /* attach to the changed signal of the combo box */
    g_signal_connect(editor->loopsel, "changed",
                     G_CALLBACK(swamigui_sample_editor_loopsel_cb_changed),
                     editor);

    swami_control_func_assign_funcs(SWAMI_CONTROL_FUNC(editor->loopsel_ctrl),
                                    swamigui_sample_editor_loopsel_ctrl_get,
                                    swamigui_sample_editor_loopsel_ctrl_set,
                                    NULL, editor);

    renderer = gtk_cell_renderer_pixbuf_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(editor->loopsel), renderer, FALSE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(editor->loopsel), renderer,
                                   "stock-id", LOOPSEL_COL_ICON, NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(editor->loopsel), renderer, TRUE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(editor->loopsel), renderer,
                                   "text", LOOPSEL_COL_LABEL, NULL);

    /* create spin button and spin control */
    editor->spinbtn_start = gtk_spin_button_new(NULL, 1.0, 0);
    gtk_spin_button_set_range(GTK_SPIN_BUTTON(editor->spinbtn_start),
                              0.0, (gdouble)G_MAXINT);
    gtk_tooltips_set_tip(tips, editor->spinbtn_start,
                         _("Set loop start position"), NULL);
    editor->spinbtn_start_ctrl = swamigui_control_new_for_widget_full
                                 (G_OBJECT(editor->spinbtn_start), G_TYPE_UINT, NULL, 0);

    item = gtk_tool_item_new();
    gtk_container_add(GTK_CONTAINER(item), editor->spinbtn_start);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar), item, -1);

    /* spin button and spin control */
    editor->spinbtn_end = gtk_spin_button_new(NULL, 1.0, 0);
    gtk_spin_button_set_range(GTK_SPIN_BUTTON(editor->spinbtn_end),
                              0.0, (gdouble)G_MAXINT);
    gtk_tooltips_set_tip(tips, editor->spinbtn_end,
                         _("Set loop end position"), NULL);
    editor->spinbtn_end_ctrl = swamigui_control_new_for_widget_full
                               (G_OBJECT(editor->spinbtn_end), G_TYPE_UINT, NULL, 0);

    item = gtk_tool_item_new();
    gtk_container_add(GTK_CONTAINER(item), editor->spinbtn_end);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar), item, -1);


    /* Disabled until sample editing operations are implemented by #define sample_edition */
#ifdef sample_edition
    /* add separator */
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       gtk_separator_tool_item_new(), -1);

    /* create cut button */
    image = gtk_image_new_from_stock(GTK_STOCK_CUT, GTK_ICON_SIZE_SMALL_TOOLBAR);
    editor->cut_button = GTK_WIDGET(gtk_tool_button_new(image, _("Cut")));
    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(editor->cut_button), tips,
                              _("Cut selected sample data"), NULL);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       GTK_TOOL_ITEM(editor->cut_button), -1);
    g_signal_connect(editor->cut_button, "clicked",
                     G_CALLBACK(swamigui_sample_editor_cb_cut_sample), editor);

    /* create crop button */
    image = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_SMALL_TOOLBAR);
    editor->crop_button = GTK_WIDGET(gtk_tool_button_new(image, _("Crop")));
    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(editor->crop_button), tips,
                              _("Crop to current selection"), NULL);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       GTK_TOOL_ITEM(editor->crop_button), -1);
    g_signal_connect(editor->crop_button, "clicked",
                     G_CALLBACK(swamigui_sample_editor_cb_crop_sample), editor);

    /* create copy to new button */
    image = gtk_image_new_from_stock(GTK_STOCK_NEW, GTK_ICON_SIZE_SMALL_TOOLBAR);
    editor->copy_new_button = GTK_WIDGET(gtk_tool_button_new(image,
                                         _("Copy to new")));
    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(editor->copy_new_button), tips,
                              _("Copy selection to new sample"), NULL);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       GTK_TOOL_ITEM(editor->copy_new_button), -1);
    g_signal_connect(editor->copy_new_button, "clicked",
                     G_CALLBACK(swamigui_sample_editor_cb_copy_new_sample),
                     editor);

    /* create sample selector button */
    editor->samplesel_button = GTK_WIDGET(gtk_toggle_tool_button_new());
    gtk_tool_button_set_label(GTK_TOOL_BUTTON(editor->samplesel_button),
                              _("Sample selector"));
    image = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_SMALL_TOOLBAR);
    gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(editor->samplesel_button),
                                    image);
    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(editor->samplesel_button), tips,
                              _("Make selection the entire sample"), NULL);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       GTK_TOOL_ITEM(editor->samplesel_button), -1);
#endif


    /* add separator */
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       gtk_separator_tool_item_new(), -1);

    /* create loop finder button */
    editor->finder_button = GTK_WIDGET(gtk_toggle_tool_button_new());
    gtk_tool_button_set_label(GTK_TOOL_BUTTON(editor->finder_button),
                              _("Find loops"));
    image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR);
    gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(editor->finder_button),
                                    image);
    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(editor->finder_button), tips,
                              _("Loop point finder"), NULL);
    gtk_toolbar_insert(GTK_TOOLBAR(editor->toolbar),
                       GTK_TOOL_ITEM(editor->finder_button), -1);
    g_signal_connect(editor->finder_button, "toggled",
                     G_CALLBACK(swamigui_sample_editor_cb_loop_finder), editor);

    gtk_box_pack_start(GTK_BOX(editor->mainvbox), editor->toolbar,
                       FALSE, FALSE, 0);

    /* lower inset frame for sample viewer */
    frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
    gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
    gtk_box_pack_start(GTK_BOX(editor->mainvbox), frame, TRUE, TRUE, 0);

    /* vbox within frame to put sample canvas */
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(frame), vbox);

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);

    pane = gtk_hpaned_new();
    gtk_box_pack_start(GTK_BOX(hbox), pane, TRUE, TRUE, 0);

    /* connect to the size-allocate signal so we can initialize the gutter pos */
    g_signal_connect_after(pane, "size-allocate",
                           G_CALLBACK(editor_cb_pane_size_allocate), NULL);


    /* create sample editor canvas */

    editor->sample_canvas = GNOME_CANVAS(gnome_canvas_new());
    gnome_canvas_set_center_scroll_region(GNOME_CANVAS(editor->sample_canvas),
                                          FALSE);
    gtk_paned_pack1(GTK_PANED(pane), GTK_WIDGET(editor->sample_canvas),
                    TRUE, TRUE);
    g_signal_connect(editor->sample_canvas, "event",
                     G_CALLBACK(swamigui_sample_editor_cb_sample_canvas_event),
                     editor);
    g_signal_connect(editor->sample_canvas, "size-allocate",
                     G_CALLBACK(swamigui_sample_editor_cb_canvas_size_allocate),
                     editor);

    /* change background color of canvas to black */
    style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET
                           (editor->sample_canvas)));
    style->bg[GTK_STATE_NORMAL] = style->black;
    gtk_widget_set_style(GTK_WIDGET(editor->sample_canvas), style);

    /* create sample view marker bar border horizontal line */
    editor->sample_border_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->marker_border_color,
                                "width-pixels", 1,
                                NULL);

    /* create snap lines */
    editor->xsnap_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->snap_line_color,
                                "width-pixels", 2,
                                NULL);
    gnome_canvas_item_hide(editor->xsnap_line);

    editor->ysnap_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->snap_line_color,
                                "width-pixels", 2,
                                NULL);
    gnome_canvas_item_hide(editor->ysnap_line);

    /* create sample loop canvas */

    editor->loop_canvas = GNOME_CANVAS(gnome_canvas_new());
    gnome_canvas_set_center_scroll_region(GNOME_CANVAS(editor->loop_canvas),
                                          FALSE);
    gtk_paned_pack2(GTK_PANED(pane), GTK_WIDGET(editor->loop_canvas),
                    FALSE, TRUE);

    g_signal_connect(editor->loop_canvas, "event",
                     G_CALLBACK(swamigui_sample_editor_cb_loop_canvas_event),
                     editor);
    g_signal_connect(editor->loop_canvas, "size-allocate",
                     G_CALLBACK(swamigui_sample_editor_cb_canvas_size_allocate),
                     editor);

    /* change background color of canvas to black */
    style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET
                           (editor->loop_canvas)));
    style->bg[GTK_STATE_NORMAL] = style->black;
    gtk_widget_set_style(GTK_WIDGET(editor->loop_canvas), style);

    /* create vertical center line for loop view */
    editor->loop_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->loop_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->loop_line_color,
                                "width-pixels", 1,
                                NULL);

    /* create loop view marker bar border horizontal line */
    editor->loop_border_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->loop_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->marker_border_color,
                                "width-pixels", 1,
                                NULL);

    /* create zoom snap line for loop view */
    editor->loop_snap_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->loop_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->snap_line_color,
                                "width-pixels", 2,
                                NULL);
    gnome_canvas_item_hide(editor->loop_snap_line);

    /* attach a horizontal scrollbar to the sample viewer */
    editor->hscrollbar = gtk_hscrollbar_new(NULL);
    gtk_box_pack_start(GTK_BOX(editor->mainvbox), editor->hscrollbar,
                       FALSE, FALSE, 0);

    g_signal_connect_after(gtk_range_get_adjustment
                           (GTK_RANGE(editor->hscrollbar)), "value-changed",
                           G_CALLBACK(swamigui_sample_editor_cb_scroll),
                           editor);

    gtk_widget_show_all(editor->mainvbox);

    /* create marker 0 for selection and hide it */
    id = swamigui_sample_editor_add_marker(editor, 0, NULL, NULL);
    swamigui_sample_editor_show_marker(editor, id, FALSE);

    loop_finder = editor->loop_finder_gui->loop_finder;

    /* create marker for loop finder start search window and hide it */
    id = swamigui_sample_editor_add_marker(editor, 0, &marker_start, &marker_end);
    swamigui_sample_editor_show_marker(editor, id, FALSE);

    /* connect the marker controls to the loop finder properties */
    swami_control_prop_connect_to_control(G_OBJECT(loop_finder),
                                          "window1-start", marker_start,
                                          SWAMI_CONTROL_CONN_BIDIR_INIT);
    swami_control_prop_connect_to_control(G_OBJECT(loop_finder),
                                          "window1-end", marker_end,
                                          SWAMI_CONTROL_CONN_BIDIR_INIT);

    /* create marker for loop finder end search window and hide it */
    id = swamigui_sample_editor_add_marker(editor, 0, &marker_start, &marker_end);
    swamigui_sample_editor_show_marker(editor, id, FALSE);

    /* connect the marker controls to the loop finder properties */
    swami_control_prop_connect_to_control(G_OBJECT(loop_finder),
                                          "window2-start", marker_start,
                                          SWAMI_CONTROL_CONN_BIDIR_INIT);
    swami_control_prop_connect_to_control(G_OBJECT(loop_finder),
                                          "window2-end", marker_end,
                                          SWAMI_CONTROL_CONN_BIDIR_INIT);
}

/* Disabled until sample editing operations are implemented by #define sample_edition */
#ifdef sample_edition
static void
swamigui_sample_editor_cb_cut_sample(GtkToggleToolButton *button,
                                     gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    MarkerInfo *marker;
    IpatchSampleData *data;
    IpatchSampleStore *store;

    /* shouldn't happen, but.. */
    if(!editor->markers || !editor->markers->data)
    {
        return;
    }

    /* first marker is selection */
    marker = (MarkerInfo *)(editor->markers->data);

    if(!marker->visible)
    {
        return;
    }

    data = get_selection_sample_data(editor);	/* ++ ref */

    if(!data)
    {
        return;
    }

    store = ipatch_sample_data_get_default_store(data);	/* ++ ref */

    list = ipatch_sample_list_new();
    ipatch_sample_list_append(list, store, 0, 0, 0);
    ipatch_sample_list_cut(list, marker->start_pos,
                           marker->end_pos - marker->start_pos + 1);

    g_object_unref(data);	/* -- unref */
}

static void
swamigui_sample_editor_cb_crop_sample(GtkToggleToolButton *button,
                                      gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    IpatchSampleData *data;

    data = get_selection_sample_data(editor);

    if(!data)
    {
        return;
    }
}

static void
swamigui_sample_editor_cb_copy_new_sample(GtkToggleToolButton *button,
        gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    IpatchSampleData *data;

    data = get_selection_sample_data(editor);

    if(!data)
    {
        return;
    }
}

/* returns a IpatchSampleData object for the current selection or NULL if
 * not a single item which has an IpatchSample interface which allows
 * get/set of sample data.  Caller owns ref to sample data
 */
static IpatchSampleData *
get_selection_sample_data(SwamiguiSampleEditor *editor)
{
    IpatchSample *sample;

    if(!editor->selection || !editor->selection->items
            || editor->selection->items->next
            || !IPATCH_IS_SAMPLE(editor->selection->items->data))
    {
        return (NULL);
    }

    sample = IPATCH_SAMPLE(editor->selection->items->data);

    if(!ipatch_sample_has_data(sample))
    {
        return (NULL);
    }

    return (ipatch_sample_get_data(sample));	/* !! caller takes reference */
}
#endif

static void
swamigui_sample_editor_cb_loop_finder(GtkToggleToolButton *button,
                                      gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);

    editor->loop_finder_active = gtk_toggle_tool_button_get_active(button);

    if(editor->loop_finder_active)
    {
        /* repack the main vbox into the loop finder pane */
        g_object_ref(editor->mainvbox);
        gtk_container_remove(GTK_CONTAINER(editor), editor->mainvbox);
        gtk_paned_pack2(GTK_PANED(editor->loop_finder_pane), editor->mainvbox,
                        TRUE, TRUE);
        g_object_unref(editor->mainvbox);

        /* pack the loop finder pane into the editor widget */
        gtk_box_pack_start(GTK_BOX(editor), editor->loop_finder_pane, TRUE, TRUE, 0);
    }
    else
    {
        /* remove the loop finder pane from the editor widget */
        gtk_container_remove(GTK_CONTAINER(editor), editor->loop_finder_pane);

        /* repack the main vbox into the editor */
        g_object_ref(editor->mainvbox);
        gtk_container_remove(GTK_CONTAINER(editor->loop_finder_pane),
                             editor->mainvbox);
        gtk_box_pack_start(GTK_BOX(editor), editor->mainvbox, TRUE, TRUE, 0);
        g_object_unref(editor->mainvbox);
    }

    /* show/hide finder markers */
    swamigui_sample_editor_show_marker(editor,
                                       SWAMIGUI_SAMPLE_EDITOR_MARKER_ID_LOOP_FIND_START, editor->loop_finder_active);
    swamigui_sample_editor_show_marker(editor,
                                       SWAMIGUI_SAMPLE_EDITOR_MARKER_ID_LOOP_FIND_END, editor->loop_finder_active);
}

/* size-allocate callback to initialize hpane gutter position */
static void
editor_cb_pane_size_allocate(GtkWidget *pane, GtkAllocation *allocation,
                             gpointer user_data)
{
    int width;

    /* set loop view size to 1/5 total size or 160 pixels whichever is smallest */
    width = allocation->width / 5;

    if(width > 160)
    {
        width = 160;
    }

    /* disconnect the from the signal (one time only init) */
    g_signal_handlers_disconnect_by_func(pane, editor_cb_pane_size_allocate,
                                         user_data);

    gtk_paned_set_position(GTK_PANED(pane), allocation->width - width);
}

static void
swamigui_sample_editor_cb_canvas_size_allocate(GtkWidget *widget,
        GtkAllocation *allocation,
        gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    GnomeCanvas *canvas = GNOME_CANVAS(widget);

    swamigui_sample_editor_update_canvas_size(editor, canvas);
}

static void
swamigui_sample_editor_update_sizes(SwamiguiSampleEditor *editor)
{
    swamigui_sample_editor_update_canvas_size(editor, editor->sample_canvas);
    swamigui_sample_editor_update_canvas_size(editor, editor->loop_canvas);
}

static void
swamigui_sample_editor_update_canvas_size(SwamiguiSampleEditor *editor,
        GnomeCanvas *canvas)
{
    TrackInfo *track_info;
    GnomeCanvasItem *item;
    int width, height, sample_height, count, y, y2, i;
    double zoom, fullzoom = 0.0;
    GList *p;

    width = GTK_WIDGET(canvas)->allocation.width;
    height = GTK_WIDGET(canvas)->allocation.height;
    sample_height = height - editor->marker_bar_height;

    if(width <= 0)
    {
        width = 1;
    }

    if(height <= 0)
    {
        height = 1;
    }

    if(sample_height <= 0)
    {
        sample_height = 1;
    }


    /* update marker bar border line */
    item = canvas == editor->sample_canvas ? editor->sample_border_line
           : editor->loop_border_line;

    swamigui_util_canvas_line_set(item, 0, editor->marker_bar_height - 1,
                                  width, editor->marker_bar_height - 1);

    p = editor->tracks;

    if(!p)
    {
        return;
    }

    /* get count and last track in list */
    count = 1;

    while(p->next)
    {
        p = g_list_next(p);
        count++;
    }

    /* if sample canvas, calculate full sample zoom and use it if current zoom
     * would cause complete sample view to be less than the canvas width */
    if(canvas == editor->sample_canvas)
    {
        fullzoom = (double)(editor->sample_size) / width;
        track_info = (TrackInfo *)(p->data);
        g_object_get(track_info->sample_view, "zoom", &zoom, NULL);
    }

    /* loop in reverse over list (top sample down) */
    for(i = 1, y = editor->marker_bar_height; p; p = g_list_previous(p), i++)
    {
        y2 = i * sample_height / count + editor->marker_bar_height;

        track_info = (TrackInfo *)(p->data);

        /* update data view */
        item = (canvas == editor->sample_canvas) ? track_info->sample_view
               : track_info->loop_view;
        g_object_set(item,
                     "y", y,
                     "width", width,
                     "height", y2 - y,
                     NULL);

        /* set zoom value if current sample zoom would be less than canvas width,
         * or zoom_all is currently set */
        if(canvas == editor->sample_canvas
                && (editor->zoom_all || fullzoom < zoom))
        {
            g_object_set(item, "zoom", fullzoom, NULL);
        }

        /* update horizontal center line */
        item = (canvas == editor->sample_canvas)
               ? track_info->sample_center_line : track_info->loop_center_line;

        swamigui_util_canvas_line_set(item, 0, y + (y2 - y) / 2, width - 1,
                                      y + (y2 - y) / 2);
        y = y2 + 1;
    }

    if(canvas == editor->sample_canvas)
    {
        marker_control_update_all(editor);    /* update all markers */
    }
    else if(canvas == editor->loop_canvas)
        swamigui_util_canvas_line_set(editor->loop_line, width / 2, 0,
                                      width / 2, height);
}

/* horizontal scroll bar value changed callback */
static void
swamigui_sample_editor_cb_scroll(GtkAdjustment *adj, gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    marker_control_update_all(editor);  /* update all markers */
}

static gboolean
swamigui_sample_editor_cb_sample_canvas_event(GnomeCanvas *canvas,
        GdkEvent *event, gpointer data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(data);
    SwamiguiSampleCanvas *sample_view;
    TrackInfo *track_info;
    MarkerInfo *marker_info;
    GdkEventButton *btn_event;
    GdkEventMotion *motion_event;
    GdkCursor *cursor;
    int val;
    guint newstart, newend;
    gboolean onbox;
    int sample_size = editor->sample_size;

    if(!editor->tracks)
    {
        return (FALSE);
    }

    track_info = (TrackInfo *)(editor->tracks->data);
    sample_view = SWAMIGUI_SAMPLE_CANVAS(track_info->sample_view);

    if(event->type == GDK_BUTTON_PRESS)
    {
        btn_event = (GdkEventButton *)event;
    }

    /* don't process zoom/scroll mod events if its a middle marker click */
    if(event->type != GDK_BUTTON_PRESS
            || btn_event->button != 2
            || btn_event->y > editor->marker_bar_height)
    {
        /* handle zoom/scroll related events */
        if(swamigui_canvas_mod_handle_event(editor->sample_mod, event))
        {
            return (FALSE);    /* was zoom/scroll event - return */
        }
    }

    switch(event->type)
    {
    case GDK_MOTION_NOTIFY:
        motion_event = (GdkEventMotion *)event;

        if(editor->sel_marker != -1)	/* active marker? */
        {
            marker_info = g_list_nth_data(editor->markers, editor->sel_marker);

            if(!marker_info)
            {
                break;
            }

            val = swamigui_sample_canvas_xpos_to_sample(sample_view, (int)motion_event->x,
                    NULL);
            newstart = marker_info->start_pos;
            newend = marker_info->end_pos;

            if(editor->sel_marker_edge == -1)	/* start edge selected? */
            {
                newstart = CLAMP(val, 0, sample_size);
            }
            else if(editor->sel_marker_edge == 1)	/* end edge selected? */
            {
                newend = CLAMP(val, 0, sample_size);
            }
            else		/* range move (both edges selected) */
            {
                val -= editor->move_range_ofs;
                newstart = CLAMP(val, 0, (int)(sample_size - (newend - newstart)));
                newend += newstart - marker_info->start_pos;
            }

            /* swap selection if endpoints in reverse order */
            if(newstart > newend)
            {
                editor->sel_marker_edge = -editor->sel_marker_edge;
            }

            /* set the marker */
            swamigui_sample_editor_set_marker_info(editor, marker_info,
                                                   newstart, newend);
            break;
        }
        else if(editor->sel_state != SEL_INACTIVE)	/* if drag sel not inactive.. */
        {
            val = swamigui_sample_canvas_xpos_to_sample(sample_view, (int)motion_event->x,
                    NULL);
            val = CLAMP(val, 0, sample_size);

            /* get selection marker info */
            marker_info = g_list_nth_data(editor->markers, 0);

            if(editor->sel_state == SEL_MAYBE)	/* drag just begun? */
            {
                if(val == editor->sel_temp)
                {
                    break;    /* drag position not changed? */
                }

                /* just assume end drag, swap below if needed */
                editor->sel_state = SEL_END;
                newstart = editor->sel_temp;
                newend = val;
            }
            else if(editor->sel_state == SEL_START)
            {
                newstart = val;
                newend = marker_info->end_pos;
            }
            else
            {
                newstart = marker_info->start_pos;
                newend = val;
            }

            if(newstart > newend)	/* swap start/end if needed */
            {
                editor->sel_state = editor->sel_state == SEL_START ? SEL_END : SEL_START;
            }

            /* set selection marker - start/end swapped in function if needed */

            /* Disabled until sample editing operations are implemented by #define sample_edition */
#ifdef sample_edition
            swamigui_sample_editor_show_marker(editor, 0, TRUE);
#endif
            swamigui_sample_editor_set_marker(editor, 0, newstart, newend);
        }
        else  /* no marker sel or range sel active - mouse cursor over marker? */
        {
            /* see if mouse is over a marker */
            marker_info = pos_is_marker(editor, (int)motion_event->x, (int)motion_event->y,
                                        NULL, &onbox);

            /* see if cursor should be changed */
            if(!onbox && (marker_info != NULL) != editor->marker_cursor)
            {
                if(marker_info)
                {
                    cursor = gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);    /* ++ ref */
                }
                else
                {
                    cursor = gdk_cursor_new(GDK_LEFT_PTR);
                }

                gdk_window_set_cursor(GTK_WIDGET(editor->sample_canvas)->window,
                                      cursor);
                gdk_cursor_unref(cursor);
                editor->marker_cursor = marker_info != NULL;
            }
        }

        break;

    case GDK_BUTTON_PRESS:
        btn_event = (GdkEventButton *)event;

        if(swamigui_root_is_middle_click(NULL, btn_event))          /* middle click moves marker ranges */
        {
            marker_info = pos_is_marker(editor, (int)btn_event->x, (int)btn_event->y,
                                        NULL, &onbox);

            if(marker_info && onbox)	/* if click on a range box.. */
            {
                editor->sel_marker = g_list_index(editor->markers, marker_info);
                editor->sel_marker_edge = 0;

                /* calculate offset in samples from range start to click pos */
                val = swamigui_sample_canvas_xpos_to_sample(sample_view, (int)btn_event->x,
                        NULL);
                editor->move_range_ofs = val - marker_info->start_pos;
            }

            break;
        }

        /* make sure its button 1 */
        if(btn_event->button != 1)
        {
            break;
        }

        /* is it a marker click? */
        marker_info = pos_is_marker(editor, (int)btn_event->x, (int)btn_event->y,
                                    &editor->sel_marker_edge, NULL);

        if(marker_info)
        {
            editor->sel_marker = g_list_index(editor->markers, marker_info);
            break;
        }

        /* get sample position of click and assign to sel_temp */
        editor->sel_temp
            = swamigui_sample_canvas_xpos_to_sample(sample_view, (int)btn_event->x, NULL);

        /* if click is within the sample, we have a potential selection */
        if(editor->sel_temp >= 0 && editor->sel_temp < sample_size)
        {
            editor->sel_state = SEL_MAYBE;
        }

        break;

    case GDK_BUTTON_RELEASE:
        btn_event = (GdkEventButton *)event;

        if(btn_event->button == 1)
        {
            editor->sel_marker = -1;
            editor->sel_state = SEL_INACTIVE;
        }
        else if(btn_event->button == 2)
        {
            editor->sel_marker = -1;
        }

        break;

    default:
        break;
    }

    return (FALSE);
}

static gboolean
swamigui_sample_editor_cb_loop_canvas_event(GnomeCanvas *canvas,
        GdkEvent *event, gpointer data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(data);

    /* handle zoom/scroll related events */
    swamigui_canvas_mod_handle_event(editor->loop_mod, event);

    return (FALSE);	/* was zoom/scroll event - return */
}

/* check if a given xpos is on a marker */
static MarkerInfo *
pos_is_marker(SwamiguiSampleEditor *editor, int xpos, int ypos,
              int *marker_edge, gboolean *is_onbox)
{
    MarkerInfo *marker_info, *match = NULL;
    gboolean match_marker_edge = -1;
    SwamiguiSampleCanvas *sample_view;
    GnomeCanvasItem *item;
    int x, ofs = -1;
    int inview, onsample, pos;
    int startdiff, enddiff;
    GList *p;
    int i;

    if(is_onbox)
    {
        *is_onbox = FALSE;
    }

    if(marker_edge)
    {
        *marker_edge = -1;
    }

    if(!editor->markers || !editor->tracks)
    {
        return (NULL);
    }

    sample_view = SWAMIGUI_SAMPLE_CANVAS
                  (((TrackInfo *)(editor->tracks->data))->sample_view);

    /* click in marker bar area? */
    if(ypos <= editor->marker_bar_height)
    {
        /* retrieve canvas item at the given coordinates */
        item = gnome_canvas_get_item_at(editor->sample_canvas, xpos, ypos);

        if(!item)
        {
            return (NULL);
        }

        for(i = 0, p = editor->markers; p; i++, p = p->next)
        {
#ifndef sample_edition

            /* Marker 0 is ignored until sample edition will be implemented by #define sample_edition */
            if(i == 0)
            {
                continue;
            }

#endif

            /* don't search marker 1 and 2 if loop finder isn't active */
            if(((i == 1) || (i == 2)) && !editor->loop_finder_active)
            {
                continue;
            }

            marker_info = (MarkerInfo *)(p->data);

            if(marker_info->range_box == item)	/* range box matches? */
            {
                if(marker_edge)
                {
                    pos = swamigui_sample_canvas_xpos_to_sample(sample_view, xpos,
                            &onsample);
                    startdiff = marker_info->start_pos - pos;
                    enddiff = marker_info->end_pos - pos;

                    if(ABS(startdiff) <= ABS(enddiff))
                    {
                        *marker_edge = -1;
                    }
                    else
                    {
                        *marker_edge = 1;
                    }
                }

                if(is_onbox)
                {
                    *is_onbox = TRUE;
                }

                return (marker_info);
            }
        }

        return (NULL);
    }

    /* click not in marker bar area */

    for(i = 0, p = editor->markers; p; i++, p = p->next)
    {
#ifndef sample_edition

        /* Marker 0 is ignored until sample edition will be implemented by #define sample_edition */
        if(i == 0)
        {
            continue;
        }

#endif

        /* don't search marker 1 and 2 if loop finder isn't active */
        if(((i == 1) || (i == 2)) && !editor->loop_finder_active)
        {
            continue;
        }

        marker_info = (MarkerInfo *)(p->data);

        x = swamigui_sample_canvas_sample_to_xpos(sample_view,
                marker_info->start_pos, &inview);

        if(inview == 0)
        {
            x = ABS(xpos - x);

            if(x <= MARKER_CLICK_DISTANCE && (ofs == -1 || x < ofs))
            {
                match = marker_info;
                match_marker_edge = -1;
                ofs = x;
            }
        }

        x = swamigui_sample_canvas_sample_to_xpos(sample_view,
                marker_info->end_pos, &inview);

        if(inview == 0)
        {
            x = ABS(xpos - x);

            if(x <= MARKER_CLICK_DISTANCE && (ofs == -1 || x < ofs))
            {
                match = marker_info;
                match_marker_edge = 1;
                ofs = x;
            }
        }
    }

    if(marker_edge)
    {
        *marker_edge = match_marker_edge;
    }

    if(is_onbox)
    {
        *is_onbox = FALSE;
    }

    return (match);
}

/* canvas zoom/scroll modulator update signal handler */
static void
swamigui_sample_editor_sample_mod_update(SwamiguiCanvasMod *mod, double xzoom,
        double yzoom, double xscroll,
        double yscroll, double xpos,
        double ypos, gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    GnomeCanvasItem *sample_view;
    double zoom;
    int val;

    if(!editor->tracks)
    {
        return;
    }

    if(xzoom != 1.0)
    {
        swamigui_sample_editor_zoom_ofs(editor, xzoom, xpos);
    }

    if(xscroll != 0.0)
    {
        /* get first sample view item */
        sample_view = ((TrackInfo *)(editor->tracks->data))->sample_view;

        /* get zoom from first sample canvas (samples/pixel) */
        g_object_get(sample_view, "zoom", &zoom, NULL);

        xscroll *= zoom;	/* convert from scroll pixels/sec to samples/sec */

        /* do accumulation of scroll value, since we can only scroll by samples */
        editor->scroll_acc += xscroll;

        if(editor->scroll_acc >= 1.0)
        {
            val = (int)editor->scroll_acc;
        }
        else if(editor->scroll_acc <= -1.0)
        {
            val = -(int)(-editor->scroll_acc);
        }
        else
        {
            val = 0;
        }

        if(val != 0)
        {
            editor->scroll_acc -= val;
            swamigui_sample_editor_scroll_ofs(editor, val);
        }
    }

    /* no support for Y zoom/scroll yet */
}

/* canvas zoom/scroll modulator snap signal handler */
static void
swamigui_sample_editor_sample_mod_snap(SwamiguiCanvasMod *mod, guint actions,
                                       double xsnap, double ysnap,
                                       gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    int height, width;

    width = GTK_WIDGET(editor->sample_canvas)->allocation.width;
    height = GTK_WIDGET(editor->sample_canvas)->allocation.height;

    if(actions & (SWAMIGUI_CANVAS_MOD_ZOOM_X | SWAMIGUI_CANVAS_MOD_SCROLL_X))
    {
        swamigui_util_canvas_line_set(editor->xsnap_line, xsnap,
                                      editor->marker_bar_height, xsnap, height);
        gnome_canvas_item_show(editor->xsnap_line);
        editor->scroll_acc = 0.0;
    }
    else
    {
        gnome_canvas_item_hide(editor->xsnap_line);
    }

    if(actions & (SWAMIGUI_CANVAS_MOD_ZOOM_Y | SWAMIGUI_CANVAS_MOD_SCROLL_Y))
    {
        swamigui_util_canvas_line_set(editor->ysnap_line, 0, ysnap, width, ysnap);
        gnome_canvas_item_show(editor->ysnap_line);
    }
    else
    {
        gnome_canvas_item_hide(editor->ysnap_line);
    }
}

/* loop canvas zoom modulator update signal handler (scrolling does not apply) */
static void
swamigui_sample_editor_loop_mod_update(SwamiguiCanvasMod *mod, double xzoom,
                                       double yzoom, double xscroll,
                                       double yscroll, double xpos,
                                       double ypos, gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);

    if(xzoom != 1.0)
    {
        swamigui_sample_editor_loop_zoom(editor, xzoom);
    }

    /* no support for Y zoom yet */
}

/* loop canvas zoom modulator snap signal handler */
static void
swamigui_sample_editor_loop_mod_snap(SwamiguiCanvasMod *mod, guint actions,
                                     double xsnap, double ysnap,
                                     gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    int height;

    height = GTK_WIDGET(editor->sample_canvas)->allocation.height;

    if(actions & SWAMIGUI_CANVAS_MOD_ZOOM_X)
    {
        swamigui_util_canvas_line_set(editor->loop_snap_line, xsnap,
                                      editor->marker_bar_height, xsnap, height);
        gnome_canvas_item_show(editor->loop_snap_line);
    }
    else
    {
        gnome_canvas_item_hide(editor->loop_snap_line);
    }
}

/**
 * swamigui_sample_editor_new:
 *
 * Create a new sample view object
 *
 * Returns: new widget of type SwamiguiSampleEditor
 */
GtkWidget *
swamigui_sample_editor_new(void)
{
    return (GTK_WIDGET(gtk_type_new(swamigui_sample_editor_get_type())));
}

/**
 * swamigui_sample_editor_zoom_ofs:
 * @editor: Sample editor object
 * @zoom_amt: Zoom multiplier (> 1 = zoom in, < 1 = zoom out)
 * @zoom_xpos: X coordinate position to keep stationary
 *
 * Zoom the sample canvas the specified scale amount and modify the
 * start sample position to keep the given X coordinate stationary.
 */
void
swamigui_sample_editor_zoom_ofs(SwamiguiSampleEditor *editor, double zoom_amt,
                                double zoom_xpos)
{
    GnomeCanvasItem *sample_view;
    TrackInfo *track_info;
    double zoom, newzoom, sample_ofs;
    guint start, newstart;
    int width;
    GList *p;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    if(!editor->tracks)
    {
        return;
    }

    /* get first sample view item */
    sample_view = ((TrackInfo *)(editor->tracks->data))->sample_view;

    /* get values from first sample in editor */
    g_object_get(sample_view,
                 "zoom", &zoom,
                 "start", &start,
                 "width", &width,
                 NULL);

    sample_ofs = zoom_xpos * zoom; /* offset, in samples, to zoom xpos */
    newzoom = zoom * (1.0 / zoom_amt);
    newstart = start;

    editor->zoom_all = FALSE;

    /* do some bounds checking on the zoom value */
    if(newzoom < SAMPLE_CANVAS_MIN_ZOOM)
    {
        newzoom = SAMPLE_CANVAS_MIN_ZOOM;
    }
    else if(width * newzoom > editor->sample_size)
    {
        /* view exceeds sample data */
        newstart = 0;
        newzoom = editor->sample_size / (double)width;
        editor->zoom_all = TRUE;
    }
    else
    {
        sample_ofs -= zoom_xpos * newzoom; /* subtract new zoom offset */

        if(sample_ofs < 0.0 && -sample_ofs > newstart)
        {
            newstart = 0;
        }
        else
        {
            newstart += (guint)(sample_ofs + 0.5);
        }

        /* make sure sample doesn't end in the middle of the display */
        if(newstart + width * newzoom > editor->sample_size)
        {
            newstart = (guint)(editor->sample_size - width * newzoom);
        }
    }

    if(newzoom == zoom && newstart == start)
    {
        return;
    }

    p = editor->tracks;

    while(p)
    {
        track_info = (TrackInfo *)(p->data);
        g_object_set(track_info->sample_view,
                     "zoom", newzoom,
                     "start", newstart,
                     NULL);
        p = g_list_next(p);
    }
}

/**
 * swamigui_sample_editor_scroll_ofs:
 * @editor: Sample editor object
 * @sample_ofs: Offset amount in samples
 *
 * Scroll the sample canvas by a given offset.
 */
void
swamigui_sample_editor_scroll_ofs(SwamiguiSampleEditor *editor,
                                  int sample_ofs)
{
    GnomeCanvasItem *sample_view;
    double zoom;
    guint start_sample;
    int newstart, width;
    int last_sample;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    if(sample_ofs == 0)
    {
        return;
    }

    /* get first sample view item */
    sample_view = ((TrackInfo *)(editor->tracks->data))->sample_view;

    g_object_get(sample_view,
                 "start", &start_sample,
                 "zoom", &zoom,
                 "width", &width,
                 NULL);

    last_sample = (int)(editor->sample_size - zoom * width);

    if(last_sample < 0)
    {
        return;    /* sample too small for current zoom? */
    }

    newstart = (int)start_sample + sample_ofs;
    newstart = CLAMP(newstart, 0, last_sample);

    if(newstart == start_sample)
    {
        return;
    }

    g_object_set(sample_view, "start", newstart, NULL);
}

/**
 * swamigui_sample_editor_loop_zoom:
 * @editor: Sample editor object
 * @zoom_amt: Zoom multiplier (> 1 = zoom in, < 1 = zoom out)
 *
 * Zoom the loop viewer canvas the specified scale amount.  Zoom always occurs
 * around center of loop cross overlap.
 */
void
swamigui_sample_editor_loop_zoom(SwamiguiSampleEditor *editor, double zoom_amt)
{
    GnomeCanvasItem *loop_view;
    TrackInfo *track_info;
    double zoom, newzoom;
    GList *p;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    if(!editor->tracks)
    {
        return;
    }

    /* get first loop view item */
    loop_view = ((TrackInfo *)(editor->tracks->data))->loop_view;

    /* get values from first sample in editor */
    g_object_get(loop_view,
                 "zoom", &zoom,
                 NULL);

    newzoom = zoom * (1.0 / zoom_amt);

    /* do some bounds checking on the zoom value */
    if(newzoom < LOOP_CANVAS_MIN_ZOOM)
    {
        newzoom = LOOP_CANVAS_MIN_ZOOM;
    }
    else if(newzoom > 1.0)
    {
        newzoom = 1.0;
    }

    if(newzoom == zoom)
    {
        return;
    }

    editor->loop_zoom = newzoom;

    p = editor->tracks;

    while(p)
    {
        track_info = (TrackInfo *)(p->data);
        g_object_set(track_info->loop_view,
                     "zoom", newzoom,
                     NULL);
        p = g_list_next(p);
    }
}

/**
 * swamigui_sample_editor_set_selection:
 * @editor: Sample editor object
 * @items: List of selected items or %NULL to unset selection
 *
 * Set the items of a sample editor widget. The @items list will usually
 * contain a single patch item that has sample data associated with it,
 * although sometimes multiple items will be handled as in the case of
 * stereo pairs.
 */
void
swamigui_sample_editor_set_selection(SwamiguiSampleEditor *editor,
                                     IpatchList *items)
{
    if(swamigui_sample_editor_real_set_selection(editor, items))
    {
        g_object_notify(G_OBJECT(editor), "item-selection");
    }
}

static gboolean
swamigui_sample_editor_real_set_selection(SwamiguiSampleEditor *editor,
        IpatchList *items)
{
    SwamiguiSampleEditorHandler hfunc;
    GList *p;

    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), FALSE);
    g_return_val_if_fail(!items || IPATCH_IS_LIST(items), FALSE);

    if(editor->selection)
    {
        g_object_unref(editor->selection);    /* -- unref old */
    }

    /* if items list then duplicate it and take over the reference */
    if(items)
    {
        editor->selection = ipatch_list_duplicate(items);    /* !! */
    }
    else
    {
        editor->selection = NULL;
    }

    if(editor->handler)		/* active handler? */
    {
        editor->status = SWAMIGUI_SAMPLE_EDITOR_UPDATE;

        if(!items || !(*editor->handler)(editor))
        {
            swamigui_sample_editor_deactivate_handler(editor);
        }
    }

    if(items && !editor->handler)  /* re-test in case it was de-activated */
    {
        editor->status = SWAMIGUI_SAMPLE_EDITOR_INIT;
        p = sample_editor_handlers;

        while(p)			/* try handlers */
        {
            hfunc = (SwamiguiSampleEditorHandler)(p->data);

            if((*hfunc)(editor))  /* selection handled? */
            {
                editor->handler = hfunc;
                break;
            }

            p = g_list_next(p);
        }
    }

    editor->status = SWAMIGUI_SAMPLE_EDITOR_NORMAL;

    return (TRUE);
}

static void
swamigui_sample_editor_deactivate_handler(SwamiguiSampleEditor *editor)
{
    swamigui_sample_editor_reset(editor);
    editor->handler = NULL;
    editor->handler_data = NULL;
}

/**
 * swamigui_sample_editor_get_selection:
 * @editor: Sample editor widget
 *
 * Get the list of active items in a sample editor widget.
 *
 * Returns: New list containing selected items which has a ref count of one
 *   which the caller owns or %NULL if no items selected. Remove the
 *   reference when finished with it.
 */
IpatchList *
swamigui_sample_editor_get_selection(SwamiguiSampleEditor *editor)
{
    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), NULL);

    if(editor->selection && editor->selection->items)
    {
        return (ipatch_list_duplicate(editor->selection));
    }
    else
    {
        return (NULL);
    }
}

/**
 * swamigui_sample_editor_register_handler:
 * @handler: Sample editor handler function to register
 * @check_func: Function used to check if an item selection is valid for this handler
 *
 * Registers a new handler for sample editor widgets. Sample editor handlers
 * interface patch item's of particular types with sample data and related
 * parameters (such as looping params).
 *
 * MT: This function should only be called within GUI thread.
 */
void
swamigui_sample_editor_register_handler(SwamiguiSampleEditorHandler handler,
                                        SwamiguiPanelCheckFunc check_func)
{
    g_return_if_fail(handler != NULL);
    g_return_if_fail(check_func != NULL);

    sample_editor_handlers = g_list_prepend(sample_editor_handlers, handler);
    sample_editor_check_funcs = g_list_prepend(sample_editor_check_funcs, check_func);
}

/**
 * swamigui_sample_editor_unregister_handler:
 * @handler: Handler function to unregister
 *
 * Unregisters a handler previously registered with
 * swamigui_sample_editor_register_handler().
 *
 * MT: This function should only be called in GUI thread.
 */
void
swamigui_sample_editor_unregister_handler(SwamiguiSampleEditorHandler handler)
{
    int handler_index;
    GList *p;

    g_return_if_fail(handler != NULL);

    handler_index = g_list_index(sample_editor_handlers, handler);
    g_return_if_fail(handler_index != -1);

    sample_editor_handlers = g_list_remove(sample_editor_handlers, handler);

    /* remove the corresponding check function */
    p = g_list_nth(sample_editor_check_funcs, handler_index);
    sample_editor_check_funcs = g_list_delete_link(sample_editor_check_funcs, p);
}

/**
 * swamigui_sample_editor_reset:
 * @editor: Sample editor widget
 *
 * Resets a sample editor by removing all tracks and markers and disconnecting
 * loop view controls. Usually only used by sample editor handlers.
 */
void
swamigui_sample_editor_reset(SwamiguiSampleEditor *editor)
{
    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    g_object_set(editor->loop_finder_gui->loop_finder, "sample", NULL, NULL);

    swamigui_sample_editor_remove_all_tracks(editor);
    swamigui_sample_editor_remove_all_markers(editor);

    /* disable loop type selector */
    swamigui_sample_editor_set_loop_types(editor, NULL, FALSE);

    /* disconnect any controls connected to loop view hub controls */
    swami_control_disconnect_all(editor->loop_start_hub);
    swami_control_disconnect_all(editor->loop_end_hub);
    swami_control_disconnect_all(editor->loopsel_ctrl);

    /* disconnect any controls to spin buttons */
    swami_control_disconnect_all(editor->spinbtn_start_ctrl);
    swami_control_disconnect_all(editor->spinbtn_end_ctrl);
}

/**
 * swamigui_sample_editor_get_loop_controls:
 * @editor: Sample editor object
 * @loop_start: Output - Loop view start point control (%NULL to ignore)
 * @loop_end: Output - Loop view end point control (%NULL to ignore)
 *
 * Get the loop start and end controls that are connected to all loop view
 * start and end properties. Essentially controls for the loop view start
 * and end points.
 */
void
swamigui_sample_editor_get_loop_controls(SwamiguiSampleEditor *editor,
        SwamiControl **loop_start,
        SwamiControl **loop_end)
{
    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    if(loop_start)
    {
        *loop_start = editor->loop_start_hub;
    }

    if(loop_end)
    {
        *loop_end = editor->loop_end_hub;
    }
}

/**
 * swamigui_sample_editor_add_track:
 * @editor: Sample editor object
 * @sample: Object with an #IpatchSample interface to add
 * @right_chan: Only used if @sample is stereo. If %TRUE the right channel
 *   will be used, %FALSE will use the left channel.
 *
 * Add a sample track to a sample editor. Usually only done by
 * sample editor handlers. This function can be used to add multiple
 * samples for stereo or multi-track audio.
 *
 * Returns: The index of the new track in the sample editor object (starting
 * from 0).
 */
int
swamigui_sample_editor_add_track(SwamiguiSampleEditor *editor,
                                 IpatchSampleData *sample,
                                 gboolean right_chan)
{
    TrackInfo *track_info;
    SwamiControl *ctrl;
    double zoom;
    guint sample_size;
    int width;

    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), 0);
    g_return_val_if_fail(IPATCH_IS_SAMPLE_DATA(sample), 0);

    /* calculate zoom to view entire sample */
    g_object_get(sample, "sample-size", &sample_size, NULL);
    width = GTK_WIDGET(editor->sample_canvas)->allocation.width;
    zoom = (double)sample_size / width;

    if(!editor->tracks)     /* if no samples have been added yet */
    {
        editor->sample_size = sample_size;    /* cache sample size */
    }

    track_info = g_new(TrackInfo, 1);
    track_info->sample = g_object_ref(sample);  /* ++ ref sample */
    track_info->right_chan = right_chan;

    /* create sample view horizontal center line */
    track_info->sample_center_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->center_line_color,
                                "width-pixels", 1,
                                NULL);
    track_info->sample_view =  /* create the sample view canvas item */
        gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                              SWAMIGUI_TYPE_SAMPLE_CANVAS,
                              "sample", sample,
                              "right-chan", right_chan,
                              "zoom", zoom,

                              /* only the first sample item will update adj. */
                              "update-adj", editor->tracks ? FALSE : TRUE,
                              "adjustment", gtk_range_get_adjustment
                              (GTK_RANGE(editor->hscrollbar)),
                              NULL);

    /* create loop view horizontal center line */
    track_info->loop_center_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->loop_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", editor->center_line_color,
                                "width-pixels", 1,
                                NULL);
    track_info->loop_view =	/* create the loop view canvas item */
        gnome_canvas_item_new(gnome_canvas_root(editor->loop_canvas),
                              SWAMIGUI_TYPE_SAMPLE_CANVAS,
                              "sample", sample,
                              "right-chan", right_chan,
                              "loop-mode", TRUE,
                              "zoom", editor->loop_zoom,
                              NULL);

    gnome_canvas_item_lower_to_bottom(track_info->sample_center_line);
    gnome_canvas_item_lower_to_bottom(track_info->loop_center_line);

    gnome_canvas_item_lower_to_bottom(track_info->sample_view);
    gnome_canvas_item_lower_to_bottom(track_info->loop_view);

    gnome_canvas_item_lower_to_bottom(editor->loop_line);

    /* add track to track list */
    editor->tracks = g_list_append(editor->tracks, track_info);


    /* create loop view "loop-start" property control and connect to hub */
    ctrl = SWAMI_CONTROL(swami_get_control_prop_by_name  /* ++ ref */
                         (G_OBJECT(track_info->loop_view), "loop-start"));
    swami_control_connect(editor->loop_start_hub, ctrl, 0);  /* hub is queued */
    g_object_unref(ctrl);	/* -- unref */

    /* create loop view "loop-end" property control and connect to hub */
    ctrl = SWAMI_CONTROL(swami_get_control_prop_by_name  /* ++ ref */
                         (G_OBJECT(track_info->loop_view), "loop-end"));
    swami_control_connect(editor->loop_end_hub, ctrl, 0);  /* hub is queued */
    g_object_unref(ctrl);	/* -- unref */

    swamigui_sample_editor_update_sizes(editor);

    return (g_list_length(editor->tracks) - 1);
}

/**
 * swamigui_sample_editor_get_track_info:
 * @editor: Sample editor object
 * @track: Track index to get info from (starting from 0)
 * @sample: Output - Sample object (%NULL to ignore)
 * @sample_view: Output - The sample view canvas (%NULL to ignore)
 * @loop_view: Output - The loop view canvas (%NULL to ignore)
 *
 * Get info for a sample track. No reference counting is done, since
 * track will not get removed unless the sample editor handler does
 * so. Returned object pointers should only be used within editor
 * callback or references should be added.
 *
 * Returns: %TRUE if @index is a valid sample index, %FALSE otherwise in
 * which case the returned pointers are undefined.
 */
gboolean
swamigui_sample_editor_get_track_info(SwamiguiSampleEditor *editor,
                                      guint track,
                                      IpatchSampleData **sample,
                                      SwamiguiSampleCanvas **sample_view,
                                      SwamiguiSampleCanvas **loop_view)
{
    TrackInfo *track_info;

    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), FALSE);

    track_info = (TrackInfo *)g_list_nth_data(editor->tracks, track);

    if(!track_info)
    {
        return (FALSE);
    }

    if(sample)
    {
        *sample = track_info->sample;
    }

    if(sample_view)
    {
        *sample_view = SWAMIGUI_SAMPLE_CANVAS(track_info->sample_view);
    }

    if(loop_view)
    {
        *loop_view = SWAMIGUI_SAMPLE_CANVAS(track_info->loop_view);
    }

    return (TRUE);
}

/**
 * swamigui_sample_editor_remove_track:
 * @editor: Sample editor object
 * @track: Index of track to remove
 *
 * Remove a track from a sample editor by its index (starts from 0).
 */
void
swamigui_sample_editor_remove_track(SwamiguiSampleEditor *editor, guint track)
{
    GList *found_track;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    found_track = g_list_nth(editor->tracks, track);
    g_return_if_fail(found_track != NULL);

    swamigui_sample_editor_remove_track_item(editor, found_track, TRUE);

    swamigui_sample_editor_update_sizes(editor);
}

/* internal function for removing by track info list node, destroy flag is
   used by finalize (set to FALSE since no need to destroy GtkObjects) */
static void
swamigui_sample_editor_remove_track_item(SwamiguiSampleEditor *editor,
        GList *p, gboolean destroy)
{
    TrackInfo *track_info;

    track_info = (TrackInfo *)(p->data);
    g_object_unref(track_info->sample);  /* -- unref sample */

    if(destroy)
    {
        /* destroy sample canvas items */
        gtk_object_destroy(GTK_OBJECT(track_info->sample_view));
        gtk_object_destroy(GTK_OBJECT(track_info->loop_view));

        /* destroy horizontal center lines */
        gtk_object_destroy(GTK_OBJECT(track_info->sample_center_line));
        gtk_object_destroy(GTK_OBJECT(track_info->loop_center_line));
    }

    g_free(track_info);

    /* if removing the first track and there are others.. */
    if(p == editor->tracks && editor->tracks->next)
    {
        track_info = (TrackInfo *)(editor->tracks->next->data);

        /* the new first sample canvas item now is adjustment master */
        g_object_set(track_info->sample_view, "update-adj", TRUE, NULL);
    }

    /* remove from list */
    editor->tracks = g_list_delete_link(editor->tracks, p);
}

/**
 * swamigui_sample_editor_remove_all_tracks:
 * @editor: Sample editor object
 *
 * Remove all tracks from a sample editor.
 */
void
swamigui_sample_editor_remove_all_tracks(SwamiguiSampleEditor *editor)
{
    GList *p, *temp;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    p = editor->tracks;

    while(p)
    {
        temp = p;
        p = g_list_next(p);
        swamigui_sample_editor_remove_track_item(editor, temp, TRUE);
    }
}

/**
 * swamigui_sample_editor_add_marker:
 * @editor: Sample editor object
 * @flags: #SwamiguiSampleEditorMarkerFlags
 *   - #SWAMIGUI_SAMPLE_EDITOR_MARKER_SINGLE is used for markers which define
 *   a single value.
 *   - #SWAMIGUI_SAMPLE_EDITOR_MARKER_VIEW is used for markers which are view
 *   only and not controllable by user.
 *   - #SWAMIGUI_SAMPLE_EDITOR_MARKER_SIZE is used to define a start/size
 *   marker instead of a start/end marker.  This causes the second control to
 *   be the current size of the marker instead of the end position.
 *   Note that passing 0 defines a interactive range marker.
 * @start: Output - New control for start of marker or %NULL to ignore
 * @end: Output - New control for end/size of marker (ranges only) or %NULL to
 *   ignore
 *
 * Add a new marker to a sample editor. Markers can be used for loop points,
 * sample start/end points, selections and other position indicators/controls.
 * Marker 0 is the sample selection marker and it is always present although it
 * may be hidden.
 *
 * Returns: New marker index
 */
guint
swamigui_sample_editor_add_marker(SwamiguiSampleEditor *editor,
                                  guint flags, SwamiControl **start,
                                  SwamiControl **end)
{
    MarkerInfo *mark_info;
    guint color;
    int id;

    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), 0);

    mark_info = g_new0(MarkerInfo, 1);

    mark_info->flags = flags;
    mark_info->editor = editor;
    mark_info->visible = TRUE;

    id = g_list_length(editor->markers);
    color = default_marker_colors [id % G_N_ELEMENTS(default_marker_colors)];

    mark_info->start_line
        = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                GNOME_TYPE_CANVAS_LINE,
                                "fill-color-rgba", color,
                                "width-pixels", MARKER_DEFAULT_WIDTH,
                                NULL);
    gnome_canvas_item_hide(mark_info->start_line);

    /* create start marker control (!! editor takes over ref) */
    mark_info->start_ctrl = swamigui_control_new(SWAMI_TYPE_CONTROL_FUNC);
    swami_control_set_value_type(mark_info->start_ctrl, G_TYPE_UINT);
    swami_control_func_assign_funcs(SWAMI_CONTROL_FUNC(mark_info->start_ctrl),
                                    start_marker_control_get_value_func,
                                    start_marker_control_set_value_func,
                                    NULL, mark_info);

    /* create end line and range box if a range marker */
    if(!(flags & SWAMIGUI_SAMPLE_EDITOR_MARKER_SINGLE))
    {
        mark_info->end_line
            = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                    GNOME_TYPE_CANVAS_LINE,
                                    "fill-color-rgba", color,
                                    "width-pixels", MARKER_DEFAULT_WIDTH,
                                    NULL);
        gnome_canvas_item_hide(mark_info->end_line);

        mark_info->range_box
            = gnome_canvas_item_new(gnome_canvas_root(editor->sample_canvas),
                                    GNOME_TYPE_CANVAS_RECT,
                                    "fill-color-rgba", color,
                                    NULL);
        gnome_canvas_item_hide(mark_info->range_box);

        /* create end/size marker control (!! editor takes over ref) */
        mark_info->end_ctrl = swamigui_control_new(SWAMI_TYPE_CONTROL_FUNC);
        swami_control_set_value_type(mark_info->end_ctrl, G_TYPE_UINT);
        swami_control_func_assign_funcs(SWAMI_CONTROL_FUNC(mark_info->end_ctrl),
                                        end_marker_control_get_value_func,
                                        end_marker_control_set_value_func,
                                        NULL, mark_info);
    }

    editor->markers = g_list_append(editor->markers, mark_info);

    marker_control_update_all(editor);

    if(start)
    {
        *start = mark_info->start_ctrl;
    }

    if(end)
    {
        *end = mark_info->end_ctrl;
    }

    return (id);
}

/**
 * swamigui_sample_editor_get_marker_info:
 * @editor: Sample editor object
 * @marker: Marker index to get info on (starts at 0 - the selection marker)
 * @flags: #SwamiguiSampleEditorMarkerFlags
 * @start_line: Output - Gnome canvas line for the start marker (%NULL to ignore)
 * @end_line: Output - Gnome canvas line for the end marker (%NULL to ignore),
 *   will be %NULL for single markers (non ranges)
 * @start_ctrl: Output - Control for the start marker (%NULL to ignore)
 * @end_ctrl: Output - Control for the end/size marker (%NULL to ignore), will be
 *   %NULL for single markers (non ranges)
 *
 * Get info for the given @marker index. No reference counting is
 * done, since marker will not get removed unless the sample editor
 * handler does so. Returned object pointers should only be used
 * within editor callback or references should be added.
 *
 * Returns: %TRUE if a marker with the given index exists, %FALSE otherwise.
 */
gboolean
swamigui_sample_editor_get_marker_info(SwamiguiSampleEditor *editor,
                                       guint marker, guint *flags,
                                       GnomeCanvasItem **start_line,
                                       GnomeCanvasItem **end_line,
                                       SwamiControl **start_ctrl,
                                       SwamiControl **end_ctrl)
{
    MarkerInfo *marker_info;

    g_return_val_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor), FALSE);

    marker_info = (MarkerInfo *)g_list_nth_data(editor->markers, marker);

    if(!marker_info)
    {
        return (FALSE);
    }

    if(flags)
    {
        *flags = marker_info->flags;
    }

    if(start_line)
    {
        *start_line = marker_info->start_line;
    }

    if(end_line)
    {
        *end_line = marker_info->end_line;
    }

    if(start_ctrl)
    {
        *start_ctrl = marker_info->start_ctrl;
    }

    if(end_ctrl)
    {
        *end_ctrl = marker_info->end_ctrl;
    }

    return (TRUE);
}

/**
 * swamigui_sample_editor_set_marker:
 * @editor: Sample editor widget
 * @marker: Marker number
 * @start: Marker start position
 * @end: Marker end position
 *
 * Set the marker start and end positions.
 */
void
swamigui_sample_editor_set_marker(SwamiguiSampleEditor *editor, guint marker,
                                  guint start, guint end)
{
    MarkerInfo *marker_info;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    marker_info = (MarkerInfo *)g_list_nth_data(editor->markers, marker);

    if(!marker_info)
    {
        return;
    }

    swamigui_sample_editor_set_marker_info(editor, marker_info, start, end);
}

static void
swamigui_sample_editor_set_marker_info(SwamiguiSampleEditor *editor,
                                       MarkerInfo *marker_info,
                                       guint start, guint end)
{
    GValue value = { 0 };
    gboolean size_marker;
    gboolean start_changed = FALSE;
    guint temp;

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

    size_marker = (marker_info->flags & SWAMIGUI_SAMPLE_EDITOR_MARKER_SIZE) != 0;

    g_value_init(&value, G_TYPE_UINT);

    if(start != marker_info->start_pos)
    {
        marker_info->start_pos = start;
        g_value_set_uint(&value, start);
        swami_control_transmit_value(marker_info->start_ctrl, &value);
        start_changed = TRUE;
    }

    if(end != marker_info->end_pos || (size_marker && start_changed))
    {
        marker_info->end_pos = end;

        if(size_marker)
        {
            g_value_set_uint(&value, end - start + 1);
        }
        else
        {
            g_value_set_uint(&value, end);
        }

        swami_control_transmit_value(marker_info->end_ctrl, &value);
    }

    g_value_unset(&value);
    marker_control_update(marker_info);	/* update the marker GUI */
}

/**
 * swamigui_sample_editor_remove_marker:
 * @editor: Sample editor object
 * @marker: Index of marker to remove (builtin markers are hidden rather than
 *   removed)
 *
 * Remove a marker.
 */
void
swamigui_sample_editor_remove_marker(SwamiguiSampleEditor *editor,
                                     guint marker)
{
    GList *p;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    if(marker < BUILTIN_MARKER_COUNT)
    {
        swamigui_sample_editor_show_marker(editor, marker, FALSE);
        return;
    }

    p = g_list_nth(editor->markers, marker);

    if(!p)
    {
        return;
    }

    swamigui_sample_editor_remove_marker_item(editor, p, TRUE);
}

/**
 * swamigui_sample_editor_remove_all_markers:
 * @editor: Sample editor object
 *
 * Remove all markers (except selection marker 0, which is always present).
 */
void
swamigui_sample_editor_remove_all_markers(SwamiguiSampleEditor *editor)
{
    GList *p, *temp;
    int i;

    p = g_list_nth(editor->markers, BUILTIN_MARKER_COUNT);

    while(p)
    {
        temp = p;
        p = g_list_next(p);
        swamigui_sample_editor_remove_marker_item(editor, temp, TRUE);
    }

    /* hide builtin markers */
    for(i = 0; i < BUILTIN_MARKER_COUNT; i++)
    {
        swamigui_sample_editor_show_marker(editor, i, FALSE);
    }
}

/* remove a marker, destroy flag is used by finalize function since GtkObjects
   dont need to be destroyed on finalize */
static void
swamigui_sample_editor_remove_marker_item(SwamiguiSampleEditor *editor,
        GList *p, gboolean destroy)
{
    MarkerInfo *marker_info;
    SwamiControl *ctrl;

    marker_info = (MarkerInfo *)(p->data);

    /* remove from marker list */
    editor->markers = g_list_delete_link(editor->markers, p);

    /* destroy line canvas item */
    if(destroy)
    {
        gtk_object_destroy(GTK_OBJECT(marker_info->start_line));

        if(marker_info->end_line)
        {
            gtk_object_destroy(GTK_OBJECT(marker_info->end_line));
        }

        if(marker_info->range_box)
        {
            gtk_object_destroy(GTK_OBJECT(marker_info->range_box));
        }
    }

    /* we don't want to depend on the existence of mark_info anymore */
    /* discconnect and unref start control */
    ctrl = marker_info->start_ctrl;
    swami_control_disconnect_unref(ctrl);

    ctrl = marker_info->end_ctrl;

    /* clear data in marker info */
    memset(marker_info, 0, sizeof(MarkerInfo));

    /* add a week references to the marker controls to free the marker_info
       structures (since we still might receive queued events) */
    g_object_weak_ref(G_OBJECT(ctrl), (GWeakNotify)g_free, marker_info);

    /* discconnect and unref end control */
    swami_control_disconnect_unref(ctrl);
}

/* start marker control get value method */
static void
start_marker_control_get_value_func(SwamiControl *control, GValue *value)
{
    MarkerInfo *marker_info = SWAMI_CONTROL_FUNC_DATA(control);
    g_value_set_uint(value, marker_info->start_pos);
}

/* start marker control set value method */
static void
start_marker_control_set_value_func(SwamiControl *control,
                                    SwamiControlEvent *event,
                                    const GValue *value)
{
    MarkerInfo *marker_info = SWAMI_CONTROL_FUNC_DATA(control);
    guint u;

    if(!marker_info->start_line)
    {
        return;    /* in process of being destroyed? */
    }

    u = g_value_get_uint(value);

    if(u != marker_info->start_pos)
    {
        marker_info->start_pos = u;
        marker_control_update(marker_info);
    }
}

/* end marker control get value method */
static void
end_marker_control_get_value_func(SwamiControl *control, GValue *value)
{
    MarkerInfo *marker_info = SWAMI_CONTROL_FUNC_DATA(control);

    if(marker_info->flags & SWAMIGUI_SAMPLE_EDITOR_MARKER_SIZE)
    {
        g_value_set_uint(value, marker_info->end_pos - marker_info->start_pos + 1);
    }
    else
    {
        g_value_set_uint(value, marker_info->end_pos);
    }
}

/* end marker control set value method */
static void
end_marker_control_set_value_func(SwamiControl *control,
                                  SwamiControlEvent *event,
                                  const GValue *value)
{
    MarkerInfo *marker_info = SWAMI_CONTROL_FUNC_DATA(control);
    guint u;

    if(!marker_info->end_line)
    {
        return;    /* in process of being destroyed? */
    }

    u = g_value_get_uint(value);

    if(marker_info->flags & SWAMIGUI_SAMPLE_EDITOR_MARKER_SIZE)
    {
        u += marker_info->start_pos - 1;
    }

    if(u != marker_info->end_pos && u >= marker_info->start_pos)
    {
        marker_info->end_pos = u;
        marker_control_update(marker_info);
    }
}

/* update the display of a single marker in the sample view */
static void
marker_control_update(MarkerInfo *marker_info)
{
    SwamiguiSampleEditor *editor = marker_info->editor;
    SwamiguiSampleCanvas *sample_view = NULL;
    TrackInfo *track_info;
    GnomeCanvasPoints *points;
    int startx, endx, sview = -1, eview = -1;
    int width, height;
    int visible_count, pos = 0;
    int y1 = 0, y2 = 0;
    GList *p;

    /* can't position marker lines if no sample canvases, exception is if marker
       is hidden, then we don't need to update the position */
    if(!editor->tracks && marker_info->visible)
    {
        return;
    }

    /* count number of visible markers and locate visible position of this marker */
    for(p = editor->markers, visible_count = 0; p; p = p->next)
    {
        if(p->data == marker_info)
        {
            pos = visible_count;
        }

        if(((MarkerInfo *)(p->data))->visible)
        {
            visible_count++;
        }
    }

    pos = visible_count - pos - 1;	/* adjust for Y-coord order */

    if(editor->tracks)
    {
        track_info = (TrackInfo *)(editor->tracks->data);
        sample_view = SWAMIGUI_SAMPLE_CANVAS(track_info->sample_view);
    }

    width = GTK_WIDGET(editor->sample_canvas)->allocation.width;
    height = GTK_WIDGET(editor->sample_canvas)->allocation.height;

    /* calculate Y positions of range bar */
    if(visible_count > 0)
    {
        y1 = (pos * (editor->marker_bar_height - 1)) / visible_count;
        y2 = ((pos + 1) * (editor->marker_bar_height - 1)) / visible_count - 1;
    }

    points = gnome_canvas_points_new(2);
    points->coords[1] = y2;
    points->coords[3] = height;

    /* update start marker line */
    if(sample_view)
        startx = swamigui_sample_canvas_sample_to_xpos(sample_view,
                 marker_info->start_pos, &sview);

    if(sample_view && sview == 0 && marker_info->visible)
    {
        points->coords[0] = startx;
        points->coords[2] = startx;

        g_object_set(marker_info->start_line, "points", points, NULL);
        gnome_canvas_item_show(marker_info->start_line);
    }
    else
    {
        gnome_canvas_item_hide(marker_info->start_line);
        startx = 0;
    }

    /* update end marker and range box (if any) */
    if(marker_info->end_line)
    {
        if(sample_view)
            endx = swamigui_sample_canvas_sample_to_xpos(sample_view,
                    marker_info->end_pos, &eview);

        if(sample_view && eview == 0 && marker_info->visible)
        {
            points->coords[0] = endx;
            points->coords[2] = endx;
            endx++;	/* for marker range_box below */

            g_object_set(marker_info->end_line, "points", points, NULL);
            gnome_canvas_item_show(marker_info->end_line);
        }
        else
        {
            gnome_canvas_item_hide(marker_info->end_line);
            endx = width;
        }

        /* is marker range box in view? */
        if(sample_view && (sview == 0 || eview == 0 || (sview == -1 && eview == 1))
                && marker_info->visible)
        {
            g_object_set(marker_info->range_box,
                         "x1", (double)startx,
                         "x2", (double)endx,
                         "y1", (double)y1,
                         "y2", (double)y2,
                         NULL);

            gnome_canvas_item_show(marker_info->range_box);
        }
        else
        {
            gnome_canvas_item_hide(marker_info->range_box);
        }
    }

    gnome_canvas_points_free(points);
}

static void
marker_control_update_all(SwamiguiSampleEditor *editor)
{
    MarkerInfo *marker_info;
    GList *p;

    for(p = editor->markers; p; p = g_list_next(p))
    {
        marker_info = (MarkerInfo *)(p->data);
        marker_control_update(marker_info);
    }
}

/**
 * swamigui_sample_editor_show_marker:
 * @editor: Sample editor widget
 * @marker: Marker number to set visibility of
 * @show_marker: %TRUE to show marker, %FALSE to hide
 *
 * Set the visibility of a marker.
 */
void
swamigui_sample_editor_show_marker(SwamiguiSampleEditor *editor,
                                   guint marker, gboolean show_marker)
{
    MarkerInfo *marker_info;
    GList *p;

    g_return_if_fail(SWAMIGUI_IS_SAMPLE_EDITOR(editor));

    p = g_list_nth(editor->markers, marker);

    if(!p)
    {
        return;
    }

    marker_info = (MarkerInfo *)(p->data);

    if(marker_info->visible == show_marker)
    {
        return;
    }

    marker_info->visible = show_marker;
    marker_control_update_all(editor);
}

/**
 * swamigui_sample_editor_set_loop_types:
 * @editor: Sample editor object
 * @types: -1 terminated array of #IpatchSampleLoopType values to show in
 *   loop selector (%NULL to hide loop selector)
 * @loop_play_btn: If @types is %NULL, setting this to %TRUE will cause a
 *   play loop toggle button to be shown (for items that dont have a loop type
 *   property, but it is desirable for the user to be able to listen to the loop).
 *
 * Usually only used by sample editor handlers.  Sets the available loop types
 * in the loop selector menu.
 */
void
swamigui_sample_editor_set_loop_types(SwamiguiSampleEditor *editor, int *types,
                                      gboolean loop_play_btn)
{
    GtkTreeIter iter;
    int i, i2;

    gtk_list_store_clear(editor->loopsel_store);

    if(types)
    {
        for(i = 0; i < G_N_ELEMENTS(loop_type_info); i++)
        {
            for(i2 = 0; types[i2] != -1; i2++)
            {
                if(loop_type_info[i].loop_type == types[i2])
                {
                    gtk_list_store_append(editor->loopsel_store, &iter);
                    gtk_list_store_set(editor->loopsel_store, &iter,
                                       LOOPSEL_COL_LOOP_TYPE, types[i2],
                                       LOOPSEL_COL_ICON, loop_type_info[i].icon,
                                       LOOPSEL_COL_LABEL, loop_type_info[i].label,
                                       LOOPSEL_COL_TOOLTIP, loop_type_info[i].tooltip,
                                       -1);
                }
            }
        }

        gtk_widget_set_sensitive(editor->loopsel, TRUE);
    }
    else
    {
        gtk_widget_set_sensitive(editor->loopsel, FALSE);
    }
}

/**
 * swamigui_sample_editor_set_active_loop_type:
 * @editor: Sample editor widget
 * @type: #IpatchSampleLoopType or TRUE/FALSE if using loop play button.
 *
 * Set the active loop type in the loop type selector or loop play button.
 * Usually only used by sample editor handlers.
 */
void
swamigui_sample_editor_set_active_loop_type(SwamiguiSampleEditor *editor,
        int type)
{
    GtkTreeModel *model = GTK_TREE_MODEL(editor->loopsel_store);
    GtkTreeIter iter;
    int cmptype;

    if(!gtk_tree_model_get_iter_first(model, &iter))
    {
        return;
    }

    do
    {
        gtk_tree_model_get(model, &iter, LOOPSEL_COL_LOOP_TYPE, &cmptype, -1);

        if(cmptype == type)
        {
            gtk_combo_box_set_active_iter(GTK_COMBO_BOX(editor->loopsel), &iter);
            break;
        }
    }
    while(gtk_tree_model_iter_next(model, &iter));
}

/* Default IpatchSample interface sample editor handler */
static gboolean
swamigui_sample_editor_default_handler(SwamiguiSampleEditor *editor)
{
    IpatchSample *sample;
    IpatchSampleData *sampledata;
    SwamiControl *loop_view_start, *loop_view_end;
    SwamiControl *mark_loop_start, *mark_loop_end;
    SwamiControl *loop_start_prop, *loop_end_prop, *loop_type_prop;
    int *loop_types;
    GObject *obj;

    /* check if selection is handled (single item with IpatchSample interface) */
    if(editor->status == SWAMIGUI_SAMPLE_EDITOR_INIT
            || editor->status == SWAMIGUI_SAMPLE_EDITOR_UPDATE)
    {
        /* only handle single item with IpatchSample interface */
        if(!editor->selection || !editor->selection->items
                || editor->selection->items->next)
        {
            return (FALSE);
        }

        obj = editor->selection->items->data;

        if(!IPATCH_IS_SAMPLE(obj))
        {
            return (FALSE);
        }

        sample = IPATCH_SAMPLE(obj);

        /* same item already selected? */
        if((gpointer)sample == (gpointer)(editor->handler_data))
        {
            return (TRUE);
        }

        sampledata = ipatch_sample_get_sample_data(sample);         /* ++ ref sample data */

        /* clear all tracks and markers if updating and a new item is selected */
        if(editor->status == SWAMIGUI_SAMPLE_EDITOR_UPDATE)
        {
            swamigui_sample_editor_reset(editor);
        }

        editor->handler_data = sample; /* set handler data to selected item */

        /* clear loop finder results */
        swamigui_loop_finder_clear_results(editor->loop_finder_gui);

        /* stereo sample data?? */
        if(IPATCH_SAMPLE_FORMAT_GET_CHANNELS(ipatch_sample_get_format(sample))
                == IPATCH_SAMPLE_STEREO)
        {
            swamigui_sample_editor_add_track(editor, sampledata, FALSE);
            swamigui_sample_editor_add_track(editor, sampledata, TRUE);
        }
        else			/* mono data */
        {
            swamigui_sample_editor_add_track(editor, sampledata, FALSE);
        }

        /* set sample of loop finder */
        g_object_set(editor->loop_finder_gui->loop_finder, "sample", sample, NULL);

        /* create loop range marker */
        swamigui_sample_editor_add_marker(editor, 0, &mark_loop_start,
                                          &mark_loop_end);

        /* ++ ref sample loop property controls */
        loop_start_prop = swami_get_control_prop_by_name(G_OBJECT(sample), "loop-start");
        loop_end_prop = swami_get_control_prop_by_name(G_OBJECT(sample), "loop-end");

        /* connect sample properties to loop markers */
        swami_control_connect(loop_start_prop, mark_loop_start,
                              SWAMI_CONTROL_CONN_BIDIR | SWAMI_CONTROL_CONN_INIT);
        swami_control_connect(loop_end_prop, mark_loop_end,
                              SWAMI_CONTROL_CONN_BIDIR | SWAMI_CONTROL_CONN_INIT);

        /* connect sample properties to spin buttons */
        swami_control_connect(loop_start_prop, editor->spinbtn_start_ctrl,
                              SWAMI_CONTROL_CONN_BIDIR | SWAMI_CONTROL_CONN_INIT);
        swami_control_connect(loop_end_prop, editor->spinbtn_end_ctrl,
                              SWAMI_CONTROL_CONN_BIDIR | SWAMI_CONTROL_CONN_INIT);

        /* get loop view controls */
        swamigui_sample_editor_get_loop_controls(editor, &loop_view_start,
                &loop_view_end);

        /* connect sample properties to loop view controls */
        swami_control_connect(loop_start_prop, loop_view_start,
                              SWAMI_CONTROL_CONN_INIT);
        swami_control_connect(loop_end_prop, loop_view_end,
                              SWAMI_CONTROL_CONN_INIT);

        /* unref sample property controls */
        g_object_unref(loop_start_prop);
        g_object_unref(loop_end_prop);

        /* set the available loop types of the loop selector */
        loop_types = ipatch_sample_get_loop_types(sample);
        swamigui_sample_editor_set_loop_types(editor, loop_types, FALSE);

        if(loop_types)
        {
            /* ++ ref new prop control */
            loop_type_prop = swami_get_control_prop_by_name(G_OBJECT(sample), "loop-type");
            swami_control_connect(loop_type_prop, editor->loopsel_ctrl,
                                  SWAMI_CONTROL_CONN_BIDIR
                                  | SWAMI_CONTROL_CONN_INIT);
            g_object_unref(loop_type_prop);	/* -- unref prop control */
        }

        /* show finder markers (if finder enabled) */
        if(editor->loop_finder_active)
        {
            swamigui_sample_editor_show_marker(editor,
                                               SWAMIGUI_SAMPLE_EDITOR_MARKER_ID_LOOP_FIND_START, TRUE);
            swamigui_sample_editor_show_marker(editor,
                                               SWAMIGUI_SAMPLE_EDITOR_MARKER_ID_LOOP_FIND_END, TRUE);
        }

        g_object_unref(sampledata);         /* -- unref sample data */
    } /* if INIT or UPDATE */

    return (TRUE);
}

static gboolean
swamigui_sample_editor_default_handler_check_func(IpatchList *selection,
        GType *selection_types)
{
    IpatchSampleData *sampledata;

    /* only handle single item with IpatchSample interface which returns sample data */
    if(selection->items->next
            || !g_type_is_a(*selection_types, IPATCH_TYPE_SAMPLE)
            || !(sampledata = ipatch_sample_get_sample_data   /* ++ ref sample data */
                              ((IpatchSample *)(selection->items->data))))
    {
        return (FALSE);
    }

    g_object_unref(sampledata);           /* -- unref sample data */

    return (TRUE);
}

/* get value function for loopsel_ctrl */
static void
swamigui_sample_editor_loopsel_ctrl_get(SwamiControl *control, GValue *value)
{
    SwamiguiSampleEditor *editor =
        SWAMIGUI_SAMPLE_EDITOR(SWAMI_CONTROL_FUNC_DATA(control));
    GtkTreeIter iter;
    int loop_type = 0;

    /* get active loop type or failing that get first loop type */
    if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(editor->loopsel), &iter)
            || gtk_tree_model_get_iter_first(GTK_TREE_MODEL(editor->loopsel_store),
                    &iter))
    {
        gtk_tree_model_get(GTK_TREE_MODEL(editor->loopsel_store), &iter,
                           LOOPSEL_COL_LOOP_TYPE, &loop_type, -1);
    }

    g_value_set_enum(value, loop_type);
}

/* set value function for loopsel_ctrl */
static void
swamigui_sample_editor_loopsel_ctrl_set(SwamiControl *control,
                                        SwamiControlEvent *event,
                                        const GValue *value)
{
    SwamiguiSampleEditor *editor =
        SWAMIGUI_SAMPLE_EDITOR(SWAMI_CONTROL_FUNC_DATA(control));
    int loop_type;

    loop_type = g_value_get_enum(value);

    /* block changed signal while setting the combo box selection */
    g_signal_handlers_block_by_func(editor->loopsel,
                                    swamigui_sample_editor_loopsel_cb_changed, editor);

    swamigui_sample_editor_set_active_loop_type(editor, loop_type);

    g_signal_handlers_unblock_by_func(editor->loopsel,
                                      swamigui_sample_editor_loopsel_cb_changed, editor);
}

/* callback for when the loop selector combo box is changed */
static void
swamigui_sample_editor_loopsel_cb_changed(GtkComboBox *widget,
        gpointer user_data)
{
    SwamiguiSampleEditor *editor = SWAMIGUI_SAMPLE_EDITOR(user_data);
    GtkTreeIter iter;
    int loop_type;
    GValue value = { 0 };

    if(gtk_combo_box_get_active_iter(widget, &iter))
    {
        gtk_tree_model_get(GTK_TREE_MODEL(editor->loopsel_store), &iter,
                           LOOPSEL_COL_LOOP_TYPE, &loop_type, -1);

        /* transmit the loop type change */
        g_value_init(&value, G_TYPE_INT);
        g_value_set_int(&value, loop_type);
        swami_control_transmit_value((SwamiControl *)editor->loopsel_ctrl,
                                     &value);
        g_value_unset(&value);
    }
}
