/*
 * Copyright (c) 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 <unistd.h>
#include <errno.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "sg-util.h"
#include "sgtk-hig.h"
#include "sgtk-util.h"
#include "st-shell.h"
#include "st-browser-tab.h"
#include "st-settings.h"
#include "sgtk-message-dialog.h"
#include "sgtk-find-dialog.h"
#include "st-dialog-api.h"
#include "st-action.h"
#include "st-about-dialog.h"
#include "st-stream-properties-dialog.h"
#include "st-preferences-dialog.h"
#include "st-stock.h"
#include "st-stream-menu-items.h"
#include "st-handlers.h"
#include "st-thread.h"
#include "st-link.h"
#include "st-statusbar.h"
#include "st-preselections.h"
#include "st-main.h"
#include "st-browser-tab-label.h"
#include "st-stream-store.h"
#include "st-util.h"
#include "st-stream-columns-dialog.h"
#include "st-session.h"

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

#define FIND_NAVIGATION_SENSITIVE \
  (st_settings.find_token && *st_settings.find_token)

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

struct _STShellPrivate
{
  GtkWidget			*window;
  GtkAccelGroup			*accel_group;
  GtkWidget			*box;

  /* menubar */
  GtkItemFactory		*factory;
  GtkWidget			*menubar;
  STStreamMenuItems		*stream_items;
  GtkWidget			*select_all_item;
  GtkWidget			*find_item;
  GtkWidget			*find_next_item;
  GtkWidget			*find_previous_item;
  GtkWidget			*search_in_all_categories_item;
  GtkWidget			*stop_item;
  GtkWidget			*reload_item;
  GtkWidget			*view_tab_icons_item;
  GtkWidget			*directories_menu;
  GtkWidget			*directory_preferences_item;
  GtkWidget			*directory_homepage_item;
  GtkWidget			*previous_directory_item;
  GtkWidget			*next_directory_item;
  GtkWidget			*move_directory_left_item;
  GtkWidget			*move_directory_right_item;
  GSList			*directory_items;

  /* toolbar */
  GtkWidget			*toolbar;
  GtkWidget			*tune_in_button;
  GtkWidget			*record_button;
  GtkWidget			*browse_button;
  GtkWidget			*stop_button;
  GtkWidget			*reload_button;
  GtkWidget			*link;

  /* notebook */
  GtkWidget			*notebook;
  unsigned int			switch_page_hid;

  /* statusbox */
  GtkWidget			*statusbox;

  GSList			*tabs;
  char				*find_token;
  gboolean			find_case_sensitive;

  STBrowserTabStreamTask	*tune_in_task;

  gboolean			restart;

  /* dialogs */
  GtkWidget			*stream_properties;
  GtkWidget			*stream_columns;
  GtkWidget			*find;
  GtkWidget			*preferences;
  GtkWidget			*about;

  /* fullscreen mode */
  gboolean			fullscreen;
  GtkWidget			*leave_fullscreen_popup;
  gboolean			leave_fullscreen_popup_visible;
  unsigned int			leave_fullscreen_timeout_id;
  unsigned int			screen_size_changed_hid;
  unsigned int			key_press_event_hid;
};

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

static GObjectClass *parent_class = NULL;
STShell *st_shell = NULL;

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

static void st_shell_class_init (STShellClass *class);
static void st_shell_init (STShell *shell);
static void st_shell_finalize (GObject *object);

static void st_shell_make_window (STShell *shell);
static void st_shell_make_menubar (STShell *shell);
static void st_shell_menubar_make_directory_items (STShell *shell);
static void st_shell_menubar_select_directory_item (STShell *shell);
static void st_shell_make_toolbar (STShell *shell);
static void st_shell_make_notebook (STShell *shell);
static void st_shell_make_statusbox (STShell *shell);

static void st_shell_menubar_init_view_item (STShell *shell,
					     const char *path,
					     gboolean *var);
static void st_shell_menubar_view_toggled_h (GtkCheckMenuItem *item,
					     gpointer user_data);
static void st_shell_menubar_init_toolbar_style_item (STShell *shell,
						      const char *path,
						      int style);
static void st_shell_menubar_toolbar_style_toggled_h (GtkCheckMenuItem *item,
						      gpointer user_data);
static void st_shell_menubar_init_toolbar_size_item (STShell *shell,
						     const char *path,
						     int size);
static void st_shell_menubar_toolbar_size_toggled_h (GtkCheckMenuItem *item,
						     gpointer user_data);

static void st_shell_menubar_directory_toggled_h (GtkCheckMenuItem *item,
						  gpointer user_data);
static gboolean st_shell_window_state_event_h (GtkWidget *widget,
					       GdkEventWindowState *event,
					       gpointer user_data);
static gboolean st_shell_window_delete_event_h (GtkWidget *widget,
						GdkEvent *event,
						gpointer user_data);

static void st_shell_fullscreen (STShell *shell);
static void st_shell_unfullscreen (STShell *shell);
static void st_shell_leave_fullscreen_button_clicked_h (GtkButton *button,
							gpointer user_data);
static gboolean st_shell_leave_fullscreen_timeout_cb (gpointer data);
static gboolean st_shell_leave_fullscreen_enter_notify_event_h (GtkWidget *widget,
								GdkEventCrossing *event,
								gpointer user_data);
static gboolean st_shell_leave_fullscreen_leave_notify_event_h (GtkWidget *widget,
								GdkEventCrossing *event,
								gpointer user_data);
static void st_shell_update_leave_fullscreen_popup_position (STShell *shell);
static void st_shell_screen_size_changed_h (GdkScreen *screen, gpointer user_data);
static gboolean st_shell_window_key_press_event_h (GtkWidget *widget,
						   GdkEventKey *event,
						   gpointer user_data);

static void st_shell_toolbar_button_clicked_h (GtkButton *button,
					       gpointer user_data);

static void st_shell_tab_label_drag_begin_h (GtkWidget *widget,
					     GdkDragContext *drag_context,
					     gpointer user_data);
static void st_shell_tab_label_drag_end_h (GtkWidget *widget,
					   GdkDragContext *drag_context,
					   gpointer user_data);
static int st_shell_handlers_compare (gconstpointer a,
				      gconstpointer b,
				      gpointer user_data);
static void st_shell_tab_label_drag_motion_h (GtkWidget *widget,
					      GdkDragContext *drag_context,
					      int x,
					      int y,
					      unsigned int _time,
					      gpointer user_data);

static void st_shell_switch_page_h (GtkNotebook *notebook,
				    GtkNotebookPage *page,
				    unsigned int page_num,
				    gpointer user_data);
static void st_shell_tab_selected (STShell *shell);
static void st_shell_tab_moved (STShell *shell);

static void st_shell_update_visibility (STShell *shell);
static void st_shell_update_toolbar_style (STShell *shell);
static void st_shell_update_toolbar_size (STShell *shell);
static void st_shell_update_title (STShell *shell);
static void st_shell_update_sensitivity (STShell *shell);

static STBrowserTab *st_shell_get_selected_tab (STShell *shell);

static gboolean st_shell_can_stop (STShell *shell);
static void st_shell_stop (STShell *shell);

static gboolean st_shell_can_reload (STShell *shell);
static void st_shell_reload (STShell *shell);

static gboolean st_shell_can_select_all (STShell *shell);
static void st_shell_select_all (STShell *shell);

static gboolean st_shell_can_find (STShell *shell);
static void st_shell_find (STShell *shell);

static void st_shell_find_notify_h (GObject *object,
				    GParamSpec *pspec,
				    gpointer user_data);
static void st_shell_find_response_h (GtkDialog *dialog,
				      int response,
				      gpointer data);

static gboolean st_shell_can_find_next (STShell *shell);
static void st_shell_find_next (STShell *shell);

static gboolean st_shell_can_find_previous (STShell *shell);
static void st_shell_find_previous (STShell *shell);

static void st_shell_find_real (STShell *shell,
				sGtkDirection direction,
				gboolean wrap_around);

static gboolean st_shell_can_search_in_all_categories (STShell *shell);
static void st_shell_search_in_all_categories (STShell *shell);

static void st_shell_stream_properties_response_h (GtkDialog *dialog,
						   int response,
						   gpointer data);

static void st_shell_present_preferences (STShell *shell);
static void st_shell_present_about (STShell *shell);
static void st_shell_dialog_response_h (GtkDialog *dialog,
					int response,
					gpointer data);

static gboolean st_shell_can_present_directory_preferences (STShell *shell);
static void st_shell_present_directory_preferences (STShell *shell);

static gboolean st_shell_can_visit_directory_homepage (STShell *shell);
static void st_shell_visit_directory_homepage (STShell *shell);

static void st_shell_stream_properties_update_stream (STShell *shell);

static gboolean st_shell_can_select_previous_tab (STShell *shell);
static void st_shell_select_previous_tab (STShell *shell);

static gboolean st_shell_can_select_next_tab (STShell *shell);
static void st_shell_select_next_tab (STShell *shell);

static gboolean st_shell_can_move_tab (STShell *shell, int direction);
static void st_shell_move_tab (STShell *shell, int direction);

static gboolean st_shell_can_move_tab_left (STShell *shell);
static void st_shell_move_tab_left (STShell *shell);

static gboolean st_shell_can_move_tab_right (STShell *shell);
static void st_shell_move_tab_right (STShell *shell);

static void st_shell_help (STShell *shell);
static void st_shell_homepage (STShell *shell);

static void st_shell_new_preselection (STShell *shell);

static STBrowserTab *st_shell_get_tab_with_handler (STShell *shell,
						    STHandler *handler);
static void st_shell_select_tab (STShell *shell,
				 STBrowserTab *tab,
				 gboolean force_update);

static void st_shell_set_statusbar_of_tab (STShell *shell, STBrowserTab *tab);

static void st_shell_category_selection_changed_h (STCategoryView *view,
						   gpointer user_data);
static void st_shell_stream_selection_changed_h (STStreamView *view,
						 gpointer user_data);
static void st_shell_stream_activated_h (GtkTreeView *treeview,
					 GtkTreePath *arg1,
					 GtkTreeViewColumn *arg2,
					 gpointer user_data);
static void st_shell_tab_notify_running_h (GObject *object,
					   GParamSpec *pspec,
					   gpointer user_data);
static void st_shell_tab_stream_task_added_h (STBrowserTab *tab,
					      STBrowserTabStreamTask *task,
					      gpointer user_data);
static void st_shell_tab_stream_task_removed_h (STBrowserTab *tab,
						STBrowserTabStreamTask *task,
						gpointer user_data);

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

GType
st_shell_get_type (void)
{
  static GType shell_type = 0;
  
  if (! shell_type)
    {
      static const GTypeInfo shell_info = {
	sizeof(STShellClass),
	NULL,
	NULL,
	(GClassInitFunc) st_shell_class_init,
	NULL,
	NULL,
	sizeof(STShell),
	0,
	(GInstanceInitFunc) st_shell_init,
      };
      
      shell_type = g_type_register_static(G_TYPE_OBJECT,
					  "STShell",
					  &shell_info,
					  0);
    }
  
  return shell_type;
}

static void
st_shell_class_init (STShellClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STShellPrivate));

  object_class->finalize = st_shell_finalize;
}

static void
st_shell_init (STShell *shell)
{
  STHandler *handler = NULL;

  g_return_if_fail(st_shell == NULL);
  st_shell = shell;

  shell->priv = G_TYPE_INSTANCE_GET_PRIVATE(shell, ST_TYPE_SHELL, STShellPrivate);

  st_shell_make_window(shell);
  st_shell_make_menubar(shell);
  st_shell_make_toolbar(shell);
  st_shell_make_notebook(shell);
  st_shell_make_statusbox(shell);

  st_shell_update_visibility(shell);
  st_shell_update_toolbar_style(shell);
  st_shell_update_toolbar_size(shell);

  /* select a tab and focus its stream view */

  if (st_settings.selected_handler_name)
    handler = st_handlers_find_by_name(st_settings.selected_handler_name);
  if (! handler && st_handlers_list)
    handler = st_handlers_list->data; /* fallback */
  if (handler)
    {
      STBrowserTab *tab;
      
      tab = st_shell_get_tab_with_handler(shell, handler);
      st_shell_select_tab(shell, tab, TRUE);
      gtk_widget_grab_focus(GTK_WIDGET(tab->stream_view));
    }

  /* initialize the sensitivity */

  st_shell_update_sensitivity(shell);
}

static void
st_shell_finalize (GObject *object)
{
  STShell *shell = ST_SHELL(object);
  GSList *l;
  STBrowserTab *selected_tab;

  /* stop everything */
  
  SG_LIST_FOREACH(l, shell->priv->tabs)
    if (st_browser_tab_can_stop(l->data))
      st_browser_tab_stop(l->data);

  /* store some settings */
  
  selected_tab = st_shell_get_selected_tab(shell);
  if (selected_tab)
    {
      g_free(st_settings.selected_handler_name);
      st_settings.selected_handler_name = g_strdup(st_handler_get_name(selected_tab->handler));
    }

  /* destroy the main window before the final processing (HIG 2.0) */

  gtk_widget_destroy(shell->priv->window);

  while (gtk_events_pending())
    gtk_main_iteration();

  /* save session and restart or quit */

  st_session_save();

  if (shell->priv->restart)
    {
      char *secondary;
      char *normalized;

      execvp(st_main_argv[0], st_main_argv);

      secondary = g_strdup_printf(_("Unable to restart streamtuner: %s"), g_strerror(errno));
      normalized = st_dialog_normalize(secondary);
      g_free(secondary);

      st_error_dialog(_("A fatal error has occurred"), "%s", normalized);
      g_free(normalized);
    }
  
  gtk_main_quit();

  parent_class->finalize(object);
}

static void
st_shell_make_window (STShell *shell)
{
  shell->priv->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_object_add_weak_pointer(G_OBJECT(shell->priv->window), (gpointer *) &shell->priv->window);

  shell->priv->accel_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(shell->priv->window), shell->priv->accel_group);

  st_window_set_icon(GTK_WINDOW(shell->priv->window));

  shell->priv->box = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(shell->priv->window), shell->priv->box);
  gtk_widget_show(shell->priv->box);

  sgtk_window_link_size(GTK_WINDOW(shell->priv->window),
			&st_settings.main_window_width,
			&st_settings.main_window_height);

  g_object_connect(shell->priv->window,
		   "signal::window-state-event", st_shell_window_state_event_h, shell,
		   "signal::delete-event", st_shell_window_delete_event_h, shell,
		   NULL);
}

static gboolean
st_shell_window_state_event_h (GtkWidget *widget,
			       GdkEventWindowState *event,
			       gpointer user_data)
{
  STShell *shell = user_data;

  if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
    {
      if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN)
	st_shell_fullscreen(shell);
      else
	st_shell_unfullscreen(shell);
    }

  return FALSE;			/* propagate event */
}

static gboolean
st_shell_window_delete_event_h (GtkWidget *widget,
				GdkEvent *event,
				gpointer user_data)
{
  STShell *shell = user_data;

  g_object_unref(shell);

  return TRUE;			/* do not propagate event */
}

static void
st_shell_fullscreen (STShell *shell)
{
  GtkWidget *button;

  g_return_if_fail(ST_IS_SHELL(shell));

  shell->priv->fullscreen = TRUE;
  shell->priv->leave_fullscreen_popup = gtk_window_new(GTK_WINDOW_POPUP);
  
  button = gtk_button_new_from_stock(ST_STOCK_LEAVE_FULLSCREEN);
  gtk_container_add(GTK_CONTAINER(shell->priv->leave_fullscreen_popup), button);
  gtk_widget_show(button);

  g_signal_connect(button, "clicked", G_CALLBACK(st_shell_leave_fullscreen_button_clicked_h), shell);

  st_shell_update_visibility(shell);
  st_shell_update_toolbar_style(shell);

  shell->priv->leave_fullscreen_popup_visible = TRUE;
  st_shell_update_leave_fullscreen_popup_position(shell);
  gtk_widget_show(shell->priv->leave_fullscreen_popup);

  shell->priv->screen_size_changed_hid
    = g_signal_connect(gdk_screen_get_default(),
		       "size-changed",
		       G_CALLBACK(st_shell_screen_size_changed_h),
		       shell);
  shell->priv->key_press_event_hid
    = g_signal_connect(shell->priv->window,
		       "key-press-event",
		       G_CALLBACK(st_shell_window_key_press_event_h),
		       shell);

  g_object_connect(shell->priv->leave_fullscreen_popup,
		   "signal::enter-notify-event", st_shell_leave_fullscreen_enter_notify_event_h, shell,
		   "signal::leave-notify-event", st_shell_leave_fullscreen_leave_notify_event_h, shell,
		   NULL);

  shell->priv->leave_fullscreen_timeout_id = g_timeout_add(5000, st_shell_leave_fullscreen_timeout_cb, shell);
}

static void
st_shell_unfullscreen (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  g_signal_handler_disconnect(gdk_screen_get_default(), shell->priv->screen_size_changed_hid);
  g_signal_handler_disconnect(shell->priv->window, shell->priv->key_press_event_hid);

  if (shell->priv->leave_fullscreen_timeout_id)
    {
      g_source_remove(shell->priv->leave_fullscreen_timeout_id);
      shell->priv->leave_fullscreen_timeout_id = 0;
    }

  gtk_widget_destroy(shell->priv->leave_fullscreen_popup);
  shell->priv->leave_fullscreen_popup = NULL;
  shell->priv->fullscreen = FALSE;

  st_shell_update_visibility(shell);
  st_shell_update_toolbar_style(shell);
}

static void
st_shell_leave_fullscreen_button_clicked_h (GtkButton *button,
					    gpointer user_data)
{
  STShell *shell = user_data;

  gtk_window_unfullscreen(GTK_WINDOW(shell->priv->window));
}

static gboolean
st_shell_leave_fullscreen_timeout_cb (gpointer data)
{
  STShell *shell = data;

  GDK_THREADS_ENTER();

  shell->priv->leave_fullscreen_popup_visible = FALSE;
  st_shell_update_leave_fullscreen_popup_position(shell);
  shell->priv->leave_fullscreen_timeout_id = 0;

  GDK_THREADS_LEAVE();
  
  return FALSE;			/* remove timeout */
}

static gboolean
st_shell_leave_fullscreen_enter_notify_event_h (GtkWidget *widget,
						GdkEventCrossing *event,
						gpointer user_data)
{
  STShell *shell = user_data;

  if (shell->priv->leave_fullscreen_timeout_id)
    {
      g_source_remove(shell->priv->leave_fullscreen_timeout_id);
      shell->priv->leave_fullscreen_timeout_id = 0;
    }

  shell->priv->leave_fullscreen_popup_visible = TRUE;
  st_shell_update_leave_fullscreen_popup_position(shell);

  return FALSE;			/* propagate event */
}

static gboolean
st_shell_leave_fullscreen_leave_notify_event_h (GtkWidget *widget,
						GdkEventCrossing *event,
						gpointer user_data)
{
  STShell *shell = user_data;

  if (shell->priv->leave_fullscreen_timeout_id)
    {
      g_source_remove(shell->priv->leave_fullscreen_timeout_id);
      shell->priv->leave_fullscreen_timeout_id = 0;
    }

  shell->priv->leave_fullscreen_timeout_id = g_timeout_add(5000, st_shell_leave_fullscreen_timeout_cb, shell);

  return FALSE;			/* propagate event */
}

static void
st_shell_update_leave_fullscreen_popup_position (STShell *shell)
{
  GdkScreen *screen;
  int monitor_num;
  GdkRectangle screen_rect;
  int popup_width;
  int popup_height;
  int y;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(shell->priv->leave_fullscreen_popup != NULL);

  screen = gdk_screen_get_default();
  monitor_num = gdk_screen_get_monitor_at_window(screen, shell->priv->window->window);
  gdk_screen_get_monitor_geometry(screen, monitor_num, &screen_rect);
  gtk_window_get_size(GTK_WINDOW(shell->priv->leave_fullscreen_popup), &popup_width, &popup_height);

  y = screen_rect.y;
  if (! shell->priv->leave_fullscreen_popup_visible)
    y -= popup_height - 2;

  gtk_window_move(GTK_WINDOW(shell->priv->leave_fullscreen_popup), screen_rect.width - popup_width, y);
}

static void
st_shell_screen_size_changed_h (GdkScreen *screen, gpointer user_data)
{
  STShell *shell = user_data;

  st_shell_update_leave_fullscreen_popup_position(shell);
}

static gboolean
st_shell_window_key_press_event_h (GtkWidget *widget,
				   GdkEventKey *event,
				   gpointer user_data)
{
  STShell *shell = user_data;
  static unsigned int escape_key = 0;
  static GdkModifierType escape_mods;

  if (! escape_key)
    gtk_accelerator_parse("Escape", &escape_key, &escape_mods);

  if (event->keyval == escape_key && (escape_mods & event->state) == escape_mods)
    {
      gtk_window_unfullscreen(GTK_WINDOW(shell->priv->window));
      return TRUE;	/* Escape is the shortcut for "Stop", block it */
    }
  else
    return FALSE;	/* propagate event */
}

static void
st_shell_make_menubar (STShell *shell)
{
  /* keep in sync with the stock items in st-stock.c */
  static GtkItemFactoryEntry entries[] = {
    {
      N_("/_Stream"),				NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/Stream/_New Preselection..."),	"<Control>N",
      st_shell_new_preselection,		0,
      "<StockItem>",				ST_STOCK_NEW_PRESELECTION
    },
    {
      "/Stream/separator1",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      "/Stream/separator2",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Stream/_Quit"),			NULL,
      g_object_unref,				0,
      "<StockItem>",				GTK_STOCK_QUIT
    },
    {
      N_("/_Edit"),				NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/Edit/Select _All"),			"<Control>A",
      st_shell_select_all,			0,
      "<StockItem>",				ST_STOCK_SELECT_ALL
    },
    {
      "/Edit/separator1",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Edit/_Find..."),			NULL,
      st_shell_find,				0,
      "<StockItem>",				GTK_STOCK_FIND
    },
    {
      N_("/Edit/Find Ne_xt"),			"<Control>G",
      st_shell_find_next,			0,
      "<Item>",					NULL
    },
    {
      N_("/Edit/Find Pre_vious"),		"<Shift><Control>G",
      st_shell_find_previous,			0,
      "<Item>",					NULL
    },
    {
      "/Edit/separator2",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Edit/_Search in All Categories..."),	"<Shift><Control>S",
      st_shell_search_in_all_categories,	0,
      "<Item>",					NULL
    },
    {
      "/Edit/separator3",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Edit/_Preferences"),			NULL,
      st_shell_present_preferences,		0,
      "<StockItem>",				GTK_STOCK_PREFERENCES
    },
    {
      N_("/_View"),				NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/View/_Toolbar"),			NULL,
      NULL,					0,
      "<CheckItem>",				NULL
    },
    {
      N_("/View/Ta_bs"),			NULL,
      NULL,					0,
      "<CheckItem>",				NULL
    },
    {
      N_("/View/Tab Ic_ons"),			NULL,
      NULL,					0,
      "<CheckItem>",				NULL
    },
    {
      N_("/View/St_atusbar"),			NULL,
      NULL,					0,
      "<CheckItem>",				NULL
    },
    {
      "/View/separator1",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/View/Toolbar Styl_e"),		NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/View/Toolbar Style/_Desktop Default"), NULL,
      NULL,					0,
      "<RadioItem>",				NULL
    },
    {
      "/View/Toolbar Style/separator1",		NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/View/Toolbar Style/I_cons Only"),	NULL,
      NULL,					0,
      N_("/View/Toolbar Style/Desktop Default"), NULL
    },
    {
      N_("/View/Toolbar Style/_Text Only"),	NULL,
      NULL,					0,
      N_("/View/Toolbar Style/Desktop Default"), NULL
    },
    {
      N_("/View/Toolbar Style/Text Belo_w Icons"), NULL,
      NULL,					0,
      N_("/View/Toolbar Style/Desktop Default"), NULL
    },
    {
      N_("/View/Toolbar Style/Text Be_side Icons"), NULL,
      NULL,					0,
      N_("/View/Toolbar Style/Desktop Default"), NULL
    },
    {
      N_("/View/Toolbar Si_ze"),		NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/View/Toolbar Size/_Desktop Default"), NULL,
      NULL,					0,
      "<RadioItem>",				NULL
    },
    {
      "/View/Toolbar Size/separator1",		NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/View/Toolbar Size/_Small"),		NULL,
      NULL,					0,
      N_("/View/Toolbar Size/Desktop Default"),	NULL
    },
    {
      N_("/View/Toolbar Size/_Large"),		NULL,
      NULL,					0,
      N_("/View/Toolbar Size/Desktop Default"),	NULL
    },
    {
      "/View/separator2",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/View/Stream _Columns"),		NULL,
      st_shell_present_stream_columns,		0,
      "<StockItem>",				ST_STOCK_STREAM_COLUMNS
    },
    {
      "/View/separator3",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/View/_Stop"),			"Escape",
      st_shell_stop,				0,
      "<StockItem>",				GTK_STOCK_STOP
    },
    {
      N_("/View/_Reload"),			"<Control>R",
      st_shell_reload,				0,
      "<StockItem>",				ST_STOCK_RELOAD
    },
    {
      N_("/_Directories"),			NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/Directories/_Directory Preferences"), NULL,
      st_shell_present_directory_preferences,	0,
      "<StockItem>",				ST_STOCK_DIRECTORY_PREFERENCES
    },
    {
      N_("/Directories/_Visit Directory Homepage"), NULL,
      st_shell_visit_directory_homepage,	0,
      "<StockItem>",				ST_STOCK_VISIT_DIRECTORY_HOMEPAGE
    },
    {
      "/Directories/separator1",		NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Directories/_Previous Directory"),	"<Control>Page_Up",
      st_shell_select_previous_tab,		0,
      "<StockItem>",				GTK_STOCK_GO_BACK
    },
    {
      N_("/Directories/_Next Directory"),	"<Control>Page_Down",
      st_shell_select_next_tab,			0,
      "<StockItem>",				GTK_STOCK_GO_FORWARD
    },
    {
      "/Directories/separator2",		NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Directories/Move Directory _Left"),	"<Shift><Control>Page_Up",
      st_shell_move_tab_left,			0,
      "<Item>",					NULL
    },
    {
      N_("/Directories/Move Directory _Right"),	"<Shift><Control>Page_Down",
      st_shell_move_tab_right,			0,
      "<Item>",					NULL
    },
    {
      N_("/_Help"),				NULL,
      NULL,					0,
      "<Branch>",				NULL
    },
    {
      N_("/Help/_Contents"),			"F1",
      st_shell_help,				0,
      "<StockItem>",				GTK_STOCK_HELP
    },
    {
      N_("/Help/_Homepage"),			NULL,
      st_shell_homepage,			0,
      "<StockItem>",				GTK_STOCK_HOME
    },
    {
      "/Help/separator1",			NULL,
      NULL,					0,
      "<Separator>",				NULL
    },
    {
      N_("/Help/_About"),			NULL,
      st_shell_present_about,			0,
      "<StockItem>",				GTK_STOCK_DIALOG_INFO
    }
  };
  GtkWidget *menu;
  char *accel_path;

  /* create factory */

  shell->priv->factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR,
					      "<streamtuner-Browser>",
					      shell->priv->accel_group);

  gtk_item_factory_set_translate_func(shell->priv->factory,
				      sgtk_translate_func,
				      NULL,
				      NULL);
  gtk_item_factory_create_items(shell->priv->factory,
				G_N_ELEMENTS(entries),
				entries,
				shell);

  /* add stream items */

  menu = gtk_item_factory_get_widget(shell->priv->factory, N_("/Stream"));

  shell->priv->stream_items = st_stream_menu_items_new(shell->priv->accel_group);
  st_stream_menu_items_insert_into_shell(shell->priv->stream_items, GTK_MENU_SHELL(menu), 2);

  accel_path = g_strdup_printf("<streamtuner-Browser>/Stream/%s", _("Tune in"));
  sgtk_accel_map_add_entry(accel_path, "<Control>T");
  g_free(accel_path);

  accel_path = g_strdup_printf("<streamtuner-Browser>/Stream/%s", _("Delete"));
  sgtk_accel_map_add_entry(accel_path, "Delete");
  g_free(accel_path);

  accel_path = g_strdup_printf("<streamtuner-Browser>/Stream/%s", _("Properties"));
  sgtk_accel_map_add_entry(accel_path, "<Alt>Return");
  g_free(accel_path);

  /* remember the items we'll need later on */

  shell->priv->menubar = gtk_item_factory_get_widget(shell->priv->factory, "<streamtuner-Browser>");
  shell->priv->select_all_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Edit/Select All"));
  shell->priv->find_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Edit/Find..."));
  shell->priv->find_next_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Edit/Find Next"));
  shell->priv->find_previous_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Edit/Find Previous"));
  shell->priv->search_in_all_categories_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Edit/Search in All Categories..."));
  shell->priv->stop_item = gtk_item_factory_get_item(shell->priv->factory, N_("/View/Stop"));
  shell->priv->reload_item = gtk_item_factory_get_item(shell->priv->factory, N_("/View/Reload"));
  shell->priv->view_tab_icons_item = gtk_item_factory_get_item(shell->priv->factory, N_("/View/Tab Icons"));
  shell->priv->directories_menu = gtk_item_factory_get_widget(shell->priv->factory, N_("/Directories"));
  shell->priv->directory_preferences_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Directories/Directory Preferences"));
  shell->priv->directory_homepage_item = gtk_item_factory_get_item(shell->priv->factory, N_("/Directories/Visit Directory Homepage"));
  shell->priv->previous_directory_item = gtk_item_factory_get_widget(shell->priv->factory, N_("/Directories/Previous Directory"));
  shell->priv->next_directory_item = gtk_item_factory_get_widget(shell->priv->factory, N_("/Directories/Next Directory"));
  shell->priv->move_directory_left_item = gtk_item_factory_get_widget(shell->priv->factory, N_("/Directories/Move Directory Left"));
  shell->priv->move_directory_right_item = gtk_item_factory_get_widget(shell->priv->factory, N_("/Directories/Move Directory Right"));

  /* add directory items */

  st_shell_menubar_make_directory_items(shell);

  /* configure the view menu items */

  st_shell_menubar_init_view_item(shell, N_("/View/Toolbar"), &st_settings.view_toolbar);
  st_shell_menubar_init_view_item(shell, N_("/View/Tabs"), &st_settings.view_tabs);
  st_shell_menubar_init_view_item(shell, N_("/View/Tab Icons"), &st_settings.view_tab_icons);
  st_shell_menubar_init_view_item(shell, N_("/View/Statusbar"), &st_settings.view_statusbar);

  st_shell_menubar_init_toolbar_style_item(shell, N_("/View/Toolbar Style/Desktop Default"), ST_SHELL_TOOLBAR_STYLE_GTK);
  st_shell_menubar_init_toolbar_style_item(shell, N_("/View/Toolbar Style/Icons Only"), GTK_TOOLBAR_ICONS);
  st_shell_menubar_init_toolbar_style_item(shell, N_("/View/Toolbar Style/Text Only"), GTK_TOOLBAR_TEXT);
  st_shell_menubar_init_toolbar_style_item(shell, N_("/View/Toolbar Style/Text Below Icons"), GTK_TOOLBAR_BOTH);
  st_shell_menubar_init_toolbar_style_item(shell, N_("/View/Toolbar Style/Text Beside Icons"), GTK_TOOLBAR_BOTH_HORIZ);

  st_shell_menubar_init_toolbar_size_item(shell, N_("/View/Toolbar Size/Desktop Default"), ST_SHELL_TOOLBAR_SIZE_GTK);
  st_shell_menubar_init_toolbar_size_item(shell, N_("/View/Toolbar Size/Small"), GTK_ICON_SIZE_SMALL_TOOLBAR);
  st_shell_menubar_init_toolbar_size_item(shell, N_("/View/Toolbar Size/Large"), GTK_ICON_SIZE_LARGE_TOOLBAR);

  /* pack the menubar */
  
  gtk_box_pack_start(GTK_BOX(shell->priv->box), shell->priv->menubar, FALSE, FALSE, 0);
  gtk_widget_show(shell->priv->menubar);
}

static void
st_shell_menubar_make_directory_items (STShell *shell)
{
  GtkWidget *separator;
  GSList *group = NULL;
  GSList *l;
  unsigned int num = 0;

  g_return_if_fail(ST_IS_SHELL(shell));

  /* destroy the old items */
  g_slist_foreach(shell->priv->directory_items, (GFunc) gtk_widget_destroy, NULL);

  g_slist_free(shell->priv->directory_items);
  shell->priv->directory_items = NULL;

  separator = gtk_separator_menu_item_new();
  gtk_widget_show(separator);
  gtk_menu_shell_append(GTK_MENU_SHELL(shell->priv->directories_menu), separator);
  shell->priv->directory_items = g_slist_append(shell->priv->directory_items, separator);

  SG_LIST_FOREACH(l, st_handlers_list)
    {
      STHandler *handler = l->data;
      const char *label = st_handler_get_label(handler);
      GtkWidget *item;
      unsigned int hid;

      item = gtk_radio_menu_item_new_with_label(group, label);
      group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));

      gtk_widget_show(item);
      gtk_menu_shell_append(GTK_MENU_SHELL(shell->priv->directories_menu), item);

      if (++num < 10)
	{
	  char *accel_path;
	  char *accelerator;

	  accel_path = g_strdup_printf("<streamtuner-Browser>/Directories/%s", label);
	  accelerator = g_strdup_printf("<Control>%i", num);
	  sgtk_accel_map_change_entry(accel_path, accelerator, TRUE);
	  g_free(accel_path);
	  g_free(accelerator);
	}

      hid = g_signal_connect(item, "toggled", G_CALLBACK(st_shell_menubar_directory_toggled_h), shell);

      g_object_set_data(G_OBJECT(item), "handler", handler);
      g_object_set_data(G_OBJECT(item), "hid", GINT_TO_POINTER(hid));

      shell->priv->directory_items = g_slist_append(shell->priv->directory_items, item);
    }
}

/*
 * Select the menu item corresponding to the currently selected tab.
 */
static void
st_shell_menubar_select_directory_item (STShell *shell)
{
  STBrowserTab *tab;
  GSList *l;
  
  g_return_if_fail(ST_IS_SHELL(shell));

  tab = st_shell_get_selected_tab(shell);

  SG_LIST_FOREACH(l, shell->priv->directory_items)
    {
      GtkWidget *item = l->data;
      STHandler *handler = g_object_get_data(G_OBJECT(item), "handler");
      unsigned int hid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "hid"));
      
      if (handler == tab->handler)
	{
	  g_signal_handler_block(item, hid);
	  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
	  g_signal_handler_unblock(item, hid);
	  
	  break;
	}
    }
}

static void
st_shell_menubar_init_view_item (STShell *shell,
				 const char *path,
				 gboolean *var)
{
  GtkWidget *item;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(path != NULL);
  g_return_if_fail(var != NULL);

  item = gtk_item_factory_get_item(shell->priv->factory, path);
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), *var);

  if (var == &st_settings.view_tabs)
    gtk_widget_set_sensitive(shell->priv->view_tab_icons_item, st_settings.view_tabs);

  g_object_set_data(G_OBJECT(item), "shell", shell);
  g_signal_connect(item, "toggled", G_CALLBACK(st_shell_menubar_view_toggled_h), var);
}

static void
st_shell_menubar_view_toggled_h (GtkCheckMenuItem *item, gpointer user_data)
{
  gboolean *var = user_data;
  STShell *shell = g_object_get_data(G_OBJECT(item), "shell");

  *var = gtk_check_menu_item_get_active(item);
  st_shell_update_visibility(shell);

  if (var == &st_settings.view_tabs)
    gtk_widget_set_sensitive(shell->priv->view_tab_icons_item, st_settings.view_tabs);
}

static void
st_shell_menubar_init_toolbar_style_item (STShell *shell,
					  const char *path,
					  int style)
{
  GtkWidget *item;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(path != NULL);

  item = gtk_item_factory_get_item(shell->priv->factory, path);
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), st_settings.toolbar_style == style);

  g_object_set_data(G_OBJECT(item), "shell", shell);
  g_signal_connect(item, "toggled", G_CALLBACK(st_shell_menubar_toolbar_style_toggled_h), GINT_TO_POINTER(style));
}

static void
st_shell_menubar_toolbar_style_toggled_h (GtkCheckMenuItem *item,
					  gpointer user_data)
{
  if (gtk_check_menu_item_get_active(item))
    {
      int style = GPOINTER_TO_INT(user_data);
      STShell *shell = g_object_get_data(G_OBJECT(item), "shell");

      st_settings.toolbar_style = style;
      st_shell_update_toolbar_style(shell);
    }
}

static void
st_shell_menubar_init_toolbar_size_item (STShell *shell,
					 const char *path,
					 int size)
{
  GtkWidget *item;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(path != NULL);

  item = gtk_item_factory_get_item(shell->priv->factory, path);
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), st_settings.toolbar_size == size);

  g_object_set_data(G_OBJECT(item), "shell", shell);
  g_signal_connect(item, "toggled", G_CALLBACK(st_shell_menubar_toolbar_size_toggled_h), GINT_TO_POINTER(size));
}

static void
st_shell_menubar_toolbar_size_toggled_h (GtkCheckMenuItem *item,
					 gpointer user_data)
{
  if (gtk_check_menu_item_get_active(item))
    {
      int size = GPOINTER_TO_INT(user_data);
      STShell *shell = g_object_get_data(G_OBJECT(item), "shell");

      st_settings.toolbar_size = size;
      st_shell_update_toolbar_size(shell);
    }
}

static void
st_shell_menubar_directory_toggled_h (GtkCheckMenuItem *item,
				      gpointer user_data)
{
  if (gtk_check_menu_item_get_active(item))
    {
      STShell *shell = user_data;
      STHandler *handler = g_object_get_data(G_OBJECT(item), "handler");
      STBrowserTab *tab = st_shell_get_tab_with_handler(shell, handler);

      st_shell_select_tab(shell, tab, FALSE);
    }
}

static void
st_shell_make_toolbar (STShell *shell)
{
  shell->priv->toolbar = gtk_toolbar_new();

  shell->priv->tune_in_button = gtk_toolbar_insert_stock(GTK_TOOLBAR(shell->priv->toolbar),
							 ST_STOCK_TUNE_IN,
							 _("Listen to the selected stream"),
							 NULL,
							 (GtkSignalFunc) st_shell_toolbar_button_clicked_h,
							 shell,
							 -1);
  shell->priv->record_button = gtk_toolbar_insert_stock(GTK_TOOLBAR(shell->priv->toolbar),
							ST_STOCK_RECORD,
							_("Record the selected stream"),
							NULL,
							(GtkSignalFunc) st_shell_toolbar_button_clicked_h,
							shell,
							-1);
  shell->priv->browse_button = gtk_toolbar_insert_stock(GTK_TOOLBAR(shell->priv->toolbar),
							ST_STOCK_BROWSE,
							_("Browse the selected stream's web page"),
							NULL,
							(GtkSignalFunc) st_shell_toolbar_button_clicked_h,
							shell,
							-1);
  shell->priv->stop_button = gtk_toolbar_insert_stock(GTK_TOOLBAR(shell->priv->toolbar),
						      GTK_STOCK_STOP,
						      _("Stop the selected tab"),
						      NULL,
						      (GtkSignalFunc) st_shell_toolbar_button_clicked_h,
						      shell,
						      -1);
  shell->priv->reload_button = gtk_toolbar_insert_stock(GTK_TOOLBAR(shell->priv->toolbar),
							ST_STOCK_RELOAD,
							_("Reload the selected category"),
							NULL,
							(GtkSignalFunc) st_shell_toolbar_button_clicked_h,
							shell,
							-1);

  gtk_toolbar_append_space(GTK_TOOLBAR(shell->priv->toolbar));

  shell->priv->link = st_link_new();
  gtk_widget_show(shell->priv->link);
  gtk_container_set_border_width(GTK_CONTAINER(shell->priv->link), SGTK_HIG_CONTROL_SPACING);

  gtk_toolbar_append_widget(GTK_TOOLBAR(shell->priv->toolbar), shell->priv->link, NULL, NULL);

  gtk_box_pack_start(GTK_BOX(shell->priv->box), shell->priv->toolbar, FALSE, FALSE, 0);
  gtk_widget_show(shell->priv->toolbar);
}

static void
st_shell_toolbar_button_clicked_h (GtkButton *button, gpointer user_data)
{
  STShell *shell = user_data;

  if ((GtkWidget *) button == shell->priv->tune_in_button)
    st_shell_tune_in(shell);
  else if ((GtkWidget *) button == shell->priv->record_button)
    st_shell_record(shell);
  else if ((GtkWidget *) button == shell->priv->browse_button)
    st_shell_browse(shell);
  else if ((GtkWidget *) button == shell->priv->stop_button)
    st_shell_stop(shell);
  else if ((GtkWidget *) button == shell->priv->reload_button)
    st_shell_reload(shell);
  else
    g_return_if_reached();
}

static void
st_shell_make_notebook (STShell *shell)
{
  GSList *l;
  GdkPixbuf *drag_pixbuf;

  shell->priv->notebook = gtk_notebook_new();
  gtk_notebook_set_scrollable(GTK_NOTEBOOK(shell->priv->notebook), TRUE);

  /*
   * The tab labels will be moved in realtime by
   * st_shell_tab_label_drag_motion_h(): using a drag icon would be
   * visually redundant, so we use a blank icon.
   */
  drag_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
  gdk_pixbuf_fill(drag_pixbuf, 0); /* fill with transparent black */

  SG_LIST_FOREACH(l, st_handlers_list)
    {
      STHandler *handler = l->data;
      GtkWidget *tab;
      GtkWidget *tab_label;
      static GtkTargetEntry drag_targets[] = {
	{ "STBrowserTabLabel", 0, 0 }
      };
      STStreamMenuItems *stream_menu_items;
	  
      tab = st_browser_tab_new(handler);
      tab_label = ST_BROWSER_TAB(tab)->label;
      
      stream_menu_items = st_stream_menu_items_new(shell->priv->accel_group);
      st_stream_view_set_menu_items(ST_BROWSER_TAB(tab)->stream_view, stream_menu_items);

      gtk_drag_source_set(tab_label,
			  GDK_BUTTON1_MASK,
			  drag_targets,
			  G_N_ELEMENTS(drag_targets),
			  GDK_ACTION_MOVE);
      gtk_drag_dest_set(tab_label,
			GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
			drag_targets,
			G_N_ELEMENTS(drag_targets),
			GDK_ACTION_MOVE);
      gtk_drag_source_set_icon_pixbuf(tab_label, drag_pixbuf);

      g_object_connect(tab_label,
		       "signal::drag-begin", st_shell_tab_label_drag_begin_h, shell,
		       "signal::drag-end", st_shell_tab_label_drag_end_h, shell,
		       "signal::drag-motion", st_shell_tab_label_drag_motion_h, shell,
		       NULL);

      g_object_connect(tab,
		       "signal::notify::running", st_shell_tab_notify_running_h, shell,
		       "signal::stream-task-added", st_shell_tab_stream_task_added_h, shell,
		       "signal::stream-task-removed", st_shell_tab_stream_task_removed_h, shell,
		       NULL);

      if (ST_BROWSER_TAB(tab)->category_view)
	g_signal_connect(ST_BROWSER_TAB(tab)->category_view,
			 "selection-changed",
			 G_CALLBACK(st_shell_category_selection_changed_h),
			 shell);
      
      g_object_connect(ST_BROWSER_TAB(tab)->stream_view,
		       "signal::selection-changed", st_shell_stream_selection_changed_h, shell,
		       "signal::row-activated", st_shell_stream_activated_h, shell,
		       NULL);

      gtk_widget_show(tab);

      gtk_notebook_append_page(GTK_NOTEBOOK(shell->priv->notebook), tab, tab_label);
      shell->priv->tabs = g_slist_append(shell->priv->tabs, tab);
    }

  g_object_unref(drag_pixbuf);

  shell->priv->switch_page_hid = g_signal_connect_after(shell->priv->notebook,
							"switch-page",
							G_CALLBACK(st_shell_switch_page_h),
							shell);

  gtk_box_pack_start(GTK_BOX(shell->priv->box), shell->priv->notebook, TRUE, TRUE, 0);
  gtk_widget_show(shell->priv->notebook);
}

static void
st_shell_tab_label_drag_begin_h (GtkWidget *widget,
				 GdkDragContext *drag_context,
				 gpointer user_data)
{
  STShell *shell = user_data;
  GdkCursor *cursor = gdk_cursor_new(GDK_FLEUR);

  gdk_pointer_grab(shell->priv->notebook->window,
		   FALSE,
		   GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
		   NULL,
		   cursor,
		   drag_context->start_time);
  gdk_cursor_unref(cursor);
}

static void
st_shell_tab_label_drag_end_h (GtkWidget *widget,
			       GdkDragContext *drag_context,
			       gpointer user_data)
{
  STShell *shell = user_data;

  /* a tab might have been moved */
  st_shell_tab_moved(shell);
}

static int
st_shell_handlers_compare (gconstpointer a,
			   gconstpointer b,
			   gpointer user_data)
{
  STShell *shell = user_data;
  STBrowserTab *tab1 = st_shell_get_tab_with_handler(shell, (STHandler *) a);
  STBrowserTab *tab2 = st_shell_get_tab_with_handler(shell, (STHandler *) b);
  int num1 = gtk_notebook_page_num(GTK_NOTEBOOK(shell->priv->notebook), (GtkWidget *) tab1);
  int num2 = gtk_notebook_page_num(GTK_NOTEBOOK(shell->priv->notebook), (GtkWidget *) tab2);

  return num1 - num2;
}

static void
st_shell_tab_label_drag_motion_h (GtkWidget *widget,
				  GdkDragContext *drag_context,
				  int x,
				  int y,
				  unsigned int _time,
				  gpointer user_data)
{
  GtkWidget *source;

  source = gtk_drag_get_source_widget(drag_context);
  if (source)
    {
      STShell *shell = user_data;
      STBrowserTabLabel *source_tab_label = ST_BROWSER_TAB_LABEL(source);
      STBrowserTabLabel *dest_tab_label = ST_BROWSER_TAB_LABEL(widget);
      int num = gtk_notebook_page_num(GTK_NOTEBOOK(shell->priv->notebook), GTK_WIDGET(dest_tab_label->tab));

      g_return_if_fail(num != -1);
      gtk_notebook_reorder_child(GTK_NOTEBOOK(shell->priv->notebook),
				 GTK_WIDGET(source_tab_label->tab),
				 num);
    }
}

static void
st_shell_switch_page_h (GtkNotebook *notebook,
			GtkNotebookPage *page,
			unsigned int page_num,
			gpointer user_data)
{
  STShell *shell = user_data;
  
  st_shell_tab_selected(shell);
}

static void
st_shell_tab_selected (STShell *shell)
{
  STBrowserTab *tab;
  const char *description;
  char *markup;
  
  g_return_if_fail(ST_IS_SHELL(shell));

  tab = st_shell_get_selected_tab(shell);
  st_shell_update_title(shell);
  
  /* update toolbar */

  description = st_handler_get_description(tab->handler);
  markup = g_markup_printf_escaped("<span size=\"x-large\">%s</span>", description ? description : st_handler_get_label(tab->handler));
  st_link_set_text(ST_LINK(shell->priv->link), markup);
  g_free(markup);

  st_link_set_uri(ST_LINK(shell->priv->link), st_handler_get_home(tab->handler));

  /* update menubar */

  st_shell_menubar_select_directory_item(shell);

  /* pack statusbar */

  st_shell_set_statusbar_of_tab(shell, tab);

  /* misc */

  if (! gtk_tree_view_get_model(GTK_TREE_VIEW(tab->stream_view)))
    st_browser_tab_update(tab);
  
  st_shell_update_sensitivity(shell);

  if (shell->priv->stream_properties)
    st_stream_properties_dialog_update_sensitivity(ST_STREAM_PROPERTIES_DIALOG(shell->priv->stream_properties));
  if (shell->priv->stream_columns)
    st_stream_columns_dialog_set_stream_view(ST_STREAM_COLUMNS_DIALOG(shell->priv->stream_columns), tab->stream_view);
  if (shell->priv->find)
    {
      sgtk_find_dialog_set_previous_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
      sgtk_find_dialog_set_next_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
    }
}

static void
st_shell_tab_moved (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  /* reorder the handlers lists */
  st_handlers_list = g_slist_sort_with_data(st_handlers_list,
					    st_shell_handlers_compare,
					    shell);
  st_handlers_full_list = g_slist_sort_with_data(st_handlers_full_list,
						 st_shell_handlers_compare,
						 shell);

  /* reorder the directories menu by recreating it */
  st_shell_menubar_make_directory_items(shell);
  st_shell_menubar_select_directory_item(shell);

  st_shell_update_sensitivity(shell);
}

static void
st_shell_make_statusbox (STShell *shell)
{
  shell->priv->statusbox = gtk_event_box_new();
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(shell->priv->statusbox), FALSE);

  gtk_box_pack_start(GTK_BOX(shell->priv->box), shell->priv->statusbox, FALSE, FALSE, 0);
  gtk_widget_show(shell->priv->statusbox);
}

static void
st_shell_update_visibility (STShell *shell)
{
  GSList *l;

  /*
   * FIXME: fullscreen mode: HIG 2.0 says:
   *
   * "However, make its menus and items accessible from the keyboard
   * as usual."
   */

  g_object_set(shell->priv->menubar, "visible", ! shell->priv->fullscreen, NULL);
  g_object_set(shell->priv->toolbar, "visible", st_settings.view_toolbar, NULL);
  gtk_notebook_set_show_tabs(GTK_NOTEBOOK(shell->priv->notebook), st_settings.view_tabs);
  SG_LIST_FOREACH(l, shell->priv->tabs)
    {
      STBrowserTab *tab = l->data;
      st_browser_tab_label_set_icon_visible(ST_BROWSER_TAB_LABEL(tab->label), st_settings.view_tab_icons);
    }
  g_object_set(shell->priv->statusbox, "visible", st_settings.view_statusbar && ! shell->priv->fullscreen, NULL);
}

static void
st_shell_update_toolbar_style (STShell *shell)
{
  GtkToolbarStyle style = st_settings.toolbar_style;

  if (style == ST_SHELL_TOOLBAR_STYLE_GTK)
    {
      GtkSettings *settings;
      
      settings = gtk_widget_get_settings(GTK_WIDGET(shell->priv->toolbar));
      g_object_get(settings, "gtk-toolbar-style", &style, NULL);
    }

  if (shell->priv->fullscreen && style == GTK_TOOLBAR_BOTH)
    style = GTK_TOOLBAR_ICONS;
    
  gtk_toolbar_set_style(GTK_TOOLBAR(shell->priv->toolbar), style);
}

static void
st_shell_update_toolbar_size (STShell *shell)
{
  GtkIconSize size = st_settings.toolbar_size;

  if (size == ST_SHELL_TOOLBAR_SIZE_GTK)
    {
      GtkSettings *settings;
      
      settings = gtk_widget_get_settings(GTK_WIDGET(shell->priv->toolbar));
      g_object_get(settings, "gtk-toolbar-icon-size", &size, NULL);
    }

  gtk_toolbar_set_icon_size(GTK_TOOLBAR(shell->priv->toolbar), size);
}

STShell *
st_shell_new (void)
{
  return g_object_new(ST_TYPE_SHELL, NULL);
}

static void
st_shell_update_title (STShell *shell)
{
  STBrowserTab *selected_tab;
  GString *title;

  g_return_if_fail(ST_IS_SHELL(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  if (selected_tab)
    {
      STCategoryBag *category_bag;

      title = g_string_new(st_handler_get_label(selected_tab->handler));

      category_bag = st_handler_get_selected_category(selected_tab->handler);
      g_return_if_fail(category_bag != NULL);

      if (ST_CATEGORY(category_bag)->label)
	{
	  g_string_append_printf(title, ", %s", ST_CATEGORY(category_bag)->label);
	  g_object_unref(category_bag);
	}
    }
  else
    title = g_string_new("streamtuner");
  
  gtk_window_set_title(GTK_WINDOW(shell->priv->window), title->str);
  g_string_free(title, TRUE);
}

static void
st_shell_update_sensitivity (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  /* menubar */
  st_stream_menu_items_update_sensitivity(shell->priv->stream_items);
  gtk_widget_set_sensitive(shell->priv->select_all_item, st_shell_can_select_all(shell));
  gtk_widget_set_sensitive(shell->priv->find_item, st_shell_can_find(shell));
  gtk_widget_set_sensitive(shell->priv->find_next_item, st_shell_can_find_next(shell));
  gtk_widget_set_sensitive(shell->priv->find_previous_item, st_shell_can_find_previous(shell));
  gtk_widget_set_sensitive(shell->priv->search_in_all_categories_item, st_shell_can_search_in_all_categories(shell));
  gtk_widget_set_sensitive(shell->priv->stop_item, st_shell_can_stop(shell));
  gtk_widget_set_sensitive(shell->priv->reload_item, st_shell_can_reload(shell));
  gtk_widget_set_sensitive(shell->priv->directory_preferences_item, st_shell_can_present_directory_preferences(shell));
  gtk_widget_set_sensitive(shell->priv->directory_homepage_item, st_shell_can_visit_directory_homepage(shell));
  gtk_widget_set_sensitive(shell->priv->previous_directory_item, st_shell_can_select_previous_tab(shell));
  gtk_widget_set_sensitive(shell->priv->next_directory_item, st_shell_can_select_next_tab(shell));
  gtk_widget_set_sensitive(shell->priv->move_directory_left_item, st_shell_can_move_tab_left(shell));
  gtk_widget_set_sensitive(shell->priv->move_directory_right_item, st_shell_can_move_tab_right(shell));

  /* toolbar */
  gtk_widget_set_sensitive(shell->priv->tune_in_button, st_shell_can_tune_in(shell));
  gtk_widget_set_sensitive(shell->priv->record_button, st_shell_can_record(shell));
  gtk_widget_set_sensitive(shell->priv->browse_button, st_shell_can_browse(shell));
  gtk_widget_set_sensitive(shell->priv->stop_button, st_shell_can_stop(shell));
  gtk_widget_set_sensitive(shell->priv->reload_button, st_shell_can_reload(shell));

  /* tabs */
  g_slist_foreach(shell->priv->tabs, (GFunc) st_browser_tab_update_sensitivity, NULL);
}

static STBrowserTab *
st_shell_get_selected_tab (STShell *shell)
{
  int num;

  g_return_val_if_fail(ST_IS_SHELL(shell), NULL);

  num = gtk_notebook_get_current_page(GTK_NOTEBOOK(shell->priv->notebook));
  return num != -1
    ? (STBrowserTab *) gtk_notebook_get_nth_page(GTK_NOTEBOOK(shell->priv->notebook), num)
    : NULL;
}

static gboolean
st_shell_can_stop (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_stop(selected_tab);
}

static void
st_shell_stop (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_stop(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_stop(selected_tab);
}

static gboolean
st_shell_can_reload (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_reload(selected_tab);
}

static void
st_shell_reload (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_reload(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_reload(selected_tab);
}

static gboolean
st_shell_can_select_all (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab != NULL;
}

static void
st_shell_select_all (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_select_all(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_stream_view_select_all(selected_tab->stream_view);
}

static gboolean
st_shell_can_find (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab != NULL;
}

static void
st_shell_find (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  if (shell->priv->find)
    gtk_window_present(GTK_WINDOW(shell->priv->find));
  else
    {
      shell->priv->find = sgtk_find_dialog_new(GTK_WINDOW(shell->priv->window));
      g_object_add_weak_pointer(G_OBJECT(shell->priv->find), (gpointer *) &shell->priv->find);

      sgtk_find_dialog_set_token(SGTK_FIND_DIALOG(shell->priv->find), st_settings.find_token ? st_settings.find_token : "");
      sgtk_find_dialog_set_history(SGTK_FIND_DIALOG(shell->priv->find), st_settings.find_history);
      sgtk_find_dialog_set_case_sensitive(SGTK_FIND_DIALOG(shell->priv->find), st_settings.find_case_sensitive);
      sgtk_find_dialog_set_wrap_around(SGTK_FIND_DIALOG(shell->priv->find), st_settings.find_wrap_around);

      sgtk_find_dialog_set_previous_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
      sgtk_find_dialog_set_next_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
      
      g_object_connect(shell->priv->find,
		       "signal::notify", st_shell_find_notify_h, NULL,
		       "signal::response", st_shell_find_response_h, shell,
		       NULL);

      gtk_widget_show(shell->priv->find);
    }
}

static void
st_shell_find_notify_h (GObject *object,
			GParamSpec *pspec,
			gpointer user_data)
{
  sGtkFindDialog *dialog = SGTK_FIND_DIALOG(object);
  const char *name = g_param_spec_get_name(pspec);

  if (! strcmp(name, "token"))
    {
      const char *token;

      token = sgtk_find_dialog_get_token(dialog);

      g_free(st_settings.find_token);
      st_settings.find_token = *token ? g_strdup(token) : NULL;

      sgtk_find_dialog_set_previous_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
      sgtk_find_dialog_set_next_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
    }
  else if (! strcmp(name, "history"))
    {
      sg_pointers_free(st_settings.find_history);
      st_settings.find_history = sgtk_find_dialog_get_history(dialog);

      if (g_slist_length(st_settings.find_history) > ST_SETTINGS_FIND_HISTORY_MAX_LENGTH)
	{
	  GSList *elem;

	  elem = g_slist_nth(st_settings.find_history, ST_SETTINGS_FIND_HISTORY_MAX_LENGTH - 1);
	  g_return_if_fail(elem != NULL);

	  sg_pointers_free(elem->next);
	  elem->next = NULL;

	  sgtk_find_dialog_set_history(dialog, st_settings.find_history);
	}
    }
  else if (! strcmp(name, "case-sensitive"))
    {
      st_settings.find_case_sensitive = sgtk_find_dialog_get_case_sensitive(dialog);
      sgtk_find_dialog_set_previous_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
      sgtk_find_dialog_set_next_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
    }
  else if (! strcmp(name, "wrap-around"))
    {
      st_settings.find_wrap_around = sgtk_find_dialog_get_wrap_around(dialog);
      if (st_settings.find_wrap_around)
	{
	  sgtk_find_dialog_set_previous_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
	  sgtk_find_dialog_set_next_sensitive(dialog, FIND_NAVIGATION_SENSITIVE);
	}
    }
}

static void
st_shell_find_response_h (GtkDialog *dialog,
			  int response,
			  gpointer data)
{
  STShell *shell = data;
  sGtkDirection direction;
  const char *token;

  switch (response)
    {
    case SGTK_RESPONSE_PREVIOUS:
      direction = SGTK_PREVIOUS;
      break;
      
    case SGTK_RESPONSE_NEXT:
      direction = SGTK_NEXT;
      break;

    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_CLOSE:
      gtk_widget_destroy(GTK_WIDGET(dialog));
      return;			/* finish */

    default:
      g_return_if_reached();
    }

  token = sgtk_find_dialog_get_token(SGTK_FIND_DIALOG(dialog));

  g_free(shell->priv->find_token);
  shell->priv->find_token = *token ? g_strdup(token) : NULL;
  shell->priv->find_case_sensitive = sgtk_find_dialog_get_case_sensitive(SGTK_FIND_DIALOG(dialog));
  st_shell_find_real(shell, direction, sgtk_find_dialog_get_wrap_around(SGTK_FIND_DIALOG(dialog)));
}

static gboolean
st_shell_can_find_next (STShell *shell)
{
  return st_shell_can_find(shell) && shell->priv->find_token;
}

static void
st_shell_find_next (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  st_shell_find_real(shell, SGTK_NEXT, TRUE);
}

static gboolean
st_shell_can_find_previous (STShell *shell)
{
  return st_shell_can_find(shell) && shell->priv->find_token;
}

static void
st_shell_find_previous (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  st_shell_find_real(shell, SGTK_PREVIOUS, TRUE);
}

static void
st_shell_find_real (STShell *shell,
		    sGtkDirection direction,
		    gboolean wrap_around)
{
  STBrowserTab *selected_tab;
  gboolean found;

  g_return_if_fail(ST_IS_SHELL(shell));
  
  selected_tab = st_shell_get_selected_tab(shell);
  found = st_stream_view_find(selected_tab->stream_view,
			      direction,
			      shell->priv->find_token,
			      shell->priv->find_case_sensitive,
			      wrap_around);
  
  if (shell->priv->find)
    {
      if (found)
	switch (direction)
	  {
	  case SGTK_PREVIOUS:
	    sgtk_find_dialog_set_next_sensitive(SGTK_FIND_DIALOG(shell->priv->find), TRUE);
	    break;
	    
	  case SGTK_NEXT:
	    sgtk_find_dialog_set_previous_sensitive(SGTK_FIND_DIALOG(shell->priv->find), TRUE);
	    break;
	
	  default:
	    g_return_if_reached();
	  }
      else
	switch (direction)
	  {
	  case SGTK_PREVIOUS:
	    sgtk_find_dialog_set_previous_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FALSE);
	    break;
	    
	  case SGTK_NEXT:
	    sgtk_find_dialog_set_next_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FALSE);
	    break;
	
	  default:
	    g_return_if_reached();
	  }
    }
}

static gboolean
st_shell_can_search_in_all_categories (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab
    && st_handler_get_stock_category(selected_tab->handler, ST_CATEGORY_BAG_SEARCH);
}

static void
st_shell_search_in_all_categories (STShell *shell)
{
  STBrowserTab *selected_tab;
  STCategoryBag *search_category;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_search_in_all_categories(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  g_return_if_fail(selected_tab != NULL);

  search_category = st_handler_get_stock_category(selected_tab->handler, ST_CATEGORY_BAG_SEARCH);
  g_return_if_fail(search_category != NULL);

  st_category_view_select_category(selected_tab->category_view, search_category);
  st_browser_tab_reload(selected_tab);
}

gboolean
st_shell_can_tune_in (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_TUNE_IN);
}

void
st_shell_tune_in (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_tune_in(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_TUNE_IN);
}

gboolean
st_shell_can_record (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_RECORD);
}

void
st_shell_record (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_record(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_RECORD);
}

gboolean
st_shell_can_browse (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_BROWSE);
}

void
st_shell_browse (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_browse(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_BROWSE);
}

gboolean
st_shell_can_present_stream_properties (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab
    && st_stream_view_has_selected_streams(selected_tab->stream_view);
}

void
st_shell_present_stream_properties (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  if (shell->priv->stream_properties)
    gtk_window_present(GTK_WINDOW(shell->priv->stream_properties));
  else
    {
      shell->priv->stream_properties = st_stream_properties_dialog_new(GTK_WINDOW(shell->priv->window));
      g_object_add_weak_pointer(G_OBJECT(shell->priv->stream_properties), (gpointer *) &shell->priv->stream_properties);

      g_signal_connect(shell->priv->stream_properties,
		       "response",
		       G_CALLBACK(st_shell_stream_properties_response_h),
		       shell);
      
      st_shell_stream_properties_update_stream(shell);
      gtk_widget_show(shell->priv->stream_properties);
    }
}

static void
st_shell_stream_properties_response_h (GtkDialog *dialog,
				       int response,
				       gpointer data)
{
  STShell *shell = data;
  STBrowserTab *selected_tab = st_shell_get_selected_tab(shell);
  
  switch (response)
    {
    case SGTK_RESPONSE_PREVIOUS:
      st_stream_view_select_previous(selected_tab->stream_view);
      break;

    case SGTK_RESPONSE_NEXT:
      st_stream_view_select_next(selected_tab->stream_view);
      break;

    case GTK_RESPONSE_APPLY:
      st_stream_properties_dialog_apply(ST_STREAM_PROPERTIES_DIALOG(dialog));
      break;

    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_CANCEL:
      gtk_widget_hide(GTK_WIDGET(dialog));
      st_stream_properties_dialog_cancel(ST_STREAM_PROPERTIES_DIALOG(dialog));
      gtk_widget_destroy(GTK_WIDGET(dialog));
      break;
      
    case GTK_RESPONSE_OK:
      gtk_widget_hide(GTK_WIDGET(dialog));
      st_stream_properties_dialog_apply(ST_STREAM_PROPERTIES_DIALOG(dialog));
      gtk_widget_destroy(GTK_WIDGET(dialog));
      break;
    }
}

void
st_shell_present_stream_columns (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  if (shell->priv->stream_columns)
    gtk_window_present(GTK_WINDOW(shell->priv->stream_columns));
  else
    {
      STBrowserTab *selected_tab;

      shell->priv->stream_columns = st_stream_columns_dialog_new(GTK_WINDOW(shell->priv->window));
      g_object_add_weak_pointer(G_OBJECT(shell->priv->stream_columns), (gpointer *) &shell->priv->stream_columns);

      selected_tab = st_shell_get_selected_tab(shell);
      if (selected_tab)
	st_stream_columns_dialog_set_stream_view(ST_STREAM_COLUMNS_DIALOG(shell->priv->stream_columns), selected_tab->stream_view);

      g_signal_connect(shell->priv->stream_columns,
		       "response",
		       G_CALLBACK(st_shell_dialog_response_h),
		       NULL);

      gtk_widget_show(shell->priv->stream_columns);
    }
}

static void
st_shell_present_preferences (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  st_shell_present_handler_preferences(shell, NULL);
}

static void
st_shell_present_about (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  if (shell->priv->about)
    gtk_window_present(GTK_WINDOW(shell->priv->about));
  else
    {
      shell->priv->about = st_about_dialog_new(GTK_WINDOW(shell->priv->window));
      g_object_add_weak_pointer(G_OBJECT(shell->priv->about), (gpointer *) &shell->priv->about);

      g_signal_connect(shell->priv->about,
		       "response",
		       G_CALLBACK(st_shell_dialog_response_h),
		       NULL);
      
      gtk_widget_show(shell->priv->about);
    }
}

static void
st_shell_dialog_response_h (GtkDialog *dialog, int response, gpointer data)
{
  gtk_widget_destroy(GTK_WIDGET(dialog));
}

static gboolean
st_shell_can_present_directory_preferences (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_present_preferences(selected_tab);
}

static void
st_shell_present_directory_preferences (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_present_directory_preferences(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_present_preferences(selected_tab);
}

void
st_shell_present_handler_preferences (STShell *shell, STHandler *handler)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  if (! shell->priv->preferences)
    {
      shell->priv->preferences = st_preferences_dialog_new(GTK_WINDOW(shell->priv->window));
      g_object_add_weak_pointer(G_OBJECT(shell->priv->preferences), (gpointer *) &shell->priv->preferences);
    }

  if (handler)
    st_preferences_dialog_select_handler(ST_PREFERENCES_DIALOG(shell->priv->preferences), handler);

  gtk_window_present(GTK_WINDOW(shell->priv->preferences));
}

static gboolean
st_shell_can_visit_directory_homepage (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_visit_homepage(selected_tab);
}

static void
st_shell_visit_directory_homepage (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_visit_directory_homepage(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_visit_homepage(selected_tab);
}

gboolean
st_shell_can_add_bookmarks (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab && st_browser_tab_can_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_ADD_BOOKMARKS);
}

void
st_shell_add_bookmarks (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_add_bookmarks(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  st_browser_tab_run_stream_task(selected_tab, ST_BROWSER_TAB_STREAM_TASK_ADD_BOOKMARKS);
}

gboolean
st_shell_can_delete_streams (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab
    && st_handler_event_is_bound(selected_tab->handler, ST_HANDLER_EVENT_STREAM_DELETE)
    && st_stream_view_has_selected_streams(selected_tab->stream_view);
}

void
st_shell_delete_streams (STShell *shell)
{
  STBrowserTab *selected_tab;
  GSList *selected_streams;
  GSList *l;
  gboolean confirmed = TRUE;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_delete_streams(shell));

  selected_tab = st_shell_get_selected_tab(shell);
  selected_streams = st_stream_view_get_selected_streams(selected_tab->stream_view);

  if (ST_HANDLER_MUST_CONFIRM_DELETION(selected_tab->handler))
    {
      GtkWidget *dialog;

      dialog = sgtk_message_dialog_new(GTK_WINDOW(shell->priv->window),
				       GTK_STOCK_DIALOG_WARNING,
				       ngettext("Delete selected stream?",
						"Delete selected streams?",
						g_slist_length(selected_streams)),
				       _("The deletion will probably be irreversible."));

      gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
      gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_DELETE, GTK_RESPONSE_YES);

      confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES;
      gtk_widget_destroy(dialog);
    }
  
  if (confirmed)
    SG_LIST_FOREACH(l, selected_streams)
      {
	GError *err = NULL;
	
	if (! st_stream_bag_delete(l->data, &err))
	  {
	    char *normalized;
	    
	    normalized = st_dialog_normalize(err->message);
	    g_error_free(err);
	    
	    st_error_dialog(_("Unable to delete stream"), "%s", normalized);
	    g_free(normalized);
	  }
      }
  
  sg_objects_free(selected_streams);
}

gboolean
st_shell_can_select_previous_stream (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab
    && st_stream_view_can_select_previous(selected_tab->stream_view);
}

gboolean
st_shell_can_select_next_stream (STShell *shell)
{
  STBrowserTab *selected_tab;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  selected_tab = st_shell_get_selected_tab(shell);

  return selected_tab
    && st_stream_view_can_select_next(selected_tab->stream_view);
}

static void
st_shell_stream_properties_update_stream (STShell *shell)
{
  STBrowserTab *selected_tab;
  GSList *selected_streams;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(shell->priv->stream_properties != NULL);

  selected_tab = st_shell_get_selected_tab(shell);
  g_return_if_fail(selected_tab != NULL);

  selected_streams = st_stream_view_get_selected_streams(selected_tab->stream_view);
  if (selected_streams)
    {
      st_stream_properties_dialog_set_stream(ST_STREAM_PROPERTIES_DIALOG(shell->priv->stream_properties), selected_streams->data);
      sg_objects_free(selected_streams);
    }
}

static STBrowserTab *
st_shell_get_tab_with_handler (STShell *shell, STHandler *handler)
{
  GSList *l;

  g_return_val_if_fail(ST_IS_SHELL(shell), NULL);
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  SG_LIST_FOREACH(l, shell->priv->tabs)
    {
      STBrowserTab *tab = l->data;

      if (tab->handler == handler)
	return tab;
    }

  return NULL;
}

static void
st_shell_select_tab (STShell *shell,
		     STBrowserTab *tab,
		     gboolean force_update)
{
  int num;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  
  num = gtk_notebook_page_num(GTK_NOTEBOOK(shell->priv->notebook), GTK_WIDGET(tab));
  g_return_if_fail(num != -1);

  if (force_update)
    {
      g_signal_handler_block(shell->priv->notebook, shell->priv->switch_page_hid);
      gtk_notebook_set_page(GTK_NOTEBOOK(shell->priv->notebook), num);
      g_signal_handler_unblock(shell->priv->notebook, shell->priv->switch_page_hid);

      st_shell_tab_selected(shell);
    }
  else
    gtk_notebook_set_page(GTK_NOTEBOOK(shell->priv->notebook), num);
}

static gboolean
st_shell_can_select_previous_tab (STShell *shell)
{
  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  return gtk_notebook_get_current_page(GTK_NOTEBOOK(shell->priv->notebook)) > 0;
}

static void
st_shell_select_previous_tab (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_select_previous_tab(shell));

  gtk_notebook_prev_page(GTK_NOTEBOOK(shell->priv->notebook));
}

static gboolean
st_shell_can_select_next_tab (STShell *shell)
{
  int num;
  int npages;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  num = gtk_notebook_get_current_page(GTK_NOTEBOOK(shell->priv->notebook));
  npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(shell->priv->notebook));

  return num < npages - 1;
}

static void
st_shell_select_next_tab (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_select_next_tab(shell));

  gtk_notebook_next_page(GTK_NOTEBOOK(shell->priv->notebook));
}

static gboolean
st_shell_can_move_tab (STShell *shell, int direction)
{
  int num;
  int npages;

  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  num = gtk_notebook_get_current_page(GTK_NOTEBOOK(shell->priv->notebook)) + direction;
  npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(shell->priv->notebook));

  return npages > 1 && num >= 0 && num < npages;
}

static void
st_shell_move_tab (STShell *shell, int direction)
{
  int num;
  GtkWidget *child;

  g_return_if_fail(ST_IS_SHELL(shell));

  num = gtk_notebook_get_current_page(GTK_NOTEBOOK(shell->priv->notebook));
  child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(shell->priv->notebook), num);
  gtk_notebook_reorder_child(GTK_NOTEBOOK(shell->priv->notebook), child, num + direction);

  st_shell_tab_moved(shell);
}

static gboolean
st_shell_can_move_tab_left (STShell *shell)
{
  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  return st_shell_can_move_tab(shell, -1);
}

static void
st_shell_move_tab_left (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_move_tab_left(shell));

  st_shell_move_tab(shell, -1);
}

static gboolean
st_shell_can_move_tab_right (STShell *shell)
{
  g_return_val_if_fail(ST_IS_SHELL(shell), FALSE);

  return st_shell_can_move_tab(shell, +1);
}

static void
st_shell_move_tab_right (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(st_shell_can_move_tab_right(shell));

  st_shell_move_tab(shell, +1);
}

static void
st_shell_help (STShell *shell)
{
  st_show_help(NULL);
}

static void
st_shell_homepage (STShell *shell)
{
  g_return_if_fail(ST_IS_SHELL(shell));

  st_visit_homepage(ST_HOME);
}

static void
st_shell_new_preselection (STShell *shell)
{
  STBrowserTab *tab;
  STStreamBag *stream_bag;

  g_return_if_fail(ST_IS_SHELL(shell));

  /* switch to the preselections tab */

  tab = st_shell_get_tab_with_handler(shell, st_preselections_handler);
  st_shell_select_tab(shell, tab, FALSE);

  /* create a new preselection, select and present it */

  stream_bag = st_preselections_new();
  st_stream_view_select_stream(tab->stream_view, stream_bag);
  st_stream_view_present_stream(tab->stream_view, stream_bag);

  /* present the stream properties dialog and cleanup */

  st_shell_present_stream_properties(shell);
  g_object_unref(stream_bag);
}

GtkWindow *
st_shell_get_window (STShell *shell)
{
  g_return_val_if_fail(ST_IS_SHELL(shell), NULL);

  return (GtkWindow *) shell->priv->window;
}

static void
st_shell_set_statusbar_of_tab (STShell *shell, STBrowserTab *tab)
{
  STCategoryBag *category_bag;

  g_return_if_fail(ST_IS_SHELL(shell));
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  category_bag = st_handler_get_selected_category(tab->handler);
  g_return_if_fail(category_bag != NULL);

  if (GTK_BIN(shell->priv->statusbox)->child)
    gtk_container_remove(GTK_CONTAINER(shell->priv->statusbox), GTK_BIN(shell->priv->statusbox)->child);

  gtk_container_add(GTK_CONTAINER(shell->priv->statusbox), category_bag->statusbar);
  gtk_widget_show(category_bag->statusbar);

  /* work-around a bug in the Bluecurve theme engine */
  gtk_widget_realize(GTK_WIDGET(ST_STATUSBAR(category_bag->statusbar)->progress_bar));
}

static void
st_shell_category_selection_changed_h (STCategoryView *view,
				       gpointer user_data)
{
  STShell *shell = user_data;
  STBrowserTab *tab;

  st_shell_update_title(shell);
  st_shell_update_sensitivity(shell);

  tab = st_shell_get_selected_tab(shell);
  st_shell_set_statusbar_of_tab(shell, tab);
}

static void
st_shell_stream_selection_changed_h (STStreamView *view, gpointer user_data)
{
  STShell *shell = user_data;

  st_shell_update_sensitivity(shell);
  if (shell->priv->stream_properties)
    {
      st_stream_properties_dialog_apply(ST_STREAM_PROPERTIES_DIALOG(shell->priv->stream_properties));

      st_stream_properties_dialog_update_sensitivity(ST_STREAM_PROPERTIES_DIALOG(shell->priv->stream_properties));
      st_shell_stream_properties_update_stream(shell);
    }
  if (shell->priv->find)
    {
      sgtk_find_dialog_set_previous_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
      sgtk_find_dialog_set_next_sensitive(SGTK_FIND_DIALOG(shell->priv->find), FIND_NAVIGATION_SENSITIVE);
    }
}

static void
st_shell_stream_activated_h (GtkTreeView *treeview,
			     GtkTreePath *arg1,
			     GtkTreeViewColumn *arg2,
			     gpointer user_data)
{
  STShell *shell = user_data;

  if (st_shell_can_tune_in(shell))
    st_shell_tune_in(shell);
  else if (st_shell_can_browse(shell))
    st_shell_browse(shell);
}

static void
st_shell_tab_notify_running_h (GObject *object,
			       GParamSpec *pspec,
			       gpointer user_data)
{
  STBrowserTab *tab = ST_BROWSER_TAB(object);
  STShell *shell = user_data;

  if (tab->running)
    /*
     * The selected_category label might have been changed by url_cb,
     * so we update the shell title.
     */
    st_shell_update_title(shell);

  st_shell_update_sensitivity(shell);
}

static void
st_shell_tab_stream_task_added_h (STBrowserTab *tab,
				  STBrowserTabStreamTask *task,
				  gpointer user_data)
{
  STShell *shell = user_data;

  if (task->type == ST_BROWSER_TAB_STREAM_TASK_TUNE_IN)
    {
      if (shell->priv->tune_in_task)
	st_thread_abort(shell->priv->tune_in_task->thread);
      shell->priv->tune_in_task = task;
    }
}

static void
st_shell_tab_stream_task_removed_h (STBrowserTab *tab,
				    STBrowserTabStreamTask *task,
				    gpointer user_data)
{
  STShell *shell = user_data;

  if (task == shell->priv->tune_in_task)
    shell->priv->tune_in_task = NULL;
}

void
st_shell_set_restart (STShell *shell, gboolean restart)
{
  g_return_if_fail(ST_IS_SHELL(shell));
  
  shell->priv->restart = restart;
}
