/* Swfdec
 * Copyright (C) 2006-2007 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>
#include "swfdec_fb_widget.h"
#include "swfdec_fb_keys.h"
#include "swfdec_fb_player.h"
#include "swfdec_fb_drawing.h"
#include <swfdec/swfdec_marshal.h>

struct _SwfdecFbWidgetPrivate
{
  SwfdecPlayer *	player;		/* the video we play */

  gboolean		renderer_set;	/* TRUE if a special renderer has been set */
  cairo_surface_type_t	renderer_type;	/* the renderer that was set */
  SwfdecRenderer *	renderer;	/* renderer in use */
  gboolean		interactive;	/* TRUE if this widget propagates keyboard and mouse events */
  SwfdecRectangle invalid;		/* invalid regions we didn't yet repaint */
  guint			invalidator;	/* GSource used for invalidating window contents */
  gboolean		fullscreen_mode;/* TRUE if we're a fullscreen widget */
  gboolean		fullscreen;		/* TRUE if we're fullscreen */
  unsigned char	 *  buffer;
  int			buffer_maxw, buffer_maxh, buffer_stride;
  cairo_surface_t *surface;
  int			xpos, ypos, width, height;
  guint32	bg;
  gboolean	resized;
  gboolean	disposing;
};

enum {
  INITIALIZED,
  INVALIDATE,
  HANDLE_KEY,
  HANDLE_MOUSE,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0, };

enum {
  PROP_0,
  PROP_FULLSCREEN_MODE,
  PROP_INTERACTIVE,
  PROP_PLAYER,
  PROP_RENDERER_SET,
  PROP_RENDERER,
  PROP_XPOS,
  PROP_YPOS,
  PROP_WIDTH,
  PROP_HEIGHT
};

/* widget xpos/ypos */
extern int g_x, g_y;

/*** fb-doc ***/

/**
 * SECTION:SwfdecFbWidget
 * @title: SwfdecFbWidget
 * @short_description: a #FbWidget for embedding SWF files
 *
 * This is a widget for playing Flash movies rendered with Swfdec in a Fb 
 * application. It supports a lot of advanced features, if you want to use
 * them. If you don't want to use them and just want to embed a movie in 
 * your application, swfdec_fb_widget_new() will probably be the only 
 * function you need.
 *
 * @see_also: SwfdecFbPlayer
 */

/**
 * SwfdecFbWidget:
 *
 * The structure for the Swfdec Fb widget contains no public fields.
 */

/*** SWFDEC_FB_WIDGET ***/

G_DEFINE_TYPE (SwfdecFbWidget, swfdec_fb_widget, G_TYPE_OBJECT)

static guint
swfdec_fb_code_to_keycode (guint code)
{
  guint ret;

  /* last resort: try to translate the hardware keycode directly */
  ret = swfdec_fb_keycode_from_hardware_keycode (code);
  if (ret == 0)
    g_printerr ("could not translate key to Flash keycode. HW keycode %u\n", code);
  return ret;
}

static guint
swfdec_fb_keyval_to_unicode (guint keyval)
{
  guint ret;

  /* last resort: try to translate the hardware keycode directly */
  ret = swfdec_fb_unicode_from_hardware_keyval (keyval);
  if (ret == 0)
    g_printerr ("could not translate key to Flash character. HW keyval %u\n", keyval);
  return ret;
}

static gboolean
swfdec_fb_widget_handle_key (SwfdecFbWidget *fbwidget, 
		guint code, guint keyval, gboolean down, GObject *data)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (fbwidget);
  SwfdecFbWidgetPrivate *priv = widget->priv;

  if (priv->interactive && priv->player) {
	guint keycode = swfdec_fb_code_to_keycode (code);
  	if (keycode != 0) {
   	  SwfdecPlayer *player = priv->player;
      swfdec_player_set_allow_fullscreen (player, TRUE);
      if (down)
    	swfdec_player_key_press (player, keycode, 
    		swfdec_fb_keyval_to_unicode(keyval));
   	  else
    	swfdec_player_key_release (player, keycode, 
    		swfdec_fb_keyval_to_unicode(keyval));
	  swfdec_player_set_allow_fullscreen (player, FALSE);
  	}
  	return TRUE;
  }
  return FALSE;
}

static gboolean
swfdec_fb_widget_handle_mouse (SwfdecFbWidget *fbwidget, 
		double x, double y, int button, GObject *data)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (fbwidget);
  SwfdecFbWidgetPrivate *priv = widget->priv;

  SwfdecPlayer *player = priv->player;

	/* mouse move */  
  if (button == 0)
  {
  	if (priv->interactive && priv->player)
  	  swfdec_player_mouse_move (priv->player, x, y);
  	return FALSE;
  }  
  swfdec_player_set_allow_fullscreen (player, TRUE);	
  if (button  > 0)
	/* mouse press */
	swfdec_player_mouse_press (player, x, y, button);
  else
	/* mouse relse */
	swfdec_player_mouse_release (player, x, y, button);
  swfdec_player_set_allow_fullscreen (player, FALSE);
  
  return FALSE;
}

static void
swfdec_fb_widget_clear_invalidations (SwfdecFbWidget *widget)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;

  if (priv->invalidator) {
    g_source_remove (priv->invalidator);
    priv->invalidator = 0;
    swfdec_rectangle_init_empty(&priv->invalid);
  } else {
#if 0
    g_assert (swfdec_rectangle_is_empty(&priv->invalid));
#endif
  }
}

static cairo_surface_t *
swfdec_fb_widget_create_renderer (SwfdecFbWidget *widget, cairo_surface_type_t type, int width, int height)
{
  return widget->priv->surface;
}

static gboolean
swfdec_fb_widget_expose (GObject *fbwidget, const SwfdecRectangle *rect, const SwfdecRectangle *old_rect)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (fbwidget);
  SwfdecFbWidgetPrivate *priv = widget->priv;
  cairo_t *cr;
  cairo_surface_t *surface = NULL;
  SwfdecRectangle r;
  int off_x = 0, off_y = 0;

  /* FIXME: This might be ugly */
#if 0
  g_print("resized = %d\n", priv->resized);
  g_print("[x,y,w,h] = [%d,%d,%d,%d]\n", rect->x, rect->y, rect->width, rect->height);
  g_print("[x,y,w,h] = [%d,%d,%d,%d]\n", priv->invalid.x, priv->invalid.y, priv->invalid.width, priv->invalid.height);  
#endif
  if (old_rect != NULL)
  	g_print("[x,y,w,h] = [%d,%d,%d,%d]\n", old_rect->x, old_rect->y, old_rect->width, old_rect->height);
  swfdec_rectangle_union (&r, rect, &priv->invalid);
  if (swfdec_rectangle_is_empty(&r))
	return FALSE;

  if ((surface = swfdec_fb_widget_create_renderer (widget, priv->renderer_type,
	      r.width, r.height)) == NULL) 
  {
	return FALSE;
  }
  if (!priv->fullscreen)
  {
	off_x += priv->xpos;
	off_y += priv->ypos;
  }
  cr = cairo_create (surface);
  if (priv->resized || old_rect != NULL)
  {
	cairo_save(cr);
  	cairo_set_source_rgba (cr, 0, 0, 0, 0);
  	cairo_rectangle (cr, 0, 0, 960, 540);
	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
	cairo_paint(cr);
	cairo_restore(cr);
  }
  double rr, gg, bb, aa;
  aa = (double)((priv->bg >> 24) & 0xFF);
  rr = (double)((priv->bg >> 16) & 0xFF);
  gg = (double)((priv->bg >> 8)  & 0xFF);
  bb = (double)((priv->bg)  	 & 0xFF);
#if 0
  g_print("[aa,rr,gg,bb] = [%f,%f,%f,%f]\n", aa, rr, gg, bb);
  g_print("[x,y,w,h] = [%d,%d,%d,%d]\n", r.x, r.y, r.width, r.height);
#endif
  cairo_set_source_rgba (cr, rr/255.0, gg/255.0, bb/255.0, aa/255.0);
  cairo_rectangle (cr, r.x, r.y, r.width, r.height);
  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
  cairo_clip (cr);
  cairo_paint(cr);
  if (priv->player == NULL) {
    cairo_set_source_rgb (cr, 1, 1, 1);
    cairo_paint (cr);
  } else {
    swfdec_player_render (priv->player, cr);
  }
  cairo_destroy (cr);
  if (priv->resized || old_rect != NULL)
  {
	int w1, h1;
	w1 = r.x;
	if (r.x > 0)
  	  swfdec_fb_drawing_blit(0, 0, w1, 540, 0, 0);
	if (off_x > 0)
  	  swfdec_fb_drawing_blit(960 - off_x, 0, off_x, 540, w1, 0);
	w1 = r.x + off_x;
	h1 = r.y;
	if (r.y > 0 && w1 < 960)
  	  swfdec_fb_drawing_blit(w1, 0, 960 - w1, h1, w1, 0);
	if (off_y > 0 && w1 < 960)
  	  swfdec_fb_drawing_blit(w1, 540 - off_y, 960 - w1, off_y, w1, h1);
	h1 = r.y + off_y;
  	swfdec_fb_drawing_blit(r.x, r.y, 960 - w1, 540 - h1, w1, h1);
	priv->resized = FALSE;
  }
  else
  {
  	swfdec_fb_drawing_blit(r.x, r.y, r.width, r.height, r.x + off_x, r.y + off_y);
  }
  swfdec_fb_widget_clear_invalidations (widget);
  return TRUE;
}

static void
swfdec_fb_widget_redraw_cb (SwfdecFbWidget *widget,
		const SwfdecRectangle *rect, guint n_rects, GObject *data)
{
#if 0
  g_printerr("n_rects = %d\n", n_rects);
#endif
  swfdec_fb_widget_expose((GObject *)widget, &rect[0], n_rects > 1 ? &rect[1] : NULL); 
}

static void
swfdec_fb_widget_get_property (GObject *object, guint param_id, GValue *value, 
    GParamSpec * pspec)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (object);
  SwfdecFbWidgetPrivate *priv = widget->priv;
  
  switch (param_id) {
    case PROP_PLAYER:
      g_value_set_object (value, priv->player);
      break;
    case PROP_INTERACTIVE:
      g_value_set_boolean (value, priv->interactive);
      break;
    case PROP_RENDERER_SET:
      g_value_set_boolean (value, priv->renderer_set);
      break;
    case PROP_RENDERER:
      g_value_set_uint (value, priv->renderer_type);
      break;
    case PROP_XPOS:
      g_value_set_int (value, priv->xpos);
      break;
    case PROP_YPOS:
      g_value_set_int (value, priv->ypos);
      break;
    case PROP_WIDTH:
      g_value_set_int (value, priv->width);
      break;
    case PROP_HEIGHT:
      g_value_set_int (value, priv->height);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
  }
}

static void
swfdec_fb_widget_set_property (GObject *object, guint param_id, const GValue *value,
    GParamSpec *pspec)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (object);
  SwfdecFbWidgetPrivate *priv = widget->priv;
  
  g_print("PROP, %d\n", param_id);
  
  switch (param_id) {
    case PROP_FULLSCREEN_MODE:
      priv->fullscreen_mode = g_value_get_boolean (value);
      break;
    case PROP_INTERACTIVE:
      swfdec_fb_widget_set_interactive (widget, g_value_get_boolean (value));
      break;
    case PROP_PLAYER:
      swfdec_fb_widget_set_player (widget, g_value_get_object (value));
      break;
    case PROP_RENDERER_SET:
      priv->renderer_set = g_value_get_boolean (value);
      // invalid ?
      break;
    case PROP_RENDERER:
      priv->renderer_type = g_value_get_uint (value);
      //if (priv->renderer_set)
	  // invalid ?
      break;
    case PROP_XPOS:
      priv->xpos = g_value_get_int (value);
      // invalid ?
      break;
    case PROP_YPOS:
      priv->ypos = g_value_get_int (value);
      // invalid ?
      break;
    case PROP_WIDTH:    	
      priv->width = g_value_get_int (value);
      // invalid ?
      break;
    case PROP_HEIGHT:
      priv->height = g_value_get_int (value);
      // invalid ?
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
  }
}

static void
swfdec_fb_widget_dispose (GObject *object)
{
  SwfdecFbWidget *widget = SWFDEC_FB_WIDGET (object);
  SwfdecFbWidgetPrivate *priv = widget->priv;

  priv->disposing = TRUE;
  swfdec_fb_widget_set_player (widget, NULL);

  g_assert (priv->renderer == NULL);
  swfdec_rectangle_init_empty(&priv->invalid);
  g_assert (priv->invalidator == 0);

  if (priv->surface != NULL)
  {
	cairo_surface_destroy (priv->surface);
	priv->surface = NULL;
  }
  swfdec_fb_drawing_uninit(priv->buffer, priv->buffer_maxw, priv->buffer_maxh, priv->buffer_stride);

  G_OBJECT_CLASS (swfdec_fb_widget_parent_class)->dispose (object);
}

static void
swfdec_fb_widget_update_renderer (SwfdecFbWidget *widget)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;
  gboolean needs_renderer;

  needs_renderer = priv->player != NULL;

  if (priv->renderer != NULL) {
    swfdec_player_set_renderer (priv->player, NULL);
    g_object_unref (priv->renderer);
    priv->renderer = NULL;
  }
  if (needs_renderer) {
    if (priv->fullscreen_mode) {
      /* FIXME: We should really use the renderer of the app that 
       * fullscreened us, but for now, we'll assume that app uses a
       * similar renderer */
      priv->renderer = swfdec_player_get_renderer (priv->player);
      g_object_ref (priv->renderer);
    } else if (priv->renderer_set) {
      cairo_surface_t *surface = swfdec_fb_widget_create_renderer (widget, priv->renderer_type, 1, 1);
      if (surface) {
		priv->renderer = swfdec_renderer_new_for_player (
	    	surface, priv->player);
		swfdec_player_set_renderer (priv->player, priv->renderer);
      }
    } else {
    }
  }
}

static void
swfdec_fb_widget_update_background (SwfdecFbWidget *widget)
{
  SwfdecRectangle r = {0, 0, widget->priv->width, widget->priv->height};
  if (widget->priv->player) {
    widget->priv->bg = swfdec_player_get_background_color (widget->priv->player);
    swfdec_player_get_size(widget->priv->player, &r.width, &r.height);
  } else {
    /* white */
    widget->priv->bg = 0xFFFFFFFF;
  }
  // invalid whole area ?
#if 1
  g_signal_emit (widget, signals[INVALIDATE], 0, &r, 1);
#else
  swfdec_fb_widget_expose(G_OBJECT(widget), &r, NULL);
#endif  
}

static void
swfdec_fb_widget_class_init (SwfdecFbWidgetClass * g_class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (g_class);

  object_class->dispose = swfdec_fb_widget_dispose;
  object_class->get_property = swfdec_fb_widget_get_property;
  object_class->set_property = swfdec_fb_widget_set_property;

  g_object_class_install_property (object_class, PROP_FULLSCREEN_MODE,
      g_param_spec_boolean ("fullscreen-mode", "fullscreen mode", "special mode for fullscreen display",
	  FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property (object_class, PROP_INTERACTIVE,
      g_param_spec_boolean ("interactive", "interactive", "if mouse events are processed",
	  TRUE, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PLAYER,
      g_param_spec_object ("player", "player", "player that is displayed",
	  SWFDEC_TYPE_PLAYER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
  g_object_class_install_property (object_class, PROP_RENDERER_SET,
      g_param_spec_boolean ("renderer-set", "renderer set", "if an intermediate renderer should be used",
	  TRUE, G_PARAM_READWRITE));
  /* FIXME: get an enum for cairo_surface_type_t */
  g_object_class_install_property (object_class, PROP_RENDERER,
      g_param_spec_uint ("renderer", "renderer", "cairo_surface_type_t of intermediate renderer to use",
	  0, G_MAXUINT, CAIRO_SURFACE_TYPE_IMAGE, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_XPOS,
      g_param_spec_int ("xpos", "xpos", "xpos",
	  0, 959, 0, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_YPOS,
      g_param_spec_int ("ypos", "ypos", "ypos",
	  0, 539, 0, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_WIDTH,
      g_param_spec_int ("width", "width", "width",
	  0, 960, 480, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_HEIGHT,
      g_param_spec_int ("height", "height", "height",
	  0, 540, 360, G_PARAM_READWRITE));	  	  	  

  g_type_class_add_private (object_class, sizeof (SwfdecFbWidgetPrivate));
  
  signals[INITIALIZED] = g_signal_new ("initialized", G_TYPE_FROM_CLASS (g_class),
    G_SIGNAL_RUN_LAST, 0, NULL, NULL, swfdec_marshal_VOID__POINTER_UINT,
    G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);

  signals[INVALIDATE] = g_signal_new ("invalidate", G_TYPE_FROM_CLASS (g_class),
    G_SIGNAL_RUN_LAST, 0, NULL, NULL, swfdec_marshal_VOID__POINTER_UINT,
    G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);

  signals[HANDLE_KEY] = g_signal_new ("handle-key", G_TYPE_FROM_CLASS (g_class),
    G_SIGNAL_RUN_LAST, 0, NULL, NULL, swfdec_marshal_BOOLEAN__UINT_UINT_BOOLEAN,
    G_TYPE_BOOLEAN, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN);
    
  signals[HANDLE_MOUSE] = g_signal_new ("handle-mouse", G_TYPE_FROM_CLASS (g_class),
    G_SIGNAL_RUN_LAST, 0, NULL, NULL, swfdec_marshal_BOOLEAN__DOUBLE_DOUBLE_INT,
    G_TYPE_BOOLEAN, 3, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT);
    
}

static void
swfdec_fb_widget_init (SwfdecFbWidget *widget)
{
  SwfdecFbWidgetPrivate *priv;
  
  priv = widget->priv = G_TYPE_INSTANCE_GET_PRIVATE (widget, SWFDEC_TYPE_FB_WIDGET, SwfdecFbWidgetPrivate);

  swfdec_fb_drawing_init(&priv->buffer, &priv->buffer_maxw, &priv->buffer_maxh, &priv->buffer_stride);
  priv->interactive = TRUE;
  priv->renderer_type = CAIRO_SURFACE_TYPE_IMAGE;
  swfdec_rectangle_init_empty(&priv->invalid);
  priv->bg = 0x00000000;
  priv->resized = TRUE;
  priv->disposing = FALSE;

  if (priv->renderer_type == CAIRO_SURFACE_TYPE_IMAGE) {
   	priv->surface = cairo_image_surface_create_for_data (priv->buffer, 
					CAIRO_FORMAT_ARGB32, 
					960, 
					540, 
					priv->buffer_stride);
  } else {
  	priv->surface = NULL;
  }
  g_signal_connect (widget, "invalidate", G_CALLBACK (swfdec_fb_widget_redraw_cb), widget);
  g_signal_connect (widget, "handle-key", G_CALLBACK (swfdec_fb_widget_handle_key), widget);	
  g_signal_connect (widget, "handle-mouse", G_CALLBACK (swfdec_fb_widget_handle_mouse), widget);
  swfdec_fb_widget_update_renderer (SWFDEC_FB_WIDGET (widget));
}

static gboolean
swfdec_fb_widget_do_invalidate (gpointer widgetp)
{
  SwfdecFbWidget *widget = widgetp;
  SwfdecFbWidgetPrivate *priv = widget->priv;

#if 1
  g_signal_emit (widget, signals[INVALIDATE], 0, &priv->invalid, 1);
#else
  swfdec_fb_widget_expose(G_OBJECT(widget), &priv->invalid, NULL);
#endif
  swfdec_fb_widget_clear_invalidations (widget);
  return FALSE;
}

static void
swfdec_fb_widget_invalidate_cb (SwfdecPlayer *player,
    const SwfdecRectangle *rect, guint n_rects, SwfdecFbWidget *widget)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;
  guint i;
  SwfdecRectangle r;

  for (i = 0; i < n_rects; i++) {
    swfdec_rectangle_union(&r, &priv->invalid, &rect[i]);
	priv->invalid = r;
  }
#if 1
  if (priv->invalidator == 0) {
    priv->invalidator = g_idle_add_full (SWFDEC_FB_PRIORITY_REDRAW,
		swfdec_fb_widget_do_invalidate, widget, NULL);
  }
#else
  swfdec_fb_widget_do_invalidate(widget);
#endif
}

static void
swfdec_fb_widget_do_fullscreen (SwfdecFbWidget *widget, gboolean fullscreen)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;
  SwfdecRectangle r[2] =
  {
  	{0, 0, 0, 0},
  	{0, 0, 960, 540},
  };

  g_printerr("do_fullscreen, fullscreen = %d\n", fullscreen);

  if (priv->fullscreen_mode)
    return;

  if (priv->fullscreen == fullscreen)
    return;

  // reset size 
  swfdec_fb_widget_clear_invalidations (widget);
  // invalidate 
  priv->fullscreen = fullscreen;
  if (fullscreen)
  {
	g_x = 0;	
	g_y = 0;
	r[0].width = 960;
	r[0].height = 540;
  	g_printerr("do_fullscreen, [x,y,w,h] = [%d,%d,%d,%d]\n",
		g_x, g_y, r[0].width, r[0].height);
  }
  else
  {
	g_x = priv->xpos;
	g_y = priv->ypos;
	r[0].width = priv->width;
	r[0].height = priv->height;
  	g_printerr("do_fullscreen, [x,y,w,h] = [%d,%d,%d,%d]\n",
		g_x, g_y, r[0].width, r[0].height);
  }
  g_signal_emit (widget, signals[INVALIDATE], 0, r, 2);
}

static void
swfdec_fb_widget_update_cursor (SwfdecFbWidget *widget)
{
}

static void
swfdec_fb_widget_notify_cb (SwfdecPlayer *player, GParamSpec *pspec, SwfdecFbWidget *widget)
{
  if (g_str_equal (pspec->name, "mouse-cursor")) {
	/* nop */
  } else if (g_str_equal (pspec->name, "initialized")) {
	g_signal_emit (widget, signals[INITIALIZED], 0, 0, 0);
  } else if (g_str_equal (pspec->name, "fullscreen")) {
    swfdec_fb_widget_do_fullscreen (widget, swfdec_player_get_fullscreen (player));
  } else if (g_str_equal (pspec->name, "background-color")) {
    swfdec_fb_widget_update_background (widget);
  } else if (g_str_equal (pspec->name, "selection")) {
    const char *text = swfdec_player_get_selection (player);
    g_print("text = %s\n", text);
  }
}

static gboolean
swfdec_fb_widget_query_size_cb (SwfdecPlayer *player, gboolean fullscreen,
    int *width, int *height, SwfdecFbWidget *widget)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;

  /* don't connect to this signal in fullscreen mode, it reports wrong sizes */
  if (priv->fullscreen_mode)
    return FALSE;

  if (fullscreen) {
    *width = 960;
    *height = 540;
  } else {
#if 1
    *width = priv->width;	// size ?
    *height = priv->height;	// size ?
#else
    *width = 480;	// size ?
    *height = 360;	// size ?
#endif
  }
  return TRUE;
}

/*** PUBLIC API ***/

/**
 * swfdec_fb_widget_set_player:
 * @widget: a #SwfdecFbWidget
 * @player: the #SwfdecPlayer to display or %NULL for none
 *
 * Sets the new player to display in @widget.
 **/
void
swfdec_fb_widget_set_player (SwfdecFbWidget *widget, SwfdecPlayer *player)
{
  SwfdecFbWidgetPrivate *priv;

  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));
  g_return_if_fail (player == NULL || SWFDEC_IS_PLAYER (player));
  
  priv = widget->priv;
  if (priv->player == player)
    return;

  swfdec_fb_widget_clear_invalidations (widget);
  if (player) {
    g_signal_connect (player, "invalidate", G_CALLBACK (swfdec_fb_widget_invalidate_cb), widget);
    g_signal_connect (player, "notify", G_CALLBACK (swfdec_fb_widget_notify_cb), widget);
    g_signal_connect (player, "query-size", G_CALLBACK (swfdec_fb_widget_query_size_cb), widget);
    g_object_ref (player);
    swfdec_fb_widget_update_cursor (widget);
    swfdec_player_set_focus (player, TRUE);
  } else {
  }
  if (priv->player) {
    g_signal_handlers_disconnect_by_func (priv->player, swfdec_fb_widget_invalidate_cb, widget);
    g_signal_handlers_disconnect_by_func (priv->player, swfdec_fb_widget_notify_cb, widget);
    g_signal_handlers_disconnect_by_func (priv->player, swfdec_fb_widget_query_size_cb, widget);
    g_object_unref (priv->player);
  }
  priv->player = player;
  g_object_notify (G_OBJECT (widget), "player");
//fb_widget_queue_resize (FB_WIDGET (widget));
  swfdec_fb_widget_update_renderer (widget);
  if (priv->disposing)
	return;
  swfdec_fb_widget_update_background (widget);
  swfdec_fb_widget_do_fullscreen (widget, player ? swfdec_player_get_fullscreen (player) : FALSE);
}

/**
 * swfdec_fb_widget_get_player:
 * @widget: a #SwfdecFbWidget
 *
 * Gets the player that is currently played back in this @widget.
 *
 * Returns: the #SwfdecPlayer or %NULL if none
 **/
SwfdecPlayer *
swfdec_fb_widget_get_player (SwfdecFbWidget *widget)
{
  g_return_val_if_fail (SWFDEC_IS_FB_WIDGET (widget), NULL);

  return widget->priv->player;
}

/**
 * swfdec_fb_widget_new:
 * @player: a #SwfdecPlayer or %NULL
 *
 * Creates a new #SwfdecFbWidget to display @player.
 *
 * Returns: the new widget that displays @player
 **/
GObject *
swfdec_fb_widget_new (SwfdecPlayer *player)
{
  SwfdecFbWidget *widget;
  
  g_return_val_if_fail (player == NULL || SWFDEC_IS_PLAYER (player), NULL);

  widget = g_object_new (SWFDEC_TYPE_FB_WIDGET, "player", player, NULL);
  widget->priv->fullscreen = FALSE;
  g_x = 0;
  g_y = 0;

  return G_OBJECT (widget);
}

/**
 * swfdec_fb_widget_new_fullscreen:
 * @player: a #SwfdecPlayer
 *
 * Creates a new #SwfdecFbWidget to display @player inside a fullscreen 
 * window.
 *
 * Returns: the new widget that displays @player in fullscreen mode.
 **/
GObject *
swfdec_fb_widget_new_fullscreen (SwfdecPlayer *player)
{
  SwfdecFbWidget *widget;
  
  g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL);

  /* NB: We must set fullscreen mode before setting the player, or we 
   * get inf-loops when setting the player creates fullscreen windows */
  widget = g_object_new (SWFDEC_TYPE_FB_WIDGET, "fullscreen-mode", TRUE, "player", player, NULL);
  widget->priv->fullscreen = TRUE;
  g_x = 0;
  g_y = 0;

  return G_OBJECT (widget);
}

/**
 * swfdec_fb_widget_new_withposition:
 * @player: a #SwfdecPlayer or %NULL
 *
 * Creates a new #SwfdecFbWidget to display @player.
 *
 * Returns: the new widget that displays @player
 **/
GObject *
swfdec_fb_widget_new_withposition	(SwfdecPlayer *		player,
		int xpos, int ypos, int width, int height)
{
  SwfdecFbWidget *widget;
  
  g_return_val_if_fail (player == NULL || SWFDEC_IS_PLAYER (player), NULL);

  widget = g_object_new (SWFDEC_TYPE_FB_WIDGET, "xpos", xpos, "ypos", ypos, 
  	"width", width, "height", height, "player", player, NULL);
  widget->priv->fullscreen = FALSE;
  g_x = xpos;
  g_y = ypos;
  swfdec_player_set_size (player, width, height);

  return G_OBJECT (widget);
}

/**
 * swfdec_fb_widget_set_interactive:
 * @widget: a #SwfdecFbWidget
 * @interactive: %TRUE to make the widget interactive
 *
 * Sets the widget to be interactive or not. An interactive widget processes 
 * mouse and keyboard events, while a non-interactive widget does not care about
 * user input. Widgets are interactive by default.
 **/
void
swfdec_fb_widget_set_interactive (SwfdecFbWidget *widget, gboolean interactive)
{
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));

  widget->priv->interactive = interactive;
  swfdec_fb_widget_update_cursor (widget);
  g_object_notify (G_OBJECT (widget), "interactive");
}

/**
 * swfdec_fb_widget_get_interactive:
 * @widget: a #SwfdecFbWidget
 *
 * Queries if the @widget is currently interactive. See 
 * swfdec_fb_widget_set_interactive() for details.
 *
 * Returns: %TRUE if the widget is interactive, %FALSE otherwise.
 **/
gboolean
swfdec_fb_widget_get_interactive (SwfdecFbWidget *widget)
{
  g_return_val_if_fail (SWFDEC_IS_FB_WIDGET (widget), FALSE);

  return widget->priv->interactive;
}

/**
 * swfdec_fb_widget_set_renderer:
 * @widget: a #SwfdecFbWidget
 * @renderer: a #cairo_surface_type_t for the intermediate renderer
 *
 * Tells @widget to use an intermediate surface for rendering. This is
 * useful for debugging or performance measurements inside swfdec and is 
 * probably not interesting for anyone else.
 **/
void
swfdec_fb_widget_set_renderer (SwfdecFbWidget *widget, cairo_surface_type_t renderer)
{
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));

  widget->priv->renderer_type = renderer;
  if (widget->priv->renderer_set == FALSE) {
    widget->priv->renderer_set = TRUE;
    g_object_notify (G_OBJECT (widget), "renderer-set");
  }
  g_object_notify (G_OBJECT (widget), "renderer");
  swfdec_fb_widget_update_renderer (widget);
}

/**
 * swfdec_fb_widget_unset_renderer:
 * @widget: a #SwfdecFbWidget
 *
 * Unsets the use of an intermediate rendering surface. See 
 * swfdec_fb_widget_set_renderer() for details.
 **/
void
swfdec_fb_widget_unset_renderer (SwfdecFbWidget *widget)
{
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));

  if (widget->priv->renderer_set == FALSE)
    return;
  widget->priv->renderer_set = FALSE;
  g_object_notify (G_OBJECT (widget), "renderer-set");
  swfdec_fb_widget_update_renderer (widget);
}

/**
 * swfdec_fb_widget_get_renderer:
 * @widget: a #SwfdecFbWidget
 *
 * Gets the intermediate renderer that is or would be in use by @widget. Use
 * swfdec_fb_widget_uses_renderer() to check if an intermediate renderer is in
 * use. See swfdec_fb_widget_set_renderer() for details.
 *
 * Returns: the type of the intermediate renderer
 **/
cairo_surface_type_t
swfdec_fb_widget_get_renderer (SwfdecFbWidget *widget)
{
  g_return_val_if_fail (SWFDEC_IS_FB_WIDGET (widget), CAIRO_SURFACE_TYPE_IMAGE);

  return widget->priv->renderer_type;
}

/**
 * swfdec_fb_widget_uses_renderer:
 * @widget: a #SwfdecFbWidget
 *
 * Queries if an intermediate renderer set via swfdec_fb_widget_set_renderer()
 * is currently in use.
 *
 * Returns: %TRUE if an intermediate renderer is used.
 **/
gboolean
swfdec_fb_widget_uses_renderer (SwfdecFbWidget *widget)
{
  g_return_val_if_fail (SWFDEC_IS_FB_WIDGET (widget), FALSE);

  return widget->priv->renderer_set;
}

/**
 * swfdec_fb_widget_get_position:
 * @widget: a #SwfdecFbWidget
 * @xpos: xpos
 * @ypos: ypos
 *
 * get position
 **/
void
swfdec_fb_widget_get_position (SwfdecFbWidget *widget,
    int * xpos,
    int * ypos)
{
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));
	
  *xpos = widget->priv->xpos;
  *ypos = widget->priv->ypos;
}

/**
 * swfdec_player_set_position:
 * @widget: a #SwfdecFbWidget
 * @xpos: xpos
 * @ypos: ypos
 *
 * set position
 **/
void
swfdec_fb_widget_set_position (SwfdecFbWidget *widget,
    int xpos,
    int ypos)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;
  SwfdecRectangle r[2] = 
  {
	{0, 0, priv->width, priv->height},
	{priv->xpos, priv->ypos, priv->width, priv->height},
  };
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));	  
  
  if (xpos == priv->xpos && ypos == priv->ypos)
  	return;
  if (priv->width == 0 || priv->height == 0)
	swfdec_player_get_size(priv->player, &priv->width, &priv->height);
  r[0].width = r[1].width = priv->width;
  r[0].height = r[1].height = priv->height;
  widget->priv->xpos = xpos;
  widget->priv->ypos = ypos;
  g_x = xpos;
  g_y = ypos;
  g_printerr("emit, [%d, %d, %d, %d]\n", r[0].x, r[0].y, r[0].width, r[0].height);
  g_printerr("emit, [%d, %d, %d, %d]\n", r[1].x, r[1].y, r[1].width, r[1].height);
  g_signal_emit (widget, signals[INVALIDATE], 0, r, 2);
}

/**
 * swfdec_player_set_position:
 * @widget: a #SwfdecFbWidget
 * @x: x
 * @y: y
 * @w: w 
 * @h: h
 *
 * set position
 **/
void
swfdec_fb_widget_resize (SwfdecFbWidget *widget,
    int x,
    int y,
    int w,
    int h)
{
  SwfdecFbWidgetPrivate *priv = widget->priv;
  SwfdecRectangle r[2] = 
  {
	{0, 0, w, h},
	{0, 0, 960, 540},
  };
  g_return_if_fail (SWFDEC_IS_FB_WIDGET (widget));	  
  
  if (x == priv->xpos && y == priv->ypos
		&& w == priv->width && h == priv->height)
  	return;
  if (x < 0 || y < 0 
		|| w == 0 || h == 0)
	return;
  priv->xpos = x;
  priv->ypos = y;
  priv->width = w;
  priv->height = h;
  priv->resized = TRUE;
  swfdec_player_set_size(priv->player, priv->width, priv->height);
  g_x = x;
  g_y = y;
  g_printerr("emit, [%d, %d, %d, %d]\n", r[0].x, r[0].y, r[0].width, r[0].height);
  g_printerr("emit, [%d, %d, %d, %d]\n", r[1].x, r[1].y, r[1].width, r[1].height);
//g_signal_emit (widget, signals[INVALIDATE], 0, r, 2);
}

/**
 * swfdec_fb_widget_emit_key:
 * @widget: a #SwfdecFbWidget
 * @code: code
 * @keyval: keyval
 * @down: down 
 *
 * emit key
 **/
void
swfdec_fb_widget_emit_invalidate (SwfdecFbWidget *fbwidget, 
		const SwfdecRectangle *rect, guint n_rects)
{
  g_signal_emit (fbwidget, signals[INVALIDATE], 0, rect, n_rects);
}

/**
 * swfdec_fb_widget_emit_key:
 * @widget: a #SwfdecFbWidget
 * @code: code
 * @keyval: keyval
 * @down: down 
 *
 * emit key
 **/
void
swfdec_fb_widget_emit_key (SwfdecFbWidget *fbwidget, 
		guint code, guint keyval, gboolean down)
{
  gboolean ret;
  g_signal_emit (fbwidget, signals[HANDLE_KEY], 0, code, keyval, down, &ret);
}

/**
 * swfdec_fb_widget_emit_mouse:
 * @widget: a #SwfdecFbWidget
 * @x: x
 * @y: y
 * @button: button 
 *
 * emit mouse
 **/
void
swfdec_fb_widget_emit_mouse (SwfdecFbWidget *fbwidget, 
		double x, double y, int button)
{
  gboolean ret;
  g_signal_emit (fbwidget, signals[HANDLE_MOUSE], 0, x, y, button, &ret);
}
