/*
 * 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 <gdk/gdkkeysyms.h>
#include "sg-util.h"
#include "st-handler.h"
#include "st-handler-field.h"
#include "st-stream-api.h"
#include "st-stream-view.h"
#include "st-stream-menu-items.h"
#include "st-stock.h"
#include "st-shell.h"
#include "st-stream-bag.h"
#include "st-stream-store.h"

#define N_VISIBLE_STOCK_COLUMNS		1

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

enum {
  SELECTION_CHANGED,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_HANDLER
};
    
struct _STStreamViewPrivate
{
  STHandler		*handler;
  GtkWidget		*menu;
  STStreamMenuItems	*menu_items;
};
  
/*** variable declarations ***************************************************/

static GObjectClass *parent_class = NULL;
static unsigned int stream_view_signals[LAST_SIGNAL] = { 0 };

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

static void st_stream_view_init		(STStreamView		*view);
static void st_stream_view_class_init	(STStreamViewClass	*class);
static void st_stream_view_finalize	(GObject		*object);

static GObject *st_stream_view_constructor (GType type,
					    unsigned int n_construct_properties,
					    GObjectConstructParam *construct_params);
static void st_stream_view_set_property (GObject	*object,
					 unsigned int	prop_id,
					 const GValue	*value,
					 GParamSpec	*pspec);

static gboolean st_stream_view_column_drop_cb (GtkTreeView *view,
					       GtkTreeViewColumn *column,
					       GtkTreeViewColumn *prev_column,
					       GtkTreeViewColumn *next_column,
					       gpointer data);

static void st_stream_view_construct_columns		(STStreamView *view);
static void st_stream_view_column_add_field	(GtkTreeViewColumn *column,
						 STHandlerField    *field,
						 int               column_id);

static void st_stream_view_construct_column_menu  (STStreamView       *view,
						   GtkTreeViewColumn  *column);

static gboolean st_stream_view_column_popup_menu_h (GtkWidget *widget,
						    gpointer user_data);
static gboolean st_stream_view_column_button_press_event_h (GtkWidget *widget,
							    GdkEventButton *button,
							    gpointer user_data);

static void st_stream_view_column_notify_visible_h	(GObject      *object,
							 GParamSpec   *pspec,
							 gpointer     data);
static void st_stream_view_column_notify_width_h	(GObject      *object,
							 GParamSpec   *pspec,
							 gpointer     data);
static void st_stream_view_column_hide_activate_h	(GtkMenuItem  *item,
							 gpointer     data);
static void st_stream_view_column_show_all_activate_h	(GtkMenuItem  *item,
							 gpointer     data);
static void st_stream_view_column_stream_columns_activate_h (GtkMenuItem *item,
							     gpointer    data);
static void st_stream_view_selection_changed_h	(GtkTreeSelection     *selection,
						 gpointer	      user_data);
static void st_stream_view_columns_changed_h	(GtkTreeView          *view,
						 gpointer             user_data);
static void st_stream_view_destroy_h		(GtkObject            *object,
						 gpointer             user_data);

static void	st_stream_view_order_columns       (STStreamView      *view);
static gboolean	st_stream_view_get_first_selection (STStreamView      *view,
						    GtkTreeIter       *iter);

static gboolean	st_stream_view_model_iter_move     (GtkTreeModel      *model,
						    GtkTreeIter       *iter,
						    sGtkDirection     direction,
						    gboolean          wrap_around,
						    gboolean          *edge_hit);

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

GType
st_stream_view_get_type (void)
{
  static GType stream_view_type = 0;
  
  if (! stream_view_type)
    {
      static const GTypeInfo stream_view_info = {
	sizeof(STStreamViewClass),
	NULL,
	NULL,
	(GClassInitFunc) st_stream_view_class_init,
	NULL,
	NULL,
	sizeof(STStreamView),
	0,
	(GInstanceInitFunc) st_stream_view_init,
      };
      
      stream_view_type = g_type_register_static(GTK_TYPE_TREE_VIEW,
						"STStreamView",
						&stream_view_info,
						0);
    }

  return stream_view_type;
}

static void
st_stream_view_class_init (STStreamViewClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STStreamViewPrivate));

  object_class->finalize = st_stream_view_finalize;
  object_class->constructor = st_stream_view_constructor;
  object_class->set_property = st_stream_view_set_property;

  g_object_class_install_property(object_class,
				  PROP_HANDLER,
				  g_param_spec_pointer("handler",
						       NULL,
						       NULL,
						       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

  stream_view_signals[SELECTION_CHANGED] = g_signal_new("selection-changed",
							ST_TYPE_STREAM_VIEW,
							G_SIGNAL_RUN_LAST,
							G_STRUCT_OFFSET(STStreamViewClass, selection_changed),
							NULL,
							NULL,
							g_cclosure_marshal_VOID__VOID,
							G_TYPE_NONE,
							0);
}

static void
st_stream_view_init (STStreamView *view)
{
  view->priv = G_TYPE_INSTANCE_GET_PRIVATE(view, ST_TYPE_STREAM_VIEW, STStreamViewPrivate);
}

static void
st_stream_view_finalize (GObject *object)
{
  STStreamView *view = ST_STREAM_VIEW(object);

  if (view->priv->menu)
    gtk_widget_destroy(view->priv->menu);
  if (view->priv->menu_items)
    st_stream_menu_items_free(view->priv->menu_items);

  parent_class->finalize(object);
}

static GObject *
st_stream_view_constructor (GType type,
			    unsigned int n_construct_properties,
			    GObjectConstructParam *construct_params)
{
  GObject *object; 
  STStreamView *view;
  GtkTreeSelection *selection;

  object = parent_class->constructor(type, n_construct_properties, construct_params);
  view = ST_STREAM_VIEW(object);

  gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), TRUE);
  gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(view),
				      st_stream_store_search_equal_func,
				      NULL,
				      NULL);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

  gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
  g_signal_connect(selection,
		   "changed",
		   G_CALLBACK(st_stream_view_selection_changed_h),
		   view);

  st_stream_view_construct_columns(view);
  st_stream_view_order_columns(view);

  g_object_connect(view,
		   "signal::columns-changed", st_stream_view_columns_changed_h, NULL,
		   "signal::destroy", st_stream_view_destroy_h, NULL,
		   NULL);

  gtk_tree_view_set_column_drag_function(GTK_TREE_VIEW(view),
					 st_stream_view_column_drop_cb,
					 NULL,
					 NULL);

  return object;
}

static void
st_stream_view_set_property (GObject *object,
			     unsigned int prop_id,
			     const GValue *value,
			     GParamSpec *pspec)
{
  STStreamView *view = ST_STREAM_VIEW(object);

  switch (prop_id)
    {
    case PROP_HANDLER:
      view->priv->handler = g_value_get_pointer(value);
      break;

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

static gboolean
st_stream_view_column_drop_cb (GtkTreeView *view,
			       GtkTreeViewColumn *column,
			       GtkTreeViewColumn *prev_column,
			       GtkTreeViewColumn *next_column,
			       gpointer data)
{
  STHandlerField *prev_field;
  STHandlerField *next_field;

  prev_field = prev_column ? st_stream_view_column_get_field(prev_column) : NULL;
  next_field = next_column ? st_stream_view_column_get_field(next_column) : NULL;

  /*
   * Do not allow to move between two stock columns, or between a
   * stock column and an edge (we recognize stock columns because they
   * have no associated STHandlerField).
   */
  return prev_field || next_field;
}

static void
st_stream_view_construct_columns (STStreamView *view)
{
  GSList *l;
  int vi = ST_STREAM_STORE_N_STOCK_COLUMNS; /* visible iterator */
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GSList *pending_fields = NULL;

  g_return_if_fail(ST_IS_STREAM_VIEW(view));

  renderer = gtk_cell_renderer_pixbuf_new();
  column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
						    "stock-id", ST_STREAM_STORE_COLUMN_STOCK_ID,
						    "pixbuf", ST_STREAM_STORE_COLUMN_PIXBUF,
						    NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);

  SG_LIST_FOREACH(l, st_handler_get_fields(view->priv->handler))
    {
      STHandlerField *field = l->data;

      if (ST_HANDLER_FIELD_IS_VISIBLE(field))
	{
	  if (ST_HANDLER_FIELD_HAS_DEDICATED_COLUMN(field))
	    {
	      GSList *m;
	      int pi = vi - g_slist_length(pending_fields); /* pending iterator */
	      int width;
      
	      column = gtk_tree_view_column_new();

	      SG_LIST_FOREACH(m, pending_fields)
		st_stream_view_column_add_field(column, m->data, pi++);
	      g_slist_free(pending_fields);
	      pending_fields = NULL;

	      st_stream_view_column_add_field(column, field, vi);

	      gtk_tree_view_column_set_title(column, st_handler_field_get_label(field));
	      gtk_tree_view_column_set_visible(column, st_handler_field_get_user_visible(field));
	      gtk_tree_view_column_set_sort_column_id(column, vi);
	      gtk_tree_view_column_set_resizable(column, TRUE);
	      gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	      gtk_tree_view_column_set_reorderable(column, TRUE);

	      g_object_set_data(G_OBJECT(column), "field", field);

	      width = st_handler_field_get_width(field);
	      /* width == 0 only if loaded from an old config file */
	      gtk_tree_view_column_set_fixed_width(column, width > 0 ? width : ST_HANDLER_FIELD_DEFAULT_WIDTH);

	      gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
	      st_stream_view_construct_column_menu(view, column);

	      g_object_connect(column,
			       "signal::notify::visible", st_stream_view_column_notify_visible_h, NULL,
			       "signal::notify::width", st_stream_view_column_notify_width_h, NULL,
			       NULL);
	    }
	  else
	    pending_fields = g_slist_append(pending_fields, field);

	  vi++;
	}
    }

  g_slist_free(pending_fields);
  pending_fields = NULL;
}

static void
st_stream_view_column_add_field (GtkTreeViewColumn *column,
				 STHandlerField *field,
				 int column_id)
{
  GType type;
  GtkCellRenderer *renderer;
  const char *property;
  gboolean expand;

  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
  g_return_if_fail(field != NULL);

  type = st_handler_field_get_type(field);
  if (type == GDK_TYPE_PIXBUF)
    {
      renderer = gtk_cell_renderer_pixbuf_new();
      property = "pixbuf";
      expand = FALSE;
    }
  else
    {
      renderer = gtk_cell_renderer_text_new();
      property = "text";
      expand = TRUE;
    }

  gtk_tree_view_column_pack_start(column, renderer, expand);
  gtk_tree_view_column_set_attributes(column, renderer, property, column_id, NULL);
}

static void
st_stream_view_construct_column_menu (STStreamView *view,
				      GtkTreeViewColumn *column)
{
  GtkWidget *menu;
  GtkWidget *hide;
  GtkWidget *show_all;
  GtkWidget *separator;
  GtkWidget *stream_columns;
  
  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
  g_return_if_fail(GTK_IS_WIDGET(column->button));

  menu = gtk_menu_new();

  hide = gtk_image_menu_item_new_from_stock(ST_STOCK_HIDE_THIS_COLUMN, NULL);
  show_all = gtk_image_menu_item_new_from_stock(ST_STOCK_SHOW_ALL_COLUMNS, NULL);
  separator = gtk_separator_menu_item_new();
  stream_columns = gtk_image_menu_item_new_from_stock(ST_STOCK_STREAM_COLUMNS, NULL);

  gtk_widget_show(hide);
  gtk_widget_show(show_all);
  gtk_widget_show(separator);
  gtk_widget_show(stream_columns);
  
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), hide);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), show_all);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), stream_columns);

  g_object_set_data_full(G_OBJECT(column), "menu", menu, (GDestroyNotify) gtk_widget_destroy);

  g_signal_connect(hide,
		   "activate",
		   G_CALLBACK(st_stream_view_column_hide_activate_h),
		   column);
  g_signal_connect(show_all,
		   "activate",
		   G_CALLBACK(st_stream_view_column_show_all_activate_h),
		   view);
  g_signal_connect(stream_columns,
		   "activate",
		   G_CALLBACK(st_stream_view_column_stream_columns_activate_h),
		   view);

  g_object_connect(column->button,
		   "signal::popup-menu", st_stream_view_column_popup_menu_h, column,
		   "signal::button-press-event", st_stream_view_column_button_press_event_h, column,
		   NULL);
}

static gboolean
st_stream_view_column_popup_menu_h (GtkWidget *widget, gpointer user_data)
{
  GtkTreeViewColumn *column = user_data;
  GtkMenu *menu = g_object_get_data(G_OBJECT(column), "menu");

  gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
  
  return TRUE;			/* a menu was activated */
}

static gboolean
st_stream_view_column_button_press_event_h (GtkWidget *widget,
					    GdkEventButton *event,
					    gpointer user_data)
{
  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
    {
      GtkTreeViewColumn *column = user_data;
      GtkMenu *menu = g_object_get_data(G_OBJECT(column), "menu");

      /* workaround for http://bugzilla.gnome.org/show_bug.cgi?id=159640 */
      column->maybe_reordered = FALSE;

      gtk_menu_popup(menu,
		     NULL,
		     NULL,
		     NULL,
		     NULL,
		     event->button,
		     event->time);
    }

  return FALSE;			/* propagate event */
}

static void
st_stream_view_column_notify_visible_h (GObject *object,
					GParamSpec *pspec,
					gpointer data)
{
  STHandlerField *field = st_stream_view_column_get_field(GTK_TREE_VIEW_COLUMN(object));
  st_handler_field_set_user_visible(field, gtk_tree_view_column_get_visible(GTK_TREE_VIEW_COLUMN(object)));
}

static void
st_stream_view_column_notify_width_h (GObject *object,
				      GParamSpec *pspec,
				      gpointer data)
{
  STHandlerField *field = st_stream_view_column_get_field(GTK_TREE_VIEW_COLUMN(object));
  st_handler_field_set_width(field, gtk_tree_view_column_get_width(GTK_TREE_VIEW_COLUMN(object)));
}

static void
st_stream_view_column_hide_activate_h (GtkMenuItem *item, gpointer data)
{
  GtkTreeViewColumn *column = data;

  gtk_tree_view_column_set_visible(column, FALSE);
}

static void
st_stream_view_column_show_all_activate_h (GtkMenuItem *item, gpointer data)
{
  STStreamView *view = data;
  GtkTreeViewColumn *column;
  int i = N_VISIBLE_STOCK_COLUMNS;

  while ((column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), i++)))
    gtk_tree_view_column_set_visible(column, TRUE);
}

static void
st_stream_view_column_stream_columns_activate_h (GtkMenuItem *item, gpointer data)
{
  st_shell_present_stream_columns(st_shell);
}

static void
st_stream_view_selection_changed_h (GtkTreeSelection *selection,
				    gpointer user_data)
{
  STStreamView *view = user_data;
  
  g_signal_emit(view, stream_view_signals[SELECTION_CHANGED], 0);
}

static void
st_stream_view_columns_changed_h (GtkTreeView *view, gpointer user_data)
{
  GtkTreeViewColumn *column;
  int i = N_VISIBLE_STOCK_COLUMNS;
  int pos = 0;
  
  while ((column = gtk_tree_view_get_column(view, i++)))
    {
      STHandlerField *field = st_stream_view_column_get_field(column);
      st_handler_field_set_position(field, pos++);
    }
}

static void
st_stream_view_destroy_h (GtkObject *object, gpointer user_data)
{
  g_signal_handlers_disconnect_by_func(object, st_stream_view_columns_changed_h, NULL);
}

static void
st_stream_view_order_columns (STStreamView *view)
{
  GList *columns;
  GList *l;

  g_return_if_fail(ST_IS_STREAM_VIEW(view));

  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
  SG_LIST_FOREACH(l, g_list_nth(columns, N_VISIBLE_STOCK_COLUMNS))
    {
      GtkTreeViewColumn *column = l->data;
      STHandlerField *field;
      int position;

      field = st_stream_view_column_get_field(column);
      position = st_handler_field_get_position(field);

      if (position != -1)
	{
	  GtkTreeViewColumn *after;

	  after = gtk_tree_view_get_column(GTK_TREE_VIEW(view), position + N_VISIBLE_STOCK_COLUMNS - 1);
	  if (after != column)
	    gtk_tree_view_move_column_after(GTK_TREE_VIEW(view), column, after);
	}
    }
  g_list_free(columns);
}

GtkWidget *
st_stream_view_new (STHandler *handler)
{
  return g_object_new(ST_TYPE_STREAM_VIEW,
		      "handler", handler,
		      NULL);
}

STHandler *
st_stream_view_get_handler (STStreamView *view)
{
  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), NULL);

  return view->priv->handler;
}

void
st_stream_view_update_sensitivity (STStreamView *view)
{
  g_return_if_fail(ST_IS_STREAM_VIEW(view));

  if (view->priv->menu_items)
    st_stream_menu_items_update_sensitivity(view->priv->menu_items);
}

/*
 * Select the iter belonging to stream BAG, and let that trigger the
 * emission of a selection-changed signal.
 */
void
st_stream_view_select_stream (STStreamView *view, STStreamBag *bag)
{
  GtkTreeSelection *selection;

  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(ST_IS_STREAM_BAG(bag));

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

  gtk_tree_selection_unselect_all(selection);
  gtk_tree_selection_select_iter(selection, &bag->iter);
}

void
st_stream_view_present_stream (STStreamView *view, STStreamBag *bag)
{
  GtkTreeModel *model;
  GtkTreePath *path;
  
  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(ST_IS_STREAM_BAG(bag));

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  path = gtk_tree_model_get_path(model, &bag->iter);
  gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view), path, NULL, FALSE, 0, 0);
  gtk_tree_path_free(path);
}

gboolean
st_stream_view_can_select_previous (STStreamView *view)
{
  GtkTreeIter iter;
  gboolean can = FALSE;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), FALSE);

  if (st_stream_view_get_first_selection(view, &iter))
    {
      GtkTreeModel *model;
      
      model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
      can = sgtk_tree_model_iter_prev(model, &iter);
    }

  return can;
}

void
st_stream_view_select_previous (STStreamView *view)
{
  GtkTreeModel *model;
  GtkTreeSelection *selection;
  GtkTreeIter iter;
  GtkTreePath *path;
  gboolean status;
  
  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(st_stream_view_can_select_previous(view));
  
  status = st_stream_view_get_first_selection(view, &iter);
  g_return_if_fail(status == TRUE);

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  path = gtk_tree_model_get_path(model, &iter);
  status = gtk_tree_path_prev(path);
  g_return_if_fail(status == TRUE);

  status = gtk_tree_model_get_iter(model, &iter, path);
  g_return_if_fail(status == TRUE);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
  gtk_tree_selection_unselect_all(selection);
  gtk_tree_selection_select_iter(selection, &iter);
  gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view), path, NULL, FALSE, 0, 0);

  gtk_tree_path_free(path);
}

gboolean
st_stream_view_can_select_next (STStreamView *view)
{
  GtkTreeModel *model;
  GtkTreeIter iter;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), FALSE);

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

  return st_stream_view_get_first_selection(view, &iter)
    && gtk_tree_model_iter_next(model, &iter);
}

void
st_stream_view_select_next (STStreamView *view)
{
  GtkTreeModel *model;
  GtkTreeSelection *selection;
  GtkTreeIter iter;
  GtkTreePath *path;
  gboolean status;

  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(st_stream_view_can_select_next(view));
  
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

  status = st_stream_view_get_first_selection(view, &iter);
  g_return_if_fail(status == TRUE);
  status = gtk_tree_model_iter_next(model, &iter);
  g_return_if_fail(status == TRUE);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
  gtk_tree_selection_unselect_all(selection);
  gtk_tree_selection_select_iter(selection, &iter);

  path = gtk_tree_model_get_path(model, &iter);
  gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view), path, NULL, FALSE, 0, 0);
  gtk_tree_path_free(path);
}

static gboolean
st_stream_view_get_first_selection (STStreamView *view, GtkTreeIter *iter)
{
  GtkTreeSelection *selection;
  GtkTreeModel *model;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), FALSE);
  g_return_val_if_fail(iter != NULL, FALSE);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

  if (model)
    {
      GList *paths;
      
      paths = gtk_tree_selection_get_selected_rows(selection, NULL);
      if (paths)
	{
	  GtkTreePath *path = paths->data;
	  gboolean status;
	  
	  status = gtk_tree_model_get_iter(model, iter, path);
	  g_return_val_if_fail(status == TRUE, FALSE);

	  g_list_foreach(paths, (GFunc) gtk_tree_path_free, NULL);
	  g_list_free(paths);

	  return TRUE;
	}
    }

  return FALSE;
}

/*
 * This owns ITEMS. The caller must not free it.
 */
void
st_stream_view_set_menu_items (STStreamView *view, STStreamMenuItems *items)
{
  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  g_return_if_fail(view->priv->menu_items == NULL);
  g_return_if_fail(items != NULL);

  view->priv->menu = gtk_menu_new();
  view->priv->menu_items = items;
  
  st_stream_menu_items_insert_into_shell(view->priv->menu_items, GTK_MENU_SHELL(view->priv->menu), 0);
  sgtk_widget_set_popup_menu(GTK_WIDGET(view), GTK_MENU(view->priv->menu));
}

void
st_stream_view_set_store (STStreamView *view, STStreamStore *store)
{
  g_return_if_fail(ST_IS_STREAM_VIEW(view));
  
  gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));

  g_signal_emit(view, stream_view_signals[SELECTION_CHANGED], 0);
}

GSList *
st_stream_view_get_selected_streams (STStreamView *view)
{
  GtkTreeModel *model;
  GSList *selected_streams = NULL;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), NULL);

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  if (model)
    {
      GtkTreeSelection *selection;
      GList *paths;
      
      selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
      paths = gtk_tree_selection_get_selected_rows(selection, NULL);
      if (paths)
	{
	  GList *l;

	  SG_LIST_FOREACH(l, paths)
	    {
	      GtkTreePath *path = l->data;
	      GtkTreeIter iter;
	      gboolean status;
	      STStreamBag *bag;
	  
	      status = gtk_tree_model_get_iter(model, &iter, path);
	      g_return_val_if_fail(status == TRUE, NULL);

	      gtk_tree_model_get(model, &iter, ST_STREAM_STORE_COLUMN_BAG, &bag, -1);
	      selected_streams = g_slist_append(selected_streams, bag);

	      gtk_tree_path_free(path);
	    }

	  g_list_free(paths);
	}
    }

  return selected_streams;
}

gboolean
st_stream_view_has_selected_streams (STStreamView *view)
{
  GSList *selected_streams;
  gboolean has;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), FALSE);

  selected_streams = st_stream_view_get_selected_streams(view);
  has = selected_streams != NULL;
  sg_objects_free(selected_streams);

  return has;
}

void
st_stream_view_select_all (STStreamView *view)
{
  GtkTreeSelection *selection;

  g_return_if_fail(ST_IS_STREAM_VIEW(view));

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
  gtk_tree_selection_select_all(selection);
}

static gboolean
st_stream_view_model_iter_move (GtkTreeModel *model,
				GtkTreeIter *iter,
				sGtkDirection direction,
				gboolean wrap_around,
				gboolean *edge_hit)
{
  gboolean valid;

  g_return_val_if_fail(GTK_IS_TREE_MODEL(model), FALSE);
  g_return_val_if_fail(iter != NULL, FALSE);

  valid = sgtk_tree_model_iter_move(model, iter, direction);
  if (! valid && wrap_around)
    {
      if (edge_hit)
	*edge_hit = TRUE;

      switch (direction)
	{
	case SGTK_PREVIOUS:
	  valid = sgtk_tree_model_get_iter_last(model, iter);
	  break;
	  
	case SGTK_NEXT:
	  valid = gtk_tree_model_get_iter_first(model, iter);
	  break;
	  
	default:
	  g_return_val_if_reached(FALSE);
	}
    }

  return valid;
}

gboolean
st_stream_view_find (STStreamView *view,
		     sGtkDirection direction,
		     const char *token,
		     gboolean case_sensitive,
		     gboolean wrap_around)
{
  GtkTreeModel *model;
  gboolean found = FALSE;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), FALSE);
  g_return_val_if_fail(token != NULL, FALSE);

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
  if (model)
    {
      GSList *selected_streams;
      gboolean valid;
      GtkTreeIter iter;
      gboolean edge_hit = FALSE;

      selected_streams = st_stream_view_get_selected_streams(view);
      if (selected_streams)
	{
	  GSList *last = g_slist_last(selected_streams);
	  STStreamBag *bag = last->data;

	  iter = bag->iter;
	  valid = st_stream_view_model_iter_move(model, &iter, direction, wrap_around, &edge_hit);

	  sg_objects_free(selected_streams);
	}
      else
	valid = gtk_tree_model_get_iter_first(model, &iter);

      while (valid)
	{
	  STStreamBag *bag;

	  gtk_tree_model_get(model, &iter, ST_STREAM_STORE_COLUMN_BAG, &bag, -1);
	  found = st_stream_bag_matches(bag, token, case_sensitive);
	  if (found)
	    {
	      st_stream_view_select_stream(view, bag);
	      st_stream_view_present_stream(view, bag);
	    }
	  g_object_unref(bag);

	  if (found)
	    break;

	  valid = st_stream_view_model_iter_move(model, &iter, direction, wrap_around && ! edge_hit, &edge_hit);
	}
    }

  return found;
}

GSList *
st_stream_view_get_columns (STStreamView *view)
{
  GSList *columns = NULL;
  int i = N_VISIBLE_STOCK_COLUMNS;
  GtkTreeViewColumn *column;

  g_return_val_if_fail(ST_IS_STREAM_VIEW(view), NULL);

  while ((column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), i++)))
    columns = g_slist_append(columns, column);

  return columns;
}

STHandlerField *
st_stream_view_column_get_field (GtkTreeViewColumn *column)
{
  g_return_val_if_fail(GTK_IS_TREE_VIEW_COLUMN(column), NULL);

  return g_object_get_data(G_OBJECT(column), "field");
}
