/* Jungle Monkey
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "ggui_files.h"
#include "ggui.h"
#include "ggui_channels.h"
#include "ggui_chat.h"
#include "ggui_config.h"
#include "ggui_debug.h"
#include "ggui_download.h"
#include "ggui_properties.h"
#include "ggui_search.h"
#include "entry_dialog.h"
#include "pixmaps.h"

#include "util/util.h"
#include "util/jmgnet.h"
#include "util/sha_async.h"
#include "util/sha_cache.h"

#include <sys/types.h>
#include <dirent.h>



/* ******************** */

typedef struct _File
{
  MtpLocalMirror* mirror;
  GURL*	      url;
  gchar*      path;
  GSHAAsyncID sha_id;
  JMMSPVFSID* search_id;

  GURL*	      channel; /* channel 		     */
  gchar*      apath;   /* path to announcement	     */

} File;


static GladeXML*   menu_xml;
static GtkCList*   files_clist;
static GtkWidget*  files_popup;
		   
static JMChannel*  shown_channel;
static JMAnn*	   shown_ann;
static JMAnn*	   selected_ann;
static ShaCache*   sha_cache;

static GHashTable* url_to_file;
static GHashTable* name_to_file;


enum {
  DND_TARGET_STRING,
  DND_TARGET_URI_LIST,
};

static GtkTargetEntry dnd_target_table[] = {
  { "text/uri-list", 	0, 	DND_TARGET_URI_LIST },
  { "text/plain", 	0, 	DND_TARGET_STRING },
  { "STRING",     	0, 	DND_TARGET_STRING },
};




/* ******************** */

static void  shutdown_hfunc (gpointer key, gpointer value, gpointer user_data);

static void  load_file_func (const ElfNode* elf);
static void  save_file_hfunc (gpointer key, gpointer value, gpointer user_data);

static gint  jmann_compare (gconstpointer a, gconstpointer b);
static int   get_order (const GURL* url);
 
static void     add_file (const GURL* channel, const gchar* cpath, const gchar* path);
static void	sha_cb (GSHA* sha, gpointer user_data);
static void     add_url (const GURL* channel, const gchar* path, const gchar* name, 
			 const GURL* url);
static gboolean create_dir (const GURL* channel, const gchar* path, const gchar* name);
static void     add_dir (const GURL* channel, const gchar* cpath, const gchar* path);
static void     add_file_r (const GURL* channel, const gchar* cpath, const gchar* path);

static void	file_remove_r (const GURL* curl, JMAnn* ann);


static void  file_add (File* file);
static void  file_delete (File* file);
static File* file_get (const GURL* channel, const gchar* path);
static guint file_hash (gconstpointer p);
static gint  file_equal (gconstpointer p1, gconstpointer p2);

void on_files_select_row (GtkCList* list, gint row, gint column, GdkEvent* event);
void on_files_unselect_row (GtkCList* list, gint row, gint column, GdkEvent* event);
gint on_files_button_press (GtkCList* list, GdkEventButton *event, gpointer user_data);

gboolean on_files_drag_motion (GtkCList* list, GdkDragContext* context,
			       gint x, gint y, guint time);
void on_files_drag_data_received (GtkCList* list, GdkDragContext* context,
				  gint x, gint y, GtkSelectionData* data,
				  guint info, guint time, gpointer user_data);
static void dnd_parse (gchar* buf, gint len);


#define POPUPFUNC(F) void F(void)

POPUPFUNC (on_files_get_file);
POPUPFUNC (on_files_cancel_download);
POPUPFUNC (on_files_remove_file);
POPUPFUNC (on_files_open_url);
POPUPFUNC (on_files_remove_url);
POPUPFUNC (on_files_join_channel);
POPUPFUNC (on_files_remove_channel);
POPUPFUNC (on_files_remove_sel_channel);
POPUPFUNC (on_files_leave_channel);
POPUPFUNC (on_files_join_chat);
POPUPFUNC (on_files_remove_chat);
POPUPFUNC (on_files_leave_chat);
POPUPFUNC (on_files_join_search);
POPUPFUNC (on_files_remove_search);
POPUPFUNC (on_files_leave_search);
POPUPFUNC (on_files_add_file);
POPUPFUNC (on_files_add_directory);
POPUPFUNC (on_files_add_url);
POPUPFUNC (on_files_create_chat);
POPUPFUNC (on_files_create_search);
POPUPFUNC (on_files_create_directory);
POPUPFUNC (on_files_create_channel);
POPUPFUNC (on_files_create_channel_from_directory);
POPUPFUNC (on_files_open_channel);
POPUPFUNC (on_files_create_root_channel);
POPUPFUNC (on_files_remove_current_channel);
POPUPFUNC (on_files_leave_current_channel);
POPUPFUNC (on_files_remove);
POPUPFUNC (on_files_properties);


typedef enum {
  IT_CREATE_ROOT_CHANNEL,
  IT_CREATE_CHANNEL,
  IT_CREATE_CHANNEL_FROM_DIR,
  IT_OPEN_CHANNEL,
  IT_JOIN_CHANNEL,
  IT_LEAVE_CHANNEL,
  IT_LEAVE_CURRENT_CHANNEL,
  IT_REMOVE_CHANNEL,
  IT_REMOVE_CURRENT_CHANNEL,
  
  IT_ADD_FILE,
  IT_GET_FILE,
  IT_CANCEL_DOWNLOAD,
  IT_REMOVE_FILE,

  IT_CREATE_DIR,
  IT_ADD_DIR,
  IT_GET_DIR,
  IT_REMOVE_DIR,

  IT_ADD_URL,
  IT_OPEN_URL,
  IT_REMOVE_URL,

  IT_CREATE_CHAT,
  IT_JOIN_CHAT,
  IT_LEAVE_CHAT,
  IT_REMOVE_CHAT,

  IT_CREATE_SEARCH,
  IT_JOIN_SEARCH,
  IT_LEAVE_SEARCH,
  IT_REMOVE_SEARCH,

  IT_REMOVE,

  IT_PROPERTIES,

  IT_SEPARATOR1,
  IT_SEPARATOR2,
  IT_SEPARATOR3,

  IT_EXIT

} Item;

static void toolbar_append (Item item, GdkPixmap* gpixmap, GdkBitmap* gbitmap,
			    const gchar* tooltip, void* func);

static void update_items (void* widget);
static void show_item (void* widget, Item it);



void
ggui_file_init (void)
{
  menu_xml = ggui_get_glade_xml("files_popup", "master_popup.glade");
  g_assert (menu_xml != NULL);
  glade_xml_signal_autoconnect (menu_xml);

  /* Get the widgets */
  files_clist = GTK_CLIST(glade_xml_get_widget(jmw_xml,   "files_clist"));
  files_popup = GTK_WIDGET(glade_xml_get_widget(menu_xml, "files_popup"));

  g_assert (files_clist != NULL);
  g_assert (files_popup != NULL);

  gtk_clist_column_title_passive (files_clist, 0);
  gtk_clist_column_title_passive (files_clist, 1);

  /* Set up tooltips */
  ggui_set_tooltip (GTK_WIDGET(files_clist), 
		    _("Files, directories, and channels available. "
		      "Right-click on something to get a popup-menu of options.  "
		      "Double-click on something to activate it."));

  /* Set up drop and drag */
  gtk_drag_dest_set (GTK_WIDGET(files_clist),
		     GTK_DEST_DEFAULT_ALL,
		     dnd_target_table, 
		     sizeof(dnd_target_table) / sizeof(dnd_target_table[0]),
		     GDK_ACTION_COPY /* | GDK_ACTION_MOVE */);


  url_to_file = g_hash_table_new (gnet_url_hash, gnet_url_equal);
  name_to_file = g_hash_table_new (file_hash, file_equal);
  sha_cache = sha_cache_new ();
}


void
ggui_file_shutdown (void)
{
  /* Reset vars */
  shown_channel = NULL;
  shown_ann     = NULL;
  selected_ann  = NULL;
  g_hash_table_destroy (url_to_file);
  url_to_file = NULL;

  /* Delete sha cache */
  sha_cache_delete (sha_cache);
  sha_cache = NULL;

  /* Delete files */
  g_hash_table_foreach (name_to_file, shutdown_hfunc, NULL);
  g_hash_table_destroy (name_to_file);
  name_to_file = NULL;
}


static void
shutdown_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  file_delete ((File*) value);
}


void
ggui_file_load (void)
{
  gchar* sc_name;
  FILE*  sc_file;

  g_return_if_fail (jm_btp_bpeer);

  /* Load sha cache */
  sc_name = g_strconcat (jm_config_home, G_DIR_SEPARATOR_S, "sha-cache", NULL);
  sc_file = fopen (sc_name, "r");
  if (sc_file)
    {
      sha_cache_read (sha_cache, sc_file);
      fclose (sc_file);
    }
  g_free (sc_name);

  ggui_load_elf ("file", load_file_func);

}


static void
load_file_func (const ElfNode* elf)
{
  const gchar* curl_str;
  GURL* curl = NULL;
  const gchar* apath;
  const gchar* url_str;
  GURL* url = NULL;
  const gchar* path;
  File* file;
  JMAnn* ann;

  curl_str = elf_get_attribute (elf, "curl");
  if (!curl_str) return;
  curl = gnet_url_new (curl_str);
  if (!curl) return;
  apath = elf_get_attribute (elf, "apath");
  if (!apath) goto done;

  /* Clean URL */
  if (!SAFESTRCMP(jm_hostname, curl->hostname) &&
      (!jm_btp_last_port || curl->port == jm_btp_last_port))
    gnet_url_set_port (curl, jm_btp_bpeer->port);
  /* Ignore if we have */
  if (file_get (curl, apath)) goto done;

  url_str = elf_get_attribute (elf, "url");
  if (url_str) url = gnet_url_new (url_str);
  path = elf_get_attribute (elf, "path");

  if (!strcmp (elf->name, "file") && path)
    {
      GURL* murl;
      MtpLocalMirror* mirror;
      GSHA* sha;

      /* Ignore if files does not exist */
      if (!file_exists(path))
	goto done;

      /* Make MTP */
      murl = gnet_url_clone (jm_mtp_rvous_url);
      gnet_url_set_resource (murl, apath);
      mirror = mtp_mirror_path (jm_mtp_server, murl, path);
      if (!mirror) 
	{
	  gnet_url_delete (murl);
	  goto done;
	}

      file = g_new0 (File, 1);
      file->mirror  = mirror;
      file->path    = g_strdup (path);
      file->url     = murl;
      file->apath   = g_strdup (apath);
      file->channel = curl; 
      file->search_id = ggui_search_vfs_add (murl, g_basename(path), file_size (path));
      file_add (file);

      /* Get SHA and announce (or get sha asynchronously) */
      sha = sha_cache_get (sha_cache, path);
      if (sha)
	{
	  GGUIP (8, "sha cache hit %s\n", path);
	  ggui_channel_add_path_sha (curl, apath, murl, sha);
	  gnet_sha_delete (sha);
	}
      else
	file->sha_id = gnet_sha_new_file_async (path, sha_cb, file);
      curl = NULL;
    }
  else if (!strcmp (elf->name, "url") && url)
    {
      ann = ggui_channel_add_path (curl, apath, url);
      if (!ann) goto done;

      file = g_new0 (File, 1);
      file->url     = url;
      file->channel = curl; curl = NULL;
      file->apath   = g_strdup (apath);
      file_add (file);
    }
  else if (!strcmp (elf->name, "dir"))
    {
      ann = ggui_channel_add_path (curl, apath, NULL);
      if (!ann) goto done;

      file = g_new0 (File, 1);
      file->channel = curl; curl = NULL;
      file->apath = g_strdup (apath);
      file_add (file);
    }

 done:
  gnet_url_delete (curl);
  gnet_url_delete (url);
}



void
ggui_file_save (void)
{
  gchar*  sc_name;
  FILE*   sc_file;

  /* Save sha cache */
  sc_name = g_strconcat (jm_config_home, G_DIR_SEPARATOR_S, "sha-cache", NULL);
  sc_file = fopen (sc_name, "w");
  if (sc_file)
    {
      sha_cache_write (sha_cache, sc_file);
      fclose (sc_file);
    }
  else
    g_warning ("Can't open file %s/sha-cache\n", jm_config_home);
  g_free (sc_name);

  /* Save files */
  ggui_save_hashtable ("file", name_to_file, save_file_hfunc);
}


static void
save_file_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  File*    file = (File*) value;
  FILE*    File = (FILE*) user_data;
  ElfNode* elf;
  gchar*   buf;
  gint     len;
  gchar*   curl;

  g_return_if_fail (file->channel && file->apath);

  elf = elf_new (NULL);
  curl = gnet_url_get_nice_string (file->channel);
  elf_set_attribute (elf, "curl", curl);
  g_free (curl);
  elf_set_attribute (elf, "apath", file->apath);

  if (file->mirror)
    {
      elf_set_name (elf, "file");
      elf_set_attribute (elf, "path", file->path);
    }
  else if (file->url)
    {
      gchar* url;

      elf_set_name (elf, "url");
      url = gnet_url_get_nice_string (file->url);
      elf_set_attribute (elf, "url", url);
      g_free (url);
    }
  else
    {
      elf_set_name (elf, "dir");
    }

  elf_write (elf, &buf, &len);
  fwrite (buf, len, 1, File);

  elf_delete (elf);
  g_free (buf);
}



/* ********** */

static gint 
jmann_compare (gconstpointer a, gconstpointer b)
{
  const JMAnn* ann1 = (const JMAnn*) a;
  const JMAnn* ann2 = (const JMAnn*) b;
  gint order1, order2;
      
  order1 = get_order (ann1->url);
  order2 = get_order (ann2->url);

  if (order1 < order2)      return -1;
  else if (order1 > order2) return 1;

  return strcmp (ann1->name, ann2->name);
}

      
/* Order: channel (0), dir (1), chat(2), search(3), non-file (4), file (5) */
int
get_order (const GURL* url)
{
  if (!url)
    return 1;
  else if (gnet_url_is_scheme (url, "jm"))
    return 0;
  else if (gnet_url_is_scheme (url, "jmchat"))
    return 2;
  else if (gnet_url_is_scheme (url, "jmmsp"))
    return 3;
  else if (gnet_url_is_scheme (url, "mtp"))
    return 5;

  return 4;
}


/* ******************** */

void
ggui_file_show (JMChannel* channel, JMAnn* ann)
{
  GList* sorted;
  GList* i;

  g_return_if_fail ((channel && ann) || (!channel && !ann));

  /* Ignore if we're already showing this */
  if (channel == shown_channel && ann == shown_ann)
    return;
      
  GGUIP (8, "file_show %s %s\n", channel?jmchannel_get_name(channel):"<null>",
	 ann?ann->path:"<null>");

  /* Reset selected_ann */
  selected_ann = NULL;

  /* Clear the list */
  gtk_clist_clear (files_clist);

  /* Set the shown channel/ann */
  shown_channel = channel;
  shown_ann     = ann;

  /* Update tool bar */
  ggui_file_update_toolbar ();

  /* We're done if the channel/ann is NULL */
  if (!channel && !ann)
    return;

  gtk_clist_freeze (files_clist);

  /* Add all children files */
  sorted = g_list_copy (ann->kids);
  sorted = g_list_sort (sorted, jmann_compare);
  sorted = g_list_reverse (sorted);
  for (i = sorted; i != NULL; i = i->next)
    ggui_file_announce_update (channel, (JMAnn*) i->data);
  g_list_free (sorted);
      
  /* Add a back button if this has a parent */
  if (ann->parent)
    {
      int row;
      gchar* texts[2] = {NULL, NULL};

      row = gtk_clist_prepend (files_clist, texts);  
      gtk_clist_set_row_data (files_clist, row, back_pixmap);
      gtk_clist_set_pixtext (files_clist, row, 0, _(".."), 5, 
			     back_pixmap, back_mask);
    }

  gtk_clist_thaw (files_clist);
}


void
ggui_file_get_shown (JMChannel** channelp, JMAnn** annp)
{
  g_return_if_fail (channelp && annp);

  *channelp = shown_channel;
  *annp     = shown_ann;
}


/* ******************** */

gboolean
ggui_file_have (const GURL* url)
{

  return (g_hash_table_lookup(url_to_file, url) != NULL);
}


/* ******************** */

void
ggui_file_announce_update (JMChannel* channel, JMAnn* ann)
{
  gint       row;
  GdkPixmap* pixmap;
  GdkBitmap* mask;
  gint 	     percent = -1;
  gint       length;
  gchar*     text1 = NULL;
  gchar*     text2 = NULL;

  g_return_if_fail (channel && ann);
  
  /* Ignore if not shown */
  if (channel != shown_channel || ann->parent != shown_ann)
    return;

  /* Ignore if an index file */
  if (jmann_is_index (ann))
    return;

  /* Add to clist if not there */
  row = gtk_clist_find_row_from_data (files_clist, ann);
  if (row < 0)
    {
      gchar* texts[2] = {NULL, NULL};

      /* Insert after back, if back, otherwise prepend */
      if (gtk_clist_get_row_data(files_clist, 0) == back_pixmap)
	row = gtk_clist_insert (files_clist, 1, texts);
      else
	row = gtk_clist_prepend (files_clist, texts);
      gtk_clist_set_row_data (files_clist, row, ann);
    }

  /* Get download status */
  if (ann->url)
    percent = ggui_download_get_percent_complete(ann->url);
  if (percent >= 0 && percent < 100)
    text1 = g_strdup_printf (_("%s (downloading)"), ann->name);
  else if (percent == -2)
    text1 = g_strdup_printf (_("%s (down)"), ann->name);
  else
    text1 = g_strdup (ann->name);

  /* Get length */
  length = jmann_get_attr_int (ann, "length");
  if (length)
    text2 = filesize_to_string(length);

  if (ann->url)
    ggui_get_pixmaps (ann->url, &pixmap, &mask);
  else
    {
      pixmap = folder_pixmap;
      mask   = folder_mask;
    }
      
  ggui_clist_update_row2 (files_clist, row, text1, text2, pixmap, mask);
  g_free (text1);
  g_free (text2);
}


void
ggui_file_announce_remove (JMChannel* channel, JMAnn* ann)
{
  gint row;

  g_return_if_fail (channel && ann);

  if (channel == shown_channel && ann == shown_ann)
    ggui_file_show (channel, channel->root_ann);
      
  row = gtk_clist_find_row_from_data (files_clist, ann);
  if (row >= 0)	gtk_clist_remove (files_clist, row);

  /* If the file had a File object, it would have been deleted in
     ggui_file_remove */
}



/* ************************************************************ */

static GtkFileSelection* add_file_selector;
static GURL* 		 add_file_channel;
static gchar*		 add_file_path;

static void add_file_ok (GtkButton* button, GtkFileSelection* fs);
static void add_file_cancel (GtkButton* button, GtkFileSelection* fs);


void
ggui_file_add_file (const GURL* channel, const gchar* cpath, const gchar* path)
{
  if (!path)
    {
      if (add_file_selector)
	{
	  gtk_widget_show (GTK_WIDGET(add_file_selector));
	  return;
	}

      if (channel && cpath)
	{
	  add_file_channel = gnet_url_clone (channel);
	  add_file_path    = g_strdup (cpath);
	}

      add_file_selector = GTK_FILE_SELECTION(gtk_file_selection_new(_("Add file to channel")));
      GTK_WINDOW(add_file_selector)->position = GTK_WIN_POS_MOUSE;
      gtk_signal_connect (GTK_OBJECT (add_file_selector), "destroy",
			  (GtkSignalFunc) add_file_cancel, NULL);
      gtk_signal_connect (GTK_OBJECT (add_file_selector->ok_button), "clicked", 
			  (GtkSignalFunc) add_file_ok, NULL);
      gtk_signal_connect (GTK_OBJECT (add_file_selector->cancel_button), "clicked",  
			  (GtkSignalFunc) add_file_cancel, NULL);
      gtk_widget_show (GTK_WIDGET(add_file_selector));

      return;
    }

  add_file (channel, cpath, path);
}


static void
add_file_ok (GtkButton* button, GtkFileSelection* fs) 
{
  gchar* path;

  if (!add_file_selector)
    return;

  path = gtk_file_selection_get_filename (add_file_selector);
  if (path && *path)
    add_file (add_file_channel, add_file_path, path);
  add_file_cancel (NULL, NULL);
}


static void
add_file_cancel (GtkButton* button, GtkFileSelection* fs)
{
  if (!add_file_selector)
    return;

  gtk_widget_destroy(GTK_WIDGET(add_file_selector));
  add_file_selector = NULL;
  gnet_url_delete (add_file_channel);
  add_file_channel = NULL;
  g_free (add_file_path);
  add_file_path = NULL;
}


static void
add_file (const GURL* channel, const gchar* cpath, const gchar* path)
{
  JMAnn*     pann;
  GURL*      url;
  gchar*     resource;
  MtpLocalMirror* mirror;
  File*	     file;
  GSHA*	     sha;

  g_return_if_fail (channel);
  g_return_if_fail (cpath);
  g_return_if_fail (jm_mtp_rvous_url && jm_mtp_server);

  /* Check if file exists */
  if (!path || !*path || !is_file (path))
    {
      ggui_show_message ("warning", _("Bad path: %s\n"), path?path:"<null>");
      return;
    }

  /* Check path */
  if (!ggui_channel_check_path(channel, cpath, g_basename(path)))
    return;

  /* Create MTP URL */
  url = gnet_url_clone (jm_mtp_rvous_url);
  pann = ggui_channel_get_ann (channel, cpath);
  if (!strcmp(pann->path, "/"))
    resource = g_strconcat ("/", g_basename(path), NULL);
  else
    resource = g_strconcat (pann->path, "/", g_basename(path), NULL);
  gnet_url_set_resource (url, resource);
  g_free (resource);

  /* Make sure we aren't already mirroring this file */
  g_return_if_fail (!g_hash_table_lookup (url_to_file, url));

  /* Create MTP client */
  mirror = mtp_mirror_path (jm_mtp_server, url, path);
  if (!mirror)
    {
      ggui_show_message ("warning", _("MTP Error - could not add file: %s\n"), path);
      gnet_url_delete (url);
      return;
    }

  /* Save file in list */
  file = g_new0 (File, 1);
  file->mirror = mirror;
  file->url = url;
  file->path = g_strdup (path);
  file->channel = gnet_url_clone (channel);
  file->apath = jmann_make_path (pann, g_basename(path));
  file->search_id = ggui_search_vfs_add (url, g_basename(path), file_size (path));
  file_add (file);

  /* Get SHA */
  sha = sha_cache_get (sha_cache, path);

  /* If we have the SHA, send an announcement now */
  if (sha)
    {
      GGUIP (8, "sha cache hit %s\n", path);
      ggui_channel_add_path_sha (channel, file->apath, url, sha);
      gnet_sha_delete (sha);
    }

  /* Otherwise, calculate the SHA asynchronously */
  else
    file->sha_id = gnet_sha_new_file_async (path, sha_cb, file);
}


static void
sha_cb (GSHA* sha, gpointer user_data)
{
  File* file = (File*) user_data;

  file->sha_id = NULL;

  if (sha)
    {
      /* Add to cache */
      sha_cache_add (sha_cache, file->path, sha);

      /* Announce */
      ggui_channel_add_path_sha (file->channel, file->apath, file->url, sha);
      gnet_sha_delete (sha);

      /* TODO: Mirror on SHA? */
    }
  else
    g_warning ("Couldn't calculate SHA for %s\n", file->apath);
}



/* ******************** */

static GtkFileSelection* add_dir_selector;
static GURL* 		 add_dir_channel;
static gchar*		 add_dir_path;

static void add_dir_ok (GtkButton* button, GtkFileSelection* fs);
static void add_dir_cancel (GtkButton* button, GtkFileSelection* fs);


void
ggui_file_add_dir (const GURL* channel, const gchar* cpath, const gchar* path)
{
  if (!path)
    {
      if (add_dir_selector)
	{
	  gtk_widget_show (GTK_WIDGET(add_dir_selector));
	  return;
	}

      if (channel && cpath)
	{
	  add_dir_channel = gnet_url_clone (channel);
	  add_dir_path    = g_strdup (cpath);
	}

      add_dir_selector = GTK_FILE_SELECTION(gtk_file_selection_new(_("Add directory to channel")));
      GTK_WINDOW(add_dir_selector)->position = GTK_WIN_POS_MOUSE;
      gtk_signal_connect (GTK_OBJECT (add_dir_selector), "destroy",
			  (GtkSignalFunc) add_dir_cancel, NULL);
      gtk_signal_connect (GTK_OBJECT (add_dir_selector->ok_button), "clicked", 
			  (GtkSignalFunc) add_dir_ok, NULL);
      gtk_signal_connect (GTK_OBJECT (add_dir_selector->cancel_button), "clicked",  
			  (GtkSignalFunc) add_dir_cancel, NULL);
      gtk_widget_show (GTK_WIDGET(add_dir_selector));

      return;
    }

  add_dir (channel, cpath, path);
}


static void
add_dir_ok (GtkButton* button, GtkFileSelection* fs) 
{
  gchar* path;

  if (!add_dir_selector)
    return;

  path = gtk_file_selection_get_filename (add_dir_selector);
  if (path && *path)
    add_dir (add_dir_channel, add_dir_path, path);
  add_dir_cancel (NULL, NULL);
}


static void
add_dir_cancel (GtkButton* button, GtkFileSelection* fs)
{
  if (!add_dir_selector)
    return;

  gtk_widget_destroy(GTK_WIDGET(add_dir_selector));
  add_dir_selector = NULL;
  gnet_url_delete (add_dir_channel);
  add_dir_channel = NULL;
  g_free (add_dir_path);
  add_dir_path = NULL;
}


void
add_dir (const GURL* channel, const gchar* cpath, const gchar* path)
{
  g_return_if_fail (channel);
  g_return_if_fail (cpath);

  /* Add it */
  add_file_r (channel, cpath, path);
}


static void
add_file_r (const GURL* channel, const gchar* cpath, const gchar* path)
{
  /* If it's a dir, create a dir and add each file in the dir */
  if (is_dir (path))
    {
      gchar* nicepath;
      gchar* p;
      gchar* basename;
      DIR*   dir;
      gchar* dir_name;
      gchar* cpath_new;
      struct dirent* de;

      nicepath = g_strdup (path);
      p = strrchr (nicepath, G_DIR_SEPARATOR);
      if (p && p[1] == '\0') /* strip off last separator */
	*p = '\0';
      basename = g_basename (nicepath);

      /* Create directory */
      if (create_dir (channel, cpath, basename))
	{
	  g_free (nicepath);
	  return;
	}

      /* Open dir */
      dir = opendir (path);
      if (!dir)
	{
	  g_free (nicepath);
	  return;
	}

      /* Create new cpath (must escape name) */
      dir_name = stresc (basename, "/\\", '\\');
      if (!strcmp (cpath, "/"))
	cpath_new = g_strconcat ("/", dir_name, NULL);
      else
	cpath_new = g_strconcat (cpath, "/", dir_name, NULL);
      g_free (dir_name);

      while ((de = readdir(dir)) != NULL)
	{
	  gchar* name = de->d_name;
	  gchar* path_new;

	  /* Skip . and .. */
	  if (!strcmp(name, ".") || !strcmp(name, ".."))
	    continue;

	  path_new = g_strconcat (nicepath, G_DIR_SEPARATOR_S, name, NULL);
	  add_file_r (channel, cpath_new, path_new);
	  g_free (path_new);
	}

      g_free (nicepath);
      g_free (cpath_new);

      /* Close dir */
      closedir (dir);
    }

  /* If it's a file, add the file */
  else if (is_file (path))
    {
      add_file (channel, cpath, path);
    }
}






/* ******************** */

static EntryDialog* add_url_dialog;
static GURL* 	    add_url_channel;
static gchar*	    add_url_path;

static void add_url_ok (gchar* url, gchar* name);
static void add_url_cancel (void);


void
ggui_file_add_url (const GURL* channel, const gchar* path, const gchar* name, const GURL* url)
{
  if (!name || !url)
    {
      if (add_url_dialog)
	{
	  entry_dialog_show (add_url_dialog);
	  return;
	}

      if (channel && path)
	{
	  add_url_channel = gnet_url_clone (channel);
	  add_url_path    = g_strdup (path);
	}

      add_url_dialog  = entry_dialog_new(_("Add URL"), _("URL"), _("Name"),
					 add_url_ok, add_url_cancel);
      return;
    }

  add_url (channel, path, name, url);
}



static void
add_url_ok (gchar* url, gchar* name)
{
  GURL* gurl;
  
  gurl = gnet_url_new (url);
  if (gurl)
    {
      add_url (add_url_channel, add_url_path, name, gurl);
      gnet_url_delete (gurl);
    }
  else
    ggui_show_message ("warning", _("Malformed URL: %s\n"), url);

  add_url_cancel ();
}


static void
add_url_cancel (void)
{
  add_url_dialog = NULL;
  gnet_url_delete (add_url_channel);
  add_url_channel = NULL;
  g_free (add_url_path);
  add_url_path = NULL;
}


static void
add_url (const GURL* channel, const gchar* path, const gchar* name, const GURL* url)
{
  File*  file;
  JMAnn* ann;

  g_return_if_fail (url);
  g_return_if_fail (channel);
  g_return_if_fail (path);

  /* Check if file exists */
  if (!path || !*path)
    {
      ggui_show_message ("warning", _("Bad path: %s\n"), path?path:"<null>");
      return;
    }

  /* Check if path ok */
  if (channel && path && !ggui_channel_check_path (channel, path, name))
    return;

  /* Advertise on channel */
  ann = ggui_channel_add (channel, path, name, url);
  g_return_if_fail (ann);

  /* Create file */
  file = g_new0 (File, 1);
  file->url = gnet_url_clone (url);
  file->channel = gnet_url_clone (channel);
  file->apath = g_strdup (ann->path);
  file_add (file);
}




/* ******************** */

static EntryDialog* create_dir_dialog;
static GURL* 	    create_dir_channel;
static gchar*	    create_dir_path;

static void create_dir_ok (gchar* name, gchar* ignore);
static void create_dir_cancel (void);



void
ggui_file_create_dir (const GURL* channel, const gchar* path, const gchar* name)
{
  if (!name)
    {
      if (create_dir_dialog)
	{
	  entry_dialog_show (create_dir_dialog);
	  return;
	}

      if (channel && path)
	{
	  create_dir_channel = gnet_url_clone (channel);
	  create_dir_path    = g_strdup (path);
	}

      create_dir_dialog  = entry_dialog_new(_("Create directory"), 
					    _("Directory name"), NULL,
					    create_dir_ok, create_dir_cancel);
      return;
    }

  create_dir (channel, path, name);
}


static void
create_dir_ok (gchar* name, gchar* ignore)
{
  create_dir (create_dir_channel, create_dir_path, name);
  create_dir_cancel ();
}


static void
create_dir_cancel (void)
{
  create_dir_dialog = NULL;
  gnet_url_delete (create_dir_channel);
  create_dir_channel = NULL;
  g_free (create_dir_path);
  create_dir_path = NULL;
}



static gboolean
create_dir (const GURL* channel, const gchar* path, const gchar* name)
{
  File*  file;
  JMAnn* ann;

  g_return_val_if_fail (name, TRUE);

  /* Check if name ok */
  if (!*name)
    {
      ggui_show_message ("warning", _("Bad directory name: %s\n"), name);
      return TRUE;
    }

  /* Check if path ok */
  if (channel && path && !ggui_channel_check_path (channel, path, name))
    return TRUE;

  /* Advertise on channel */
  ann = ggui_channel_add (channel, path, name, NULL);
  g_return_val_if_fail (ann, TRUE);

  /* Create file */
  file = g_new0 (File, 1);
  file->channel = gnet_url_clone (channel);
  file->apath = g_strdup (ann->path);
  file_add (file);

  return FALSE;
}



/* ******************** */

void
ggui_file_remove (const GURL* curl, const gchar* apath)
{
  JMChannel* channel;
  JMAnn*     ann;
  File*      file;
  
  /* Get channel, announcement */
  channel = ggui_channel_get (curl);
  if (!channel) return;
  ann = jmchannel_get (channel, apath);
  if (!ann) return;

  /* Get file */
  file = file_get (curl, apath);
  if (!file) return;

  /* Remove recursively (it may be a directory with our files in it) */
  file_remove_r (curl, ann);

  /* Announce remove (this will call ggui_file_announce_remove) */
  ggui_channel_remove_path (curl, apath);
}


static void
file_remove_r (const GURL* curl, JMAnn* ann)
{
  GList* i;
  File*  file;

  /* Remove children */
  for (i = ann->kids; i != NULL; i = i->next)
    {
      JMAnn* kann;

      kann = (JMAnn*) i->data;
      file_remove_r (curl, kann);
    }

  /* Get self */
  file = file_get (curl, ann->path);
  if (!file) return;

  /* Remove */
  if (file->url) g_hash_table_remove (url_to_file, file->url);
  g_hash_table_remove (name_to_file, file);
  file_delete (file);
}


void
ggui_file_leave (const GURL* curl)
{
  JMChannel* channel;

  /* Remove are sent automatically on a leave.  We just remove the
     files we had advertised. */

  channel = ggui_channel_get (curl);
  if (!channel) return;

  file_remove_r (curl, channel->root_ann);
}


/* ******************** */

void
ggui_file_update (const GURL* url)
{
  GList* i;

  g_return_if_fail (url);

  if (!shown_channel || !shown_ann)
    return;

  for (i = shown_ann->kids; i != NULL; i = i->next)
    {
      JMAnn* ann = (JMAnn*) i->data;
      if (ann->url && gnet_url_equal(url, ann->url))
	ggui_file_announce_update (shown_channel, ann);
    }
  /* TODO: Hash from url to anns (may be collisions...) */
}


/* ******************** */



static void
file_add (File* file)
{
  g_return_if_fail (file);
  g_return_if_fail (file->channel && file->apath);

  g_hash_table_insert (name_to_file, file, file);
  if (file->url)
    g_hash_table_insert (url_to_file, file->url, file);
}


static void
file_delete (File* file)
{
  if (!file)
    return;

  mtp_unmirror (file->mirror);
  gnet_url_delete (file->url);
  g_free (file->path);
  if (file->sha_id)
    gnet_sha_new_file_async_cancel (file->sha_id);
  if (file->search_id)
    ggui_search_vfs_remove (file->search_id);
  gnet_url_delete (file->channel);
  g_free (file->apath);
  g_free (file);
}


static File*
file_get (const GURL* channel, const gchar* path)
{
  File key;

  key.channel = (GURL*)  channel;
  key.apath   = (gchar*) path;

  return (File*) g_hash_table_lookup (name_to_file, &key);
}


static guint
file_hash (gconstpointer p)
{
  const File* file = (const File*) p;
  guint h;

  g_return_val_if_fail (file, 0);

  h = g_str_hash(file->apath) ^ gnet_url_hash (file->channel);

  return h;
}


static gint
file_equal (gconstpointer p1, gconstpointer p2)
{
  const File* file1 = (const File*) p1;
  const File* file2 = (const File*) p2;

  g_return_val_if_fail (file1, 0);
  g_return_val_if_fail (file2, 0);

  if (!strcmp (file1->apath, file2->apath) &&
      gnet_url_equal (file1->channel, file2->channel))
    return 1;

  return 0;
}



/* ************************************************************ */


void 
on_files_select_row (GtkCList* list, gint row, gint column, GdkEvent* event)
{
  gpointer selectee;
  
  selectee = gtk_clist_get_row_data(list, row);
  if (selectee != back_pixmap)
    {
      selected_ann = (JMAnn*) selectee;
      ggui_file_update_toolbar ();
    }
}


void
on_files_unselect_row (GtkCList* list, gint row, gint column, GdkEvent* event)
{
  selected_ann = NULL;
  ggui_file_update_toolbar ();
}



/* Return TRUE if we handle event; FALSE to pass on */
gint
on_files_button_press (GtkCList* list, GdkEventButton *event, gpointer user_data)
{
  gint row, column;
  gboolean rv = FALSE;

  /* User double clicked - activate the thing selected */
  if (event->type == GDK_2BUTTON_PRESS && 
      (event->button == 1 || event->button == 2))
    {
      /* Get the selection information */
      if (gtk_clist_get_selection_info(list, event->x, event->y, &row, &column))
	{
	  /* Select the clicked row, if there is one */
	  gtk_clist_select_row (list, row, 0);
	
	  /* The first row is 'back' */
	  if (gtk_clist_get_row_data(list, row) == back_pixmap &&
	      shown_channel && shown_ann && shown_ann->parent)
	    {
	      ggui_file_show (shown_channel, shown_ann->parent);
	      return TRUE;
	    }

	  /* Double clicks on files and channels activate them.  We
	     don't do anything for directories because that might be
	     annoying. */
	  if (selected_ann && selected_ann->url)
	    {
	      /* Use SHA if MTP.  on_files_get_file will do this */
	      if (gnet_url_is_scheme (selected_ann->url, "mtp") &&
		  !ggui_file_have (selected_ann->url))
		on_files_get_file ();
	      else /* Pass to ggui_activate */
		ggui_activate (selected_ann->url);
	    }

	  if (selected_ann)
	    { 
	      /* Double clicks on channels also show them */
	      if (selected_ann->url && GURL_IS_CHANNEL(selected_ann->url))
		{
		  ggui_channel_select (selected_ann->url);
		  /* WARNING: this changes selected_ann as a side effect. */
		}
	      else if (!selected_ann->url)
		{
		  ggui_file_show (shown_channel, selected_ann);
		}
	    }
	}
      else
	gtk_clist_unselect_all (list);

      rv = TRUE;
    }

  /* User right-clicked - show context menu */
  else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
    {
      GList* i;

      /* Select the clicked row, if there is one */
      if (gtk_clist_get_selection_info (list, event->x, event->y, &row, &column))
	gtk_clist_select_row (list, row, 0);
      else
	gtk_clist_unselect_all (list);

      /* Hide everything */
      for (i = GTK_MENU_SHELL(files_popup)->children; i != NULL; i = i->next)
	{
	  GtkWidget* menu_item = GTK_WIDGET(i->data);
	  g_assert (GTK_MENU_ITEM(menu_item) != NULL);
	  gtk_widget_hide (menu_item);
	}

      /* Update the popup menu */
      update_items (NULL);

      /* Pop it up */
      gtk_menu_popup (GTK_MENU(files_popup), NULL, NULL, 
		      NULL, NULL, 3, event->time);

      rv = TRUE;
    }

  return rv;
}


/* ************************************************************ */


gboolean
on_files_drag_motion (GtkCList* list, GdkDragContext* context,
		      gint x, gint y, guint time)
{
  /* Get the selection information */
  if (shown_channel && shown_ann &&
      (shown_channel->is_local || !shown_channel->is_channels_only))
    {
      gdk_drag_status (context, context->suggested_action, time);
    }
    
  return TRUE;
}


void  
on_files_drag_data_received (GtkCList* list, GdkDragContext* context,
			     gint x, gint y, GtkSelectionData* sel_data,
			     guint info, guint time, gpointer user_data)
{
  /* Check if the data is valid */
  if ((sel_data->length >= 0) && (sel_data->format == 8))
    {
      if (shown_channel && shown_ann)
	{
	  if (!shown_channel->is_local && shown_channel->is_channels_only)
	    {
	      ggui_show_message ("warning", _("This channel allows channels only"));
	      goto error;
	    }

	  switch (info)
	    {
	    case DND_TARGET_STRING:
	    case DND_TARGET_URI_LIST:
	      {
		dnd_parse (sel_data->data, sel_data->length);
		gtk_drag_finish (context, TRUE, TRUE, time);
		return;
	      }
	    }
	}
    }

 error:
  gtk_drag_finish (context, FALSE, TRUE, time);

  return;
}



static void
dnd_parse (gchar* buf, gint len)
{
  gchar* token;
  gchar* next = NULL;
  
  g_return_if_fail (shown_channel && shown_ann);

  /* Parse each line */
  while ((token = strtokq(buf, "\n\r", &next)) != NULL)
    {
      buf = NULL;

      /* Skip the "file:" part, if there is one */
      if (strstr(token, "file:") == token)
	token = &token[sizeof("file:") - 1];

      /* Add it as a file, if it's absolute */
      if (token[0] == '/')
	{
	  gchar* path;
	  gchar* i;

	  /* Skip /'s */
	  while (token[1] == '/') token++;

	  /* Copy and unescape.  Mozilla likes to repeat file name at
             end. */
	  path = i = g_malloc(strlen(token)+1);
	  while (*token)
	    {
	      if (token[0] == '%' && token[1] == '2' && token[2] == '0')
		{
		  *i = ' ';
		  token += 2;
		}
	      else if (token[0] == ' ')
		break;
	      else
		*i = *token;

	      ++i;
	      ++token;
	    }
	  *i = '\0';

	  add_file_r (shown_channel->url, shown_ann->path, path);
	}

      /* Otherwise, try to add as a URL */
      else 
	{
	  GURL* url;

	  url = gnet_url_new (token);
	  if (url)
	    {
	      add_url (shown_channel->url, shown_ann->path, token, url);
	      gnet_url_delete (url);
	    }
	}
    }
}



/* ******************** */

void
ggui_file_show_toolbar (void)
{
  /* Add everything to the toolbar */

  toolbar_append (IT_OPEN_CHANNEL, channel_pixmap, channel_mask, 
		  _("Open channel"), on_files_open_channel);
  toolbar_append (IT_CREATE_CHANNEL, create_channel_pixmap, create_channel_mask, 
		  _("Create channel"), on_files_create_channel);
  toolbar_append (IT_CREATE_ROOT_CHANNEL, create_priv_channel_pixmap, create_priv_channel_mask, 
		  _("Create private channel"), on_files_create_root_channel);
  /* channel from directory */
  toolbar_append (IT_JOIN_CHANNEL, join_channel_pixmap, join_channel_mask, 
		  _("Join channel"), on_files_join_channel);
  toolbar_append (IT_LEAVE_CHANNEL, leave_channel_pixmap, leave_channel_mask, 
		  _("Leave channel"), on_files_leave_channel);

  toolbar_append (IT_ADD_FILE, page_pixmap, page_mask,
		  _("Add file"), on_files_add_file);

  toolbar_append (IT_ADD_DIR, folder_pixmap, folder_mask,
		  _("Add directory"), on_files_add_directory);
  toolbar_append (IT_CREATE_DIR, create_dir_pixmap, create_dir_mask,
		  _("Create directory"), on_files_create_directory);

  toolbar_append (IT_ADD_URL, url_pixmap, url_mask,
		  _("Add URL"), on_files_add_url);

  toolbar_append (IT_CREATE_CHAT, create_chat_pixmap, create_chat_mask,
		  _("Create chat"), on_files_create_chat);
  toolbar_append (IT_JOIN_CHAT, join_chat_pixmap, join_chat_mask,
		  _("Join chat"), on_files_join_chat);
  toolbar_append (IT_LEAVE_CHAT, leave_chat_pixmap, leave_chat_mask,
		  _("Leave chat"), on_files_leave_chat);

  toolbar_append (IT_CREATE_SEARCH, create_search_pixmap, create_search_mask,
		  _("Create search"), on_files_create_search);
  toolbar_append (IT_JOIN_SEARCH, join_search_pixmap, join_search_mask,
		  _("Join search"), on_files_join_search);
  toolbar_append (IT_LEAVE_SEARCH, leave_search_pixmap, gray_search_mask,
		  _("Leave search"), on_files_leave_search);

  toolbar_append (IT_REMOVE, x_pixmap, x_mask,
		  _("Remove"), on_files_remove);

  /* Update the toolbar */
  ggui_file_update_toolbar();
}


static void
toolbar_append (Item item, GdkPixmap* gpixmap, GdkBitmap* gbitmap,
	       const gchar* tooltip, void* func)
{
  GtkPixmap* pixmap;				     	 
  GtkWidget* widget;				     	 

  pixmap = (GtkPixmap*) gtk_pixmap_new (gpixmap, gbitmap);
  g_return_if_fail (pixmap);                         	 
  widget = gtk_toolbar_append_item (jmw_toolbar, NULL,   
				    tooltip, "private",                 
				    GTK_WIDGET(pixmap),           
				    (GtkSignalFunc) func, NULL);     
  g_return_if_fail (widget);                             
  gtk_object_set_user_data (GTK_OBJECT(widget), (gpointer) item);
}



void
ggui_file_update_toolbar (void)
{
  GList* kids;
  GList* i;

  /* Hide all */
  kids = gtk_container_children (GTK_CONTAINER(jmw_toolbar));
  for (i = kids; i != NULL; i = i->next)
    gtk_widget_set_sensitive (GTK_WIDGET(i->data), FALSE);

  /* Update toolbar */
  update_items (kids);

  g_list_free (kids);
}


/* ************************************************************ */

static const char* item_mi_name[] =
{
  "create_root_channel_mi",		/* 0 */
  "create_channel_mi",
  "create_channel_from_directory_mi",
  "open_channel_mi",
  "join_channel_mi",
  "leave_channel_mi",			/* 5 */
  "leave_current_channel_mi",
  "remove_channel_mi",
  "remove_current_channel_mi",
  
  "add_file_mi",
  "get_file_mi",			/* 10 */
  "cancel_download_mi",
  "remove_file_mi",

  "create_directory_mi",
  "add_directory_mi",
  "get_directory_mi",			/* 15 */
  "remove_directory_mi",

  "add_url_mi",
  "open_url_mi",
  "remove_url_mi",

  "create_chat_mi",			/* 20 */
  "join_chat_mi",
  "leave_chat_mi",
  "remove_chat_mi",

  "create_search_mi",
  "join_search_mi",			/* 25 */
  "leave_search_mi",
  "remove_search_mi",

  "remove_mi",

  "properties_mi",

  "separator1",
  "separator2",				/* 30 */
  "separator3",

  "exit_mi",
};


static void
update_items (void* widget)
{
  GURL* sc_url;

  show_item (widget, IT_OPEN_CHANNEL);
  show_item (widget, IT_CREATE_ROOT_CHANNEL);

  /* If there is a selected channel, show join and leave */
  if ((sc_url = ggui_channel_get_selected()) != NULL)
    {
      JMChannel* sc = ggui_channel_get (sc_url);

      if (sc->is_local)
	/* REMOVE */;
      else if (jmchannel_is_up(sc))
	show_item (widget, IT_LEAVE_CHANNEL);
      else
	show_item (widget, IT_JOIN_CHANNEL);
    }

  if (shown_channel)
    {
      if (jmchannel_is_up (shown_channel))
	{
	  if (!shown_channel->is_channels_only)
	    {
	      show_item (widget, IT_ADD_FILE);
	      show_item (widget, IT_ADD_URL);
	      show_item (widget, IT_ADD_DIR);
	      show_item (widget, IT_CREATE_CHAT);
	      show_item (widget, IT_CREATE_SEARCH);
	      show_item (widget, IT_CREATE_DIR);
	    }

	  show_item (widget, IT_CREATE_CHANNEL);
/*  	  show_item (widget, IT_CREATE_CHANNEL_FROM_DIR); */

	  show_item (widget, IT_SEPARATOR2);

	  if (shown_channel->is_local)
	    show_item (widget, IT_REMOVE_CURRENT_CHANNEL);
	  else if (jmchannel_is_up (shown_channel))
	    show_item (widget, IT_LEAVE_CURRENT_CHANNEL);
	}
    }

  if (selected_ann)
    {
      GURL* url = selected_ann->url;

      show_item (widget, IT_SEPARATOR3);
      if (url)
	show_item (widget, IT_PROPERTIES);

      g_return_if_fail (shown_channel && shown_ann);

      if (!url)
	{
	  show_item (widget, IT_SEPARATOR1);

	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_DIR);
	      show_item (widget, IT_REMOVE);
	    }
/*  	  else */
/*  	    show_item (widget, IT_GET_DIR); */
	}

      else if (GURL_IS_FILE (url))
	{
	  gint percent;

	  show_item (widget, IT_SEPARATOR1);

	  percent = ggui_download_get_percent_complete (url);

	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_FILE);
	      show_item (widget, IT_REMOVE);
	    }
	  else if (percent >= 0 && percent <= 99)
	    show_item (widget, IT_CANCEL_DOWNLOAD);
	  else if (percent == -2 || percent == 0)
	    show_item (widget, IT_GET_FILE);
	}

      else if (GURL_IS_CHANNEL (url))
	{
	  show_item (widget, IT_SEPARATOR1);

	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_CHANNEL);
	      show_item (widget, IT_REMOVE);
	    }
	  else if (ggui_channel_is_up (url))
	    show_item (widget, IT_LEAVE_CHANNEL);
	  else
	    show_item (widget, IT_JOIN_CHANNEL);
	}

      else if (GURL_IS_CHAT (url))
	{
	  show_item (widget, IT_SEPARATOR1);

	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_CHAT);
	      show_item (widget, IT_REMOVE);
	    }
	  else if (ggui_chat_is_up (url))
	    show_item (widget, IT_LEAVE_CHAT);
	  else
	    show_item (widget, IT_JOIN_CHAT);
	}

      else if (GURL_IS_SEARCH (url))
	{
	  show_item (widget, IT_SEPARATOR1);

	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_SEARCH);
	      show_item (widget, IT_REMOVE);
	    }
	  else if (ggui_search_is_up (url))
	    show_item (widget, IT_LEAVE_SEARCH);
	  else
	    show_item (widget, IT_JOIN_SEARCH);
	}

      else
	{
	  if (selected_ann->is_local)
	    {
	      show_item (widget, IT_REMOVE_URL);
	      show_item (widget, IT_REMOVE);
	    }
	  show_item (widget, IT_OPEN_URL);
	}
    }

  else
    {
      show_item (widget, IT_SEPARATOR3);
      show_item (widget, IT_EXIT);
    }
}


static void
show_item (void* widget, Item it)
{
  if (!widget)	/* Popup menu */
    {
      GtkWidget* menu_item = glade_xml_get_widget(menu_xml, item_mi_name[it]);
      if (menu_item)
	gtk_widget_show(menu_item);
    }
  else		/* Toolbar    */
    {
      GList* kids = (GList*) widget;
      GList* i;

      for (i = kids; i != NULL; i = i->next)		 
	{
	  if (it == (guint) gtk_object_get_user_data(GTK_OBJECT(i->data)))
	    {                                                  
	      gtk_widget_set_sensitive (GTK_WIDGET(i->data), TRUE); 
	      break;                                           
	    }                                                  
	}
    }
}




POPUPFUNC (on_files_get_file)
{
  GSHA*  sha;
  gchar* path = NULL;

  /* FIX: Download directory recursively */
  g_return_if_fail (shown_channel && selected_ann && selected_ann->url);

  /* TODO: Handle other directory separators */
  if (G_DIR_SEPARATOR == '/')
    path = g_strconcat (jmchannel_get_name(shown_channel), 
			selected_ann->parent->path, NULL);

  sha = jmann_get_attr_sha (selected_ann, "sha");

  ggui_download_get (selected_ann->url, path, sha);

  g_free (path);
  gnet_sha_delete (sha);
}

POPUPFUNC (on_files_cancel_download)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_download_cancel (selected_ann->url);
}

POPUPFUNC (on_files_remove_file)
{
  g_return_if_fail (shown_channel && selected_ann);
  ggui_file_remove (shown_channel->url, selected_ann->path);
}

POPUPFUNC (on_files_open_url)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_activate (selected_ann->url);
}

POPUPFUNC (on_files_remove_url)
{
  g_return_if_fail (shown_channel && selected_ann);
  ggui_file_remove (shown_channel->url, selected_ann->path);
}

POPUPFUNC (on_files_join_channel)
{
  GURL* sc_url;

  if (selected_ann && selected_ann->url && 
      gnet_url_is_scheme (selected_ann->url, "jm"))
    ggui_channel_join (selected_ann->url);
  else if ((sc_url = ggui_channel_get_selected()) != NULL)
    ggui_channel_join (sc_url);
}

POPUPFUNC (on_files_remove_channel)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_channel_delete (selected_ann->url);
}

POPUPFUNC (on_files_leave_channel)
{
  GURL* sc_url;

  if (selected_ann && selected_ann->url && 
      gnet_url_is_scheme (selected_ann->url, "jm"))
    ggui_channel_leave (selected_ann->url);
  else if ((sc_url = ggui_channel_get_selected()) != NULL)
    ggui_channel_leave (sc_url);
}

POPUPFUNC (on_files_join_chat)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_chat_join (selected_ann->url);
}

POPUPFUNC (on_files_remove_chat)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_chat_remove (selected_ann->url);
}

POPUPFUNC (on_files_leave_chat)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_chat_leave (selected_ann->url);
}

POPUPFUNC (on_files_join_search)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_search_join (selected_ann->url);
}

POPUPFUNC (on_files_remove_search)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_search_remove (selected_ann->url);
}

POPUPFUNC (on_files_leave_search)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_search_leave (selected_ann->url);
}


/* ********** */

POPUPFUNC (on_files_add_file)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_file_add_file (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_add_directory)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_file_add_dir (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_add_url)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_file_add_url (shown_channel->url, shown_ann->path, NULL, NULL);
}

POPUPFUNC (on_files_create_chat)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_chat_create (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_create_search)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_search_create (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_create_directory)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_file_create_dir (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_create_channel)
{
  g_return_if_fail (shown_channel && shown_ann);
  ggui_channel_create (shown_channel->url, shown_ann->path, NULL);
}

POPUPFUNC (on_files_create_channel_from_directory)
{
  g_print ("FIX IMPLEMENT\n");
/*    ggui_menu_create_channel_from_directory (&shown_file); FIX */
}

POPUPFUNC (on_files_open_channel)
{
  ggui_channel_join (NULL);
}

POPUPFUNC (on_files_create_root_channel)
{
  ggui_channel_create (NULL, NULL, NULL);
}

POPUPFUNC (on_files_remove_current_channel)
{
  g_return_if_fail (shown_channel);
  ggui_channel_delete (shown_channel->url);
}


POPUPFUNC (on_files_leave_current_channel)
{
  g_return_if_fail (shown_channel);
  ggui_channel_leave (shown_channel->url);
}

POPUPFUNC (on_files_remove)
{
  GURL* url;

  g_return_if_fail (selected_ann);

  url = selected_ann->url;
  if (!url)
    ggui_file_remove (shown_channel->url, selected_ann->path);
  else if (gnet_url_is_scheme (url, "jm"))
    ggui_channel_delete (url);
  else if (gnet_url_is_scheme (url, "mtp"))
    {
      if (selected_ann->is_local)
	ggui_file_remove (shown_channel->url, selected_ann->path);
      else 
	ggui_download_cancel (url);
    }
  else if (gnet_url_is_scheme (url, "jmchat"))
    ggui_chat_remove (url);
  else if (gnet_url_is_scheme (url, "jmmsp"))
    ggui_search_remove (url);
  else if (selected_ann->is_local)
    ggui_file_remove (shown_channel->url, selected_ann->path);
}


POPUPFUNC (on_files_properties)
{
  g_return_if_fail (selected_ann && selected_ann->url);
  ggui_properties_show (selected_ann->url);
  /* TODO directory properties */
}



syntax highlighted by Code2HTML, v. 0.9.1