/*
 * Copyright (C) 2002-2017 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * mediamarks - load
 *            - save
 *            - edit
 * functions
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>

#include "ui.h"
#include "utils.h"
#include "engine.h"
#include "mediamarks.h"
#include "menu.h"
#include "list_menus.h"
#include "playlist.h"
#include "play_item.h"

static GtkTreeStore *mm_store;
static GtkTreeModel *mm_model;
static GtkTreeView  *tree;
static GtkTreeSelection *sel;
static GtkWidget    *manage_dlg, *cat_dlg, *cat_entry, *mm_tree;
static GtkTreeIter   cat_iter;
static gboolean      is_visible, cat_edited;

static GtkUIManager *mm_ui;
static GtkActionGroup *media_group = NULL;
static GtkAction      *play_item;
static GtkMenu	      *menu[2];
static guint	       media_merge;

#define COLUMN_TITLE		0
#define COLUMN_MRL		1
#define COLUMN_PLAY_ITEM	2

static void mm_add_at_selected (play_item_t *play_item);

static inline const char *peek_title (GtkTreeIter *iter)
{
  static GValue v = G_VALUE_INIT;
  if (G_IS_VALUE (&v))
    g_value_unset (&v);
  gtk_tree_model_get_value (mm_model, iter, COLUMN_TITLE, &v);
  return g_value_peek_pointer (&v);
}

static inline play_item_t *peek_play_item (GtkTreeIter *iter)
{
  GValue v = G_VALUE_INIT;
  gtk_tree_model_get_value (mm_model, iter,
			    COLUMN_PLAY_ITEM, &v);
  void *p = g_value_peek_pointer (&v);
  g_value_unset (&v);
  return p;
}

static void play_cb (GtkWidget* widget, gpointer data)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
  {
    play_item_t *item = peek_play_item (&iter);
    if (item)
      playlist_play (playlist_add (play_item_copy (item), -1));
  }
}

static void menu_cb (GtkAction* action, gpointer data)
{
  if (data)
    playlist_play (playlist_add (play_item_copy (data), -1));
}

static int gen_actions_sub (GtkUIManager *ui, GtkActionGroup *actions,
			    GtkTreeIter *iter, int count)
{
  do
  {
    const char *id = peek_title (iter);
    if (id)
    {
      gchar *name = g_strdup_printf ("_mediamark_%d", count++);
      GtkAction *action = gtk_action_new (name, id, NULL, NULL);

      gtk_action_group_add_action (actions, action);
      g_free (name);
      play_item_t *item = peek_play_item (iter);
      if (item)
	g_signal_connect (G_OBJECT(action), "activate",
			  G_CALLBACK(menu_cb), (gpointer) item);
      else
      {
	GtkTreeIter children;
	if (gtk_tree_model_iter_children (mm_model, &children, iter))
	  count = gen_actions_sub (ui, actions, &children, count);
      }
    }
  } while (gtk_tree_model_iter_next (mm_model, iter));

  return count;
}

static GtkActionGroup *gen_actions (GtkUIManager *ui, GtkTreeIter *iter)
{
  GtkActionGroup *actions = gtk_action_group_new ("mediamarks");
#ifdef ENABLE_NLS
  gtk_action_group_set_translation_domain (actions, "");
#endif
  gen_actions_sub (ui, actions, iter, 0);
  return actions;
}

static int gen_items (GtkUIManager *ui, const char *path, GtkTreeIter *iter,
		      guint merge, int count)
{
  do
  {
    if (peek_title (iter))
    {
      char *name = g_strdup_printf ("_mediamark_%d", count++);
      play_item_t *item = peek_play_item (iter);
      if (item)
	gtk_ui_manager_add_ui (ui, merge, path, name, name,
			       GTK_UI_MANAGER_MENUITEM, FALSE);
      else
      {
	GtkTreeIter children;
	gtk_ui_manager_add_ui (ui, merge, path, name, name,
			       GTK_UI_MANAGER_MENU, FALSE);
	if (gtk_tree_model_iter_children (mm_model, &children, iter))
	{
	  gchar *newpath = g_strdup_printf ("%s/%s", path, name);
	  count = gen_items (ui, newpath, &children, merge, count);
	  g_free (newpath);
	}
      }
    }
  } while (gtk_tree_model_iter_next (mm_model, iter));

  return count;
}

static void update_menu (void)
{
  GtkTreeIter   iter;

  /* remove old items (if any) */
  if (media_group)
  {
    gtk_ui_manager_remove_ui (ui, media_merge);
    gtk_ui_manager_remove_action_group (ui, media_group);
    media_group = NULL;
  }

  do_pending_events ();

  /* add current items */

  if (gtk_tree_model_get_iter_first (mm_model, &iter))
  {
    unsigned int i;
    const char *tree;
    media_merge = gtk_ui_manager_new_merge_id (ui);
    media_group = gen_actions (ui, &iter);
    gtk_ui_manager_insert_action_group (ui, media_group, 0);
    for (i = 0; (tree = menu_get_tree_name (i)) != NULL; ++i)
    {
      gchar *key = g_strdup_printf ("/ui/%s/MediaMenu/MediaItems", tree);
      if (gtk_tree_model_get_iter_first (mm_model, &iter)) {
        gen_items (ui, key, &iter, media_merge, 0);
      }
      g_free (key);
    }
  }
}

/*
 * support new xml based mediamarks file format
 */

static void get_items (xml_node_t *node, int depth, GtkTreeIter *parent)
{
  /* assumption: *one* of (depth == 0) or (parent == NULL) */

  foreach_glist (node, node)
    if (!strcasecmp (node->name, "sub"))
    {
      GtkTreeIter iter;
      const char *id = xml_parser_get_property (node, "name");

      gtk_tree_store_append (mm_store, &iter, parent);
      gtk_tree_store_set (mm_store, &iter, COLUMN_TITLE, id, -1);

      get_items (node->child, depth+1, &iter);

    }
    else if (!strcasecmp (node->name, "entry"))
    {
      play_item_t *play_item = play_item_load (node->child);
      GtkTreeIter  iter;

      gtk_tree_store_append (mm_store, &iter, parent);
      gtk_tree_store_set (mm_store, &iter,
			  COLUMN_TITLE, play_item->title,
			  COLUMN_MRL, play_item->mrl,
			  COLUMN_PLAY_ITEM, play_item,
			  -1);

    }
    else
      g_printerr (_("mediamarks: error: unknown node type %s\n"), node->name);
}

static void load_new_file (xml_node_t *node)
{
  if (!strcasecmp (node->name, "gxinemm"))
    get_items (node->child, 0, NULL);
  else
    g_printerr (_("mediamarks: error: root node must be GXINEMM\n"));
}

/*
 * legacy support for old mediamarks file format
 */

static char * read_line (FILE *f)
{
  char str[1024];
  char *str2 = fgets (str, 1024, f);
  if (!str2)
    return NULL;

  size_t len = strlen (str);
  if (len && str[len - 1] == 10)
    str[len - 1] = 0;

  return strdup (str);
}

static void load_old_file (char *fname)
{
  FILE        *f;
  GtkTreeIter  parent;
  GtkTreeIter  iter;

  gtk_tree_model_get_iter_first (mm_model, &parent);

  f = fopen (fname, "r");
  if (f)
  {
    int depth = 0;

    for (;;)
    {
      char *id = read_line (f);
      if (!id)
	break;

      if (!strncmp (id, "<sub>", 5))
      {
	gtk_tree_store_append (mm_store, &iter, depth ? &parent : NULL);
	gtk_tree_store_set (mm_store, &iter, COLUMN_TITLE, &id[5], -1);

	parent = iter;
	depth++;
      }
      else if (!strncmp (id, "</sub>", 6))
      {
	/* sub-tree ends here */
	gtk_tree_model_iter_parent (mm_model,
				    &iter, &parent);
	if (--depth < 0)
	{
	  g_printerr (_("mediamarks: media marks file %s corrupted - ignoring rest of file\n"),
		   fname);
	  free (id);
          fclose(f);
	  return;
	}
	parent = iter;
      }
      else
      {
	play_item_t *play_item;
	char        *mrl, *str;
	int          time = 0;

	mrl = read_line (f);
	if (!mrl)
	{
	  free (id);
	  break;
	}

	str = read_line (f);
	if (!str)
	{
	  free (mrl);
	  free (id);
	  break;
	}

	sscanf (str, "%d", &time);
	play_item = play_item_new (id, mrl, time, 0);

	gtk_tree_store_append (mm_store, &iter, depth ? &parent : NULL);
	gtk_tree_store_set (mm_store, &iter,
			    COLUMN_TITLE, id,
			    COLUMN_MRL, mrl,
			    COLUMN_PLAY_ITEM, play_item,
			    -1);

	free (str);
	free (mrl);
      }

      /* discard newline */
      free (id);
      id = read_line (f);
      if (!id)
	break;
      free (id);
    }
    fclose (f);
  }
  else
    g_printerr (_("mediamarks: load failed!\n"));
}

static void cat_response_cb (GtkDialog* widget, int response, gpointer data)
{
  switch (response)
  {
  case GTK_RESPONSE_OK:
    cat_edited = TRUE;
  default:
    gtk_widget_hide (cat_dlg);
    gtk_main_quit();
  }
}

static gboolean do_category_edit (void)
{
  cat_edited = FALSE;
  gtk_window_set_modal (GTK_WINDOW(cat_dlg), TRUE);
  gtk_widget_grab_focus (cat_entry);
  window_show (cat_dlg, manage_dlg);

  gtk_main();

  return cat_edited;
}

static void edit_cb (GtkWidget* widget, gpointer data)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
  {
    play_item_t *play_item = peek_play_item (&iter);
    if (play_item)
    {
      logprintf ("mediamarks: got a play item\n");
      if (play_item_edit (play_item, PLAY_ITEM_MEDIAMARK, NULL, manage_dlg))
      {
	logprintf ("mediamarks: play item title after edit : %s\n",
		   play_item->title);
	gtk_tree_store_set (mm_store, &iter,
			    COLUMN_TITLE, play_item->title,
			    COLUMN_MRL, play_item->mrl,
			    COLUMN_PLAY_ITEM, play_item,
			    -1);
      }
    }
    else
    {
      memcpy (&cat_iter, &iter, sizeof (cat_iter));
      gtk_entry_set_text (GTK_ENTRY (cat_entry), peek_title (&iter));
      if (do_category_edit ())
	gtk_tree_store_set (mm_store, &cat_iter, COLUMN_TITLE,
			    gtk_entry_get_text (GTK_ENTRY(cat_entry)), -1);
    }
  }
}

static gboolean row_clicked_lcb (GtkWidget *widget, GdkEventButton *ev,
				 gpointer data)
{
  return list_menu_click_cb (tree, ev, (GtkMenu **) data);
}

static gboolean key_press_lcb (GtkWidget *widget, GdkEventKey *ev,
				 gpointer data)
{
  return list_menu_key_press_cb (tree, ev, (GtkMenu **) data);
}

static void delete_cb (GtkWidget* widget, gpointer data)
{
  GtkTreeIter iter;
  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
    gtk_tree_store_remove (mm_store, &iter);
}

static void copy_cb (GtkWidget* widget, gpointer data)
{
  clip_set_play_item_from_selection (mm_tree);
}

static void cut_cb (GtkWidget* widget, gpointer data)
{
  copy_cb (widget, data);
  delete_cb (widget, data);
}

static void paste_cb (GtkWidget* widget, gpointer data)
{
  play_item_t *item = clip_get_play_item ();
  if (item)
    mm_add_at_selected (item);
}

static void new_category_cb (GtkWidget* widget, gpointer data)
{
  GtkTreeIter        iter, parent;
  gboolean           in_root = TRUE;

  /*
   * find out where to create the new section
   */

  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
  {
    /*
     * is this a section or a play item ?
     */
    if (peek_play_item (&iter))
      in_root = !gtk_tree_model_iter_parent (mm_model,
					     &parent, &iter);
    else
    {
      GtkTreePath *path;
      path = gtk_tree_model_get_path (mm_model, &iter);

      if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (mm_tree), path))
      {
	in_root = 0;
	memcpy (&parent, &iter, sizeof (iter));
      }
      else
	in_root = !gtk_tree_model_iter_parent (mm_model,
					       &parent, &iter);

      gtk_tree_path_free (path);
    }
  }

  /*
   * show dialog, ask for section name
   */

  gtk_entry_set_text (GTK_ENTRY (cat_entry), _("New Category"));

  if (do_category_edit ())
  {
    /* create new category */
    gtk_tree_store_append (mm_store, &cat_iter, in_root ? NULL : &parent);
    gtk_tree_store_set (mm_store, &cat_iter, COLUMN_TITLE,
			gtk_entry_get_text (GTK_ENTRY(cat_entry)), -1);
  }
}

static void tree_changed_cb (GtkTreeModel *treemodel, GtkTreePath *arg1,
			     gpointer user_data)
{
  update_menu ();
}

static int load_mediamarks (gchar *fname)
{
  char *mmfile = read_entire_file (fname, NULL);

  if (mmfile)
  {
    xml_node_t *node;

    xml_parser_init_R (xml_parser_t *xml, mmfile, strlen (mmfile), XML_PARSER_CASE_INSENSITIVE);
    g_signal_handlers_block_by_func (G_OBJECT(mm_store), tree_changed_cb, NULL);

    if (xml_parser_build_tree_R (xml, &node) < 0)
      load_old_file (fname);
    else
    {
      load_new_file (node);
      xml_parser_free_tree (node);
    }
    xml_parser_finalize_R (xml);

    update_menu ();
    g_signal_handlers_unblock_by_func (G_OBJECT(mm_store), tree_changed_cb, NULL);

    free (mmfile);
  } else
    return 0;

  return 1;
}

void mm_import (void)
{
  gchar *fname = modal_file_dialog (_("Select media marks file to load..."),
				    TRUE, TRUE, FALSE, NULL, NULL, manage_dlg);
  if (fname)
  {
    if (!load_mediamarks (fname) && errno != ENOENT)
      display_error (FROM_GXINE, _("Media marks load failed."),
		     _("Couldn't load media marks file %s\n%s"),
		     fname, strerror (errno));
    free (fname);
  }
}

void mm_add_show (void *data)
{
  play_item_t *play_item;
  int          pos, pos_time, len;
  char	      *dot;
  const char  *title = xine_get_meta_info (stream, XINE_META_INFO_TITLE);

  play_item = playlist_get_current_item () ? : play_item_new ("", "", 0, 0);

  /* default title? try to get title from meta-info */
  dot = strrchr (play_item->mrl, '.');
  if (!dot)
    dot = play_item->mrl + strlen (play_item->mrl);
  len = strlen (play_item->title);
  if (title && dot - len > play_item->mrl &&
      !strncmp (dot - len, play_item->title, len))
  {
    free (play_item->title);
    play_item->title = strdup (title);
  }

  xine_get_pos_length (stream, &pos, &pos_time, &len);
  play_item->start_time = (len >= 0) ? pos_time : 0;

  if (play_item_edit (play_item, PLAY_ITEM_MEDIAMARK_NEW, title,
		      data ? manage_dlg : NULL))
    mm_add (play_item);
  else
    play_item_dispose (play_item);
}

void mm_add (play_item_t *play_item)
{
  GtkTreeIter iter;
  gtk_tree_store_append (mm_store, &iter, NULL);
  gtk_tree_store_set (mm_store, &iter,
		      COLUMN_TITLE, play_item->title,
		      COLUMN_MRL, play_item->mrl,
		      COLUMN_PLAY_ITEM, play_item,
		      -1);
}

static void mm_add_at_selected (play_item_t *play_item)
{
  GtkTreeIter iter, selected;
  if (gtk_tree_selection_get_selected (sel, NULL, &selected))
  {
    if (!peek_play_item (&selected))
      gtk_tree_store_insert_after (mm_store, &iter, &selected, NULL);
    else
      gtk_tree_store_insert_after (mm_store, &iter, NULL, &selected);

    gtk_tree_store_set (mm_store, &iter,
			COLUMN_TITLE, play_item->title,
			COLUMN_MRL, play_item->mrl,
			COLUMN_PLAY_ITEM, play_item,
			-1);
  }
  else
    mm_add (play_item);
}

void mm_manage_show (void)
{
  if (is_visible)
  {
    is_visible = FALSE;
    gtk_widget_hide (manage_dlg);
    mm_save();
  }
  else
  {
    is_visible = TRUE;
    window_show (manage_dlg, NULL);
  }
}

static inline void print_depth (FILE *f, int depth)
{
  fprintf (f, "%*s  ", depth, "");
}

static void save_items (FILE *f, int depth, GtkTreeIter *iter)
{
  do
  {
    const char *id = peek_title (iter);
    if (id)
    {
      play_item_t *play_item = peek_play_item (iter);
      if (play_item)
      {
	char *xml = play_item_xml (play_item, depth);
	fputs (xml, f);
	free (xml);
      }
      else
      {
	GtkTreeIter children;

	print_depth (f, depth);
	fprintf (f, "<SUB NAME=\"%s\">\n", id);

	if (gtk_tree_model_iter_children (mm_model, &children, iter))
	  save_items (f, depth+2, &children);

	print_depth (f, depth);
	fprintf (f, "</SUB>\n");
      }
    }

  } while (gtk_tree_model_iter_next (mm_model, iter));
}

void mm_save (void)
{
  gchar *fname = get_config_filename (FILE_MEDIAMARKS);
  FILE *f = open_write (fname, _("Failed to save media marks"));

  if (f)
  {
    GtkTreeIter iter;

    fprintf (f, "<GXINEMM VERSION=\"1\">\n");

    if (gtk_tree_model_get_iter_first (mm_model, &iter))
      save_items (f, 0, &iter);

    fprintf (f, "</GXINEMM>\n");

    close_write (fname, f, _("Failed to save media marks"));
  }

  g_free(fname);
}

static JSBool js_mm_add_show (JSContext *cx, uintN argc, jsval *vp)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  se_log_fncall_checkinit ("mm_add_show");
  mm_add_show (NULL);
  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static JSBool js_mm_manage_show (JSContext *cx, uintN argc, jsval *vp)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  se_log_fncall_checkinit ("mm_manage_show");
  mm_manage_show ();
  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static JSBool js_import_mediamarks (JSContext *cx, uintN argc, jsval *vp)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  se_log_fncall_checkinit ("import_mediamarks");
  mm_import ();
  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static void mm_response_cb (GtkDialog *dbox, int response, gpointer data)
{
  switch (response)
  {
  case GTK_RESPONSE_ACCEPT:
    mm_save ();
    break;
  default:
    is_visible = FALSE;
    gtk_widget_hide (manage_dlg);
  }
}

static gboolean map_cb (GtkWidget* widget, gpointer data)
{
  GdkWindow *window = gtk_widget_get_window(widget);
  gdk_window_set_events (window,
                         gdk_window_get_events (window) | GDK_BUTTON_PRESS_MASK);
  return FALSE;
}

static const GtkActionEntry buttons_data[] = {
  { "play",    GTK_STOCK_MEDIA_PLAY,   NULL,                   NULL,           NULL,                           G_CALLBACK(play_cb) },
  { "new",     GTK_STOCK_NEW,          NULL,                   NULL,           N_("Add an MRL"),               G_CALLBACK(mm_add_show) },
  { "newcat",  GTK_STOCK_DIRECTORY,    N_("_Category"),        "<Control>F",   N_("Add a category folder"),    G_CALLBACK(new_category_cb) },
  { "edit",    GTK_STOCK_EDIT,         NULL,                   "<Control>E",   N_("Edit the selected item"),   G_CALLBACK(edit_cb) },
  { "delete",  GTK_STOCK_DELETE,       NULL,                   "Delete",       N_("Delete the selected item"), G_CALLBACK(delete_cb) },
  { "copy",    GTK_STOCK_COPY,         NULL,                   NULL,           NULL,                           G_CALLBACK(copy_cb) },
  { "cut",     GTK_STOCK_CUT,          NULL,                   NULL,           NULL,                           G_CALLBACK(cut_cb) },
  { "paste",   GTK_STOCK_PASTE,        NULL,                   NULL,           NULL,                           G_CALLBACK(paste_cb) },
};

static const char buttons_structure[] =
  "<ui>\n"
    "<toolbar>\n"
      "<toolitem action='new' />\n"
      "<toolitem action='newcat' />\n"
      "<toolitem action='edit' />\n"
      "<toolitem action='delete' />\n"
      "<toolitem action='copy' />\n"
      "<toolitem action='cut' />\n"
      "<toolitem action='paste' />\n"
      "<separator />\n"
      "<toolitem action='play' />\n"
    "</toolbar>\n"
    "<popup name='item_menu'>\n"
      "<menuitem action='play' />\n"
      "<separator />\n"
      "<menuitem action='edit' />\n"
      "<menuitem action='delete' />\n"
      "<menuitem action='copy' />\n"
      "<menuitem action='cut' />\n"
      "<menuitem action='paste' />\n"
    "</popup>\n"
    "<popup name='list_menu'>\n"
      "<menuitem action='new' />\n"
      "<menuitem action='newcat' />\n"
      "<menuitem action='paste' />\n"
    "</popup>\n"
  "</ui>";

static void sel_changed_cb (GtkTreeSelection *sel, gpointer data)
{
  static const char *const items[] = { "edit", "delete", NULL };
  static const char *const cb_items[] = { "play", "copy", "cut", NULL };
  GtkTreeIter iter;
  gboolean have_selected = gtk_tree_selection_get_selected (sel, NULL, &iter);

  ui_mark_active (mm_ui, items, have_selected);
  if (have_selected)
    ui_mark_active (mm_ui, cb_items, peek_play_item (&iter) != NULL);
  else
    ui_mark_active (mm_ui, cb_items, FALSE);
}

void mediamarks_init (void)
{
  GtkWidget	       *scrolled_window, *hbox, *label;
  GtkBox	       *vbox;
  gchar                *fname;
  GtkCellRenderer      *cell;
  GtkTreeViewColumn    *column;
  GError	       *error = NULL;

  /*
   * create (for now invisible) mm_manage dialog
   */

  manage_dlg = gtk_dialog_new_with_buttons (_("Manage media marks..."), NULL, 0,
				GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
				GTK_STOCK_CLOSE, GTK_RESPONSE_DELETE_EVENT,
				NULL);
  gtk_window_set_default_size (GTK_WINDOW (manage_dlg), 400, 250);
  hide_on_delete (manage_dlg, &is_visible);
  vbox = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (manage_dlg)));

  /* buttons & menus */

  mm_ui = ui_create_manager ("mm", manage_dlg);
  GtkActionGroup *group = ui_get_action_group (mm_ui);
  gtk_action_group_add_actions (group, buttons_data,
				G_N_ELEMENTS (buttons_data), tree);
  gtk_ui_manager_add_ui_from_string (mm_ui, buttons_structure, -1, &error);
  if (error)
  {
    g_printerr (_("playlist XML: %s\n"), error->message);
    g_clear_error (&error);
  }
  menu[0] = GTK_MENU (gtk_ui_manager_get_widget (mm_ui, "/ui/list_menu"));
  menu[1] = GTK_MENU (gtk_ui_manager_get_widget (mm_ui, "/ui/item_menu"));
  play_item = gtk_action_group_get_action (group, "play");
  gtk_action_group_connect_accelerators (group);

  /*
   * init tree store
   */

  mm_store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
  mm_model = GTK_TREE_MODEL (mm_store);
  g_object_connect (G_OBJECT (mm_store),
	"signal::row-changed", G_CALLBACK (tree_changed_cb), NULL,
	"signal::row-deleted", G_CALLBACK (tree_changed_cb), NULL,
//	"signal::row-has-child-toggled", G_CALLBACK (tree_changed_cb), NULL,
	"signal::row-inserted", G_CALLBACK (tree_changed_cb), NULL,
	"signal::rows-reordered", G_CALLBACK (tree_changed_cb), NULL,
	NULL);

  /* connect up the signals now that the menus etc. are initialised */

  g_object_connect (G_OBJECT(manage_dlg),
	"signal::map-event", G_CALLBACK (map_cb), NULL,
	"signal::button-press-event", G_CALLBACK(list_menu_popup_cb), menu[0],
	"signal::key-press-event", G_CALLBACK(key_press_lcb), menu,
	"signal::response", G_CALLBACK(mm_response_cb), NULL,
	NULL);

  /* add a nice toolbar and tree view widget here */

  mm_tree = gtk_tree_view_new_with_model (mm_model);
  tree = GTK_TREE_VIEW (mm_tree);
  gtk_tree_view_set_rules_hint (tree, TRUE);

  sel = gtk_tree_view_get_selection (tree);
  g_signal_connect (G_OBJECT (sel), "changed",
		    G_CALLBACK (sel_changed_cb), NULL);

  gtk_box_pack_start (vbox, gtk_ui_manager_get_widget (mm_ui, "/ui/toolbar"),
		      FALSE, FALSE, 0);

  sel_changed_cb (sel, NULL);

  /* fill in and insert the tree view widget */

  cell = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT(cell),
		"ellipsize", 2, "width_chars", 16, NULL); /* foo...bar */
  column = gtk_tree_view_column_new_with_attributes (_("Media mark"), cell,
						     "text", COLUMN_TITLE, NULL);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_append_column (tree, column);

  cell = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT(cell),
		"ellipsize", 1, "width_chars", 16, NULL); /* ...bar */
  column = gtk_tree_view_column_new_with_attributes (_("MRL"), cell,
						     "text", COLUMN_MRL, NULL);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (tree, column);

  gtk_tree_view_set_reorderable (tree, TRUE);

  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
				  GTK_POLICY_AUTOMATIC,
				  GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (scrolled_window), mm_tree);

  g_object_connect (G_OBJECT (tree),
	"signal::row-activated", G_CALLBACK (edit_cb), NULL,
	"signal::button-press-event", G_CALLBACK (row_clicked_lcb), menu,
	NULL);

  gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 2);

  is_visible = FALSE;

  /*
   * now parse the mediamarks file
   */

  fname = get_config_filename (FILE_MEDIAMARKS);

  if (!load_mediamarks (fname))
  {
    /* load sample mediamarks */
    g_free (fname);
    fname = g_build_filename (miscdir, FILE_MEDIAMARKS, NULL);
    if (!load_mediamarks (fname))
    {
      asreprintf (&fname, "%s.gz", fname);
      load_mediamarks (fname);
    }
  }

  g_free(fname);

  update_menu ();

  /*
   * create (for now invisible) category name entry dialog
   */

  cat_dlg = gtk_dialog_new_with_buttons (_("Enter category name..."), NULL, 0,
				GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
				GTK_STOCK_OK, GTK_RESPONSE_OK,
				NULL);
  hide_on_delete (cat_dlg, NULL);
  g_signal_connect (G_OBJECT (cat_dlg), "response",
		    G_CALLBACK (cat_response_cb), NULL);
  gtk_dialog_set_default_response (GTK_DIALOG(cat_dlg), GTK_RESPONSE_OK);

  /* all we have is a simple text entry */

  hbox = gtk_hbox_new (2, 2);

  label = gtk_label_new (_("Category name:"));

  gtk_box_pack_start (GTK_BOX (hbox), label,
		      FALSE, FALSE, 0);

  cat_entry = gtk_entry_new ();
  gtk_entry_set_activates_default (GTK_ENTRY(cat_entry), TRUE);
/*
  g_signal_connect (G_OBJECT(cat_entry), "activate",
		    G_CALLBACK(cat_close_cb), cat_dlg);
*/
  gtk_box_pack_start (GTK_BOX (hbox), cat_entry, TRUE, TRUE, 0);

  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (cat_dlg))),
                      hbox, TRUE, TRUE, 0);

  /* script engine functions */

  {
    static const se_f_def_t defs[] = {
      { "mm_add_show", js_mm_add_show, 0, JSFUN_FAST_NATIVE,
	SE_GROUP_DIALOGUE, NULL, NULL },
      { "mm_manage_show", js_mm_manage_show, 0, JSFUN_FAST_NATIVE,
	SE_GROUP_DIALOGUE, NULL, NULL },
      { "import_mediamarks", js_import_mediamarks, 0, JSFUN_FAST_NATIVE,
	SE_GROUP_FILE, NULL, NULL },
      { NULL }
    };
    se_defuns (gse, NULL, defs);
  }
}
