/***************************************************************************
                            downloads-view.c
                            ----------------
    begin                : Wed Oct 1 2003
    copyright            : (C) 2003 by Tim-Philipp Müller
    email                : t.i.m at orange dot net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "blacklist.h"
#include "colors.h"
#include "core-conn.h"
#include "downloads-list.h"
#include "downloads-view.h"
#include "icons.h"
#include "http_get.h"
#include "mainwindow.h"
#include "misc.h"
#include "misc_gtk.h"
#include "misc_strings.h"
#include "misc_sys.h"
#include "options.h"
#include "progressbar-renderer.h"

#include "status_page.h"
#include "statusbar.h"

#include <gdk/gdkkeysyms.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtkvbox.h>

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

enum
{
 PAUSE_MODE_ALL,
 PAUSE_MODE_ZEROSPEED,
 PAUSE_MODE_NOSOURCES,
 PAUSE_MODE_NOTFULLYAVAILABLE,
 PAUSE_MODE_ALL_OUT_OF_DISKSPACE,
} ;

enum
{
 TOGGLE_SHOWHIDE_JUNK,
 TOGGLE_KEEPEXT,
 TOGGLE_VERIFYCANCELS,
 TOGGLE_NO_ACTIVE_CANCEL,
 TOGGLE_SAVE_CORRUPTED,
 TOGGLE_NO_IDS_PAUSE,
 TOGGLE_GREENPBPARTS,
 TOGGLE_SHOW_TRANS_AS_PERCENT,
 TOGGLE_BLACKLISTONDL,
 TOGGLE_UNSELECT_AFTER_ACTION,
 TOGGLE_SELMODE,
 TOGGLE_BOLD_FILENAMES,
 TOGGLE_RESUMEONCOMPLETE,
} ;

#define NUM_VIEW_COLS		        14

#define COLUMN_NUM_BITS         0x00ffff	/* 16 bits for column width           */
#define COLUMN_INVISIBLE_FLAG   0xff0000	/*  8 bits for flags like 'invisible' */


GuiDownloadList  *download_list = NULL;

static GtkWidget *downloads_view; /* NULL */

static GList     *selected;       /* NULL */ /* A list of selected GuiDownload items */

static guint      num_paused;     /* 0 */

static guint      column_widths[NUM_VIEW_COLS];


/* functions */

static gboolean   onEachMinute    (GtkWidget *fdlabel);

static gboolean   downloads_view_selection_func (GtkTreeSelection *selection,
                                                 GtkTreeModel     *model,
                                                 GtkTreePath      *path,
                                                 gboolean          currently_selected,
                                                 gpointer          userdata);

static void       downloads_view_popup_unselect_all_if_wanted (void);

static void       onPauseSelected        (GtkWidget *widget, gpointer data);

static void       onResumeSelected       (GtkWidget *widget, gpointer data);

static void       onPreviewSelected_preview_file (GuiDownload *dl);

static void       onPreviewSelected      (GtkWidget *widget, gpointer data);

static void       onWriteSelectedLinks   (GtkWidget *widget, gpointer data);

static void       onResumeAll            (GtkWidget *widget, gpointer data);

static void       onPauseAll             (GtkWidget *widget, gpointer data);

static void       onClearCompleted       (GtkWidget *widget, gpointer data);

static void       onGetJigleServerList   (GtkWidget *widget, gpointer data);

static void       onSetPriority          (GtkWidget *widget, gpointer data);

static void       onHideColumn           (GtkWidget *widget, gpointer data);

static void       onUnhideColumn         (GtkWidget *widget, gpointer data);

static void       onCancelSelected       (GtkWidget *widget, gpointer data);

static void       onOptionToggled        (GtkWidget *widget, gpointer data);

static void       downloads_view_popup_menu_append_info_string (GtkWidget *menu);

static GtkWidget *downloads_view_popup_menu_options (GdkEventButton *event);

void              downloads_view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer data);

static gboolean   onButtonPress          (GtkWidget *widget, GdkEventButton *event, gpointer data);

static gint       onKeyPress             (GtkWidget *widget, GdkEventKey *event, gpointer data);

static gint       onPopUpMenu            (GtkWidget *widget, gpointer data);

static gboolean   onRowActivated         (GtkWidget *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data);

static void       onDownloadRemoved      (GtkTreeModel *model, GuiDownload *dl, gpointer data);

static gboolean   onColumnResizedSaveWidths (gint *p_handler);

static void       onColumnResized (GObject *obj, GParamSpec *pspec, gpointer data);

static void       onOptionChanged (OptNum num);

static GtkWidget *downloads_view_create (void);

static void       onClientStats (GtkWidget *slabel,
                                 gfloat     temp,
                                 gfloat     incoming,
                                 gfloat     needed,
                                 guint      clientid,
                                 guint      connections,
                                 guint      num_on_queue,
                                 gpointer   data);


/******************************************************************************
 *
 *   onEachMinute
 *
 *   Run regularly, ie. every minute or so
 *
 ******************************************************************************/

static gboolean
onEachMinute (GtkWidget *fdlabel)
{
	const CoreOptions *copts;

	if (!GTK_IS_LABEL(fdlabel))
		return FALSE;

	gtk_label_set_text(GTK_LABEL(fdlabel), "");

	copts = gui_core_conn_get_core_options(core);

	if (gui_core_conn_is_alive(core) && gui_core_conn_is_local(core) && copts->pid > 0 )
	{
		const gchar *fdmsg;
		glong        max_open, num_open;

		if (misc_sys_get_max_open_files(&max_open) && misc_sys_get_pid_open_files(copts->pid, &num_open))
		{
			if ( max_open > 65535 )
				fdmsg = UTF8_SHORT_PRINTF(_(" No file descriptor limit "));
			else
				fdmsg = UTF8_SHORT_PRINTF(_(" core fd usage: %lu/%lu "), num_open, max_open);

			gtk_label_set_text (GTK_LABEL(fdlabel), fdmsg);
		}
	}

	return TRUE; /* call again */
}


/******************************************************************************
 *
 *   downloads_view_selection_func
 *
 *   Called whenever a row gets selected or unselected
 *
 ******************************************************************************/

static gboolean
downloads_view_selection_func (GtkTreeSelection *selection,
                               GtkTreeModel     *model,
                               GtkTreePath      *path,
                               gboolean          currently_selected,
                               gpointer          userdata)
{
	GuiDownload *dl;

	dl = gui_download_list_get_download_from_path(download_list, path);

	if (dl == NULL)
		g_return_val_if_reached(TRUE);

	selected = g_list_remove_all(selected, dl);

	/* Is going to be selected? */
	if (!currently_selected)
	{
		selected = g_list_append(selected, dl);
	}

	return TRUE;
}


/******************************************************************************
 *
 *     downloads_view_popup_unselect_all_if_wanted
 *
 ******************************************************************************/

static void
downloads_view_popup_unselect_all_if_wanted (void)
{
	if ( opt_get_bool(OPT_GUI_DOWNLOADS_UNSELECT_ALL_AFTER_ACTION) )
	{
		GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(downloads_view));
		gtk_tree_selection_unselect_all(selection);
	}
}



/******************************************************************************
 *
 *   onPauseSelected
 *
 ******************************************************************************/

static void
onPauseSelected (GtkWidget *widget, gpointer data)
{
	GuiDownload *dl;
	GList       *node;

	for ( node = selected; node != NULL; node = node->next )
	{
		dl = (GuiDownload*) node->data;

		if ((dl) && (!dl->done) && dl->status != STATUS_PAUSED && dl->status != STATUS_PAUSING )
		{
			gui_core_conn_send_pause_download(core, dl->hash);
			gui_download_list_download_change_status (download_list, dl, STATUS_PAUSING);
		}
	}

	gui_core_conn_send_get_stats(core);

	downloads_view_popup_unselect_all_if_wanted();
}


/******************************************************************************
 *
 *   onResumeSelected
 *
 ******************************************************************************/

static void
onResumeSelected (GtkWidget *widget, gpointer data)
{
	GuiDownload *dl;
	GList       *node;

	for ( node = selected;  node != NULL;  node = node->next)
	{
		dl = (GuiDownload*) node->data;

		if ((!dl) || dl->done || dl->status == STATUS_LOOKING || dl->status == STATUS_DOWNLOADING || dl->status == STATUS_RESUMING )
			continue;

		gui_core_conn_send_resume_download(core, dl->hash);
		gui_download_list_download_change_status (download_list, dl, STATUS_RESUMING);
	}

	gui_core_conn_send_get_stats(core);

	downloads_view_popup_unselect_all_if_wanted();
}



/******************************************************************************
 *
 *   onPreviewSelected_preview_file
 *
 ******************************************************************************/

static void
onPreviewSelected_preview_file (GuiDownload *dl)
{
	const CoreOptions *copts;
	gchar             *preview_filename = NULL;

	copts = gui_core_conn_get_core_options(core);

	if ( dl->status == STATUS_COMPLETE )
		preview_filename = g_strdup_printf ("%s/%s", copts->incomingdir, dl->name);

	/* TODO: XXX - should it be option_incoming or option_temp here? */
	else if ( dl->status == STATUS_CORRUPTED )
	{
		gchar *basefn = g_path_get_basename(dl->previewname);
		if (basefn)
		{
			preview_filename = g_strdup_printf("%s/%s", copts->incomingdir, preview_filename);
			g_free(basefn);
		}
	}
	else
		preview_filename = g_strdup(dl->previewname);

	if ( spawn_file_preview(preview_filename, dl->ext) == 0 )
	{
		G_FREE(preview_filename);
		return;
	}

	g_printerr (_("Could not preview '%s'!\n"), preview_filename);
	
	g_free (preview_filename);
}



/******************************************************************************
 *
 *   onPreviewSelected
 *
 ******************************************************************************/

static void
onPreviewSelected (GtkWidget *widget, gpointer data)
{
	GuiDownload *dl;
	GList       *node;

	for ( node = selected;  node != NULL;  node = node->next )
	{
		dl = (GuiDownload*)node->data;

		if ((dl) && dl->previewname)
			onPreviewSelected_preview_file (dl);
		else
			g_printerr (_("This core version does not supply us with the x.part filenames.\n"));
	}

	downloads_view_popup_unselect_all_if_wanted();
}


/******************************************************************************
 *
 *   onWriteSelectedLinks
 *
 ******************************************************************************/

static void
onWriteSelectedLinks (GtkWidget *widget, gpointer data)
{
	GuiDownload *dl;
	GList       *node;

	for (node = selected; node != NULL; node = node->next)
	{
		dl = (GuiDownload*)node->data;

		if (dl)
			status_msg_print_ed2klink (dl->name_utf8, dl->size, dl->hash);
	}

	downloads_view_popup_unselect_all_if_wanted();
}



/******************************************************************************
 *
 *   onResumeAll
 *
 ******************************************************************************/

static void
onResumeAll (GtkWidget *widget, gpointer data)
{
	GList *all, *node;

	all = gui_download_list_get_all_downloads (download_list);

	for (node = all; node != NULL; node = node->next)
	{
		GuiDownload *dl = (GuiDownload*)node->data;

		if ((!dl) || dl->done || dl->status == STATUS_LOOKING || dl->status == STATUS_DOWNLOADING || dl->status == STATUS_RESUMING )
			continue;

		gui_core_conn_send_resume_download(core, dl->hash);
		gui_download_list_download_change_status (download_list, dl, STATUS_RESUMING);
	}

	g_list_free(all);

	gui_core_conn_send_get_stats(core);

	statusbar_msg ("%s", _(" All downloads resumed."));
}



/******************************************************************************
 *
 *   onPauseAll
 *
 ******************************************************************************/

void
onPauseAll (GtkWidget *widget, gpointer data)
{
	gboolean  opt_no_IDS_pause;
	GList    *all, *node;
	guint     pmode = GPOINTER_TO_UINT(data);	/* pause mode */

	all = gui_download_list_get_all_downloads (download_list);

	num_paused = 0;
	opt_no_IDS_pause = opt_get_bool(OPT_GUI_DOWNLOADS_NO_INSUFFICIENT_DISK_SPACE_PAUSE);

	for (node = all; node != NULL; node = node->next)
	{
		GuiDownload *dl     = (GuiDownload*)node->data;
		gboolean     pause  = FALSE;

		if ((!dl) || (dl->done))
			continue;

		switch (pmode)
		{
			case PAUSE_MODE_ALL:
			case PAUSE_MODE_ALL_OUT_OF_DISKSPACE:
				pause = TRUE;
				break;
			case PAUSE_MODE_ZEROSPEED:
				pause = (dl->speed == 0.0);
				break;
			case PAUSE_MODE_NOSOURCES:
				pause = (dl->sources == 0);
				break;
			case PAUSE_MODE_NOTFULLYAVAILABLE:
				pause = (dl->avail < 100);
				break;
		}

		if ( pmode != PAUSE_MODE_ALL  &&  pmode != PAUSE_MODE_ALL_OUT_OF_DISKSPACE
		       && dl->status == STATUS_IDS  &&  (opt_no_IDS_pause)  )
		{
			pause = FALSE;
		}

		if (!pause)
			continue;

		if (dl->status == STATUS_PAUSING || dl->status == STATUS_PAUSED
		     || dl->status == STATUS_COMPLETE || dl->status == STATUS_ERRLOADING)
			continue;

		++num_paused;

		gui_core_conn_send_pause_download(core, dl->hash);

		if (pmode != PAUSE_MODE_ALL_OUT_OF_DISKSPACE)
			gui_download_list_download_change_status (download_list, dl, STATUS_PAUSING);
	}

	g_list_free(all);

	if (num_paused > 0 && pmode != PAUSE_MODE_ALL_OUT_OF_DISKSPACE)
	{
		statusbar_msg ("%s", _(" All downloads paused."));
		gui_core_conn_send_get_stats(core);
	}
}


/******************************************************************************
 *
 *   onClearCompleted
 *
 ******************************************************************************/

static void
onClearCompleted (GtkWidget *widget, gpointer data)
{
	gui_download_list_clear_completed(download_list);
}



/******************************************************************************
 *
 *   onGetJigleServerList
 *
 ******************************************************************************/

static void
onGetJigleServerList (GtkWidget *widget, gpointer data)
{
	GuiDownload  *dl;
	GList        *node;
	gchar        *url;

	for ( node = selected; node != NULL; node = node->next )
	{
		dl = (GuiDownload*)node->data;
		if ((dl) && !dl->done)
		{
			url = g_strdup_printf ("http://jigle.com/search?p=ed2k:%u:%s&ma=1&d=1&a=0&l=10&t=7&x=&sl=1&su=&v=1",
		                       dl->size, hash_to_hash_str(dl->hash));
			if (url)
			{
				invoke_browser_with_url(url);
				http_get_retrieve_serverlist(url,0);
				g_free(url);
			}
		}
	}

	downloads_view_popup_unselect_all_if_wanted();
}


/******************************************************************************
 *
 *   onSetPriority
 *
 ******************************************************************************/

static void
onSetPriority (GtkWidget *widget, gpointer data)
{
	GuiDownload  *dl;
	GList        *node, *all;
	guint         prio;

	prio = GPOINTER_TO_UINT(data);

	if ( prio == DL_PRIO_HIGHEST  &&  g_list_length(selected) > 1 )
	{
		statusbar_msg(_("You can only set ONE download to 'highest' priority."));
		downloads_view_popup_unselect_all_if_wanted();
		return;
	}

	/* only one DL can have 'highest' priority, so we need to set the
	 * previous 'highest' back to 'high' priority */
	if ( prio == DL_PRIO_HIGHEST )
	{
		all = gui_download_list_get_all_downloads(download_list);
		for ( node = all;  node != NULL;  node = node->next )
		{
			dl = (GuiDownload*)node->data;
			if (dl->priority == DL_PRIO_HIGHEST)
				dl->priority = DL_PRIO_HIGH;
		}
		g_list_free(all);
	}

	for (node = selected; node != NULL; node = node->next)
	{
		dl = (GuiDownload*)node->data;
		if ((dl) && !dl->done)
		{
			gui_core_conn_send_set_download_priority(core, dl->hash, prio);
			dl->priority = prio;
			/* force row to be re-drawn */
			gui_download_list_download_change_status (download_list, dl, dl->status);
		}
	}

	downloads_view_popup_unselect_all_if_wanted();
}



/******************************************************************************
 *
 *   onHideColumn
 *
 ******************************************************************************/

static void
onHideColumn (GtkWidget *widget, gpointer data)
{
	g_return_if_fail ( data != NULL );
	gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(data), FALSE);
}



/******************************************************************************
 *
 *   onUnhideColumn
 *
 ******************************************************************************/

static void
onUnhideColumn (GtkWidget *widget, gpointer data)
{
	g_return_if_fail ( data != NULL );
	gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(data), TRUE);
}


/******************************************************************************
 *
 *   onCancelSelected
 *
 ******************************************************************************/

static void
onCancelSelected (GtkWidget *widget, gpointer data)
{
	gboolean   blacklist = (GPOINTER_TO_UINT(data) == 1);
	gboolean   cancelerrdls = FALSE;  /* trying to cancel downloads with error?    */
	gboolean   cancelhashdls = FALSE; /* trying to cancel downloads while hashing? */
	GList     *node, *toconfirm = NULL;
	GString   *names_to_confirm = NULL;

	for ( node = selected; node != NULL; node = node->next )
	{
		GuiDownload *dl = (GuiDownload*)node->data;
		gboolean     need_confirmation;

		if (!dl)
			continue;

		if (dl->done || dl->status == STATUS_COMPLETE || dl->status == STATUS_CORRUPTED || dl->status == STATUS_COMPLETING)
			continue;

		if (dl->status == STATUS_HASHING || dl->status == STATUS_HASHING2)
		{
			cancelhashdls = TRUE;
			continue;
		}

		if (dl->status == STATUS_ERRLOADING || dl->status == STATUS_ERRHASHING)
		{
			cancelerrdls = TRUE;
			continue;
		}

		need_confirmation = opt_get_bool(OPT_GUI_DOWNLOADS_VERIFY_CANCEL);

		if (opt_get_bool(OPT_GUI_NO_ACTIVE_DOWNLOAD_CANCEL) && dl->status!=STATUS_PAUSED && dl->status!=STATUS_PAUSING)
			need_confirmation = TRUE;

		if (need_confirmation)
		{
			if (!names_to_confirm)
				names_to_confirm = g_string_new("\n");

			names_to_confirm = g_string_append(names_to_confirm, dl->name);
			names_to_confirm = g_string_append_c(names_to_confirm, '\n');

			toconfirm = g_list_append (toconfirm, dl);
		}
		else
		{
			gui_core_conn_send_cancel_download(core, dl->hash);
			gui_download_list_download_change_status(download_list, dl, STATUS_CANCELLING);
			dl->cancelled = TRUE;

			if (blacklist)
				add_to_blacklist (dl->hash, dl->size, dl->name);
		}
	}


	/* now show confirm dialog for those downloads
	 * for which we need confirmation to cancel them */

	if ( g_list_length(toconfirm) > 0 )
	{
		GtkWidget *dialog;
		gchar     *utf8a, *utf8b;
		gint       ret;

		utf8a = TO_UTF8(_("Are you sure you want to cancel the following downloads?"));
		utf8b = TO_UTF8(_("The unfinished files will be deleted from your harddrive."));

		/* filenames ARE already in UTF8 - we don't want to convert them twice (thanks to EL) */
		dialog = gtk_message_dialog_new ( GTK_WINDOW(window),
		                                  GTK_DIALOG_DESTROY_WITH_PARENT,
		                                  GTK_MESSAGE_QUESTION,
		                                  GTK_BUTTONS_YES_NO,
		                                  "%s\n%s\n%s\n\n", utf8a, names_to_confirm->str, utf8b);

		ret = gtk_dialog_run (GTK_DIALOG(dialog));

		gtk_widget_destroy(dialog);

		if ( ret == GTK_RESPONSE_YES )
		{
			for (node = toconfirm; node != NULL; node = node->next)
			{
				GuiDownload *dl = (GuiDownload*)node->data;

				gui_core_conn_send_cancel_download(core, dl->hash);
				gui_download_list_download_change_status(download_list, dl, STATUS_CANCELLING);
				dl->cancelled = TRUE;

				if (blacklist)
					add_to_blacklist (dl->hash, dl->size, dl->name);
			}
		}

		FREE_UTF8(utf8a);
		FREE_UTF8(utf8b);
	} /* end if g_list_length(toconfirm) > 0 */

	if (cancelhashdls)
	{
		misc_gtk_ok_dialog (GTK_MESSAGE_WARNING,
			_("You can't cancel downloads while they are hashing. Sorry. Please try again later."));
	}

	if (cancelerrdls)
	{
		misc_gtk_ok_dialog (GTK_MESSAGE_WARNING,
			_("One or more downloads selected have either had\nan error during loading or\n"
			  "an error during hashing.\n\nYou can't just cancel these downloads.\n\n"
			  "You will have to remove them manually\n(when the donkey is not running).\n\n"
			  "The right-click pop-up menu\nshould show you which x.part file\na certain download is.\n") );
	}

	g_list_free(toconfirm);

	if (names_to_confirm)
		g_string_free(names_to_confirm, TRUE);

	downloads_view_popup_unselect_all_if_wanted();
}



/******************************************************************************
 *
 *   onOptionToggled
 *
 ******************************************************************************/

static void
onOptionToggled (GtkWidget *widget, gpointer data)
{
	guint optnum = GPOINTER_TO_UINT(data);

	switch (optnum)
	{
		case TOGGLE_SHOWHIDE_JUNK:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK);
			g_object_set(download_list, "hide-junk", opt_get_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK), NULL);
		break;

		case TOGGLE_VERIFYCANCELS:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_VERIFY_CANCEL);
		break;

		case TOGGLE_KEEPEXT:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_KEEP_EXTENSION_WHEN_HIDING_JUNK);
			/* Force it to update the junk filenames without
			 *   installing a new property for the extension stuff
			 *   (we need a change from the old value to make it update them) */
			g_object_set(download_list, "hide-junk", !opt_get_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK), NULL);
			g_object_set(download_list, "hide-junk", opt_get_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK), NULL);
		break;

		case TOGGLE_SAVE_CORRUPTED:
		{
			gboolean savec = gui_core_conn_get_core_options(core)->save_corrupted;
			gui_core_conn_send_save_corrupted(core, !savec);
			((CoreOptions*)gui_core_conn_get_core_options(core))->save_corrupted = !savec;
		}
		break;

		case TOGGLE_NO_IDS_PAUSE:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_NO_INSUFFICIENT_DISK_SPACE_PAUSE);
		break;

		case TOGGLE_NO_ACTIVE_CANCEL:
			opt_toggle_bool(OPT_GUI_NO_ACTIVE_DOWNLOAD_CANCEL);
		break;

		case TOGGLE_GREENPBPARTS:
		{
			GuiCellRendererProgress *cell;

			opt_toggle_bool(OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR);

			cell = g_object_get_data(G_OBJECT(downloads_view), "ProgressbarRenderer");

			if ((cell) && GUI_IS_CELL_RENDERER_PROGRESS(cell))
			{
				if (opt_get_bool(OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR))
					g_object_set(cell, "available-color", "blue", NULL);
				else
					g_object_set(cell, "available-color", "green", NULL);
			}
		}
		break;

		case TOGGLE_SHOW_TRANS_AS_PERCENT:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_SHOW_TRANSFERED_IN_PERCENT);
			g_object_set(download_list, "trans-in-percent", opt_get_bool(OPT_GUI_DOWNLOADS_SHOW_TRANSFERED_IN_PERCENT), NULL);
		break;

		case TOGGLE_BLACKLISTONDL:
			opt_toggle_bool(OPT_GUI_DOWNLOADS_BLACKLIST_ON_DOWNLOAD);
		break;

		case TOGGLE_SELMODE:
		{
			GtkTreeSelection *selection;

			opt_toggle_bool(OPT_GUI_DOWNLOADS_SINGLE_SELECTION_MODE);

			/* set multi- or single- selection mode */
			selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(downloads_view));

			if ( opt_get_bool(OPT_GUI_DOWNLOADS_SINGLE_SELECTION_MODE) )
			{
				gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
			}
			else
			{
				gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
			}
		}
		break;

		case TOGGLE_UNSELECT_AFTER_ACTION:
			opt_toggle_bool(OPT_GUI_SERVERS_UNSELECT_ALL_AFTER_ACTION);
		break;

		case TOGGLE_BOLD_FILENAMES:
		{
			GtkTreeViewColumn *namecol;
			GList             *cells, *cnode;

			opt_toggle_bool(OPT_GUI_DOWNLOADS_FILENAMES_BOLD);

			namecol = gtk_tree_view_get_column(GTK_TREE_VIEW(downloads_view), 0);
			cells   = gtk_tree_view_column_get_cell_renderers(namecol);

			/* Find text cell renderer (= the one containing the file names) */
			for (cnode = cells;  cnode != NULL;  cnode = cnode->next)
			{
				if (GTK_IS_CELL_RENDERER_TEXT(cnode->data))
				{
					g_object_set (G_OBJECT(cnode->data),
			  		            "weight", PANGO_WEIGHT_BOLD,
			      		        "weight-set", opt_get_bool(OPT_GUI_DOWNLOADS_FILENAMES_BOLD),
			          		    NULL);
				}
			}

			g_list_free(cells);
		}
		break;

		case TOGGLE_RESUMEONCOMPLETE:
		{
			opt_toggle_bool(OPT_GUI_RESUME_ONE_PAUSED_DL_ON_COMPLETE);
		}
		break;

		default:
			g_printerr ("GUI: Fix me - called with unknown value in %s\n)", __FUNCTION__);
		break;
	}
}


/******************************************************************************
 *
 *   downloads_view_popup_menu_append_info_string
 *
 ******************************************************************************/

static void
downloads_view_popup_menu_append_info_string (GtkWidget *menu)
{
	GuiDownload  *dl;
	GString      *utf8;

	if (g_list_length(selected) != 1)
		return;

	dl = (GuiDownload*) selected->data;
	g_return_if_fail ( dl != NULL );

	if (!dl->previewname)
		return;

	if (dl->status == STATUS_COMPLETE || dl->status == STATUS_CORRUPTED || dl->done)
	{
		misc_gtk_add_menu_separator (menu);
		misc_gtk_add_menu_item (menu, UTF8_SHORT_PRINTF("%s", _("Finished.")),
		                        NULL, NULL, ICON_MENU_STATUS, TRUE );
		return;
	}

	utf8 = g_string_new(NULL);

	if (dl->transfered <= dl->size)
  {
		g_string_assign(utf8, UTF8_SHORT_PRINTF(_(" %s left"),
		                (gchar*)human_size(dl->size - dl->transfered)));

		if (dl->speed > 0.05)
		{
			guint  hours_left = dl->secs_left/3600;
			guint  mins_left = (dl->secs_left%3600)/60;

			if (hours_left < 500) /* avoid silly statistical effects */
				g_string_append(utf8, UTF8_SHORT_PRINTF(_(" - Time left: %uh%um"), hours_left, mins_left));
		}
		else if (dl->status == STATUS_LOOKING)
		{
			gfloat avgspeed, secs_left;
			guint  hours_left, mins_left;

			/* When status is LOOKING, dl->secs_left is basically undefined,
			 *  so we can't use it. Note: average speed is for times download was
			 *  active (incl. times not downloading!). Weigh current speed at 10% */

			avgspeed = 0.9 * (dl->speedvalsum / (gfloat) dl->num_speedvals); /* + 0.1 * dl->speed */

			secs_left = (gint) ((dl->size - dl->transfered)*1.0) / ((avgspeed*1024.0) + 0.001);

			hours_left = secs_left/3600;
			mins_left = ((guint)secs_left%3600)/60;

			if (avgspeed > 0.0  &&  hours_left < 500) /* avoid silly statistical effects */
				g_string_append(utf8, UTF8_SHORT_PRINTF(_(" - Time left: %uh%um"), hours_left, mins_left));
		}
	}
	else
  {
		g_string_assign(utf8, UTF8_SHORT_PRINTF("%s", _(" not much left")));
  }

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, utf8->str, NULL, NULL, ICON_MENU_STATUS, TRUE );

	g_string_free(utf8, TRUE);
}


/******************************************************************************
 *
 *   downloads_view_popup_menu_options
 *
 ******************************************************************************/

static GtkWidget *
downloads_view_popup_menu_options (GdkEventButton *event)
{
	GtkWidget   *menu = NULL;
	GList       *columns, *node;

	IconName     icon_yes = ICON_OPTION_TRUE;
	IconName     icon_no  = ICON_OPTION_FALSE;

	menu  = gtk_menu_new();

	misc_gtk_add_menu_item (menu, _(" hide junk in filenames shown "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_SHOWHIDE_JUNK),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_HIDE_JUNK)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" keep file extensions when removing junk in download list "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_KEEPEXT),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_KEEP_EXTENSION_WHEN_HIDING_JUNK)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, _(" verify download cancels "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_VERIFYCANCELS),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_VERIFY_CANCEL)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" never cancel non-paused downloads without confirmation "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_NO_ACTIVE_CANCEL),
	                        (opt_get_bool(OPT_GUI_NO_ACTIVE_DOWNLOAD_CANCEL)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" keep/save corrupted downloads instead of deleting them (core) "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_SAVE_CORRUPTED),
	                        (gui_core_conn_get_core_options(core)->save_corrupted) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" skip files with Insuff.Disk Space on conditional 'pause all' "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_NO_IDS_PAUSE),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_NO_INSUFFICIENT_DISK_SPACE_PAUSE)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" progress bars: use green shading for available parts "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_GREENPBPARTS),
	                        (!opt_get_bool(OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" show filenames in bold print "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_BOLD_FILENAMES),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_FILENAMES_BOLD)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" show 'transfered' in percent "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_SHOW_TRANS_AS_PERCENT),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_SHOW_TRANSFERED_IN_PERCENT)) ? icon_yes : icon_no, TRUE);


	misc_gtk_add_menu_item (menu, _(" add new downloads automatically to blacklist "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_BLACKLISTONDL),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_BLACKLIST_ON_DOWNLOAD)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, _(" when a download completes, resume another paused one "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_RESUMEONCOMPLETE),
	                        (opt_get_bool(OPT_GUI_RESUME_ONE_PAUSED_DL_ON_COMPLETE)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, _(" unselect all items after each action "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_UNSELECT_AFTER_ACTION),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_UNSELECT_ALL_AFTER_ACTION)) ? icon_yes : icon_no, TRUE);

	misc_gtk_add_menu_item (menu, _(" use single selection mode in download list "), onOptionToggled,
	                        GUINT_TO_POINTER(TOGGLE_SELMODE),
	                        (opt_get_bool(OPT_GUI_DOWNLOADS_SINGLE_SELECTION_MODE)) ? icon_yes : icon_no, TRUE);

	/* 'hide column' item */

	if (event)
	{
		GtkTreeViewColumn  *col = NULL;

		(void) gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(downloads_view), (gint)event->x, (gint)event->y, NULL, &col, NULL, NULL);

		/* if there are no download entries at all, we get col == NULL, thanks to Daniel for finding this */
		if (col)
		{
			const gchar *utf8 = UTF8_SHORT_PRINTF(_(" hide column '%s' "), gtk_tree_view_column_get_title(col));

			misc_gtk_add_menu_separator (menu);

			misc_gtk_add_menu_item (menu, utf8, onHideColumn, (gpointer) col, ICON_MENU_HIDE_COLUMN, TRUE);
		}
	}


	/* 'unhide column X' items */

	columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(downloads_view));

	if ( g_list_length(columns) > 0 )
		misc_gtk_add_menu_separator (menu);

	for ( node = columns; node != NULL; node = node->next )
	{
		GtkTreeViewColumn *col;
		const gchar       *utf8;

		if (!node->data)
			continue;

		col = GTK_TREE_VIEW_COLUMN(node->data);

		if (gtk_tree_view_column_get_visible(col))
			continue;

		utf8 = UTF8_SHORT_PRINTF(_(" show hidden column '%s' "), gtk_tree_view_column_get_title(col));

		misc_gtk_add_menu_item (menu, utf8, onUnhideColumn, (gpointer) col, ICON_MENU_UNHIDE_COLUMN, TRUE);
	}
	g_list_free(columns);

	return menu;
}


/******************************************************************************
 *
 *   downloads_view_popup_menu
 *
 ******************************************************************************/

void
downloads_view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer data)
{
	GtkTreeSelection  *selection;
	GtkWidget         *menu, *pmenu, *cmenu, *pausemenu, *omenu;
	gint               sel_count;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
	sel_count = misc_gtk_tree_selection_count_selected_rows (selection);

	/* priority sub-menu */
	pmenu  = gtk_menu_new();

	misc_gtk_add_menu_item (pmenu, _(" highest ")  , onSetPriority, GUINT_TO_POINTER(DL_PRIO_HIGHEST), ICON_MENU_PRIO_HIGH  , TRUE );
	misc_gtk_add_menu_item (pmenu, _(" high ")  , onSetPriority, GUINT_TO_POINTER(DL_PRIO_HIGH), ICON_MENU_PRIO_HIGH  , TRUE );
	misc_gtk_add_menu_item (pmenu, _(" normal "), onSetPriority, GUINT_TO_POINTER(DL_PRIO_NORMAL), ICON_MENU_PRIO_MEDIUM, TRUE );
	misc_gtk_add_menu_item (pmenu, _(" low ")   , onSetPriority, GUINT_TO_POINTER(DL_PRIO_LOW), ICON_MENU_PRIO_LOW   , TRUE );


	/* cancel sub-menu */
	cmenu = gtk_menu_new();
	misc_gtk_add_menu_item (cmenu, _(" cancel selected downloads ")  , onCancelSelected, GUINT_TO_POINTER(0), ICON_MENU_DL_CANCEL, TRUE);
	misc_gtk_add_menu_item (cmenu, _(" cancel and add to blacklist "), onCancelSelected, GUINT_TO_POINTER(1), ICON_MENU_BLACKLIST, TRUE);

	/* pause all... sub-menu */
	pausemenu = gtk_menu_new();
	misc_gtk_add_menu_item (pausemenu, _(" pause all idle downloads ")               , onPauseAll, GUINT_TO_POINTER(PAUSE_MODE_ZEROSPEED)        , ICON_MENU_PAUSE_ALL, TRUE);
	misc_gtk_add_menu_item (pausemenu, _(" pause all downloads w/o sources ")        , onPauseAll, GUINT_TO_POINTER(PAUSE_MODE_NOSOURCES)        , ICON_MENU_PAUSE_ALL, TRUE);
	misc_gtk_add_menu_item (pausemenu, _(" pause all not fully available downloads "), onPauseAll, GUINT_TO_POINTER(PAUSE_MODE_NOTFULLYAVAILABLE), ICON_MENU_PAUSE_ALL, TRUE);
	misc_gtk_add_menu_item (pausemenu, _(" pause all downloads ")                    , onPauseAll, GUINT_TO_POINTER(PAUSE_MODE_ALL)              , ICON_MENU_PAUSE_ALL, TRUE);

	/* main pop-up menu */
	menu  = gtk_menu_new();

  misc_gtk_add_menu_header(menu, _("Downloads"), NULL);

	misc_gtk_add_menu_item (menu, _(" pause ")  , onPauseSelected  , NULL, ICON_MENU_PAUSE  , ( sel_count > 0 ) );
	misc_gtk_add_menu_item (menu, _(" resume  "), onResumeSelected , NULL, ICON_MENU_RESUME , ( sel_count > 0 ) );
	misc_gtk_add_menu_item (menu, _(" preview "), onPreviewSelected, NULL, ICON_MENU_PREVIEW,
	                         ((sel_count > 0) && (gui_core_conn_is_local(core) || opt_get_bool(OPT_GUI_OVERRIDE_TEMP))));

	misc_gtk_add_submenu   (menu, _(" set priority "), ICON_MENU_PRIO_MEDIUM, pmenu, ( sel_count > 0 ) );
	misc_gtk_add_submenu   (menu, _(" cancel ")      , ICON_MENU_DL_CANCEL  , cmenu, ( sel_count > 0 ) );

	misc_gtk_add_menu_item (menu, _(" write ed2k-link to log/clipboard "), onWriteSelectedLinks, NULL, ICON_MENU_STATUS, ( sel_count > 0 ) );

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_submenu   (menu, _(" pause all ")   , ICON_MENU_PAUSE_ALL  , pausemenu, TRUE );

	misc_gtk_add_menu_item (menu, _(" resume all "), onResumeAll, NULL, ICON_MENU_RESUME_ALL, TRUE);

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, _(" clear completed downloads "), onClearCompleted, NULL, ICON_MENU_CANCEL, (gui_download_list_count_completed(download_list)> 0) );

	misc_gtk_add_menu_separator (menu);

	misc_gtk_add_menu_item (menu, _(" import and show jigle serverlist  "), onGetJigleServerList, NULL, ICON_MENU_SEARCH,
	                         (( sel_count > 0 ) && (!gui_core_conn_is_overnet(core) || gui_core_conn_is_hybrid(core))) );

	omenu = downloads_view_popup_menu_options(event);

	misc_gtk_add_submenu   (menu, _(" downloads options ") , ICON_MENU_OPTIONS , omenu, TRUE );

	downloads_view_popup_menu_append_info_string(menu);

	gtk_widget_show(menu);

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, (event) ? event->button : 0, gdk_event_get_time((GdkEvent*)event));
}

/******************************************************************************
 *
 *   onButtonPress
 *
 *   a single click on a row has happened
 *
 ******************************************************************************/

static gboolean
onButtonPress (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	gtk_widget_grab_focus(widget);

	/* right-click -> popup the menu */
	if (event->button == 3)
	{
		GtkTreeSelection *selection;

		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(widget));

		if ( misc_gtk_tree_selection_count_selected_rows (selection) <= 1 )
		{
			GtkTreePath *path = NULL;

			gtk_tree_selection_unselect_all(selection);

			if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(widget), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
				gtk_tree_selection_select_path (selection, path);

			if (path)
				gtk_tree_path_free(path);
		}

		downloads_view_popup_menu (widget, event, NULL);

		return TRUE; /* event has been dealt with */
	}

	return FALSE;
}


/******************************************************************************
 *
 *  onKeyPress
 *
 ******************************************************************************/

static gint
onKeyPress  (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	guint key = gdk_keyval_to_lower(event->keyval);

	switch (key)
	{
		case GDK_Delete:
			onCancelSelected(NULL,NULL);
			break;

		case 'p':
			onPauseSelected(NULL, NULL);
			break;

		case 'r':
			onResumeSelected(NULL, NULL);
			break;

		case 'l':
			onSetPriority(NULL, GUINT_TO_POINTER(DL_PRIO_LOW));
			break;

		case 'n':
			onSetPriority(NULL, GUINT_TO_POINTER(DL_PRIO_NORMAL));
			break;

		case 'h':
			onSetPriority(NULL, GUINT_TO_POINTER(DL_PRIO_HIGH));
			break;

		case 'u':
		{
			GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(downloads_view));
			gtk_tree_selection_unselect_all(selection);
		}
		break;
		case 'a':
		{
			GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(downloads_view));
			gtk_tree_selection_select_all(selection);
		}
		break;

		default:
			return FALSE;
	}

	return TRUE; /* has been dealt with - if key is not in list we return false right away */
}


/******************************************************************************
 *
 *   onPopUpMenu
 *
 *   this is so that user-defined stuff like Shift-F10 works.
 *
 ******************************************************************************/

static gboolean
onPopUpMenu (GtkWidget *widget, gpointer data)
{
	downloads_view_popup_menu (widget, NULL, NULL);
	return TRUE;
}

/******************************************************************************
 *
 *   onRowActivated
 *
 *   a row has been double-clicked.
 *
 ******************************************************************************/

static gboolean
onRowActivated (GtkWidget *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
{
	GuiDownload  *dl;

	if (opt_get_bool(OPT_GUI_IGNORE_DOUBLE_CLICKS))
		return TRUE;

	dl = gui_download_list_get_download_from_path(download_list, path);

	g_return_val_if_fail ( dl != NULL, TRUE );

	if (dl->done)
		return TRUE;

	switch (dl->status)
	{
		case STATUS_DOWNLOADING:
		case STATUS_LOOKING:
		case STATUS_QUEUED:
		case STATUS_IDS:
		case STATUS_PAUSED:
		{
			if (dl->status == STATUS_PAUSED)
			{
				gui_core_conn_send_resume_download(core, dl->hash);
				gui_download_list_download_change_status (download_list, dl, STATUS_RESUMING);
			}
			else
			{
				gui_core_conn_send_pause_download(core, dl->hash);
				gui_download_list_download_change_status (download_list, dl, STATUS_PAUSING);
 			}

			gui_core_conn_send_get_stats(core);

			break;
		}

		default:
			break;
	}

	return TRUE;
}


/******************************************************************************
 *
 *   downloads_view_append_column
 *
 *   Appends a column to the downloads view
 *
 ******************************************************************************/

static GtkTreeViewColumn *
downloads_view_append_column (const gchar   *title,
                              const gchar   *attr,
                              guint          attr_column,
                              gint           sortid,
                              gfloat         align,
                              guint          cell_xpad,
                              gfloat         cell_xalign,
                              guint          minwidth)
{
	GtkTreeViewColumn  *column;
	GtkCellRenderer    *renderer;

	column   = gtk_tree_view_column_new();
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);

	if (strcmp(attr,"pixbuf")==0)
	{
		renderer = gtk_cell_renderer_pixbuf_new();
		gtk_tree_view_column_pack_start(column, renderer, FALSE);
	}
	else
	{
		renderer = gtk_cell_renderer_text_new();
		gtk_tree_view_column_pack_start(column, renderer, TRUE);
	}

	gtk_tree_view_column_set_title(column, UTF8_SHORT_PRINTF("%s",title));
	gtk_tree_view_column_add_attribute(column, renderer, attr, attr_column);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_column_set_sort_column_id(column, sortid);
	gtk_tree_view_column_set_alignment(column, align);
	g_object_set (G_OBJECT(renderer), "xpad", cell_xpad, "xalign", cell_xalign, NULL);
	gtk_tree_view_column_set_min_width (column, minwidth);

	gtk_tree_view_append_column(GTK_TREE_VIEW(downloads_view), column);

	return column;
}

/***************************************************************************
 *
 *   onDownloadRemoved
 *
 *   The GuiDownloadList emits a "download-removed" signal before it
 *    removes a download, so that we can remove it from the selected
 *    item list if it is in there...
 *
 ***************************************************************************/

static void
onDownloadRemoved (GtkTreeModel *model, GuiDownload *dl, gpointer data)
{
//	g_print ("Download going to be removed from list: %s\n", dl->name_utf8);
	selected = g_list_remove_all(selected, dl);
}




/******************************************************************************
 *
 *   onColumnResizedSaveWidths
 *
 ******************************************************************************/

static gboolean
onColumnResizedSaveWidths (gint *p_handler)
{
	gchar buf[512], num[32], i;

	if (p_handler)
		*p_handler = 0;

	buf[0] = 0x00;

	for (i = 0;  i < NUM_VIEW_COLS;  ++i)
	{
		g_snprintf(num, sizeof(num), "%u;", column_widths[(guint)i]);
		g_strlcat(buf, num, sizeof(buf));
	}

	if (buf[0] != 0x00)
	{
		buf[strlen(buf)-1] = 0x00; /* remove semicolon at the end */
	}

	opt_set_str(OPT_GUI_COLUMN_WIDTHS_DOWNLOADS, buf);

	return FALSE; /* Don't call again */
}

/******************************************************************************
 *
 *   onColumnResized
 *
 ******************************************************************************/

static void
onColumnResized (GObject *obj, GParamSpec *pspec, gpointer data)
{
	GtkTreeViewColumn *col;
	static gint        handler; /* 0 */
	GList             *cols;
	gint               num;

	col = GTK_TREE_VIEW_COLUMN(obj);

	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(downloads_view));

	num = g_list_index(cols, col);

	g_list_free(cols);

	if (num < 0)
		return;

	g_return_if_fail (num < NUM_VIEW_COLS);

	column_widths[num] = gtk_tree_view_column_get_width(col);

	if (!gtk_tree_view_column_get_visible(col))
		column_widths[num] |= COLUMN_INVISIBLE_FLAG;

	if (handler == 0)
		handler = g_timeout_add(1000, (GSourceFunc) onColumnResizedSaveWidths, &handler);
}

/******************************************************************************
 *
 *  onSortColumnChanged
 *
 ******************************************************************************/

static void
onSortColumnChanged (GtkTreeSortable *sortable, gpointer data)
{
	GtkSortType order;
	gint        sortid;

	if (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order))
	{
		opt_set_int (OPT_GUI_SORT_COL_DOWNLOADS, sortid);
		opt_set_int (OPT_GUI_SORT_TYPE_DOWNLOADS, order);
	}
}

/******************************************************************************
 *
 *   restore_tree_view_column_widths
 *
 ******************************************************************************/

static void
restore_tree_view_column_widths (GtkWidget *tview)
{
	GtkTreeViewColumn *col;
	const gchar       *pos;
	GList             *cols;
	guint              n = 0;

	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(tview));

	g_assert (g_list_length(cols) == NUM_VIEW_COLS);

 	pos = opt_get_str(OPT_GUI_COLUMN_WIDTHS_DOWNLOADS);

	while ((pos) && *pos != 0x00 &&  n < NUM_VIEW_COLS)
	{
		column_widths[n] = (guint) atoi(pos);

		col = GTK_TREE_VIEW_COLUMN(g_list_nth_data(cols, n));

		g_signal_handlers_block_by_func(col, onColumnResized, NULL);

		gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
		gtk_tree_view_column_set_resizable(col, TRUE);
		gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(col), (column_widths[n] & COLUMN_NUM_BITS) );

		if (column_widths[n] & COLUMN_INVISIBLE_FLAG)
			gtk_tree_view_column_set_visible(col, FALSE);

		g_signal_handlers_unblock_by_func(col, onColumnResized, NULL);

		pos = strchr(pos, ';');

		if (pos)
			++pos; /* skip ';' */

		++n;
	}

	g_list_free(cols);
}

/******************************************************************************
 *
 *   onOptionChanged
 *
 ******************************************************************************/

static void
onOptionChanged (OptNum num)
{
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(downloads_view), opt_get_bool(OPT_GUI_SET_RULES_HINT_ON_LISTS));
}


/******************************************************************************
 *
 *   onStatusMessage
 *
 ******************************************************************************/

static void
onStatusMessage (GuiDownloadList *list, const gchar *msg, gpointer data)
{
	if (g_ascii_strncasecmp("Corrupted Download:", msg, 18) != 0)
		return;

	msg += 19;
	while (*msg != 0x00 && (*msg == ' ' || *msg =='\t'))
		++msg;

	gui_download_list_set_corrupted_from_filename(list, msg);
}

/******************************************************************************
 *
 *  onCoreConnStatus
 *
 ******************************************************************************/

static void
onCoreConnStatus (GuiCoreConn *conn, guint status, GuiDownloadList *list)
{
	if (status != CONN_STATUS_COMMUNICATING && status != CONN_STATUS_AUTHENTICATING)
		gui_download_list_clear(list);
}

/***************************************************************************
 *
 *   downloads_view_create
 *
 ***************************************************************************/

static GtkWidget *
downloads_view_create (void)
{
	GtkTreeViewColumn *col;
	GtkCellRenderer   *cell;
	GtkTreeSelection  *selection;

	opt_notify (OPT_GUI_SET_RULES_HINT_ON_LISTS, onOptionChanged);

	download_list = gui_download_list_new();

	g_signal_connect(download_list, "sort-column-changed", (GCallback) onSortColumnChanged,  NULL);

	g_signal_connect_swapped(core, "download-status", (GCallback) gui_download_list_update_status, download_list);
	g_signal_connect_swapped(core, "download-gaps",   (GCallback) gui_download_list_download_gaps, download_list);
	g_signal_connect_swapped(core, "new-download",    (GCallback) gui_download_list_new_download, download_list);
	g_signal_connect_swapped(core, "remove-download", (GCallback) gui_download_list_download_removed, download_list);

	/* To catch 'Corrupted Download: foo.txt' message */
	g_signal_connect_swapped(core, "status-message",  (GCallback) onStatusMessage, download_list);
	g_signal_connect_swapped(core, "error-message",   (GCallback) onStatusMessage, download_list);

	g_signal_connect(core, "core-conn-status", (GCallback) onCoreConnStatus, download_list);

	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(download_list),
	                                     opt_get_int(OPT_GUI_SORT_COL_DOWNLOADS),
	                                     opt_get_int(OPT_GUI_SORT_TYPE_DOWNLOADS));


	downloads_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(download_list));

	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(downloads_view));

	g_signal_connect(download_list, "download-removed", (GCallback) onDownloadRemoved, NULL);

	col = downloads_view_append_column (_("Filename"), "pixbuf",
                                 GUI_DOWNLOAD_LIST_DOT,
                                 GUI_DOWNLOAD_SORT_ID_NAME,
                                 0.0, 0, 0.0, 5);

	cell = gtk_cell_renderer_text_new();
	g_object_set (G_OBJECT(cell), "xpad", 5, NULL);
	gtk_tree_view_column_pack_start(col, cell, TRUE);
	gtk_tree_view_column_add_attribute(col, cell, "text", GUI_DOWNLOAD_LIST_NAME);

	g_object_set (cell,
	              "weight", PANGO_WEIGHT_BOLD,
	              "weight-set", opt_get_bool(OPT_GUI_DOWNLOADS_FILENAMES_BOLD),
	              NULL);

	downloads_view_append_column (_("Status"), "text",
                                 GUI_DOWNLOAD_LIST_STATUS_STR,
                                 GUI_DOWNLOAD_SORT_ID_STATUS,
                                 0.1, 5, 0.0, 5);

	downloads_view_append_column (_("Size"), "text",
                                 GUI_DOWNLOAD_LIST_SIZE_STR,
                                 GUI_DOWNLOAD_SORT_ID_SIZE,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Transfered"), "text",
                                 GUI_DOWNLOAD_LIST_TRANSFERED_STR,
                                 GUI_DOWNLOAD_SORT_ID_TRANSFERED,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Left"), "text",
                                 GUI_DOWNLOAD_LIST_LEFT_STR,
                                 GUI_DOWNLOAD_SORT_ID_LEFT,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Speed"), "text",
                                 GUI_DOWNLOAD_LIST_SPEED_STR,
                                 GUI_DOWNLOAD_SORT_ID_SPEED,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Time"), "text",
                                 GUI_DOWNLOAD_LIST_TIME_LEFT_STR,
                                 GUI_DOWNLOAD_SORT_ID_TIME_LEFT,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Priority"), "text",
                                 GUI_DOWNLOAD_LIST_PRIORITY_STR,
                                 GUI_DOWNLOAD_SORT_ID_PRIORITY,
                                 0.2, 5, 0.0, 5);

	col = gtk_tree_view_column_new();
	g_signal_connect(col, "notify::width", (GCallback) onColumnResized, NULL);

	cell = gui_cell_renderer_progress_new();
	gtk_tree_view_column_pack_start(col, cell, TRUE);
	gtk_tree_view_column_add_attribute(col, cell, "gaplist", GUI_DOWNLOAD_LIST_GAPLIST);
	gtk_tree_view_column_add_attribute(col, cell, "paused", GUI_DOWNLOAD_LIST_PAUSED_BOOL);
	gtk_tree_view_column_set_title(col, UTF8_SHORT_PRINTF("%s", _("Availability")));
	gtk_tree_view_append_column(GTK_TREE_VIEW(downloads_view),col);
	gtk_tree_view_column_set_sort_column_id(col, GUI_DOWNLOAD_SORT_ID_AVAILABILITY);
	gtk_tree_view_column_set_resizable(col, TRUE);

	if (opt_get_bool(OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR))
		g_object_set(cell, "available-color", "blue", NULL);
	else
		g_object_set(cell, "available-color", "green", NULL);

	g_object_set_data(G_OBJECT(downloads_view), "ProgressbarRenderer", cell);

	downloads_view_append_column (_("Sources"), "text",
                                 GUI_DOWNLOAD_LIST_SOURCES_STR,
                                 GUI_DOWNLOAD_SORT_ID_SOURCES,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Last Downloading"), "markup",
                                 GUI_DOWNLOAD_LIST_LAST_DL_STR,
                                 GUI_DOWNLOAD_SORT_ID_LAST_DL,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Last Complete"), "markup",
                                 GUI_DOWNLOAD_LIST_LAST_COMPLETE_STR,
                                 GUI_DOWNLOAD_SORT_ID_LAST_COMPLETE,
                                 0.9, 5, 1.0, 5);

	downloads_view_append_column (_("Type"), "text",
                                 GUI_DOWNLOAD_LIST_TYPE_STR,
                                 GUI_DOWNLOAD_SORT_ID_TYPE,
                                 0.2, 5, 0.0, 5);

	downloads_view_append_column (_("Format"), "text",
                                 GUI_DOWNLOAD_LIST_FORMAT_STR,
                                 GUI_DOWNLOAD_SORT_ID_FORMAT,
                                 0.2, 5, 0.0, 5);


	/* set multi- or single- selection mode */
	if ( opt_get_bool(OPT_GUI_DOWNLOADS_SINGLE_SELECTION_MODE) )
		gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
	else
		gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	gtk_tree_selection_set_select_function(selection, downloads_view_selection_func, NULL, NULL);

	g_signal_connect (G_OBJECT(downloads_view), "row-activated", G_CALLBACK(onRowActivated), NULL);
	g_signal_connect (G_OBJECT(downloads_view), "button_press_event", G_CALLBACK(onButtonPress), NULL);
	g_signal_connect (G_OBJECT(downloads_view), "key_press_event", G_CALLBACK(onKeyPress), NULL);
	g_signal_connect (G_OBJECT(downloads_view), "popup_menu", G_CALLBACK(onPopUpMenu), NULL);

	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(downloads_view), opt_get_bool(OPT_GUI_SET_RULES_HINT_ON_LISTS));

	gtk_widget_show(downloads_view);

	restore_tree_view_column_widths(downloads_view);

	return downloads_view;
}


/***************************************************************************
 *
 *   onClientStats
 *
 ***************************************************************************/

static void
onClientStats (GtkWidget *slabel,
               gfloat     temp,
               gfloat     incoming,
               gfloat     needed,
               guint      clientid,
               guint      connections,
               guint      num_on_queue,
               gpointer   data)
{
	const gchar *spacetxt;

	g_assert (GTK_IS_LABEL(slabel));

	if (needed == -1.0 && temp == -1.0 && incoming == -1.0)
	{
		spacetxt = UTF8_SHORT_PRINTF(_(" Downloads "));
	}
	else
	{
		if (needed == 0.0)  /* v50.x doesn't give us this value any longer */
		{
			spacetxt =  UTF8_SHORT_PRINTF(_(" Downloads - free in temp: %.1fMB - free in incoming: %.1fMB "), temp, incoming);
		}
		else
		{
			spacetxt =  UTF8_SHORT_PRINTF(_(" Downloads - need %.1fMB - free in temp: %.1fMB - "
			                                "free in incoming: %.1fMB "), needed, temp, incoming);
		}
	}

	gtk_label_set_text (GTK_LABEL(slabel), spacetxt);

	if (temp <= (opt_get_int(OPT_GUI_DOWNLOADS_MIN_FREE_DISKSPACE)*1.0))
	{
		onPauseAll (NULL, GUINT_TO_POINTER(PAUSE_MODE_ALL_OUT_OF_DISKSPACE));

		if (num_paused > 0)
			status_message_blue (_("GUI: Only %3.1f MB free in the temp folder! Pausing all downloads.\n"), temp);
	}
}


/***************************************************************************
 *
 *   downloads_view_page_create_stats_label
 *
 ***************************************************************************/

static GtkWidget *
downloads_view_page_create_stats_label (void)
{
	GtkWidget	*hbox, *emptylabel;
	GtkWidget *stats_label, *fd_label;

	hbox = gtk_hbox_new(FALSE,0);
	gtk_widget_show(hbox);

	emptylabel = gtk_label_new(" ");
	gtk_widget_show(emptylabel);

	stats_label = gtk_label_new (UTF8_SHORT_PRINTF("%s",_(" Downloads ")));
	gtk_label_set_justify (GTK_LABEL(stats_label), GTK_JUSTIFY_LEFT);
	gtk_widget_show(stats_label);

	fd_label = gtk_label_new(" ");
	gtk_label_set_justify (GTK_LABEL(fd_label), GTK_JUSTIFY_RIGHT);
	gtk_widget_show(fd_label);

	gtk_box_pack_start (GTK_BOX(hbox), stats_label, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX(hbox), emptylabel, TRUE, TRUE, 0);
	gtk_box_pack_start (GTK_BOX(hbox), fd_label, FALSE, FALSE, 0);

	if (!opt_get_bool(OPT_GUI_NO_GREEN))
		set_style_recursively (hbox, style_notebook_label);

	g_signal_connect_swapped(core, "client-stats", (GCallback) onClientStats, stats_label);

	g_timeout_add(60*1000, (GSourceFunc) onEachMinute, fd_label);

	return hbox;
}

/***************************************************************************
 *
 *   downloads_view_page_create
 *
 ***************************************************************************/

GtkWidget *
downloads_view_page_create (void)
{
	GtkWidget *scrollwin;
	GtkWidget	*view;
	GtkWidget	*vbox;
	GtkWidget	*frame;

	view = downloads_view_create();

	vbox = gtk_vbox_new (FALSE,0);

	gtk_box_pack_start (GTK_BOX (vbox), downloads_view_page_create_stats_label(), FALSE, FALSE, 0);

	frame = gtk_frame_new (NULL);
	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);

	scrollwin = gtk_scrolled_window_new (NULL,NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_container_add (GTK_CONTAINER(scrollwin), view);

	gtk_container_add (GTK_CONTAINER (frame), scrollwin);
	gtk_box_pack_start (GTK_BOX(vbox), frame, TRUE, TRUE, 0);

	gtk_widget_show_all (vbox);

	return vbox;
}








