/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxstatuspositionslider.cpp,v 1.12.2.5 2004/07/09 01:48:55 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxstatuspositionslider.h"

#include "hxplayer.h"

#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
#include "embeddedapp.h"
#endif

#include <stdio.h>
#include "hxplayer-i18n.h"
#include "playeripc.h"

// RGG - stop the timer when we're hidden

extern "C"
{
static void hsdp_play(HXStatusDisplayPositionSlider* position_status);
static void hsdp_pause(HXStatusDisplayPositionSlider* position_status);
static void hsdp_stop(HXStatusDisplayPositionSlider* position_status);
static void hsdp_seek(HXStatusDisplayPositionSlider* status, guint pos);
static void hsdp_length_changed(HXStatusDisplayPositionSlider* status, guint len);

static gboolean hsdp_button_press(HXStatusDisplayPositionSlider* position_status, GdkEvent *event);
static gboolean hsdp_button_release(HXStatusDisplayPositionSlider* position_status, GdkEvent *event);
static void     hsdp_event_after(HXStatusDisplayPositionSlider* position_status, GdkEvent *event);
static void     hsdp_value_changed(HXStatusDisplayPositionSlider* position_status);  
};

static void hxstatus_display_position_slider_class_init (HXStatusDisplayPositionSliderClass* klass);
static void hxstatus_display_position_slider_init       (HXStatusDisplayPositionSlider* status,
                                                        HXStatusDisplayPositionSliderClass* klass);

static void hxstatus_display_position_slider_set_player (HXStatusDisplay* status,
                                                        HXPlayer* player);

static HXStatusDisplayClass* g_parent_class = NULL;

GType
hxstatus_display_position_slider_get_type (void)
{
    static GType hxstatus_display_position_slider_type = 0;

    if (!hxstatus_display_position_slider_type)
    {
	static const GTypeInfo hxstatus_display_position_slider_info =
	    {
		sizeof (HXStatusDisplayPositionSliderClass),
		NULL,		/* base_init */
		NULL,		/* base_finalize */
		(GClassInitFunc) hxstatus_display_position_slider_class_init,
		NULL,		/* class_finalize */
		NULL,		/* class_data */
		sizeof (HXStatusDisplayPositionSlider),
		0,		/* n_preallocs */
		(GInstanceInitFunc) hxstatus_display_position_slider_init,
		NULL,           /* value_table */
	    };

	hxstatus_display_position_slider_type = g_type_register_static (HX_TYPE_STATUS_DISPLAY, "HXStatusDisplayPositionSlider",
                                                     &hxstatus_display_position_slider_info, (GTypeFlags)0);
    }

    return hxstatus_display_position_slider_type;
}

GtkWidget*
hxstatus_display_position_slider_new(void)
{
    HXStatusDisplayPositionSlider* status = (HXStatusDisplayPositionSlider*)g_object_new(HX_TYPE_STATUS_DISPLAY_POSITION_SLIDER, NULL);

    GtkWidget* hscale;
    
    hscale = gtk_hscale_new_with_range(0.0, 1.0, 1.0);
    gtk_scale_set_draw_value(GTK_SCALE(hscale), FALSE);
        
    g_signal_connect_swapped(G_OBJECT(hscale), "button_press_event",
                             G_CALLBACK(hsdp_button_press), status);

    g_signal_connect_swapped(G_OBJECT(hscale), "button_release_event",
                             G_CALLBACK(hsdp_button_release), status);

    /* We want to seek after the hscale's button_release_event is done. */
    g_signal_connect_swapped(G_OBJECT(hscale), "event-after",
                             G_CALLBACK(hsdp_event_after), status);
    
    g_signal_connect_swapped(G_OBJECT(hscale), "value-changed",
                             G_CALLBACK(hsdp_value_changed), status);

    status->position_scale = hscale;
    status->player_signal_handlers_array =
        g_array_new (FALSE, FALSE, sizeof (gint));
    
    gtk_container_add(GTK_CONTAINER(status), hscale);

    gtk_widget_show(hscale);
    
    return GTK_WIDGET(status);
}

#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
GtkWidget*
hxstatus_display_position_slider_new_with_embedded_window(HXEmbeddedWindow* window)
{
    GtkWidget* widget;
    HXStatusDisplayPositionSlider* status;

    widget = hxstatus_display_position_slider_new();
    status = HX_STATUS_DISPLAY_POSITION_SLIDER(widget);

    status->embedded_window = window;    
    
    return widget;
}
#endif

static guint
hxstatus_display_position_slider_get_update_interval(HXStatusDisplayPositionSlider* position_status)
{
    guint interval = 0;
    guint clip_length;
    guint pixel_length;
    const guint min_update_interval = 200; // in ms
    HXStatusDisplay* status;

    status = HX_STATUS_DISPLAY(position_status);
    
    if(!status->player)
    {
        return min_update_interval;
    }
    
    pixel_length = GTK_WIDGET(position_status->position_scale)->allocation.width;
    clip_length = hx_player_get_length(status->player);

    if(pixel_length > 0)
    {
        interval = clip_length / pixel_length;
    }

    if(interval < min_update_interval)
    {
        interval = min_update_interval;
    }

    return interval;
}

/* A callback to gtk_timeout_add() */
static gboolean
hxstatus_display_position_slider_update(gpointer user_data)
{
    guint pos;
    guint len;

    HXStatusDisplayPositionSlider* position_status = HX_STATUS_DISPLAY_POSITION_SLIDER(user_data);
    HXStatusDisplay* status = HX_STATUS_DISPLAY(user_data);
        
    g_return_val_if_fail(position_status->position_scale != NULL, TRUE);

    if(status->player)
    {
        pos = hx_player_get_position(HX_PLAYER(status->player));
        len = hx_player_get_length(HX_PLAYER(status->player));

        if(len > 0)
        {
            gtk_range_set_value(GTK_RANGE(position_status->position_scale), (gdouble)pos);
            gtk_range_set_range(GTK_RANGE(position_status->position_scale), 0, (gdouble)len);
        }
    }

    return TRUE; // don't remove
}

static void
hxstatus_display_position_slider_init(HXStatusDisplayPositionSlider* status, HXStatusDisplayPositionSliderClass* /* klass */)
{
    status->position_scale = NULL;
    status->embedded_window = NULL;

    status->button_1_is_pressed = FALSE;
    status->seek_started = FALSE;

    status->player_signal_handlers_array = NULL;
    status->player_signal_handlers_array_len = 0;
}

static void
hxstatus_display_position_slider_class_init (HXStatusDisplayPositionSliderClass* klass)
{
    HXStatusDisplayClass* status_class = HX_STATUS_DISPLAY_CLASS(klass);
    g_parent_class = (HXStatusDisplayClass*)gtk_type_class(HX_TYPE_STATUS_DISPLAY);

    status_class->set_player = hxstatus_display_position_slider_set_player;
}

static void
hxstatus_display_position_slider_seek_start(HXStatusDisplayPositionSlider* position_status)
{
    HXStatusDisplay* status;
    status = HX_STATUS_DISPLAY(position_status);
    
    hxstatus_display_stop_timer(status);    

    if(position_status->embedded_window)
    {
#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
        hxembedded_window_start_seeking(position_status->embedded_window);
#endif
    }
    else
    {
        hx_player_start_seeking(HX_PLAYER(status->player));
    }
}

static void
hxstatus_display_position_slider_seek_stop(HXStatusDisplayPositionSlider* position_status)
{
    guint pos;
    HXStatusDisplay* status;
    status = HX_STATUS_DISPLAY(position_status);

    pos = (guint)gtk_range_get_value(GTK_RANGE(position_status->position_scale));

    if(position_status->embedded_window)
    {
#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
        hxembedded_window_stop_seeking(position_status->embedded_window, pos);
#endif
    }        
    else
    {            
        hx_player_stop_seeking(HX_PLAYER(status->player));
    }

    hxstatus_display_start_timer(status);    
}


/* Function to hook up status display to player:
 * ============================================
 */

void
hxstatus_display_position_slider_set_player(HXStatusDisplay* status, HXPlayer* player)
{
    guint i;
    guint signal; 
    HXStatusDisplayPositionSlider* status_position_slider = HX_STATUS_DISPLAY_POSITION_SLIDER(status);    

    static const struct
    {
        const gchar* name;
        GCallback callback;
    } signal_map[] = 
    {
        { "play",           G_CALLBACK(hsdp_play)           },
        { "pause",          G_CALLBACK(hsdp_pause)          },
        { "stop",           G_CALLBACK(hsdp_stop)           },
        { "seek",           G_CALLBACK(hsdp_seek)           },
        { "length_changed", G_CALLBACK(hsdp_length_changed) }        
    };

    /* Disconnect from the old player, if any */    
    if(status->player)
    {
        for(i = 0; i < status_position_slider->player_signal_handlers_array_len; i++)
        {
            signal = g_array_index(status_position_slider->player_signal_handlers_array, gint, i);
            g_signal_handler_disconnect(G_OBJECT(status->player), signal);
                                        
        }
    }        

    status_position_slider->player_signal_handlers_array_len = 0;

    if(player)
    {
        /* Connect the new player */
        for(i = 0; i < sizeof(signal_map) / sizeof(*signal_map); i++)
        {    
            /* Hook up to the new player */
            signal = g_signal_connect_swapped(G_OBJECT(player),
                                              signal_map[i].name,
                                              signal_map[i].callback,
                                              status);

            g_array_insert_val(status_position_slider->player_signal_handlers_array,
                               status_position_slider->player_signal_handlers_array_len,
                               signal);
            status_position_slider->player_signal_handlers_array_len++;
        }
    }

    HX_STATUS_DISPLAY_CLASS(g_parent_class)->set_player(status, player);

    if(player)
    {
        guint interval;
        interval = hxstatus_display_position_slider_get_update_interval(status_position_slider);
        
        hxstatus_display_set_timer(status,
                                   interval,
                                   hxstatus_display_position_slider_update,
                                   status,
                                   TRUE,
                                   TRUE);
    }
}

static gboolean
hsdp_button_press(HXStatusDisplayPositionSlider* position_status,
                  GdkEvent *event)
{
    GdkEventButton* button_event = &event->button;
    
    if(button_event->button == 1)
    {    
        position_status->button_1_is_pressed = TRUE;

        /* propagate this to the slider as a button 2 press,
           as that is the behavior we want to emulate. */
        GdkEvent fake_button_press = *event;
        fake_button_press.button.button = 2;

        GtkHScaleClass* klass;
        klass = GTK_HSCALE_GET_CLASS(position_status->position_scale);
        GTK_WIDGET_CLASS(klass)->button_press_event(position_status->position_scale, &fake_button_press.button);        

        return TRUE;            
    }
    else if(button_event->button == 2)
    {
        return TRUE; // block button 2 presses
    }
    
    return FALSE; // propagate
}

static gboolean
hsdp_button_release(HXStatusDisplayPositionSlider* position_status,
                    GdkEvent *event)
{
    GdkEventButton* button_event = &event->button;

    if(button_event->button == 1)
    {
        GdkEvent fake_button_press = *event;
        fake_button_press.button.button = 2;

        /* Propagate as a button 2 release to match above */
        GtkHScaleClass* klass;
        klass = GTK_HSCALE_GET_CLASS(position_status->position_scale);
        GTK_WIDGET_CLASS(klass)->button_release_event(position_status->position_scale,
                                                      &fake_button_press.button);
        return TRUE;
    }
    else if(button_event->button == 2)
    {
        return TRUE; // block button 2 releases
    }

    return FALSE; // propagate
}

static void
hsdp_event_after(HXStatusDisplayPositionSlider* position_status,
                 GdkEvent *event)
{
    if(event->type == GDK_BUTTON_RELEASE)
    {
        GdkEventButton* button_event;
        button_event = (GdkEventButton*)event;
        
        if(button_event->button == 1)
        {    
            position_status->button_1_is_pressed = FALSE;

            if(position_status->seek_started)
            {
                hxstatus_display_position_slider_seek_stop(position_status);
                position_status->seek_started = FALSE;
            }
        }    
    }
}

static void
hsdp_value_changed(HXStatusDisplayPositionSlider* position_status)
{
    HXStatusDisplay* status = HX_STATUS_DISPLAY(position_status);
    
    if(position_status->button_1_is_pressed &&
       !position_status->seek_started)
    {
        hxstatus_display_position_slider_seek_start(position_status);
        position_status->seek_started = TRUE;
    }

    if(position_status->seek_started)
    {
        guint pos;
        pos = (guint)gtk_range_get_value(GTK_RANGE(position_status->position_scale));
        hx_player_set_position(HX_PLAYER(status->player), pos);
    }
}

static void
hsdp_play(HXStatusDisplayPositionSlider* position_status)
{
    HXStatusDisplay* status;
    gboolean is_live_or_infinite;

    status = HX_STATUS_DISPLAY(position_status);

    is_live_or_infinite = hx_player_is_live(status->player) ||
                          hx_player_is_indefinite_duration(status->player);
        
    gtk_widget_set_sensitive(position_status->position_scale, !is_live_or_infinite);
}

static void
hsdp_pause(HXStatusDisplayPositionSlider* position_status)
{
    HXStatusDisplay* status;
    gboolean is_live_or_infinite;

    status = HX_STATUS_DISPLAY(position_status);

    is_live_or_infinite = hx_player_is_live(status->player) ||
                          hx_player_is_indefinite_duration(status->player);
    
    gtk_widget_set_sensitive(position_status->position_scale, !is_live_or_infinite);
}

static void
hsdp_stop(HXStatusDisplayPositionSlider* position_status)
{
    HXStatusDisplay* status;

    status = HX_STATUS_DISPLAY(position_status);
    
    gtk_widget_set_sensitive(position_status->position_scale, FALSE);
}

static void
hsdp_seek(HXStatusDisplayPositionSlider* status, guint pos)
{
    gtk_range_set_value(GTK_RANGE(status->position_scale), (gdouble)pos);
}

void
hsdp_length_changed(HXStatusDisplayPositionSlider* status, guint /*len*/)
{
    /* Update the timer interval */
    guint interval;
    interval = hxstatus_display_position_slider_get_update_interval(status);
    hxstatus_display_change_timer_interval(HX_STATUS_DISPLAY(status), interval);
}
