/*
 * Copyright (c) 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "sg-util.h"
#include "st-stream-properties-dialog.h"
#include "st-handler.h"
#include "st-settings.h"
#include "sgtk-hig.h"
#include "sgtk-util.h"
#include "st-stream-bag.h"
#include "st-shell.h"
#include "st-dialog-api.h"
#include "st-handler-field.h"
#include "st-util-api.h"

/*** cpp *********************************************************************/

#define LABEL_DEFAULT_WIDTH		300

/*** type definitions ********************************************************/

struct _STStreamPropertiesDialogPrivate
{
  STStreamBag		*stream_bag;

  GtkWidget		*previous_button;
  GtkWidget		*next_button;
  GtkWidget		*ok_button;

  STHandler		*table_handler;
  GtkWidget		*table;
  GSList		*widgets;

  GSList		*cancel_list;
};

typedef struct
{
  STStreamBag		*stream_bag;
  GSList		*fields;
  GSList		*values;
} CancelInfo;

/*** variable declarations ***************************************************/

static GObjectClass *parent_class = NULL;

/*** function declarations ***************************************************/

static void st_stream_properties_dialog_class_init	(STStreamPropertiesDialogClass *class);
static void st_stream_properties_dialog_init		(STStreamPropertiesDialog *dialog);
static void st_stream_properties_dialog_finalize	(GObject *object);

static void st_stream_properties_dialog_unset_stream	(STStreamPropertiesDialog *dialog);
static void st_stream_properties_dialog_construct_table	(STStreamPropertiesDialog *dialog);
static void st_stream_properties_dialog_update_title	(STStreamPropertiesDialog *dialog);
static void st_stream_properties_dialog_update_data	(STStreamPropertiesDialog *dialog);

static void st_stream_properties_dialog_set_widget_data (GtkWidget *widget,
							 STStreamBag *stream_bag,
							 STHandlerField *field);

static void st_stream_properties_dialog_get_fields_and_values (STStreamPropertiesDialog *dialog,
							       GSList **fields,
							       GSList **values);
static gboolean st_stream_properties_dialog_value_equal (const GValue *value1,
							 const GValue *value2);
static void st_stream_properties_dialog_optimize_fields_and_values (STStreamBag *stream_bag,
								    GSList **fields,
								    GSList **values);

static CancelInfo *st_stream_properties_dialog_cancel_info_new (STStreamPropertiesDialog *dialog);
static void       st_stream_properties_dialog_cancel_info_free (CancelInfo *info);

static void st_stream_properties_dialog_cancel_list_append (STStreamPropertiesDialog *dialog, CancelInfo *info);
static void st_stream_properties_dialog_cancel_list_free (STStreamPropertiesDialog *dialog);

static void st_stream_properties_dialog_stream_changed_h (STStreamBag *bag,
							  gpointer user_data);
static void st_stream_properties_dialog_stream_deleted_h (STStreamBag *bag,
							  gpointer user_data);

/*** implementation **********************************************************/

GType
st_stream_properties_dialog_get_type (void)
{
  static GType stream_properties_dialog_type = 0;
  
  if (! stream_properties_dialog_type)
    {
      static const GTypeInfo stream_properties_dialog_info = {
	sizeof(STStreamPropertiesDialogClass),
	NULL,
	NULL,
	(GClassInitFunc) st_stream_properties_dialog_class_init,
	NULL,
	NULL,
	sizeof(STStreamPropertiesDialog),
	0,
	(GInstanceInitFunc) st_stream_properties_dialog_init,
      };
      
      stream_properties_dialog_type = g_type_register_static(SGTK_TYPE_DIALOG,
							     "STStreamPropertiesDialog",
							     &stream_properties_dialog_info,
							     0);
    }

  return stream_properties_dialog_type;
}

static void
st_stream_properties_dialog_class_init (STStreamPropertiesDialogClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STStreamPropertiesDialogPrivate));

  object_class->finalize = st_stream_properties_dialog_finalize;
}

static void
st_stream_properties_dialog_init (STStreamPropertiesDialog *dialog)
{
  dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE(dialog, ST_TYPE_STREAM_PROPERTIES_DIALOG, STStreamPropertiesDialogPrivate);

  dialog->priv->previous_button = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_GO_BACK, SGTK_RESPONSE_PREVIOUS);
  dialog->priv->next_button = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_GO_FORWARD, SGTK_RESPONSE_NEXT);
  gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY);
  gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
  dialog->priv->ok_button = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);

  sgtk_window_link_size(GTK_WINDOW(dialog),
			&st_settings.stream_properties_window_width,
			NULL);
}

static void
st_stream_properties_dialog_finalize (GObject *object)
{
  STStreamPropertiesDialog *dialog = ST_STREAM_PROPERTIES_DIALOG(object);

  st_stream_properties_dialog_unset_stream(dialog);
  g_slist_free(dialog->priv->widgets);
  st_stream_properties_dialog_cancel_list_free(dialog);

  parent_class->finalize(object);
}

static void
st_stream_properties_dialog_unset_stream (STStreamPropertiesDialog *dialog)
{
  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  if (dialog->priv->stream_bag)
    {
      g_object_disconnect(dialog->priv->stream_bag,
			  "any-signal", st_stream_properties_dialog_stream_changed_h, dialog,
			  "any-signal", st_stream_properties_dialog_stream_deleted_h, dialog,
			  NULL);
      g_object_unref(dialog->priv->stream_bag);
    }
}

static void
st_stream_properties_dialog_construct_table (STStreamPropertiesDialog *dialog)
{
  GSList *l;
  int n_fields;
  int vi = 0;
  int width;
  int height;
  GtkWidget *first_widget = NULL;
  GtkWidget *prev_widget = NULL;
  GtkWidget *focused_widget = NULL;
  GSList *pending_widgets = NULL;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));
  g_return_if_fail(dialog->priv->table_handler != NULL);

  if (dialog->priv->table)
    gtk_container_remove(GTK_CONTAINER(SGTK_DIALOG(dialog)->contents), dialog->priv->table);
  
  if (dialog->priv->widgets)
    {
      g_slist_free(dialog->priv->widgets);
      dialog->priv->widgets = NULL;
    }
  
  n_fields = st_handler_count_fields(dialog->priv->table_handler, ST_HANDLER_FIELD_VISIBLE | ST_HANDLER_FIELD_EDITABLE);
  dialog->priv->table = gtk_table_new(n_fields, 2, FALSE);
 
  gtk_table_set_row_spacings(GTK_TABLE(dialog->priv->table), SGTK_HIG_CONTROL_SPACING);
  gtk_table_set_col_spacings(GTK_TABLE(dialog->priv->table), SGTK_HIG_CONTROL_LABEL_SPACING);

  SG_LIST_FOREACH(l, st_handler_get_fields(dialog->priv->table_handler))
    {
      STHandlerField *field = l->data;
     
      if (ST_HANDLER_FIELD_IS_VISIBLE(field) || ST_HANDLER_FIELD_IS_EDITABLE(field))
	{
	  GtkWidget *widget;

	  if (ST_HANDLER_FIELD_IS_EDITABLE(field))
	    {
	      const char *description;

	      switch (st_handler_field_get_type(field))
		{
		case G_TYPE_BOOLEAN:
		  widget = gtk_check_button_new();
		  break;

		case G_TYPE_INT:
		  widget = gtk_spin_button_new_with_range(G_MININT, G_MAXINT, 1);
		  break;

		case G_TYPE_UINT:
		  widget = gtk_spin_button_new_with_range(0, G_MAXUINT, 1);
		  break;

		case G_TYPE_DOUBLE:
		  widget = gtk_spin_button_new_with_range(G_MINDOUBLE, G_MAXDOUBLE, 1);
		  break;

		case G_TYPE_STRING:
		  widget = gtk_entry_new();
		  break;

		default:
		  g_return_if_reached();
		}

	      if (! focused_widget)
		gtk_widget_grab_focus(focused_widget = widget);

	      if (! first_widget)
		first_widget = widget;
	      if (prev_widget && GTK_IS_ENTRY(prev_widget))
		sgtk_entry_set_next_widget(GTK_ENTRY(prev_widget), widget);
	      prev_widget = widget;

	      description = st_handler_field_get_description(field);
	      if (description)
		st_set_tooltip(widget, description);
	    }
	  else
	    {
	      if (st_handler_field_get_type(field) == GDK_TYPE_PIXBUF)
		widget = gtk_image_new();
	      else
		{
		  widget = gtk_label_new(NULL);
		  gtk_widget_set_size_request(widget, LABEL_DEFAULT_WIDTH, -1);
		  gtk_misc_set_alignment(GTK_MISC(widget), 0.0, 0.5);
		  gtk_label_set_selectable(GTK_LABEL(widget), TRUE);
		}
	    }
	  
	  g_object_set_data(G_OBJECT(widget), "field", field);
	  dialog->priv->widgets = g_slist_append(dialog->priv->widgets, widget);
	  gtk_widget_show(widget);

	  if (ST_HANDLER_FIELD_HAS_DEDICATED_COLUMN(field))
	    {
	      GtkWidget *label;
	      char *str;
	      GtkWidget *hbox;
	      GSList *m;

	      str = g_markup_printf_escaped("<span weight=\"bold\">%s:</span>", st_handler_field_get_label(field));
	      label = gtk_label_new(str);
	      g_free(str);

	      gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
	      gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);

	      hbox = gtk_hbox_new(FALSE, 0);

	      SG_LIST_FOREACH(m, pending_widgets)
		/* padding is 2, matching the default GtkCellRenderer::xpad */
		gtk_box_pack_start(GTK_BOX(hbox), m->data, FALSE, FALSE, 2);

	      g_slist_free(pending_widgets);
	      pending_widgets = NULL;

	      gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);

	      gtk_widget_show(label);
	      gtk_widget_show(hbox);

	      gtk_table_attach(GTK_TABLE(dialog->priv->table),
			       label,
			       0, 1, vi, vi + 1,
			       GTK_FILL,
			       0,
			       0,
			       0);
	      gtk_table_attach(GTK_TABLE(dialog->priv->table),
			       hbox,
			       1, 2, vi, vi + 1,
			       GTK_EXPAND | GTK_FILL,
			       0,
			       0,
			       0);

	      vi++;
	    }
	  else
	    pending_widgets = g_slist_append(pending_widgets, widget);
	}
    }
  g_slist_free(pending_widgets);

  if (! focused_widget)
    gtk_widget_grab_focus(dialog->priv->ok_button);

  if (prev_widget && GTK_IS_ENTRY(prev_widget) && prev_widget != first_widget)
    sgtk_entry_set_next_widget(GTK_ENTRY(prev_widget), first_widget);

  gtk_widget_show(dialog->priv->table);
  gtk_container_add(GTK_CONTAINER(SGTK_DIALOG(dialog)->contents), dialog->priv->table);

  /* force the dialog to recalculate its height */
  gtk_window_get_size(GTK_WINDOW(dialog), &width, &height);
  gtk_window_resize(GTK_WINDOW(dialog), width, 1);
}

static void
st_stream_properties_dialog_update_title (STStreamPropertiesDialog *dialog)
{
  char *title = NULL;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  if (st_handler_event_is_bound(dialog->priv->stream_bag->handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET))
    {
      GValue value = { 0, };

      st_stream_bag_get_stock_field(dialog->priv->stream_bag, ST_HANDLER_STOCK_FIELD_NAME, &value);
      if (g_value_get_string(&value))
	title = g_strdup_printf(_("%s Properties"), g_value_get_string(&value));
      g_value_unset(&value);
    }

  if (title)
    {
      gtk_window_set_title(GTK_WINDOW(dialog), title);
      g_free(title);
    }
  else
    gtk_window_set_title(GTK_WINDOW(dialog), _("Stream Properties"));
}

static void
st_stream_properties_dialog_update_data (STStreamPropertiesDialog *dialog)
{
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  SG_LIST_FOREACH(l, dialog->priv->widgets)
    {
      GtkWidget *widget = l->data;
      
      if (dialog->priv->stream_bag)
	{
	  STHandlerField *field = g_object_get_data(G_OBJECT(widget), "field");

	  gtk_widget_set_sensitive(widget, TRUE);
	  st_stream_properties_dialog_set_widget_data(widget, dialog->priv->stream_bag, field);
	}
      else
	gtk_widget_set_sensitive(widget, FALSE);
    }
}

static void
st_stream_properties_dialog_set_widget_data (GtkWidget *widget,
					     STStreamBag *stream_bag,
					     STHandlerField *field)
{
  GValue value = { 0, };
  
  g_return_if_fail(GTK_IS_WIDGET(widget));
  g_return_if_fail(ST_IS_STREAM_BAG(stream_bag));
  g_return_if_fail(field != NULL);

  st_stream_bag_get_field(stream_bag, field, &value);

  if (ST_HANDLER_FIELD_IS_EDITABLE(field))
    {
      switch (G_VALUE_TYPE(&value))
	{
	case G_TYPE_BOOLEAN:
	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), g_value_get_boolean(&value));
	  break;
	  
	case G_TYPE_INT:
	  gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), g_value_get_int(&value));
	  break;
	  
	case G_TYPE_UINT:
	  gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), g_value_get_uint(&value));
	  break;

	case G_TYPE_DOUBLE:
	  gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), g_value_get_double(&value));
	  break;

	case G_TYPE_STRING:
	  {
	    const char *str = g_value_get_string(&value);
	    gtk_entry_set_text(GTK_ENTRY(widget), str ? str : "");
	    break;
	  }
	  
	default:
	  g_return_if_reached();
	}
    }
  else
    {
      if (G_VALUE_HOLDS_BOOLEAN(&value))
	gtk_label_set_text(GTK_LABEL(widget), g_value_get_boolean(&value) ? _("Yes") : _("No"));
      else if (G_VALUE_HOLDS_INT(&value))
	{
	  char *str = g_strdup_printf("%i", g_value_get_int(&value));
	  gtk_label_set_text(GTK_LABEL(widget), str);
	  g_free(str);
	}
      else if (G_VALUE_HOLDS_UINT(&value))
	{
	  char *str = g_strdup_printf("%u", g_value_get_uint(&value));
	  gtk_label_set_text(GTK_LABEL(widget), str);
	  g_free(str);
	}
      else if (G_VALUE_HOLDS_DOUBLE(&value))
	{
	  char *str = g_strdup_printf("%f", g_value_get_double(&value));
	  gtk_label_set_text(GTK_LABEL(widget), str);
	  g_free(str);
	}
      else if (G_VALUE_HOLDS_STRING(&value))
	gtk_label_set_text(GTK_LABEL(widget), g_value_get_string(&value));
      else if (G_VALUE_HOLDS(&value, GDK_TYPE_PIXBUF))
	gtk_image_set_from_pixbuf(GTK_IMAGE(widget), g_value_get_object(&value));
      else if (G_VALUE_HOLDS(&value, G_TYPE_VALUE_ARRAY))
	{
	  GValueArray *value_array;
	  char *str;

	  value_array = g_value_get_boxed(&value);
	  str = value_array ? sg_value_array_get_string(value_array) : NULL;

	  gtk_label_set_text(GTK_LABEL(widget), str ? str : "");
	  g_free(str);
	}
      else
	g_return_if_reached();
    }
  
  g_value_unset(&value);
}

GtkWidget *
st_stream_properties_dialog_new (GtkWindow *parent)
{
  STStreamPropertiesDialog *dialog;

  dialog = g_object_new(ST_TYPE_STREAM_PROPERTIES_DIALOG, NULL);

  if (parent)
    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
  st_stream_properties_dialog_update_sensitivity(dialog);

  return GTK_WIDGET(dialog);
}

void
st_stream_properties_dialog_set_stream (STStreamPropertiesDialog *dialog,
					STStreamBag *stream_bag)
{
  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));
  g_return_if_fail(ST_IS_STREAM_BAG(stream_bag));

  st_stream_properties_dialog_unset_stream(dialog);

  if (stream_bag->handler != dialog->priv->table_handler)
    {
      dialog->priv->table_handler = stream_bag->handler;
      st_stream_properties_dialog_construct_table(dialog);
    }

  dialog->priv->stream_bag = g_object_ref(stream_bag);

  g_object_connect(dialog->priv->stream_bag,
		   "signal::changed", st_stream_properties_dialog_stream_changed_h, dialog,
		   "signal::deleted", st_stream_properties_dialog_stream_deleted_h, dialog,
		   NULL);

  st_stream_properties_dialog_update_title(dialog);
  st_stream_properties_dialog_update_data(dialog);
}

static void
st_stream_properties_dialog_stream_changed_h (STStreamBag *bag,
					      gpointer user_data)
{
  STStreamPropertiesDialog *dialog = user_data;

  st_stream_properties_dialog_update_title(dialog);
  st_stream_properties_dialog_update_data(dialog);
}

static void
st_stream_properties_dialog_stream_deleted_h (STStreamBag *bag,
					      gpointer user_data)
{
  STStreamPropertiesDialog *dialog = user_data;

  gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
}

void
st_stream_properties_dialog_update_sensitivity (STStreamPropertiesDialog *dialog)
{
  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  gtk_widget_set_sensitive(dialog->priv->previous_button, st_shell_can_select_previous_stream(st_shell));
  gtk_widget_set_sensitive(dialog->priv->next_button, st_shell_can_select_next_stream(st_shell));
}

static void
st_stream_properties_dialog_get_fields_and_values (STStreamPropertiesDialog *dialog,
						   GSList **fields,
						   GSList **values)
{
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));
  g_return_if_fail(fields != NULL);
  g_return_if_fail(values != NULL);

  *fields = NULL;
  *values = NULL;

  SG_LIST_FOREACH(l, dialog->priv->widgets)
    {
      GtkWidget *widget = l->data;
      STHandlerField *field = g_object_get_data(G_OBJECT(widget), "field");

      if (ST_HANDLER_FIELD_IS_EDITABLE(field))
	{
	  GValue current_value = { 0, };
	  GValue *value;
	  
	  st_stream_bag_get_field(dialog->priv->stream_bag, field, &current_value);
	  value = sg_value_new(st_handler_field_get_type(field));

	  switch (G_VALUE_TYPE(value))
	    {
	    case G_TYPE_BOOLEAN:
	      g_value_set_boolean(value, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
	      break;

	    case G_TYPE_INT:
	      g_value_set_int(value, gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget)));
	      break;

	    case G_TYPE_UINT:
	      g_value_set_uint(value, gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget)));
	      break;

	    case G_TYPE_DOUBLE:
	      g_value_set_double(value, gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
	      break;

	    case G_TYPE_STRING:
	      {
		const char *str = gtk_entry_get_text(GTK_ENTRY(widget));
		g_value_set_string(value, *str ? str : NULL);
		break;
	      }

	    default:
	      g_return_if_reached();
	    }

	  *fields = g_slist_append(*fields, field);
	  *values = g_slist_append(*values, value);

	  g_value_unset(&current_value);
	}
    }
}

static gboolean
st_stream_properties_dialog_value_equal (const GValue *value1,
					 const GValue *value2)
{
  gboolean equal;

  g_return_val_if_fail(G_IS_VALUE(value1), FALSE);
  g_return_val_if_fail(G_IS_VALUE(value2), FALSE);
  g_return_val_if_fail(G_VALUE_TYPE(value1) == G_VALUE_TYPE(value2), FALSE);

  switch (G_VALUE_TYPE(value1))
    {
    case G_TYPE_BOOLEAN:
      equal = g_value_get_boolean(value1) == g_value_get_boolean(value2);
      break;

    case G_TYPE_INT:
      equal = g_value_get_int(value1) == g_value_get_int(value2);
      break;

    case G_TYPE_UINT:
      equal = g_value_get_uint(value1) == g_value_get_uint(value2);
      break;

    case G_TYPE_DOUBLE:
      equal = sg_double_equal(g_value_get_double(value1), g_value_get_double(value2));
      break;

    case G_TYPE_STRING:
      {
	const char *str1 = g_value_get_string(value1);
	const char *str2 = g_value_get_string(value2);

	equal = (str1 == NULL && str2 == NULL) || (str1 && str2 && ! strcmp(str1, str2));
	break;
      }

    default:
      g_return_val_if_reached(FALSE);
    }

  return equal;
}

/*
 * Modify FIELDS and VALUES so that only values differing from those
 * in STREAM_BAG are included.
 */
static void
st_stream_properties_dialog_optimize_fields_and_values (STStreamBag *stream_bag,
							GSList **fields,
							GSList **values)
{
  GSList *new_fields = NULL;
  GSList *new_values = NULL;
  GSList *f;
  GSList *v;

  g_return_if_fail(ST_IS_STREAM_BAG(stream_bag));
  g_return_if_fail(fields != NULL);
  g_return_if_fail(values != NULL);
  
  for (f = *fields, v = *values; f && v; f = f->next, v = v->next)
    {
      STHandlerField *field = f->data;
      GValue *value = v->data;
      GValue current_value = { 0, };

      st_stream_bag_get_field(stream_bag, field, &current_value);

      if (! st_stream_properties_dialog_value_equal(value, &current_value))
	{
	  GValue *new_value;

	  new_value = sg_value_new(G_VALUE_TYPE(value));
	  g_value_copy(value, new_value);

	  new_fields = g_slist_append(new_fields, field);
	  new_values = g_slist_append(new_values, new_value);
	}

      g_value_unset(&current_value);
    }

  g_slist_free(*fields);

  g_slist_foreach(*values, (GFunc) sg_value_free, NULL);
  g_slist_free(*values);

  *fields = new_fields;
  *values = new_values;
}

static CancelInfo *
st_stream_properties_dialog_cancel_info_new (STStreamPropertiesDialog *dialog)
{
  CancelInfo *info = g_new0(CancelInfo, 1);
  GSList *l;

  g_return_val_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog), NULL);

  info->stream_bag = g_object_ref(dialog->priv->stream_bag);

  SG_LIST_FOREACH(l, dialog->priv->widgets)
    {
      STHandlerField *field = g_object_get_data(G_OBJECT(l->data), "field");

      if (ST_HANDLER_FIELD_IS_EDITABLE(field))
	{
	  GValue *value = g_new0(GValue, 1);

	  st_stream_bag_get_field(dialog->priv->stream_bag, field, value);

	  info->fields = g_slist_append(info->fields, field);
	  info->values = g_slist_append(info->values, value);
	}
    }

  return info;
}

static void
st_stream_properties_dialog_cancel_info_free (CancelInfo *info)
{
  g_return_if_fail(info != NULL);

  g_object_unref(info->stream_bag);
  g_slist_free(info->fields);

  g_slist_foreach(info->values, (GFunc) sg_value_free, NULL);
  g_slist_free(info->values);

  g_free(info);
}

static void
st_stream_properties_dialog_cancel_list_append (STStreamPropertiesDialog *dialog, CancelInfo *info)
{
  gboolean already_in_cancel_list = FALSE;
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));
  g_return_if_fail(info != NULL);

  SG_LIST_FOREACH(l, dialog->priv->cancel_list)
    if (info->stream_bag == ((CancelInfo *) l->data)->stream_bag)
      {
	already_in_cancel_list = TRUE;
	break;
      }

  if (already_in_cancel_list)
    st_stream_properties_dialog_cancel_info_free(info);
  else
    dialog->priv->cancel_list = g_slist_append(dialog->priv->cancel_list, info);
}

static void
st_stream_properties_dialog_cancel_list_free (STStreamPropertiesDialog *dialog)
{
  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  g_slist_foreach(dialog->priv->cancel_list, (GFunc) st_stream_properties_dialog_cancel_info_free, NULL);
  g_slist_free(dialog->priv->cancel_list);
  dialog->priv->cancel_list = NULL;
}

void
st_stream_properties_dialog_apply (STStreamPropertiesDialog *dialog)
{
  GSList *fields;
  GSList *values;
  CancelInfo *info;
  gboolean status;
  GError *err = NULL;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  if (! (dialog->priv->stream_bag
	 && st_handler_event_is_bound(dialog->priv->stream_bag->handler, ST_HANDLER_EVENT_STREAM_MODIFY)))
    return;

  info = st_stream_properties_dialog_cancel_info_new(dialog);

  st_stream_properties_dialog_get_fields_and_values(dialog, &fields, &values);
  st_stream_properties_dialog_optimize_fields_and_values(dialog->priv->stream_bag, &fields, &values);

  status = st_stream_bag_modify(dialog->priv->stream_bag, fields, values, &err);
  if (! status)
    {
      char *normalized;

      normalized = st_dialog_normalize(err->message);
      g_error_free(err);

      st_error_dialog(_("Unable to apply all modifications"), "%s", normalized);
      g_free(normalized);
    }

  g_slist_free(fields);

  g_slist_foreach(values, (GFunc) sg_value_free, NULL);
  g_slist_free(values);
  
  st_stream_properties_dialog_cancel_list_append(dialog, info);
  st_stream_properties_dialog_update_title(dialog);
}

void
st_stream_properties_dialog_cancel (STStreamPropertiesDialog *dialog)
{
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_PROPERTIES_DIALOG(dialog));

  if (! (dialog->priv->stream_bag
	 && st_handler_event_is_bound(dialog->priv->stream_bag->handler, ST_HANDLER_EVENT_STREAM_MODIFY)))
    return;

  SG_LIST_FOREACH(l, dialog->priv->cancel_list)
    {
      CancelInfo *info = l->data;
      GError *err = NULL;
      gboolean status;

      st_stream_properties_dialog_optimize_fields_and_values(info->stream_bag, &info->fields, &info->values);

      status = st_stream_bag_modify(info->stream_bag, info->fields, info->values, &err);
      if (! status)
	{
	  char *normalized;

	  normalized = st_dialog_normalize(err->message);
	  g_error_free(err);

	  st_error_dialog(_("Unable to cancel all modifications"), "%s", normalized);
	  g_free(normalized);
	}
    }

  st_stream_properties_dialog_cancel_list_free(dialog);
  st_stream_properties_dialog_update_title(dialog);
}
