/***************************************************************************
               servers.h  -  gtk+-2.x version of the servers list
               --------------------------------------------------
    begin                : Sat Feb 15 2003
    copyright            : (C) 2003 by Axel C, Tim-Philipp Müller
    email                : axel@banzais.org, t.i.m@orange.net
 ***************************************************************************/

/* TODO:
 *  - SERVERS_LAST_CONNECTED_COLUMN is not used anywhere, is it?
 *  - key bindings
 *  - fix server priorities stuff (?)
 */

#include "global.h"
#include "colors.h"
#include "core-conn.h"
#include "misc.h"
#include "options.h"

#include "misc_gtk.h"
#include "misc_strings.h"
#include "http_get.h"
#include "icons.h"
#include "statusbar.h"
#include "status_page.h"


#include "servers.h"

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

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

#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtkvbox.h>

enum
{
	SERVERS_NAME_COLUMN = 0,
	SERVERS_IP_COLUMN,
	SERVERS_PORT_COLUMN,
	SERVERS_USERS_COLUMN,
	SERVERS_FILES_COLUMN,
	SERVERS_PREFERENCE_COLUMN,
	SERVERS_PING_COLUMN,
	SERVERS_DESCRIPTION_COLUMN,
	SERVERS_REMOVE_COLUMN,
	SERVERS_NO_GOOD_COLUMN,
	SERVERS_LAST_SEEN_ALIVE_COLUMN,
	SERVERS_LAST_CONNECTED_COLUMN,
	SERVERS_LAST_CONNECTING_COLUMN,
	SERVERS_INVALID_COLUMN,
	SERVERS_PREFERENCE_STRING_COLUMN,
	SERVERS_IP_STRING_COLUMN,

	SERVERS_N_COLUMNS
};

#define SERVERS_TYPES_PLACEHOLDER\
	G_TYPE_STRING,  /* SERVERS_NAME_COLUMN              */\
	G_TYPE_UINT,    /* SERVERS_IP_COLUMN                */\
	G_TYPE_UINT,    /* SERVERS_PORT_COLUMN              */\
	G_TYPE_UINT,    /* SERVERS_USERS_COLUMN             */\
	G_TYPE_UINT,    /* SERVERS_FILES_COLUMN             */\
	G_TYPE_INT,     /* SERVERS_PREFERENCE_COLUMN        */\
	G_TYPE_UINT,    /* SERVERS_PING_COLUMN              */\
	G_TYPE_STRING,  /* SERVERS_DESCRIPTION_COLUMN       */\
	G_TYPE_BOOLEAN, /* SERVERS_REMOVE_COLUMN            */\
	G_TYPE_BOOLEAN, /* SERVERS_NO_GOOD_COLUMN           */\
	G_TYPE_UINT,    /* SERVERS_LAST_SEEN_ALIVE_COLUMN   */\
	G_TYPE_UINT,    /* SERVERS_LAST_CONNECTED_COLUMN    */\
	G_TYPE_UINT,    /* SERVERS_LAST_CONNECTING_COLUMN   */\
	G_TYPE_BOOLEAN, /* SERVERS_INVALID_COLUMN           */\
	G_TYPE_STRING,  /* SERVERS_PREFERENCE_STRING_COLUMN */\
	G_TYPE_STRING   /* SERVERS_IP_STRING_COLUMN         */\



/* struct ServerBuf: used for the 'connect to any selected' queue */
typedef struct
{
	guint32	ip;
	guint32	port;
} ServerBuf;


#define NUM_VIEW_COLS        8


/* variables */

static GtkListStore          *servers_store; /* NULL */

static GtkWidget             *servers_view; /* NULL */

static GtkWidget             *servers_stats_label; /* NULL */

static GQueue                *servers_connect_selected_queue; /* NULL */

static gint                   option_alwaysStayConnectedSaved = -1;  /* -1 = no value saved */

static guint                  servers_count; /* 0 */

static GHashTable            *servers_hashtable; ; /* NULL -- key: hash(IP+port)  value: GtkTreeRowReference */

static guint                  column_widths[NUM_VIEW_COLS];

static gint                   timeout_handle; /* 0 */


/* local functions */

static gboolean               servers_connect_queue_connect_next (void);

static const gchar           *servers_get_prio_string (ServerPriority prio);

static gpointer               servers_get_server_key (guint32 ip, guint16 port);

static gboolean               servers_find_from_address (guint32 ip, guint16 port, GtkTreeIter *iter);

static gboolean               servers_find_from_name (const gchar *name, GtkTreeIter *iter);

static void                   servers_save_alwaysconnected_option (void);

static void                   servers_register_connect_status (const gchar *ip0rName, ServersConnectStatus status);

static gint                   servers_onPlacedOnQueueDeadlockTimer (gpointer data);

static void                   servers_mark_all_for_removal (void);

static void                   servers_remove_all_old (void);

static gint                   servers_sort_ip_function ( GtkTreeModel *model,
                                                         GtkTreeIter *a,
                                                         GtkTreeIter *b,
                                                         gpointer data);


static void                   servers_popup_unselect_all_if_wanted (void);

static void                   servers_onToggleAlwaysConnected (GtkWidget *widget, gpointer data);
static void                   servers_onToggleAutoRemoveDead (GtkWidget *widget, gpointer data);
static void                   servers_onToggleIgnoreQueueDeadlock (GtkWidget *widget, gpointer data);
static void                   servers_onToggleAutoClearOnFetch (GtkWidget *widget, gpointer data);
static void                   servers_onToggleFetchNewListWhenTooFewServers (GtkWidget *widget, gpointer data);
static void                   servers_onToggleDisconnectIfFirewalled (GtkWidget *widget, gpointer data);
static void                   servers_onToggleUnselectAfterAction (GtkWidget *widget, gpointer data);
static void                   servers_onToggleAutoRemoveInvalid (GtkWidget *widget, gpointer data);
static void                   servers_onToggleSingleSelectionMode (GtkWidget *widget, gpointer data);

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

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

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


/******************************************************************************
 *
 *   everyMinute
 *
 ******************************************************************************/

static gboolean
everyMinute (gpointer data)
{
	static gint 	minutes;  /* 0 */

	if (!gui_core_conn_is_alive(core))
	{
		timeout_handle = 0;
		return FALSE; /* do not call again */
	}

	/* server rotate feature */
	if (    opt_get_int(OPT_GUI_SERVERS_ROTATE_INTERVAL) > 0
	     && (!gui_core_conn_is_overnet(core) || gui_core_conn_is_hybrid(core))
		   && minutes > 0
		   && minutes >= opt_get_int(OPT_GUI_SERVERS_ROTATE_INTERVAL)
			 && (minutes % opt_get_int(OPT_GUI_SERVERS_ROTATE_INTERVAL)) == 0)
	{
		/* will set current server to low priority */
		servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_DO_SERVER_ROTATE);
	}

	++minutes;

	return TRUE; /* call again */
}

/******************************************************************************
 *
 *   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_SERVERS, 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(servers_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 (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_SERVERS, sortid);
		opt_set_int (OPT_GUI_SORT_TYPE_SERVERS, 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_SERVERS);
	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]);

		g_signal_handlers_unblock_by_func(col, onColumnResized, NULL);

		pos = strchr(pos, ';');

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

		++n;
	}

	g_list_free(cols);
}


/*******************************************************************************
 *
 *  servers_get_prio_string
 *
 *
 ***/

static const gchar *
servers_get_prio_string (ServerPriority prio)
{
	switch(prio)
	{
		case SERVER_PRIO_LOW:
			return misc_get_prio_string(0);

		case SERVER_PRIO_NORMAL:
		return misc_get_prio_string(1);

		case SERVER_PRIO_HIGH:
			return misc_get_prio_string(2);

		default:
			break;
	}

	return "[FIXME][servers_get_prio_string]";
}


/*******************************************************************************
 *                                                                             *
 *         DATA STUFF (ADD/DELETE/FIND ENTRIES FROM MODEL/HASHTABLE)           *
 *                                                                             *
 *******************************************************************************/


#define IPPRIME1        106721
#define PORTPRIME1      71143


/******************************************************************************
 *
 *   servers_get_server_key
 *
 *   takes a server IP + port and turns it into a key for the hash table
 *
 *   OK
 *
 ***/

static gpointer
servers_get_server_key (guint32 ip, guint16 port)
{
	return GUINT_TO_POINTER( ( ip - IPPRIME1 )  ^  ( port + PORTPRIME1 ) );
}



/******************************************************************************
 *
 *   servers_find_from_address
 *
 *   Finds the iter describing the server entry in the model (servers_store)
 *    from IP+port, looking it up using the hash table.
 *
 *   If port is 0, try to find an IP without taking the port value into
 *    consideration.
 *
 *   Returns TRUE if the entry has been found and the iter has been filled,
 *    otherwise FALSE.
 *
 *   OK
 *
 ***/

static gboolean
servers_find_from_address (guint32 ip, guint16 port, GtkTreeIter *iter)
{
	GtkTreeRowReference *ref;
	gboolean             gotiterfromref, val;

	g_return_val_if_fail ( servers_hashtable != NULL, FALSE );
	g_return_val_if_fail ( iter              != NULL, FALSE );

	if ( port > 0 )
	{
		ref = (GtkTreeRowReference*) g_hash_table_lookup (servers_hashtable, servers_get_server_key(ip,port));

		if (!ref)
			return FALSE;

		gotiterfromref = misc_gtk_tree_model_get_iter_from_row_reference (GTK_TREE_MODEL(servers_store), ref, iter);
		g_return_val_if_fail ( gotiterfromref == TRUE, FALSE );

 		return TRUE;
	}

	/* if port is unspecified, we can't use the hashtable *sigh* */

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(servers_store), iter);

	while (val)
	{
		guint32 ip2 = 0;

		gtk_tree_model_get (GTK_TREE_MODEL(servers_store), iter, SERVERS_IP_COLUMN, &ip2, -1);

		if ( ip == ip2 )
			return TRUE;

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL(servers_store), iter);
	}

	return FALSE;
}


/******************************************************************************
 *
 *   servers_find_from_name
 *
 *   Finds the iter describing a server entry in the model (servers_store)
 *    from the server NAME (locale encoding?).
 *
 *   Returns TRUE if the server has been found and the iter has been filled,
 *    otherwise FALSE
 *
 *   XXX - TODO: aren't we comparing locale with an utf8 from the store here?
 *   XXX - TODO: this lookup is slow (iterating over every record) - is it worth
 *               using yet another hash table here?
 *
 *   OK
 *
 ***/

static gboolean
servers_find_from_name (const gchar *name, GtkTreeIter *iter)
{
	GtkTreeIter     it;
	gboolean        val;
	gchar          *stname = NULL;

	g_return_val_if_fail ( iter != NULL, FALSE );
	g_return_val_if_fail ( name != NULL, FALSE );

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (servers_store), &it);

	while (val)
	{
		gtk_tree_model_get ( GTK_TREE_MODEL (servers_store), &it,
		                     SERVERS_NAME_COLUMN, &stname,
		                     -1);

		/* XXX - aren't we comparing ascii (from core) with utf8 (from store) here? */

		if ( stname && ( strcmp (stname, name) == 0))
		{
			*iter = it;

			G_FREE(stname);

			return TRUE;
		}

		G_FREE(stname);

		val = gtk_tree_model_iter_next ( GTK_TREE_MODEL(servers_store), &it);
	}

	return FALSE;
}


/******************************************************************************
 *
 *   servers_cleanup
 *
 *   Removes all entries from the servers list (and hashtable of course)
 *
 *   OK
 *
 ***/

static gboolean
servers_GHRtrue (gpointer key, gpointer value, gpointer data)
{
	return TRUE; /* remove all callback */
}

void
servers_cleanup (void)
{
	/* NOTE: we can't call servers_remove_all_old() before
	 * gtk_list_store_clear(), because it puts all actual
	 * remove operations in idle callbacks (so the removes
	 * would be done after gtk_list_store_clear() has
	 * already cleared the whole list ... boom!)
	 */
/*	servers_remove_all_old(); */

	if (servers_store)
		gtk_list_store_clear (servers_store);

	if (servers_hashtable)
		g_hash_table_foreach_remove ( servers_hashtable, servers_GHRtrue, NULL );
}


static gint
servers_onPlacedOnQueueDeadlockTimer (gpointer data)
{
	// if we are called this means that we haven't gotten a "can't connect" or "connected to" message for
        // 30 seconds => deadlock assumed, send 'x' as advanced command (as message doesn't work for some reason)

	servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_PLACEDONQ_DEADLOCK);

	// XXX - maybe we should return TRUE here then?

	return 0;       // do not call us again (we'll be removed by servers_register...() anyway)
}


/*******************************************************************************
 *
 *   servers_get_count
 *
 *   OK
 *
 ***/

guint
servers_get_count (void)
{
	return servers_count;
}



/*******************************************************************************
 *
 *   servers_save_alwaysconnected_option
 *
 *   Save the current 'always stay connected' option
 *
 *   OK
 *
 ***/

static void
servers_save_alwaysconnected_option(void)
{
	/* do we already have a value saved? => do not save current value */
	if ( option_alwaysStayConnectedSaved != -1 )
		return;

	option_alwaysStayConnectedSaved = (gint) (gui_core_conn_get_core_options(core)->always_connected);
}


/*******************************************************************************
 *
 *   servers_reinstate_alwaysconnected_option
 *
 *   Reinstate the core's 'always stay connected' option as it was before
 *
 *   OK
 *
 ***/

void
servers_reinstate_alwaysconnected_option(void)
{
	/* nothing saved? => nothing to reinstate */
	if ( option_alwaysStayConnectedSaved == -1 )
		return;

	if (option_alwaysStayConnectedSaved)
	{
		gui_core_conn_send_command(core, "auto");      /* switch 'always stay connected on again' */
		gui_core_conn_send_get_options(core);
	}
	else
	{
		;  /* do nothing (auto-connect just remains switched OFF */
	}
}




/*******************************************************************************
 *
 *   servers_lookup
 *
 *   Returns TRUE if server matching IP and port is already in the server list.
 *
 *   OK
 *
 ***/

gboolean
servers_lookup (guint32 ip, guint16 port)
{
	GtkTreeIter	iter;

	return servers_find_from_address (ip, port, &iter);
}


/*******************************************************************************
 *
 *   servers_add_or_update_record
 *
 *   self-explanatory.
 *
 *   OK
 *
 ***/

void
servers_add_or_update_record ( guint32 ip,
                               guint16 port,
                               const gchar *name,
                               const gchar *desc,
                               guint32 users,
                               guint32 files,
                               guint32 ping,
                               gint8   pref )
{
	GtkTreeIter  iter;
	gboolean     new = FALSE;
	gchar       *name_utf8 = NULL;
	gchar       *desc_utf8 = NULL;
	gchar        ipstring[32];

	if ( opt_get_bool(OPT_GUI_SERVERS_AUTO_REMOVE_INVALID) )
	{
		if ( misc_ip_is_local(ip) )
		{
			gui_core_conn_send_remove_server(core, ip, port);
			return;
		}
	}

	if (name)
		name_utf8 = TO_UTF8 (name);

	if (desc)
		desc_utf8 = TO_UTF8 (desc);

	if ( servers_find_from_address (ip, port, &iter) == FALSE)
	{
		GtkTreeRowReference *ref;

		gtk_list_store_append (servers_store, &iter);

		ref = misc_gtk_tree_model_get_row_reference_from_iter ( GTK_TREE_MODEL(servers_store), &iter );

		if (!ref)
		{
			gtk_list_store_remove (servers_store, &iter);
			g_return_if_fail ( ref != NULL );
		}

		g_hash_table_insert (servers_hashtable, servers_get_server_key(ip,port), ref);

		servers_count++;
		new = TRUE;
	}

	/* all ascii, no conversion into utf8 needed */
	g_snprintf (ipstring, sizeof(ipstring)/sizeof(gchar), "%u.%u.%u.%u",
	             (ip & 0xff)       >>  0,
	             (ip & 0xff00)     >>  8,
	             (ip & 0xff0000)   >> 16,
	             (ip & 0xff000000) >> 24);

	gtk_list_store_set ( servers_store, &iter,
	                     SERVERS_IP_COLUMN, ip,
	                     SERVERS_IP_STRING_COLUMN, ipstring,
	                     SERVERS_PORT_COLUMN, port,
	                     SERVERS_DESCRIPTION_COLUMN, desc_utf8 ? desc_utf8 : "",
	                     SERVERS_NAME_COLUMN, name_utf8 ? name_utf8 : "",
	                     SERVERS_USERS_COLUMN, users,
	                     SERVERS_FILES_COLUMN, files,
	                     SERVERS_PREFERENCE_COLUMN, pref,
	                     SERVERS_PREFERENCE_STRING_COLUMN, servers_get_prio_string(pref),
	                     SERVERS_PING_COLUMN, ping,
	                     SERVERS_NO_GOOD_COLUMN, FALSE,
	                     SERVERS_REMOVE_COLUMN, FALSE,
	                     SERVERS_LAST_SEEN_ALIVE_COLUMN, (guint) time(NULL),
	                     -1);

	if (new)
	{
		gtk_list_store_set ( servers_store, &iter,
		                     SERVERS_LAST_CONNECTED_COLUMN, (guint) 0,
		                     SERVERS_LAST_CONNECTING_COLUMN, (guint) 0,
		                     -1 );
	}

	G_FREE(name_utf8);
	G_FREE(desc_utf8);
}


/*******************************************************************************
 *
 *   onServerList
 *
 *******************************************************************************/

static void
onServerList (GuiCoreConn *conn,
	            guint        num,
	            guint       *ip_arr,
	            guint       *port_arr,
	            gchar      **name_arr,
	            gchar      **desc_arr,
	            guint       *users_arr,
	            guint       *files_arr,
	            guint       *ping_arr,
              guint       *pref_arr,
              gpointer     data)
{
	guint n;

	servers_mark_all_for_removal();

	for (n = 0;  n < num;  ++n)
	{
		servers_add_or_update_record (ip_arr[n], port_arr[n], name_arr[n],
		                              desc_arr[n], users_arr[n], files_arr[n],
		                              ping_arr[n], (gint8)(pref_arr[n] & 0xff) );
	}

	servers_remove_all_old();

	statusbar_msg (" ");
}


/*******************************************************************************
 *
 *   servers_remove_iter
 *
 *   OK
 *
 ***/

static gboolean
servers_remove_iter (GtkTreeIter *iter)
{
	guint32   ip, port;

	g_return_val_if_fail ( iter != NULL, FALSE );

	gtk_tree_model_get ( GTK_TREE_MODEL (servers_store), iter,
	                     SERVERS_PORT_COLUMN, &port,
	                     SERVERS_IP_COLUMN, &ip,
	                     -1);

	gtk_list_store_remove ( servers_store, iter);

	g_return_val_if_fail ( servers_count > 0, FALSE );
	servers_count--;

	gtk_tree_iter_free (iter);

	(void) g_hash_table_remove (servers_hashtable, servers_get_server_key(ip,port));

	gui_core_conn_send_remove_server(core, ip,port);

	return FALSE;
}


/*******************************************************************************
 *
 *   servers_remove_dead
 *
 *   Remove all dead servers. Play it save this time
 *    and don't use idle timeouts for the actual removal,
 *    even if it's more code.
 *
 *   Note: we have to do the akward way we do, because we
 *    can't remove elements while iterating over a model(?)
 *
 *   OK
 *
 ***/

static gboolean
servers_remove_dead_foreach ( GtkTreeModel  *model,
                              GtkTreePath   *path,
                              GtkTreeIter   *iter,
                              GQueue       **p_queue )
{
	GtkTreeRowReference *ref;
	guint                ping = 0;

	g_return_val_if_fail (  p_queue != NULL, FALSE );
	g_return_val_if_fail ( *p_queue != NULL, FALSE );

	gtk_tree_model_get ( model, iter, SERVERS_PING_COLUMN, &ping, -1);

	/* does server appear to be alive? */
	if ( ping > 0 )
		return FALSE;

	ref = misc_gtk_tree_model_get_row_reference_from_iter ( model, iter );

	if (ref)
		g_queue_push_tail ( *p_queue, ref );

	return FALSE; /* continue with other items */
}

void
servers_remove_dead (void)
{
	GQueue  *remove_queue;

	g_return_if_fail ( servers_store != NULL );

	remove_queue = g_queue_new();

	gtk_tree_model_foreach ( GTK_TREE_MODEL(servers_store),
	                         (GtkTreeModelForeachFunc) servers_remove_dead_foreach,
	                         &remove_queue );

	while (!g_queue_is_empty(remove_queue))
	{
		GtkTreeRowReference *ref;
		GtkTreeIter          iter;

		ref = (GtkTreeRowReference*) g_queue_pop_head(remove_queue);

		if ( misc_gtk_tree_model_get_iter_from_row_reference ( GTK_TREE_MODEL(servers_store), ref, &iter ) )
		 servers_remove_iter (gtk_tree_iter_copy (&iter));

		gtk_tree_row_reference_free(ref);
	}

	g_queue_free(remove_queue);
}


/*******************************************************************************
 *
 *   servers_mark_all_for_removal
 *
 *   Flags all servers as 'to remove' before receiving the lates list of servers
 *   from the core, so that we can later identify those which aren't in the list
 *   any longer.
 *
 *   Rationale: see comment below, in servers_remove_all_old()
 *
 *   OK
 *
 ***/

static void
servers_mark_all_for_removal (void)
{
	GtkTreeIter  iter;
	gboolean     val;

	g_return_if_fail ( servers_store != NULL );

	val = gtk_tree_model_get_iter_first ( GTK_TREE_MODEL (servers_store), &iter);

	while (val)
	{
		gtk_list_store_set (servers_store, &iter, SERVERS_REMOVE_COLUMN, TRUE, -1);

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL (servers_store), &iter);
	}
}


/*******************************************************************************
 *
 *   servers_remove_all_old
 *
 *   Removes all servers which have been flagged as 'remove'.
 *
 *   Rationale: The core doesn't notify us about new servers or servers
 *              that it removes. We only get the whole server list. So
 *              what we do in parse_message.c is to flag all servers as
 *              'remove' before parsing the received list of servers,
 *              and removing that flag for all servers that we receive
 *              in the list. If after that there are any servers left
 *              which have the 'remove' flag set, then the core has
 *              removed these servers from its list since the last time
 *              we have gotten the servers list.
 *
 *   OK
 *
 ***/

static gboolean
servers_remove_all_old_foreach ( GtkTreeModel  *model,
                                 GtkTreePath   *path,
                                 GtkTreeIter   *iter,
                                 GQueue       **p_queue )
{
	GtkTreeRowReference *ref;
	gboolean             removeflag = FALSE;

	g_return_val_if_fail (  p_queue != NULL, FALSE );
	g_return_val_if_fail ( *p_queue != NULL, FALSE );

	gtk_tree_model_get ( model, iter, SERVERS_REMOVE_COLUMN, &removeflag, -1);

	/* is server old? */
	if ( !removeflag )
		return FALSE;

	ref = misc_gtk_tree_model_get_row_reference_from_iter ( model, iter );

	if (ref)
		g_queue_push_tail ( *p_queue, ref );

	return FALSE; /* continue with other items */
}

static void
servers_remove_all_old (void)
{
	GQueue  *remove_queue;

	if (!servers_store)
		return;

	remove_queue = g_queue_new();

	gtk_tree_model_foreach ( GTK_TREE_MODEL(servers_store),
	                         (GtkTreeModelForeachFunc) servers_remove_all_old_foreach,
	                         &remove_queue );

	while (!g_queue_is_empty(remove_queue))
	{
		GtkTreeRowReference *ref;
		GtkTreeIter          iter;

		ref = (GtkTreeRowReference*) g_queue_pop_head(remove_queue);

		if ( misc_gtk_tree_model_get_iter_from_row_reference ( GTK_TREE_MODEL(servers_store), ref, &iter ) )
		 servers_remove_iter (gtk_tree_iter_copy (&iter));

		gtk_tree_row_reference_free(ref);
	}

	g_queue_free(remove_queue);
}



/*******************************************************************************
 *                                                                             *
 *         CONNECT-TO-ANY-SELECTED QUEUE STUFF                                 *
 *                                                                             *
 * from the why-was-this-awful-dirty-hack--ever-implemented-at-all-department  *
 *                                                                             *
 *******************************************************************************/


/*******************************************************************************
 *
 *    servers_connect_queue_clear
 *
 *    clears the queue of selected servers to connect to
 *
 *    OK
 *
 ***/

void
servers_connect_queue_clear (void)
{
	if (!servers_connect_selected_queue)
		return;

	while(!g_queue_is_empty(servers_connect_selected_queue))
	{
		ServerBuf *data = (ServerBuf*) g_queue_pop_head(servers_connect_selected_queue);
		G_FREE(data);
	}
}


/*******************************************************************************
 *
 *    servers_connect_queue_add
 *
 *    OK
 *
 ***/

static void
servers_connect_queue_add (GtkTreeModel *model,
                           GtkTreePath  *path,
                           GtkTreeIter  *iter,
                           gpointer      data)
{
	ServerBuf *sbuf;
	guint32    ip = 0;
	guint      port = 0;

	gtk_tree_model_get ( GTK_TREE_MODEL (servers_store), iter,
	                     SERVERS_IP_COLUMN, &ip,
	                     SERVERS_PORT_COLUMN, &port,
	                     -1);

	sbuf = g_new0 ( ServerBuf, 1);

	sbuf->ip = ip;
	sbuf->port = port;

	if (!servers_connect_selected_queue)
		servers_connect_selected_queue = g_queue_new();

	g_queue_push_tail ( servers_connect_selected_queue, sbuf );
}


/*******************************************************************************
 *
 *    servers_connect_queue_connect_next
 *
 *    Returns TRUE if we did indeed ask the core to connect
 *     to the next server in our connect queue.
 *
 *    OK
 *
 ***/

static gboolean
servers_connect_queue_connect_next (void)
{
	ServerBuf  *sbuf;

	if (!servers_connect_selected_queue)
		return FALSE;

	if (g_queue_is_empty(servers_connect_selected_queue))
		return FALSE;

	sbuf = (ServerBuf *) g_queue_pop_head(servers_connect_selected_queue);

	g_return_val_if_fail ( sbuf != NULL, FALSE );

	gui_core_conn_send_connect_to_server(core, sbuf->ip, sbuf->port);

	g_free (sbuf);

	/* XXX - TODO: if 'cycle on connect to any selected' is on, then push sbuf again to tail of queue */

	return TRUE;
}



/*******************************************************************************
 *
 *   servers_onStatusConnecting
 *
 *   OK
 *
 ***/

static void
servers_onStatusConnecting (const gchar *ipOrName, GtkTreeRowReference **last_connect_attempt)
{
	GtkTreeIter  newiter;
	gboolean     found = FALSE;
	guint32      n1, n2, n3, n4;

	g_return_if_fail ( ipOrName             != NULL );
	g_return_if_fail ( last_connect_attempt != NULL );

	if (sscanf ( ipOrName, "%u.%u.%u.%u",&n1,&n2,&n3,&n4) != 4)
		found = servers_find_from_name (ipOrName, &newiter);
	else
		found = servers_find_from_address ( (n1|(n2<<8)|(n3<<16)|(n4<<24)), 0, &newiter );

	if (found == FALSE)
		return;

	gtk_list_store_set ( servers_store, &newiter, SERVERS_LAST_CONNECTING_COLUMN, (guint) time (NULL), -1);

	if (*last_connect_attempt)
		gtk_tree_row_reference_free(*last_connect_attempt);

	*last_connect_attempt = misc_gtk_tree_model_get_row_reference_from_iter (GTK_TREE_MODEL(servers_store), &newiter);
}

/*******************************************************************************
 *
 *   servers_onStatusCantConnect
 *
 *   OK
 *
 ***/

static void
servers_onStatusCantConnect (guint32 ip,
                             guint16 port,
                             guint pref,
                             GtkTreeIter *last,
                             GtkTreeRowReference **last_connect_attempt)
{
	g_return_if_fail ( last_connect_attempt != NULL );

	if ((last) && (*last_connect_attempt))
	{
		if ( pref == SERVER_PRIO_NORMAL )
		{
			gui_core_conn_send_set_server_pref(core, ip, port, (guint8) SERVER_PRIO_LOW);

			gtk_list_store_set ( servers_store, last,
			                     SERVERS_PREFERENCE_COLUMN, SERVER_PRIO_LOW,
			                     SERVERS_PREFERENCE_STRING_COLUMN, servers_get_prio_string(SERVER_PRIO_LOW),
			                     -1);
		}

		gtk_tree_row_reference_free(*last_connect_attempt);
		*last_connect_attempt = NULL;
	}

	if ( !servers_connect_queue_connect_next() )
		servers_reinstate_alwaysconnected_option();
}

/*******************************************************************************
 *
 *   servers_register_connect_status
 *
 *   The most horrible of all functions. Called if we get output from the
 *    core that indicates some kind of change in server connectivity
 *    (e.g. connecting..., can't connect, etc.). And, yes, I know that
 *    there is a switch() statement in C ;)
 *
 ***/

static void
servers_register_connect_status (const gchar *ip0rName, ServersConnectStatus status)
{
	static ServersConnectStatus    last_status = SERVERS_CONNECT_STATUS_INVALID;
	static GtkTreeRowReference    *last_connect_attempt;      /* NULL */
	static guint                   deadlock_avoid_timer;      /* 0    */
	GtkTreeIter                    lastiter;
	gint                           lc_pref;
	guint                          lc_lastconnecting;
	guint                          lc_ip, lc_port;
	gchar                         *lc_name = NULL;


	if (deadlock_avoid_timer > 0)
	{
		g_source_remove (deadlock_avoid_timer);

		deadlock_avoid_timer = 0;
	}

	if (last_connect_attempt)
	{
		if (misc_gtk_tree_model_get_iter_from_row_reference ( GTK_TREE_MODEL(servers_store), last_connect_attempt, &lastiter ))
		{
			gtk_tree_model_get ( GTK_TREE_MODEL (servers_store), &lastiter,
			                     SERVERS_IP_COLUMN, &lc_ip,
			                     SERVERS_PORT_COLUMN, &lc_port,
			                     SERVERS_NAME_COLUMN, &lc_name,
			                     SERVERS_PREFERENCE_COLUMN, &lc_pref,
			                     SERVERS_LAST_CONNECTING_COLUMN, &lc_lastconnecting,
			                     -1);
		}
		else
		{
			gtk_tree_row_reference_free(last_connect_attempt);
			last_connect_attempt = NULL;
		}
	}

	if ( status == SERVERS_CONNECT_STATUS_CONNECTING )
	{
		servers_onStatusConnecting (ip0rName, &last_connect_attempt);
	}

	else if ( status == SERVERS_CONNECT_STATUS_PLACEDONQ )
	{
		if (!opt_get_bool(OPT_GUI_SERVERS_IGNORE_QUEUE_DEADLOCKS))
			deadlock_avoid_timer = g_timeout_add (15*1000, servers_onPlacedOnQueueDeadlockTimer, NULL);
	}

	else if ( status == SERVERS_CONNECT_STATUS_CANTCONNECT )
	{
		servers_onStatusCantConnect (lc_ip, (guint16) lc_port, lc_pref,
		                             (last_connect_attempt) ? &lastiter : NULL, &last_connect_attempt);
	}

	else if ( status == SERVERS_CONNECT_STATUS_DISCONNECTED )
	{
		if (last_connect_attempt)
		{
			time_t	connected;

			connected = time (NULL) - (time_t)lc_lastconnecting;

			if (connected < 60)
				gui_core_conn_send_set_server_pref(core, lc_ip, lc_port, (guint8) SERVER_PRIO_LOW);

// XXX - high?
			if ( (connected > (5*60)) && ( connected != time (NULL)) )
				gui_core_conn_send_set_server_pref(core, lc_ip, lc_port, (guint8) SERVER_PRIO_HIGH);

			gtk_tree_row_reference_free(last_connect_attempt);
			last_connect_attempt = NULL;
		}
	}

	else if ( status ==  SERVERS_CONNECT_STATUS_PLACEDONQ_DEADLOCK )
	{
		if (last_status == SERVERS_CONNECT_STATUS_PLACEDONQ)
		{
			status_message_blue (_("GUI: detected possible core deadlock on connection attempt - trying to disconnect and reconnect.\n"));
			status_message_blue (_("GUI: explanation: this happens if server is not responding within 15-20s or core hangs on conenction queue.\n"));
			status_message_blue (_("GUI: Don't worry about this msg, it's just a 'play-it-safe' function by the GUI.\n"));
			status_message_blue (_("GUI: (you can switch this off on the options page)\n"));

			if (last_connect_attempt)
				gui_core_conn_send_set_server_pref(core, lc_ip, lc_port, (guint8) SERVER_PRIO_LOW);

			gui_core_conn_send_disconnect_from_server(core);
			gui_core_conn_send_command(core, "x");
		}
		else
				status_message_blue (_("GUI: PlacedOnQDeadlock w/o PlacedOnQ before!? (bug!)\n"));
	}

	else if ( status == SERVERS_CONNECT_STATUS_THINKS_WE_ARE_FIREWALLED )
	{
		if (last_connect_attempt)
		{
			gtk_list_store_set ( servers_store, &lastiter, SERVERS_NO_GOOD_COLUMN, TRUE, -1);

			gui_core_conn_send_set_server_pref(core, lc_ip, lc_port, (guint8) SERVER_PRIO_LOW);
		}

		/* XXX - TODO: what if we are trying to connect to selected servers only?! */
		gui_core_conn_send_disconnect_from_server(core);

		if (!gui_core_conn_get_core_options(core)->always_connected)
			gui_core_conn_send_connect_to_any_server(core);
	}

	else if ( status == SERVERS_CONNECT_STATUS_DO_SERVER_ROTATE )
	{
		gui_core_conn_send_disconnect_from_server(core);

		if (!gui_core_conn_get_core_options(core)->always_connected)
			gui_core_conn_send_connect_to_any_server(core);
	}

	else if (status == SERVERS_CONNECT_STATUS_GOT_BLACKLISTED)
	{
	}

	else g_printerr (_("GUI: unknown status in %s (bug!)\n"), __FUNCTION__);


	last_status = status;

}







/*******************************************************************************
 *
 *   servers_fetch_serverlist
 *
 *   Fetches one of the configured serverlists. Currently only called from
 *    the general timeout that is called every minute (in main.c), and only
 *    if we don't have any servers in the list any more.
 *
 *   XXX - TODO: implement!
 *
 *   OK
 *
 ***/

void
servers_fetch_serverlist (ServerListNum num)
{
	if ( num == SERVER_LIST_PRIMARY )
		http_get_retrieve_serverlist (opt_get_str(OPT_GUI_SERVERLIST_URL_1),0);

	else if ( num == SERVER_LIST_SECONDARY )
		http_get_retrieve_serverlist (opt_get_str(OPT_GUI_SERVERLIST_URL_2),0);

	else
		g_return_if_reached();
}


/*******************************************************************************
 *                                                                             *
 *                      POP-UP MENU STUFF                                      *
 *                                                                             *
 *******************************************************************************/


/******************************************************************************
 *
 *   servers_popup_unselect_all_if_wanted
 *
 *   Unselects all selected items after a pop-up menu entry has been
 *    selected and processed, IF the appropriate option has been set.
 *
 *  OK
 *
 ***/

static void
servers_popup_unselect_all_if_wanted (void)
{
	GtkTreeSelection *selection;

	if ( !opt_get_bool(OPT_GUI_SERVERS_UNSELECT_ALL_AFTER_ACTION) )
		return;

	g_return_if_fail ( servers_view != NULL );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(servers_view));

	g_return_if_fail ( selection != NULL );

	gtk_tree_selection_unselect_all(selection);
}


/******************************************************************************
 *
 *     servers_onToggleAlwaysConnected
 *
 ***/

static void
servers_onToggleAlwaysConnected (GtkWidget *widget, gpointer data)
{
	gui_core_conn_send_auto_connect(core, !gui_core_conn_get_core_options(core)->always_connected);
}

/******************************************************************************
 *
 *     servers_onToggleAutoRemoveDead
 *
 ***/

static void
servers_onToggleAutoRemoveDead (GtkWidget *widget, gpointer data)
{
	gboolean autoremdead = gui_core_conn_get_core_options(core)->autoremove_dead;

	((CoreOptions*)gui_core_conn_get_core_options(core))->autoremove_dead = !autoremdead;

	gui_core_conn_send_auto_server_remove(core, !autoremdead);
}

/******************************************************************************
 *
 *     servers_onToggleIgnoreQueueDeadlock
 *
 ***/

static void
servers_onToggleIgnoreQueueDeadlock (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_IGNORE_QUEUE_DEADLOCKS);
}

/******************************************************************************
 *
 *     servers_onToggleAutoClearOnFetch
 *
 ***/

static void
servers_onToggleAutoClearOnFetch (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_CLEAR_LIST_BEFORE_FETCHING_NEW_LIST);
}

/******************************************************************************
 *
 *     servers_onToggleFetchNewListWhenTooFewServers
 *
 ***/

static void
servers_onToggleFetchNewListWhenTooFewServers (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_FETCH_NEW_LIST_WHEN_TOO_FEW);
}

/******************************************************************************
 *
 *     servers_onToggleDisconnectIfLowID
 *
 ***/

static void
servers_onToggleDisconnectIfFirewalled (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED);
}

/******************************************************************************
 *
 *     servers_onToggleUnselectAfterAction
 *
 ***/

static void
servers_onToggleUnselectAfterAction (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_UNSELECT_ALL_AFTER_ACTION);
}


/******************************************************************************
 *
 *     servers_onToggleAutoRemoveInvalid
 *
 ***/

static void
servers_onToggleAutoRemoveInvalid (GtkWidget *widget, gpointer data)
{
	opt_toggle_bool(OPT_GUI_SERVERS_AUTO_REMOVE_INVALID);
}

/******************************************************************************
 *
 *     servers_onToggleSingleSelectionMode
 *
 ***/

static void
servers_onToggleSingleSelectionMode (GtkWidget *widget, gpointer data)
{
	GtkTreeSelection *selection;

	opt_toggle_bool(OPT_GUI_SERVERS_SINGLE_SELECTION_MODE);

	g_return_if_fail ( servers_view != NULL );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(servers_view));

	g_return_if_fail ( selection != NULL );

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





/*******************************************************************************
 *
 *    servers_onConnectAny
 *
 *    connect to any server (pop-up menu item has been clicked)
 *
 *    OK
 *
 ***/

static void
servers_onConnectAny (GtkWidget *widget, gpointer data)
{
	servers_connect_queue_clear();

	gui_core_conn_send_connect_to_any_server(core);

	servers_popup_unselect_all_if_wanted();
}


/*******************************************************************************
 *
 *    servers_onConnectAnySelected
 *
 *    connect to any of the selected servers (pop-up menu item has been clicked)
 *
 *    OK - TODO: save 'auto-connect' option here if necessary.
 *
 ***/

static void
servers_onConnectAnySelected (GtkWidget *widget, gpointer data)
{
	GtkTreeSelection  *selection;
	gint               queue_len = 0;

	g_return_if_fail ( servers_view != NULL );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (servers_view));

	queue_len = misc_gtk_tree_selection_count_selected_rows(selection);

	if ( queue_len == 0)
		return;

	/* clear any previous 'connect to any' entries */
	servers_connect_queue_clear();

	gtk_tree_selection_selected_foreach (selection, (GtkTreeSelectionForeachFunc) servers_connect_queue_add, NULL);

	g_return_if_fail ( servers_connect_selected_queue != NULL );
	g_return_if_fail ( g_queue_is_empty(servers_connect_selected_queue) == FALSE );

	servers_save_alwaysconnected_option();

	/* start connecting to any process... */
	(void) servers_connect_queue_connect_next();

	servers_popup_unselect_all_if_wanted();
}



/*******************************************************************************
 *
 *   servers_popup_menu_connect
 *
 *   creates the 'connect' sub-menu of the main server pop-up menu
 *
 *   OK
 *
 ***/

static GtkWidget *
servers_popup_menu_connect (void)
{
	GtkTreeSelection   *selection;
	GtkWidget          *cmenu = NULL;
	guint               sel_count = 0;

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

	cmenu  = gtk_menu_new();

	misc_gtk_add_menu_item ( cmenu, _(" connect to any selected "),
	                         servers_onConnectAnySelected, NULL,
	                         ICON_MENU_CONNECT, ( sel_count > 0 ) );

	misc_gtk_add_menu_item ( cmenu, _(" connect to any "),
	                         servers_onConnectAny, NULL,
	                         ICON_MENU_CONNECT, TRUE );

	return cmenu;
}





/*******************************************************************************
 *
 *   servers_popup_menu_onRemoveSelected
 *
 *   Remove all selected servers. Play it save this time
 *    and don't use idle timeouts for the actual removal,
 *    even if it's more code.
 *
 *   Note: we have to do the akward way we do, because we
 *    can't remove elements while iterating over a model
 *    (= the selection) (?)
 *
 *   OK
 *
 ****/

static gboolean
servers_popup_menu_onRemoveSelected_foreach ( GtkTreeModel *model,
                                              GtkTreePath  *path,
                                              GtkTreeIter  *iter,
                                              GQueue      **p_queue )
{
	GtkTreeRowReference *ref;

	g_return_val_if_fail (  p_queue != NULL, FALSE );
	g_return_val_if_fail ( *p_queue != NULL, FALSE );

	ref = misc_gtk_tree_model_get_row_reference_from_iter ( model, iter );

	if (ref)
		g_queue_push_tail ( *p_queue, ref );

	return FALSE; /* continue with other items */
}

static void
servers_popup_menu_onRemoveSelected (GtkWidget *widget, gpointer data)
{
	GtkTreeSelection *selection;
	GQueue           *remove_queue;

	g_return_if_fail ( servers_view != NULL );
	g_return_if_fail ( servers_store != NULL );

	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(servers_view));
	g_return_if_fail ( selection != NULL );

	remove_queue = g_queue_new();

	gtk_tree_selection_selected_foreach ( selection,
	                                      (GtkTreeSelectionForeachFunc) servers_popup_menu_onRemoveSelected_foreach,
	                                      &remove_queue );

	while (!g_queue_is_empty(remove_queue))
	{
		GtkTreeRowReference *ref;
		GtkTreeIter          iter;

		ref = (GtkTreeRowReference*) g_queue_pop_head(remove_queue);

		if ( misc_gtk_tree_model_get_iter_from_row_reference ( GTK_TREE_MODEL(servers_store), ref, &iter ) )
		 servers_remove_iter (gtk_tree_iter_copy (&iter));

		gtk_tree_row_reference_free(ref);
	}

	g_queue_free(remove_queue);
}

static void
servers_popup_menu_onRemoveDead (GtkWidget *widget, gpointer data)
{
	servers_remove_dead();
}

static void
servers_popup_menu_onDisconnect (GtkWidget *widget, gpointer data)
{
	gui_core_conn_send_disconnect_from_server(core);
}

static void
servers_popup_menu_onRefresh (GtkWidget *widget, gpointer data)
{
	gui_core_conn_send_get_serverlist(core);
}


static void
servers_onSetPriority (GtkWidget *widget, gpointer data)
{
	GtkTreeSelection  *selection;
	GQueue            *selected_queue;
	gint               pref;

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(servers_view));

	selected_queue = g_queue_new();

	gtk_tree_selection_selected_foreach ( selection,
	                                      (GtkTreeSelectionForeachFunc) servers_popup_menu_onRemoveSelected_foreach,
	                                      &selected_queue );

	pref = (gint) GPOINTER_TO_UINT(data);

	while (!g_queue_is_empty(selected_queue))
	{
		GtkTreeRowReference *ref;
		GtkTreeIter          iter;

		ref = (GtkTreeRowReference*) g_queue_pop_head(selected_queue);

		if ( misc_gtk_tree_model_get_iter_from_row_reference ( GTK_TREE_MODEL(servers_store), ref, &iter ) )
		{
			guint ip, port;

			gtk_tree_model_get ( GTK_TREE_MODEL(servers_store), &iter,
			                     SERVERS_IP_COLUMN, &ip,
			                     SERVERS_PORT_COLUMN, &port,
			                     -1);

			gtk_list_store_set ( GTK_LIST_STORE(servers_store), &iter,
			                     SERVERS_PREFERENCE_COLUMN, pref,
			                     SERVERS_PREFERENCE_STRING_COLUMN, servers_get_prio_string(pref),
			                     -1);

			gui_core_conn_send_set_server_pref(core, ip, port, (gint8) pref);
		}

		gtk_tree_row_reference_free(ref);
	}

	g_queue_free(selected_queue);

	servers_popup_unselect_all_if_wanted();
}

static GtkWidget *
servers_popup_menu_priority (void)
{
	GtkWidget  *pmenu;

	pmenu  = gtk_menu_new();

	misc_gtk_add_menu_item ( pmenu, _(" high "), servers_onSetPriority, GUINT_TO_POINTER(SERVER_PRIO_HIGH),
	                         ICON_MENU_PRIO_HIGH, TRUE );

	misc_gtk_add_menu_item ( pmenu, _(" normal "), servers_onSetPriority, GUINT_TO_POINTER(SERVER_PRIO_NORMAL),
	                         ICON_MENU_PRIO_MEDIUM, TRUE );

	misc_gtk_add_menu_item ( pmenu, _(" low "), servers_onSetPriority, GUINT_TO_POINTER(SERVER_PRIO_LOW),
	                         ICON_MENU_PRIO_LOW, TRUE );

	return pmenu;
}


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

	if ( optnum == 1 )
		http_get_retrieve_serverlist (opt_get_str(OPT_GUI_SERVERLIST_URL_1),0);

	else if ( optnum == 2 )
		http_get_retrieve_serverlist (opt_get_str(OPT_GUI_SERVERLIST_URL_2),0);

	else g_return_if_reached();
}

static void
servers_popup_menu_onClearServerList (GtkWidget *widget, gpointer data)
{
	servers_cleanup ();
}

static GtkWidget *
servers_popup_menu_serverlist (void)
{
	GtkWidget  *lmenu;
                                                                                                                                                                                  
	lmenu = gtk_menu_new();

	misc_gtk_add_menu_item ( lmenu, _(" fetch new serverlist (favourite)"),
	                         servers_popup_menu_onFetchServerList,
	                         GUINT_TO_POINTER(1),
	                         ICON_MENU_DOWNLOAD, TRUE );
        
	misc_gtk_add_menu_item ( lmenu, _(" fetch new serverlist (secondary)"),
	                         servers_popup_menu_onFetchServerList,
	                         GUINT_TO_POINTER(2),
	                         ICON_MENU_DOWNLOAD, TRUE );

	misc_gtk_add_menu_item ( lmenu, _(" refresh list (update values) "),
	                         servers_popup_menu_onRefresh, NULL,
	                         ICON_MENU_REFRESH, TRUE );

	misc_gtk_add_menu_separator ( lmenu );

	misc_gtk_add_menu_item ( lmenu, _(" clear serverlist "),
	                         servers_popup_menu_onClearServerList, NULL,
	                         ICON_MENU_CANCEL, TRUE );
                                                                                                                                                                                 
	return lmenu;
}

static GtkWidget * 
servers_popup_menu_options (void)
{
	IconName         icon_yes = ICON_OPTION_TRUE;
	IconName         icon_no  = ICON_OPTION_FALSE;

	GtkWidget       *omenu;
                                                                                                                                                                                  
	omenu  = gtk_menu_new();

	misc_gtk_add_menu_item ( omenu, _(" always stay connected "), servers_onToggleAlwaysConnected,
	                         NULL, (gui_core_conn_get_core_options(core)->always_connected) ? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_separator ( omenu );

	misc_gtk_add_menu_item ( omenu, _(" auto-clear serverlist when fetching favourite list via http  "),
	                         servers_onToggleAutoClearOnFetch, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_CLEAR_LIST_BEFORE_FETCHING_NEW_LIST))? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_item ( omenu, _(" auto-fetch new servers when we have less than 10 servers "),
	                         servers_onToggleFetchNewListWhenTooFewServers, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_FETCH_NEW_LIST_WHEN_TOO_FEW))? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_separator ( omenu );

	misc_gtk_add_menu_item ( omenu, _(" disconnect from server if it thinks that I'm firewalled "),
	                         servers_onToggleDisconnectIfFirewalled, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED)) ? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_item ( omenu, _(" automatically remove dead servers "), servers_onToggleAutoRemoveDead,
	                         NULL, (gui_core_conn_get_core_options(core)->autoremove_dead)? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_item ( omenu, _(" automatically remove invalid servers "),
	                         servers_onToggleAutoRemoveInvalid, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_AUTO_REMOVE_INVALID)) ? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_item ( omenu, _(" ignore 'placed on connection queue...' deadlocks (GUI) "), servers_onToggleIgnoreQueueDeadlock,
	                         NULL, (opt_get_bool(OPT_GUI_SERVERS_IGNORE_QUEUE_DEADLOCKS)) ? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_separator ( omenu );

	misc_gtk_add_menu_item ( omenu, _(" unselect all items after each action "),
	                         servers_onToggleUnselectAfterAction, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_UNSELECT_ALL_AFTER_ACTION))? icon_yes : icon_no, TRUE );

	misc_gtk_add_menu_item ( omenu, _(" use single selection mode in servers list "),
	                         servers_onToggleSingleSelectionMode, NULL,
	                         (opt_get_bool(OPT_GUI_SERVERS_SINGLE_SELECTION_MODE)) ? icon_yes : icon_no, TRUE );

	return omenu;
}

static void
servers_onPopupMenu ( GtkWidget *widget, GdkEventButton *event, gpointer data)
{

	GtkWidget         *menu;
	GtkTreeSelection  *selection;
	guint              selection_count = 0;

	g_return_if_fail ( servers_view != NULL );

	selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW (servers_view));

	selection_count = misc_gtk_tree_selection_count_selected_rows (selection);

	menu  = gtk_menu_new();

	misc_gtk_add_submenu ( menu, _(" connect... "), ICON_MENU_CONNECT, servers_popup_menu_connect(), TRUE);

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" disconnect from current server "),
	                         servers_popup_menu_onDisconnect, NULL,
	                         ICON_MENU_DISCONNECT, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_submenu ( menu, _(" priority... "), ICON_MENU_PRIO_MEDIUM,
	                       servers_popup_menu_priority(), ( selection_count > 0) );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_submenu ( menu, _(" server list... "), ICON_MENU_SERVERS,
	                       servers_popup_menu_serverlist(), TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" remove selected servers "),
	                         servers_popup_menu_onRemoveSelected, (gpointer)selection,
	                         ICON_MENU_CANCEL, ( selection_count > 0) );

	misc_gtk_add_menu_item ( menu, _(" remove dead servers "),
	                         servers_popup_menu_onRemoveDead, NULL,
	                         ICON_MENU_CANCEL, TRUE );

	misc_gtk_add_menu_separator ( menu );


	misc_gtk_add_submenu ( menu, _(" server pool options "), ICON_MENU_OPTIONS,
	                       servers_popup_menu_options(), TRUE );

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


/*******************************************************************************
 *
 *    servers_onPopup
 *
 *    The key-binding for 'pop-up menu' has been activated (usually Shift-F10)
 *
 *    OK
 *
 ***/

static void
servers_onPopup ( GtkWidget *widget, gpointer data)
{
	servers_onPopupMenu (widget, NULL, data);
}


/*******************************************************************************
 *
 *    servers_onButtonPress
 *
 *    Single-click in the servers list has happened. Check if it was the right
 *     mouse button and if so show the pop-up menu. Also select the one row
 *     where the click has happened and unselect all other rows IF we have
 *     no row or only one row selected at the time of click.
 *
 *    OK
 *
 ***/

static gboolean
servers_onButtonPress (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	GtkTreeSelection *selection;

	if (event->button != 3)
		return FALSE;

	g_return_val_if_fail ( servers_view != NULL, FALSE );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(servers_view));

	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 (servers_view), (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
			gtk_tree_selection_select_path (selection, path);

		if (path)
			gtk_tree_path_free(path);
	}

	servers_onPopupMenu(NULL, event, NULL);

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


/*******************************************************************************
 *
 *   servers_toolbar_icon_popup_menu
 *
 *   The servers icon has been clicked. Show a shortened pop-up menu with
 *    basic server functions.
 *
 *   XXX - TODO: what about activate time? Shouldn't we use it if it is set?
 *
 *   OK
 *
 ***/

void
servers_toolbar_icon_popup_menu (guint button, guint32 activate_time)
{
	GtkWidget *menu;

	menu  = gtk_menu_new();

	misc_gtk_add_menu_item ( menu, _(" connect to any "), servers_onConnectAny,
	                         NULL, ICON_MENU_CONNECT, TRUE );

	misc_gtk_add_menu_item ( menu, _(" disconnect from current server "), servers_popup_menu_onDisconnect,
	                         NULL, ICON_MENU_DISCONNECT, TRUE );

	misc_gtk_add_menu_separator ( menu );

	misc_gtk_add_menu_item ( menu, _(" fetch new serverlist (favourite)"), servers_popup_menu_onFetchServerList,
	                         GUINT_TO_POINTER (1), ICON_MENU_DOWNLOAD, TRUE );

	misc_gtk_add_menu_item ( menu, _(" fetch new serverlist (secondary)"), servers_popup_menu_onFetchServerList,
	                         GUINT_TO_POINTER (2), ICON_MENU_DOWNLOAD, TRUE );

	misc_gtk_add_menu_separator ( menu );

	gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, button, activate_time);
}


/*******************************************************************************
 *                                                                             *
 *                      VISUALS STUFF                                          *
 *                                                                             *
 *******************************************************************************/


/*******************************************************************************
 *
 *   servers_show_dead_alive_stats
 *
 *******************************************************************************/

void
servers_show_dead_alive_stats (void)
{
	gboolean      val;
	guint         dead=0, alive=0, users=0, files=0;
	GtkTreeIter   iter;

	val = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (servers_store), &iter);

	while (val)
	{
		guint  stping     = 0;
		guint  stlastseen = 0;
		guint  stusers    = 0;
		guint  stfiles    = 0;

		gtk_tree_model_get ( GTK_TREE_MODEL (servers_store), &iter,
		                     SERVERS_PING_COLUMN, &stping,
		                     SERVERS_USERS_COLUMN, &stusers,
		                     SERVERS_FILES_COLUMN, &stfiles,
		                     SERVERS_LAST_SEEN_ALIVE_COLUMN, &stlastseen,
		                     -1);

		if ( stping != 0 || ( (time (NULL) - stlastseen) < SERVER_WO_STATS_DEAD_SECONDS) )
		{
			alive++;
			users += stusers;
			files += stfiles;
		}
		else
			dead++;

		val = gtk_tree_model_iter_next (GTK_TREE_MODEL (servers_store), &iter);

	} /* while(val) */

	statusbar_msg (_(" Serverlist: %u alive and %u dead servers with %u users sharing %u files."), alive, dead, users,files);
}



/******************************************************************************
 *
 *   servers_sort_ip_function
 *
 *   Sort function for the model if we are sorting the IP column.
 *   As we store the IPs internally in the same way as the core
 *   stores them (ie. in reverse network byte order), we need to
 *   convert them first before we can compare them properly.
 *
 *******************************************************************************/

static gint
servers_sort_ip_function ( GtkTreeModel *model,
                           GtkTreeIter *a,
                           GtkTreeIter *b,
                           gpointer data)
{
	GValue   vala = {0,};
	GValue   valb = {0,};
	gint     ret = 0;
	guint32  ipa, ipb;

	g_return_val_if_fail ( a     != NULL, 0 );
	g_return_val_if_fail ( b     != NULL, 0 );
	g_return_val_if_fail ( model != NULL, 0 );

	gtk_tree_model_get_value (model, a, SERVERS_IP_COLUMN, &vala);
	gtk_tree_model_get_value (model, b, SERVERS_IP_COLUMN, &valb);

	ipa = GUINT32_TO_BE(GUINT32_TO_LE(g_value_get_uint(&vala)));
	ipb = GUINT32_TO_BE(GUINT32_TO_LE(g_value_get_uint(&valb)));

	if (ipa < ipb)
		ret = -1;
	else
		ret = (gint) (ipa > ipb);

	g_value_unset (&vala);
	g_value_unset (&valb);

	return ret;
}


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

static gboolean
servers_onRowActivated (GtkWidget *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
{
	GtkTreeSelection *selection;

	if (opt_get_bool(OPT_GUI_IGNORE_DOUBLE_CLICKS))
		return TRUE;

	g_return_val_if_fail ( servers_view != NULL, FALSE );

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(servers_view));
	g_return_val_if_fail ( selection != NULL, FALSE );

	/* XXX - do we need to check this??? */
	if ( misc_gtk_tree_selection_count_selected_rows(selection) != 1 )
		return FALSE;

	servers_onConnectAnySelected (NULL, NULL);

	return TRUE;
}


/******************************************************************************
 *
 *   onErrorMessage
 *
 ******************************************************************************/

static void
onErrorMessage (GuiCoreConn *conn, const gchar *msg, gpointer data)
{
/* NO, I AM NOT GOING TO IMPLEMENT THAT ONE HOUR BEFORE MAKING A RELEASE */
#if 0
	/* is the exact message 'Connection refused. Your IP is currently blacklisted.'? */
	if ((msg) && strstr(msg, "onnection refused") != NULL
	     && strstr(msg, "currently blacklisted") != NULL)
	{
		/* make sure server is removed and stays removed */
			servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_GOT_BLACKLISTED);
	}
#endif
	return;
}

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

static void
onStatusMessage (GuiCoreConn *conn, const gchar *msg, gpointer data)
{
	if (g_ascii_strncasecmp(msg,"Connecting to",12) == 0)
	{
		gchar *ip_or_name, *lastbit;

		/* Find server name or IP from whole line */
		ip_or_name = g_strdup(msg+14);
		lastbit = strstr(ip_or_name, " ...");
		if (lastbit)
			*lastbit=0x00;

		servers_register_connect_status (ip_or_name, SERVERS_CONNECT_STATUS_CONNECTING);

		g_free(ip_or_name);
	}
	else if (strstr(msg,"server is out of date") != NULL)
	{
		status_msg ("%s.\n", _("GUI: Server is rejecting us. *pah*"));
		servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_THINKS_WE_ARE_FIREWALLED);
	}
	else if (strstr(msg,"not reachable. You have a LOWID") != NULL)
	{
		/* XXX - do this even if OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED is not set?
		 * (Problem is: whether server closes connection on lowid,
		 *  depends on server, not on core)
		 */
		if (opt_get_bool(OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED))
		{
			servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_CANTCONNECT);

			g_signal_emit_by_name (conn, "server-connect-failed", "0.0.0.0");
		}
	}
}


/***************************************************************************
 *
 *   timer_get_new_serverlist_10s_after_connect
 *
 ***************************************************************************/

static gint get_serverlist_handler; /* 0 */

static gboolean
timer_get_new_serverlist_10s_after_connect (gpointer data)
{
	gui_core_conn_send_get_serverlist(core);

	get_serverlist_handler = 0;

	return FALSE;       /* we want to be called only once */
}


/******************************************************************************
 *
 *   onEDonkeyStatus
 *
 ******************************************************************************/

static void
onEDonkeyStatus (GtkWidget *slabel, const guint id, const gchar *fwstatus, GuiCoreConn *conn)
{
	const gchar *idstr;

	idstr = UTF8_SHORT_PRINTF("ClientID: %u  %s", id, fwstatus);

	if (GTK_IS_LABEL(slabel))
		gtk_label_set_text (GTK_LABEL(slabel), idstr);

	if (opt_get_bool(OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED) && id > 0  &&  id < 0xffffff)
	{
		status_msg ("%s", idstr);
		status_msg ("%s.\n", _("GUI: Server thinks you are firewalled. Disconnecting."));
		servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_THINKS_WE_ARE_FIREWALLED);
	}
}


/***************************************************************************
 *
 *   onServerConnectFailed
 *
 ***************************************************************************/

static void
onServerConnectFailed (GtkWidget *slabel, const gchar *name, GuiCoreConn *conn)
{
	if (GTK_IS_LABEL(slabel))
		gtk_label_set_text (GTK_LABEL(slabel), UTF8_SHORT_PRINTF("%s", _(" Not connected to a server. ")));

	servers_register_connect_status (NULL, SERVERS_CONNECT_STATUS_CANTCONNECT);

	if (get_serverlist_handler > 0)
		g_source_remove(get_serverlist_handler);
}


/***************************************************************************
 *
 *   onServerConnected
 *
 ***************************************************************************/

static void
onServerConnected (GuiCoreConn *conn, const gchar *name, gpointer data)
{
	if (servers_stats_label)
		gtk_label_set_text (GTK_LABEL(servers_stats_label), UTF8_SHORT_PRINTF("%s: %s", _(" Connected to a server"),name));
		
	servers_reinstate_alwaysconnected_option();

//	servers_connect_queue_clear ();

	/* add timer to get a new serverlist 10s after the connect */
	if (get_serverlist_handler == 0)
		get_serverlist_handler = g_timeout_add (10*1000, timer_get_new_serverlist_10s_after_connect, NULL);

	if (timeout_handle == 0)
		timeout_handle = g_timeout_add(60*1000, (GSourceFunc) everyMinute, NULL);
}

/******************************************************************************
 *
 *   onServerDisconnected
 *
 ******************************************************************************/

static void
onServerDisconnected (GuiCoreConn *conn, gpointer data)
{
	if (servers_stats_label)
		gtk_label_set_text (GTK_LABEL(servers_stats_label), UTF8_SHORT_PRINTF("%s", _(" Not connected to a server. ")));

/*			can't do this, because 'placed on queue' is followed by disconnect as well
			servers_reinstate_alwaysconnected_option();
			servers_clear_connect_to_list();
*/

	if (get_serverlist_handler > 0)
		g_source_remove(get_serverlist_handler);

	if (timeout_handle > 0)
	{
		g_source_remove(timeout_handle);
		timeout_handle = 0;
	}
}


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

static void
onCoreConnStatus (GuiCoreConn *conn, guint status, gpointer data)
{
	if (status != CONN_STATUS_COMMUNICATING)
	{
		gtk_label_set_text (GTK_LABEL (servers_stats_label), UTF8_SHORT_PRINTF("%s", _(" Not connected to a server. ")));

		if (status != CONN_STATUS_AUTHENTICATING)
			servers_cleanup();
	}
}


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

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


/******************************************************************************
 *
 *   onEveryFiveMinutesTasks
 *
 ******************************************************************************/
static gboolean
onEveryFiveMinutesTasks (gpointer foo)
{
	static time_t	 last_emergency_server_fetch = 0;

	if (gui_core_conn_is_alive(core))
	{
		/* we don't want to totally run out of servers, do we? */
		if ( opt_get_bool(OPT_GUI_SERVERS_FETCH_NEW_LIST_WHEN_TOO_FEW)
			&& (!gui_core_conn_is_overnet(core) || gui_core_conn_is_hybrid(core))
			&& ( servers_get_count () < 10)
			&& gui_core_conn_is_alive(core) )
		{
			if (gui_core_conn_is_alive(core))
			{
				static guint whichlist = SERVER_LIST_NUM;

				whichlist = ( (whichlist+1) % SERVER_LIST_NUM );

				/* if fetch unsuccessful, we don't want to re-fetch
				 * unsuccessfully every 5 minutes, do we?
				 *
				 *   max. is every 30 minutes
				 */
				if (last_emergency_server_fetch==0 || (time(NULL)-last_emergency_server_fetch)>1800)
				{
					last_emergency_server_fetch = time(NULL);
					status_message_blue (_("GUI: less than 10 servers in serverlist - trying to fetch more via http.\n"));

					servers_fetch_serverlist ( whichlist );
				}
			}
		}

		if (gui_core_conn_is_alive(core) && (gui_core_conn_is_overnet(core) || gui_core_conn_is_hybrid(core)))
		{
			if (gui_core_conn_get_core_options(core)->autoremove_dead)	/* in case core is too lazy */
				servers_remove_dead();

			gui_core_conn_send_get_serverlist(core);
		}
	}
	return TRUE;
}


/******************************************************************************
 *
 *   servers_page_create
 *
 ******************************************************************************/

GtkWidget *
servers_page_create (void)
{
	GtkTreeViewColumn  *column;
	GtkTreeSelection   *selection;
	GtkCellRenderer    *renderer;
	GtkWidget          *vbox;         /* contains: (hbox of the three top servers status labels) and scrollwin */
	GtkWidget          *scrollwin;    /* for treeview */
	GtkWidget          *hbox, *emptylabel, *slabel; /* hbox with left label + empty label + ID label */

	opt_notify (OPT_GUI_SET_RULES_HINT_ON_LISTS, onOptionChanged);

	vbox = gtk_vbox_new (FALSE, 5);

	scrollwin = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	servers_store = gtk_list_store_new ( SERVERS_N_COLUMNS,
	                                     SERVERS_TYPES_PLACEHOLDER );

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

	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(servers_store),
	                                     opt_get_int(OPT_GUI_SORT_COL_SERVERS),
	                                     opt_get_int(OPT_GUI_SORT_TYPE_SERVERS));

	servers_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (servers_store));

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

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes( UTF8_SHORT_PRINTF("%s",_("Name")),
	            renderer, "text", SERVERS_NAME_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_NAME_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("IP address")),
	            renderer, "text", SERVERS_IP_STRING_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_IP_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	gtk_tree_sortable_set_sort_func ( GTK_TREE_SORTABLE(servers_store),
	                                  SERVERS_IP_COLUMN,
	                                  servers_sort_ip_function,
	                                  NULL,
	                                  NULL );


	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("Port")),
	            renderer, "text", SERVERS_PORT_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_PORT_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("Users")),
	            renderer, "text", SERVERS_USERS_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_USERS_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("Files")),
	            renderer, "text", SERVERS_FILES_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_FILES_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("Preference")), renderer,
	                                                    "text", SERVERS_PREFERENCE_STRING_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_PREFERENCE_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes( UTF8_SHORT_PRINTF("%s",_("Ping")),
	             renderer, "text", SERVERS_PING_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id( column, SERVERS_PING_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ( UTF8_SHORT_PRINTF("%s",_("Description")), renderer, "text", SERVERS_DESCRIPTION_COLUMN, NULL);
	g_signal_connect(column, "notify::width", (GCallback) onColumnResized, NULL);
	gtk_tree_view_column_set_sort_column_id(column, SERVERS_DESCRIPTION_COLUMN);
	gtk_tree_view_append_column(GTK_TREE_VIEW(servers_view), column);

	gtk_container_add (GTK_CONTAINER (scrollwin), servers_view);

	hbox = gtk_hbox_new(FALSE,0);

	slabel = gtk_label_new(UTF8_SHORT_PRINTF("%s",_(" Servers - use the toolbar or drag'n'drop to add a server ")));
	gtk_label_set_justify (GTK_LABEL(slabel), GTK_JUSTIFY_LEFT);
	gtk_widget_show(slabel);

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

	servers_stats_label = gtk_label_new (NULL);
	gtk_label_set_justify (GTK_LABEL(servers_stats_label), GTK_JUSTIFY_RIGHT);
	gtk_widget_show(servers_stats_label);

	gtk_box_pack_start (GTK_BOX(hbox), slabel, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX(hbox), emptylabel, TRUE, TRUE, 0);
	gtk_box_pack_start (GTK_BOX(hbox), servers_stats_label, FALSE, FALSE, 0);

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

	gtk_box_pack_start ( GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (vbox), scrollwin, TRUE, TRUE, 0);

//	g_signal_connect (G_OBJECT(servers_view), "key_press_event", G_CALLBACK(servers_onKeyPress), NULL);
	g_signal_connect (G_OBJECT(servers_view), "button_press_event", G_CALLBACK (servers_onButtonPress), NULL);
	g_signal_connect (G_OBJECT(servers_view), "popup_menu", G_CALLBACK (servers_onPopup), NULL);
	g_signal_connect (G_OBJECT(servers_view), "row-activated", G_CALLBACK(servers_onRowActivated), NULL);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (servers_view));

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

	gtk_widget_show_all (vbox);

	/* Create hashtable, so we can lookup a server record quickly from IP + port.
	 * This is kind of done primitively, but I think it's the fastest, and I also
	 * think we don't really need any of the overhead to cater for collisions.
	 * Probability should be on our side in this case.
	 */

	servers_hashtable = g_hash_table_new_full ( g_direct_hash, NULL, NULL, (GDestroyNotify)misc_gtk_free_row_reference );

	restore_tree_view_column_widths(servers_view);

	g_signal_connect(core, "error-message",       (GCallback) onErrorMessage,       NULL);
	g_signal_connect(core, "status-message",      (GCallback) onStatusMessage,      NULL);
	g_signal_connect(core, "server-connected",    (GCallback) onServerConnected,    NULL);
	g_signal_connect(core, "server-disconnected", (GCallback) onServerDisconnected, NULL);
	g_signal_connect(core, "server-list",         (GCallback) onServerList,         NULL);
	g_signal_connect(core, "core-conn-status",    (GCallback) onCoreConnStatus,     NULL);

	g_signal_connect_swapped(core, "edonkey-status",        (GCallback) onEDonkeyStatus,       servers_stats_label);
	g_signal_connect_swapped(core, "server-connect-failed", (GCallback) onServerConnectFailed, servers_stats_label);

	g_timeout_add(5*60*1000, (GSourceFunc) onEveryFiveMinutesTasks, NULL);

	return vbox;
}

