/*
    GQ -- a GTK-based LDAP client
    Copyright (C) 1998-2003 Bert Vermeulen
    Copyright (C) 2002-2003 Peter Stamfest
    Copyright (C) 2006,2007 Sven Herzberg

    This program is released under the Gnu General Public License with
    the additional exemption that compiling, linking, and/or using
    OpenSSL is allowed.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "gq-tab-browse.h"

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

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

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */

#include "common.h"
#include "configfile.h"
#include "debug.h"
#include "encode.h"
#include "errorchain.h"
#ifdef USE_TREE_VIEW
#include "gq-browser-model.h"
#endif
#include "gq-browser-node-dn.h"
#include "gq-browser-node-server.h"
#include "gq-browser-node-reference.h"
#include "gq-diff-tab.h"
#include "gq-input-form.h"
#include "gq-server-list.h"
#include "gq-tree-view.h"
#include "gq-utilities.h"
#include "gq-window.h"
#include "ldapops.h"
#include "ldif.h"
#include "prefs.h"
#include "progress.h"
#include "search.h"
#include "state.h"
#include "template.h"

#ifdef BROWSER_DND
#include "browse-dnd.h"
#endif

struct GqTabBrowsePrivate {
	GqInputForm* input_form;
#warning "FIXME: get rid of this scrolled window"
	GtkWidget  * pane2_scrwin;
	GtkWidget  * content;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_TAB_BROWSE, struct GqTabBrowsePrivate))

static void tree_row_refresh(GqTab *tab);

void
gq_tab_browse_set_content(GqTabBrowse* self,
			  GtkWidget  * content)
{
	g_return_if_fail(GQ_IS_TAB_BROWSE(self));
	g_return_if_fail(!content || GTK_IS_WIDGET(content));

	if(GTK_IS_WIDGET(P(self)->pane2_scrwin) && GTK_BIN(P(self)->pane2_scrwin)->child) {
		gtk_container_remove(GTK_CONTAINER(P(self)->pane2_scrwin),
				     GTK_BIN(P(self)->pane2_scrwin)->child);
	}

	if(GTK_IS_WIDGET(content)) {
		gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(P(self)->pane2_scrwin),
						      content);
	}
}

void
gq_tab_browse_set_input_form(GqTabBrowse* self,
			     GqInputForm* input_form)
{
	g_return_if_fail(GQ_IS_TAB_BROWSE(self));
	g_return_if_fail(!input_form || GQ_IS_INPUT_FORM(input_form));

	if(P(self)->input_form) {
		/* but first get current hide status */
#warning "FIXME: if this is a HACK, remove it"
		/* HACK: store hide status it in the browse-tab data-structure */
		self->hidden = gq_input_form_get_hide(P(self)->input_form);
		g_object_unref(P(self)->input_form);
		P(self)->input_form = NULL;
	}

	if(input_form) {
		P(self)->input_form = g_object_ref(input_form);
		gq_tab_browse_set_content(self, GTK_WIDGET(input_form));
	}

	g_object_notify(G_OBJECT(self), "input-form");
}

#ifndef USE_TREE_VIEW
#define gq_tree_node_parent(model, node_p) (node_p = row->parent)
#define gq_browser_model_get_node(model, node) (GQ_BROWSER_NODE(gq_tree_get_node_data(model, iter)))
#else
#define gq_tree_node_parent(model, iter) gtk_tree_model_iter_parent(model, &parent, iter)
#endif

#warning "hide this one"
void
record_path(GqTab *tab,
#ifndef USE_TREE_VIEW
	    GQTreeWidget *model,
	    GQTreeWidgetNode *iter
#else
	    GtkTreeModel* model,
	    GtkTreeIter * iter
#endif
	    )
{
#ifdef USE_TREE_VIEW
	GtkTreeIter  parent = *iter;
#endif
     GqBrowserNode *e;
     GType type = -1;

     if (GQ_TAB_BROWSE(tab)->cur_path) {
	  g_list_foreach(GQ_TAB_BROWSE(tab)->cur_path, (GFunc) g_free, NULL);
	  g_list_free(GQ_TAB_BROWSE(tab)->cur_path);
     }

     GQ_TAB_BROWSE(tab)->cur_path = NULL;

#ifdef USE_TREE_VIEW
	g_return_if_fail(gq_browser_model_validate(model, iter));
#endif

	do {
#ifdef USE_TREE_VIEW
		*iter = parent;
#else
	  row = GQ_TREE_WIDGET_ROW(iter);
#endif
	  e = gq_browser_model_get_node(model, iter);

	  /* currently it is sufficient to keep changes in entry types only */
	  if (e && G_OBJECT_TYPE(e) != type) {
	       GString *str = g_string_new("");
	       g_string_append_printf(str, "%ld:%s",
				G_OBJECT_TYPE(e), GQ_BROWSER_NODE_GET_CLASS(e)->get_name(e, TRUE));

	       GQ_TAB_BROWSE(tab)->cur_path =
		    g_list_insert(GQ_TAB_BROWSE(tab)->cur_path, str->str, 0);

	       g_string_free(str, FALSE);

	       type = G_OBJECT_TYPE(e);
	  }
	} while(gq_tree_node_parent(model, iter));
}

void
dn_browse_single_add(GqServer    * server,
		     gchar const * dn,
		     GtkTreeModel* model,
		     GtkTreeIter * iter,
		     GtkTreeIter * created)
{
     char **exploded_dn = NULL;
     gchar const* label;
     GqBrowserNode *new_entry;
	GqServerDn* entry;
     int ctx;
#warning "FIXME: add a widget"
	ctx = error_new_context(_("Exploding DN"), NULL);

     if (config->show_rdn_only) {
	  /* explode DN */
	  exploded_dn = gq_ldap_explode_dn(dn, FALSE);

	  if (exploded_dn == NULL) {
	       /* problem with DN */
	       /*	  printf("problem dn: %s\n", dn); */
	       error_push(ctx, _("Cannot explode DN '%s'. Maybe problems with quoting or special characters. See RFC 2253 for details of DN syntax."), dn);

	       goto fail;
	  }
	  label = exploded_dn[0];
     } else {
	  label = dn;
     }

	entry = gq_server_dn_new(dn, server);
	new_entry = gq_browser_node_dn_new(entry);
	g_object_unref(entry);
	gq_browser_model_add_node(model, iter, new_entry);

 fail:
     if (exploded_dn) gq_exploded_free(exploded_dn);

     error_flush(ctx);
}

/*
 * adds a single server to the root node
 */
static void add_single_server_internal(GQTreeWidget *ctree,
				       GqServer *server)
{
     GqBrowserNode *entry;
     GQTreeWidgetNode *new_item;

     if (ctree && server) {
	  entry = gq_browser_node_server_new(server);

	  new_item = gq_tree_insert_node (ctree,
					  NULL,
					  NULL,
					  gq_server_get_name(server),
					  entry,
					  g_object_unref);
	  gq_tree_insert_dummy_node (ctree,
				     new_item);
     }
}

static void
add_single_server_count(GQServerList* list      G_GNUC_UNUSED,
			GqServer    * server,
			gpointer      user_data) {
	gpointer**count_and_tree = user_data;
	gint    * server_cnt = (gint*)count_and_tree[0];
	GQTreeWidget* ctree = GQ_TREE_WIDGET(count_and_tree[1]);

	add_single_server_internal(ctree, server);
	(*server_cnt)++;
}

/*
 * add all configured servers to the root node
 */
static void add_all_servers(GQTreeWidget *ctree)
{
     int server_cnt = 0;
     gpointer count_and_ctree[2] = {
	     &server_cnt,
	     ctree
     };

     gq_server_list_foreach(gq_server_list_get(), add_single_server_count, count_and_ctree);

     statusbar_msg(ngettext("One server found",
			    "%d servers found", server_cnt),
		   server_cnt);
}

#ifndef USE_TREE_VIEW
/* GTK callbacks translating to the correct entry object method calls */
static void tree_row_expanded(GQTreeWidget *ctree,
			      GQTreeWidgetNode *ctree_node,
			      GqTab *tab)
{
     GqBrowserNode *entry;

     entry = GQ_BROWSER_NODE(gq_tree_get_node_data (ctree, ctree_node));

     if (!entry) return;

     if (GQ_BROWSER_NODE_GET_CLASS(entry)->expand) {
	  int ctx = error_new_context(_("Expanding subtree"),
				      GTK_WIDGET(ctree));

	  GQ_BROWSER_NODE_GET_CLASS(entry)->expand(entry, ctx, ctree, ctree_node, tab);
	  record_path(tab, ctree, ctree_node);

	  error_flush(ctx);
     }
}
#endif

static void
#ifndef USE_TREE_VIEW
tree_row_selected(GQTreeWidget    * ctree,
		  GQTreeWidgetNode* node,
		  int               column G_GNUC_UNUSED,
		  GqTab           * tab)
#else
browser_tree_selection_changed(GtkTreeSelection* selection,
			       GqTab           * tab)
#endif
{
#ifndef USE_TREE_VIEW
     GqBrowserNode *entry;

     /* avoid recursive calls to this handler - this causes crashes!!! */
     /* This is a legitimate use of
	gtk_object_set_data/gtk_object_get_data */

     if (g_object_get_data(G_OBJECT(ctree), "in-tree_row_selected")) {
	  g_signal_stop_emission_by_name(GTK_OBJECT(ctree),
					 "tree-select-row");
	  return;
     }

     g_object_set_data(G_OBJECT(ctree), "in-tree_row_selected", GINT_TO_POINTER(1));

     entry = GQ_BROWSER_NODE(gq_tree_get_node_data (ctree, node));

     /* delete old GqInputForm (if any) */
	gq_tab_browse_set_input_form(GQ_TAB_BROWSE(tab), NULL);

     if (entry) {
	  if (GQ_BROWSER_NODE_GET_CLASS(entry)->select) {

	       /* do not update right-hand pane if update-lock is set */
	       if (! GQ_TAB_BROWSE(tab)->update_lock) {
		    int ctx = error_new_context(_("Selecting entry"),
						GTK_WIDGET(ctree));
		    GQ_BROWSER_NODE_GET_CLASS(entry)->select(entry, ctx, ctree, node, tab);
		    error_flush(ctx);
	       }
	  }
	  GQ_TAB_BROWSE(tab)->tree_row_selected = node;
     }

     g_object_set_data(G_OBJECT(ctree), "in-tree_row_selected", GINT_TO_POINTER(0));
#else
	GtkTreeModel * model;
	GtkTreeIter    iter;

	switch(gtk_tree_selection_count_selected_rows(selection)) {
		GList* selected;
	case 0:
		gq_tab_browse_set_content(GQ_TAB_BROWSE(tab), NULL);
		break;
	case 1:
		selected = gtk_tree_selection_get_selected_rows(selection, &model);
		if(gtk_tree_model_get_iter(model, &iter, selected->data)) {
			GqBrowserNode* node = gq_browser_model_get_node(GQ_BROWSER_MODEL(model), &iter);
			int ctx = error_new_context(_("Selecting entry"),
						    GTK_WIDGET(gtk_tree_selection_get_tree_view(selection)));
			gq_browser_node_select(node, ctx, model, &iter, tab);
			error_flush(ctx);
		}
		else
		{
			g_warning("Got invalid selection.");
		}
		break;
	case 2:
		break;
	default:
		break;
	}
#endif
}


static void
tree_row_refresh(GqTab *tab)
{
     GQTreeWidget *ctree;
     GqBrowserNode *entry;

     ctree = GQ_TAB_BROWSE(tab)->ctreeroot;
	entry = gq_browser_model_get_node(gtk_tree_view_get_model(ctree),
					  &GQ_TAB_BROWSE(tab)->tree_row_popped_up);

     if (!entry) return;

     if (GQ_BROWSER_NODE_GET_CLASS(entry)->refresh) {
	  int ctx = error_new_context(_("Refreshing entry"),
				      GTK_WIDGET(ctree));

		/* delete old GqInputForm (if any) */
		gq_tab_browse_set_input_form(GQ_TAB_BROWSE(tab), NULL);

	  GQ_BROWSER_NODE_GET_CLASS(entry)->refresh(entry, ctx, ctree, &GQ_TAB_BROWSE(tab)->tree_row_popped_up, tab);
	  error_flush(ctx);
     }
}

/* server & ref */
void
tree_row_close_connection(GqTab *tab)
{
	GtkTreeView* tview;
	GqServer *server;

	tview = GTK_TREE_VIEW(GQ_TAB_BROWSE(tab)->ctreeroot);
	server = gq_browser_node_get_server(gq_browser_model_get_node(gtk_tree_view_get_model(tview),
					  			      &GQ_TAB_BROWSE(tab)->tree_row_popped_up));
	close_connection(server, TRUE);

	statusbar_msg(_("Closed connection to server %s"), gq_server_get_name(server));
}



static void browse_save_snapshot(int error_context, 
				 char *state_name, GqTab *tab)
{
     char *tmp;
     state_value_set_list(state_name, "open-path", GQ_TAB_BROWSE(tab)->cur_path);

     if (GQ_TAB_BROWSE(tab)->mainpane)
	  state_value_set_int(state_name, "gutter-pos", 
			      gtk_paned_get_position(GTK_PANED(GQ_TAB_BROWSE(tab)->mainpane)));
     /* the state of the show empty attributes toggle button */


     tmp = g_malloc(strlen(state_name) + 10);
     strcpy(tmp, state_name);
     strcat(tmp, ".input");
     
     if(P(tab)->input_form) {
	  save_input_snapshot(error_context, P(tab)->input_form, tmp);
     } else {
	  rm_value(tmp);
     }

     g_free(tmp);
}

static int cmp_name(GqBrowserNode *entry, const char *name)
{
     gchar *c;
     int rc; 

     if (entry == NULL) return -1;
     c = GQ_BROWSER_NODE_GET_CLASS(entry)->get_name(entry, TRUE);
     rc = strcasecmp(name, c);
     g_free(c);
     return rc;
}

static void browse_restore_snapshot(int context,
				    char *state_name, GqTab *tab,
				    struct pbar_win *progress)
{
     GQTreeWidget *ctree = GQ_TAB_BROWSE(tab)->ctreeroot;
     int gutter = state_value_get_int(state_name, "gutter-pos", -1);
     const GList *path = state_value_get_list(state_name, "open-path");
     char *tmp;

     GqServer *server = NULL;
#ifndef USE_TREE_VIEW
     GQTreeWidgetNode *node = gq_tree_get_root_node (ctree);
#else
	GtkTreeIter node;
#endif

     if (path) {
	  const GList *I;
	  for (I = path ; I ; I = g_list_next(I)) {
	       const char *s = I->data;
	       const char *c = g_utf8_strchr(s, -1, ':');
	       char *ep;
	       GType type = strtoul(s, &ep, 10);

	       if (progress->cancelled) break;

	       if (progress) {
		    update_progress(progress, _("Opening %s"), c + 1);
	       }

	       if (c == ep) {
#warning "FIXME: solve this the OO way"
		    if(type == GQ_TYPE_BROWSER_NODE_DN) {
#ifndef USE_TREE_VIEW
			 node = show_dn(context, ctree, node, c + 1, FALSE);
#else
			 GtkTreeIter found;
			 show_dn(context,
				 gtk_tree_view_get_model(GTK_TREE_VIEW(ctree)),
				 (I == path) ? NULL : &node,
				 &found,
				 c + 1,
				 FALSE);
			 node = found;
#endif
		    }
		    else if(type == GQ_TYPE_BROWSER_NODE_REFERENCE) {
#ifndef USE_TREE_VIEW
			 gq_tree_expand_node (ctree, node);
			 node =
			      gq_tree_widget_find_by_row_data_custom(GQ_TREE_WIDGET(ctree),
								node,
								(gpointer)(c + 1),
								(GCompareFunc) cmp_name);
#endif
		    }
		    else if(type == GQ_TYPE_BROWSER_NODE_SERVER) {
			 server = gq_server_list_get_by_name(gq_server_list_get(), c + 1);
			 if (server != NULL) {
#ifndef USE_TREE_VIEW
			      node = tree_node_from_server_dn(ctree, server,
							      "");
#endif
			 } else {
			      /* FIXME - popup error? */
#ifndef USE_TREE_VIEW
			      node = NULL;
#endif
			 }
		    }
	       }
#ifndef USE_TREE_VIEW
	       if (node == NULL) break;
#endif
	  }
#ifndef USE_TREE_VIEW
	  if (node) gq_tree_select_node(ctree, node);
#else
	  g_warning("FIXME: port this");
#endif
     }

     if (!progress->cancelled) {
	  if (gutter > 0) {
	       gtk_paned_set_position(GTK_PANED(GQ_TAB_BROWSE(tab)->mainpane),
				      gutter);
	  }

	  tmp = g_malloc(strlen(state_name) + 10);
	  strcpy(tmp, state_name);
	  strcat(tmp, ".input");

		if(P(tab)->input_form) {
			restore_input_snapshot(context, P(tab)->input_form, tmp);
		}

	  g_free(tmp);
     }
}

void set_update_lock(GqTab *tab)
{
     g_assert(tab);
     GQ_TAB_BROWSE(tab)->update_lock++;
}

void release_update_lock(GqTab *tab)
{
     g_assert(tab);
     GQ_TAB_BROWSE(tab)->update_lock--;
}

GqTab*
gq_tab_browse_new(void)
{
	return g_object_new(GQ_TYPE_TAB_BROWSE, NULL);
}

#ifndef USE_TREE_VIEW
GqServer*
server_from_node(GQTreeWidget    * ctreeroot,
		 GQTreeWidgetNode* node)
{
#warning "FIXME: remove this function"
	return gq_browser_node_get_server(GQ_BROWSER_NODE(gq_tree_get_node_data(ctreeroot, node)));
}
#else
GqServer*
server_from_node(GtkTreeModel* model,
		 GtkTreeIter * iter)
{
#warning "FIXME: remove this function"
	GqBrowserNode* node = NULL;
	g_return_val_if_fail(GQ_IS_BROWSER_MODEL(model), NULL);
	g_return_val_if_fail(gq_browser_model_validate(model, iter), NULL);

	node = gq_browser_model_get_node(GQ_BROWSER_MODEL(model), iter);
	g_return_val_if_fail(GQ_IS_BROWSER_NODE(node), NULL);

	return gq_browser_node_get_server(node);
}
#endif

static int dn_row_compare_func(GqBrowserNodeDn *row_data, char *dn)
{
     if (row_data == NULL || !GQ_IS_BROWSER_NODE_DN(row_data)) return 1;

     return strcasecmp(dn, gq_browser_node_dn_get_dn(row_data));
}

/*
 * returns this DN's GQTreeWidgetNode
 */
#ifndef USE_TREE_VIEW
GQTreeWidgetNode *node_from_dn(GQTreeWidget *ctreeroot,
			   GQTreeWidgetNode *top,
			   char *dn)
{
     return gq_tree_widget_find_by_row_data_custom(ctreeroot,
					      top, dn,
					      (GCompareFunc) dn_row_compare_func);
}
#else
gboolean
node_from_dn(GtkTreeModel* model,
	     GtkTreeIter * parent,
	     GtkTreeIter * found,
	     gchar const * dn)
{
	g_warning("FIXME: port");
	return FALSE;
}
#endif

struct server_dn {
     const GqServer *server;
     const char *dn;
     GqServer *currserver;
     GQTreeWidgetNode *found;
};

static void tree_node_from_server_dn_check_func(GQTreeWidget *ctree,
						GQTreeWidgetNode *node,
						struct server_dn *sd)
{
     GqBrowserNode *e;

     e = GQ_BROWSER_NODE(gq_tree_get_node_data (ctree, node));

     if (e == NULL) return;
     if (GQ_IS_BROWSER_NODE_SERVER(e)) {
	  sd->currserver = GQ_BROWSER_NODE_SERVER(e)->server;
	  if (strlen(sd->dn) == 0 && sd->currserver == sd->server) {
	       sd->found = node;
	  }
	  return;
     }
     if (GQ_IS_BROWSER_NODE_DN(e)) {
	  if (strcasecmp(sd->dn, gq_browser_node_dn_get_dn(GQ_BROWSER_NODE_DN(e))) == 0 &&
	      sd->currserver && sd->currserver == sd->server) {
	       sd->found = node;
	  }
	  return;
     }
}

/* NOTE: used by server_node_from_server() as well */

GQTreeWidgetNode *tree_node_from_server_dn(GQTreeWidget *ctree,
				       GqServer *server,
				       const char *dn)
{
     GQTreeWidgetNode *thenode;

     struct server_dn *sd = g_malloc(sizeof(struct server_dn));
     sd->server = g_object_ref(server);
     sd->dn = dn;
     sd->currserver = NULL;
     sd->found = NULL;

     gq_tree_widget_pre_recursive(ctree,
			     NULL, /* root */
			     (GQTreeWidgetFunc) tree_node_from_server_dn_check_func,
			     sd);

     thenode = sd->found;
     g_object_unref(server);

     g_free(sd);
     return thenode;
}


static GQTreeWidgetNode *server_node_from_server(GQTreeWidget *ctree,
					     GqServer *server)
{

     return tree_node_from_server_dn(ctree, server, "");
}

void refresh_subtree_new_dn(int error_context, 
			    GQTreeWidget *ctree, 
			    GQTreeWidgetNode *node,
			    const char *newdn, 
			    int options)
{
     GQTreeWidgetNode *new_node;
     GqBrowserNodeDn *e =
	  GQ_BROWSER_NODE_DN(gq_tree_get_node_data (ctree, node));

     if (GQ_IS_BROWSER_NODE_DN(e)) {
	  GQTreeWidgetNode *parent, *sibling;
	  char **exploded_dn;
	  char *label;
	  gboolean is_expanded;
	  int n_expand = 2;

	  /* mark the entry to be uncached before we check for it again */
	  e->uncache = TRUE;
	  gtk_clist_freeze(GTK_CLIST(ctree));

	  is_expanded = gq_tree_is_node_expanded (ctree, node);

	  if (newdn) {
		GqServerDn* entry = gq_server_dn_new(newdn, server_from_node(ctree, node));
	       parent = GQ_TREE_WIDGET_ROW(node)->parent;
	       sibling = GQ_TREE_WIDGET_ROW(node)->sibling;

	       /* disconnecting entry from row doesn't work without calling
		  the destroy notify function - thus copy the entry */
	       e = GQ_BROWSER_NODE_DN(gq_browser_node_dn_new(entry));
	       g_object_unref(entry);

	       gq_tree_widget_unselect(ctree, node);

	       exploded_dn = gq_ldap_explode_dn(gq_browser_node_dn_get_dn(e), FALSE);
	       if (exploded_dn == NULL) {
		    /* parsing problem */
	       }

	       label = exploded_dn[0];

	       /* add a new entry - alternatively we could just set
                  the new info (new rdn) for the existing node, but we
                  would nevertheless have to remove child nodes and
                  mark the node as unvisited. Therefore we just start
                  with a completely fresh tree */
	       new_node = gq_tree_insert_node (ctree,
					       parent, sibling,
					       label,
					       e,
					       g_object_unref);

	       /* add dummy node to have something to expand */
	       gq_tree_insert_dummy_node (ctree,
					  new_node);

	       /* select the newly added node */
	       gq_tree_select_node(ctree, new_node);

	       /* delete old node */
	       gq_tree_remove_node(ctree, node);
	       node = new_node;

	       gq_exploded_free(exploded_dn);
	  } else {
	       /* ! newdn */
	       e->seen = FALSE;

	       /* make the node unexpanded */
	       if (is_expanded)
		    gq_tree_toggle_expansion(ctree, node);
	  }
	  /* toggle expansion at least twice to fire the expand
	     callback (NOTE: do we need this anymore?) */
	  
	  if (options & REFRESH_FORCE_EXPAND) {
	       n_expand = 1;
	  } else if (options & REFRESH_FORCE_UNEXPAND) {
	       n_expand = 0;
	  } else if (is_expanded) {
	       n_expand = 1;
	  } else {
	       n_expand = 0;
	  }
	  
	  while (n_expand-- > 0) {
	       gq_tree_toggle_expansion(ctree, node);
	  }

	  show_server_dn(error_context, 
			 ctree, server_from_node(ctree, node),
			 gq_browser_node_dn_get_dn(e), TRUE);


	  gtk_clist_thaw(GTK_CLIST(ctree));
     }
}

void refresh_subtree(int error_context, 
		     GQTreeWidget *ctree, 
		     GQTreeWidgetNode *node)
{
     refresh_subtree_new_dn(error_context, ctree, node, NULL, 0);
}

void refresh_subtree_with_options(int error_context, 
				  GQTreeWidget *ctree,
				  GQTreeWidgetNode *node,
				  int options)
{
     refresh_subtree_new_dn(error_context, ctree, node, NULL, options);
}

#ifndef USE_TREE_VIEW
GQTreeWidgetNode *show_server_dn(int context, 
			     GQTreeWidget *tree, 
			     GqServer *server, const char *dn,
			     gboolean select_node)
{
     GQTreeWidgetNode *node = tree_node_from_server_dn(tree, server, "");
     if (node) {
	  gq_tree_expand_node(tree, node);
	  return show_dn(context, tree, node, dn, select_node);
     }
     return NULL;
}
#else
void
show_server_dn(int           context,
	       GtkTreeModel* model,
	       GqServer    * server,
	       gchar const * dn,
	       gboolean      select_node)
{
	g_warning("FIXME: implement show_server_dn()");
}
#endif

#ifndef USE_TREE_VIEW
GQTreeWidgetNode*
#else
void
#endif
show_dn(int               error_context,
#ifndef USE_TREE_VIEW
	GQTreeWidget    * tree,
	GQTreeWidgetNode* node,
#else
	GtkTreeModel    * model,
	GtkTreeIter     * iter,
	GtkTreeIter     * found,
#endif
	gchar const     * dn, 
	gboolean          select_node)
{
     char **dnparts;
     int i;
     GString *s;
#ifndef USE_TREE_VIEW
     GQTreeWidgetNode *found = NULL;
#endif
     char *attrs[] = { LDAP_NO_ATTRS, NULL };

#ifndef USE_TREE_VIEW
     if (!dn) return NULL;
     if (!tree) return NULL;
#else
	g_return_if_fail(GQ_IS_BROWSER_MODEL(model));
	if(!dn) {
		return;
	}
#endif
     
     s = g_string_new("");
     dnparts = gq_ldap_explode_dn(dn, 0);
     
     for(i = 0 ; dnparts[i] ; i++) {
     }

     for(i-- ; i >= 0 ; i--) {
	  if (*dnparts[i] == 0) continue;  /* skip empty DN elements (ie always the last one???) - ah, forget to think about it. */
	  g_string_insert(s, 0, dnparts[i]);

/*  	  printf("try %s at %08lx\n", s->str, node); */
#ifndef USE_TREE_VIEW
	  if (node) gq_tree_expand_node(tree, node);
#else
	  g_warning("FIXME: port this");
#endif

#ifndef USE_TREE_VIEW
	  found = node_from_dn(model, iter, s->str);

	  if (gq_browser_model_validate(model, &found)) {
	       node = found;
	  } else if (node) {
#else
		if(!node_from_dn(model, iter, found, s->str) && iter) {
#endif
	       /* check if the object with this dn actually exists. If
                  it does, we add it to the tree by hand, as we
                  probably cannot see it due to a size limit */
#ifndef USE_TREE_VIEW
	       GqServer *server = server_from_node(tree, node);
#else
	       GqServer* server = server_from_node(model, iter);
#endif
	       LDAP *ld;

	       if ((ld = open_connection(error_context, server)) != NULL) {
		    LDAPMessage *res, *e;
		    LDAPControl c;
		    LDAPControl *ctrls[2] = { NULL, NULL } ;
		    int rc;

		    c.ldctl_oid			= LDAP_CONTROL_MANAGEDSAIT;
		    c.ldctl_value.bv_val	= NULL;
		    c.ldctl_value.bv_len	= 0;
		    c.ldctl_iscritical		= 1;
		    
		    ctrls[0] = &c;

		    rc = ldap_search_ext_s(ld, s->str,
					   LDAP_SCOPE_BASE, 
					   "(objectClass=*)",
					   attrs,
					   0, 
					   ctrls,	/* serverctrls */
					   NULL,	/* clientctrls */
					   NULL,	/* timeout */
					   LDAP_NO_LIMIT,	/* sizelimit */
					   &res);

		    if(rc == LDAP_NOT_SUPPORTED) {
			 rc = ldap_search_s(ld, s->str, LDAP_SCOPE_BASE,
					    "(objectClass=*)",
					    attrs, 0, &res);
		    }

		    if (rc == LDAP_SUCCESS) {
			 e = ldap_first_entry(ld, res);
			 if (e) {
			      /* have it!! */
			      char *dn2 = ldap_get_dn(ld, e);
#ifndef USE_TREE_VIEW
			      found = dn_browse_single_add(server, dn2, tree, node);

			      node = found;
#else
			      dn_browse_single_add(server, dn2, model, iter, found);
#endif
			      if (dn2) ldap_memfree(dn2);
			 }
		    } else {
			 /* FIXME report error */
		    }

		    if (res) ldap_msgfree(res);
		    res = NULL;

		    close_connection(server, FALSE);
	       } else {
		    /* ERROR - cannot open connection to server,
		       either it was just restarted or the server is
		       down. In order to not prolong the time for
		       deeply nested DNs to finally fail just break
		       out of this DN parts loop.
		    */
		    break;
	       }
	  }

/*	  else break; */

	  g_string_insert(s, 0, ",");
     }

     gq_exploded_free(dnparts);
     g_string_free(s, TRUE);

#ifndef USE_TREE_VIEW
     if (gq_browser_model_validate(gtk_tree_view_get_model(self->ctreeroot), &found) && select_node) {
	  gq_tree_select_node(tree, found);
	  gq_tree_widget_scroll_to(tree, node,
				0,
				0.5, 0);
     }
#else
		g_warning("FIXME: implement this passage");
#endif
#ifndef USE_TREE_VIEW
     if (found) return found;
     return NULL;
#endif
}

static void
browse_tab_open_diff(GqTabBrowse* self)
{
	GtkTreeModel* model;
	GqTab* tab = new_modetab(&mainwin, GQ_TYPE_DIFF_TAB, TRUE);
	GList* selected = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(self->ctreeroot), &model);
	GList* it;
	gint i;
	for(i = 0, it = selected; it; i++, it = g_list_next(it)) {
		GtkTreeIter  iter;
		if(!gtk_tree_model_get_iter(model, &iter, it->data)) {
			g_warning("Got an invalid selection");
			continue;
		}
		gq_diff_tab_set_entry(GQ_DIFF_TAB(tab), i,
				      gq_browser_node_dn_get_entry(gq_browser_model_get_node(GQ_BROWSER_MODEL(model),
						      					     &iter)));
	}
	g_list_foreach(selected, gtk_tree_path_free, NULL);
	g_list_free(selected);
}

static void
browse_tab_check_dn_node(GtkTreeModel* model,
			 GtkTreePath * path,
			 GtkTreeIter * iter,
			 gpointer      data)
{
	gboolean* is_dn = data;

	if(!GQ_IS_BROWSER_NODE_DN(gq_browser_model_get_node(GQ_BROWSER_MODEL(model), iter))) {
		*is_dn = FALSE;
	}
}

/*
 * Button pressed on a tree item. Button 3 gets intercepted and puts up
 * a popup menu, all other buttons get passed along to the default handler
 *
 * the return values here are related to handling of button events only.
 */
static gboolean
button_press_on_tree_item(GtkWidget     * tree,
			  GdkEventButton* event,
			  GqTab         * tab)
{
	if(event->type == GDK_BUTTON_PRESS && event->button == 3)
	{
		GqBrowserNodeDn* entry;
		GtkTreeModel* model;
		GtkTreePath * path;
		GtkWidget   * item;
		GtkMenu     * menu;
		gchar       * name;

		if(event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tree)))
		{
			return FALSE;
		}

		if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), event->x, event->y,
					      	 &path, NULL, NULL, NULL))
		{
			return FALSE;
		}

		model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));

		if(!gtk_tree_model_get_iter(model, &GQ_TAB_BROWSE(tab)->tree_row_popped_up, path))
		{
			gtk_tree_path_free(path);
			return FALSE;
		}

		entry = gq_browser_model_get_node(GQ_BROWSER_MODEL(model), &GQ_TAB_BROWSE(tab)->tree_row_popped_up);
		
		name = gq_browser_node_get_name(GQ_BROWSER_NODE(entry), FALSE);
		menu = gtk_menu_new();

		item = gtk_menu_item_new_with_label(name);
		gtk_widget_set_sensitive(item, FALSE);
		gtk_widget_show(item);
		gtk_menu_append(GTK_MENU(menu), item);
		gtk_menu_set_title(GTK_MENU(menu), name);

		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_menu_append(GTK_MENU(menu), item);

		gq_browser_node_popup(entry, menu,
				      GTK_TREE_VIEW(tree), &GQ_TAB_BROWSE(tab)->tree_row_popped_up, tab);

		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_menu_append(GTK_MENU(menu), item);

		item = gtk_menu_item_new_with_label(_("Compare Items"));
		g_signal_connect_swapped(item, "activate",
					 G_CALLBACK(browse_tab_open_diff), tab);
		gtk_widget_show(item);
		gtk_menu_append(GTK_MENU(menu), item);

		if(gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(tree)) == 2) {
			gboolean is_dn = TRUE;
			gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(tree),
							    browse_tab_check_dn_node,
							    &is_dn);

			gtk_widget_set_sensitive(item, is_dn);
		}
		else
		{
			gtk_widget_set_sensitive(item, FALSE);
		}

		/* The common Refresh item */
		item = gtk_image_menu_item_new_from_stock(GTK_STOCK_REFRESH, NULL);
		gtk_widget_show(item);
		gtk_menu_append(GTK_MENU(menu), item);

		g_signal_connect_swapped(item, "activate",
					 G_CALLBACK(tree_row_refresh), tab);

		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
			       event->button, event->time);
		gtk_tree_path_free(path);
		g_free(name);
		return TRUE;
	}
	return FALSE;
}


typedef struct {
     GList *list;
} cb_server_list;

static void get_current_servers_list_check_func(GQTreeWidget *ctree,
						GQTreeWidgetNode *node,
						cb_server_list *csl)
{
     GqBrowserNode *e;
     e = GQ_BROWSER_NODE(gq_tree_get_node_data (ctree, node));

     if (e == NULL) return;
     if (GQ_IS_BROWSER_NODE_SERVER(e)) {
	  GqServer *thisserver = GQ_BROWSER_NODE_SERVER(e)->server;

	  csl->list = g_list_append(csl->list, g_object_ref(thisserver));
     }
}

static void
add_missing_server(GQServerList* list G_GNUC_UNUSED,
		   GqServer    * server,
		   gpointer      user_data)
{
	GQTreeWidget* tree = GQ_TREE_WIDGET(user_data);

	if(!server_node_from_server(tree, server)) {
		add_single_server_internal(tree, server);
	}
}

void
update_browse_serverlist(GqTab *tab G_GNUC_UNUSED)
{
#ifndef USE_TREE_VIEW
     GQTreeWidget *ctree;
     cb_server_list csl;
     GList *l;

     ctree = GQ_TAB_BROWSE(tab)->ctreeroot;
     /* walk the list of ldapservers, add any not yet in the show list */

     gq_server_list_foreach(gq_server_list_get(), add_missing_server, ctree);

     csl.list = NULL;
     gq_tree_widget_pre_recursive_to_depth(ctree,
				      NULL, /* root node */
				      1,    /* max depth */
				      (GQTreeWidgetFunc) get_current_servers_list_check_func,
				      &csl);

     /* walk list of shown servers, remove any no longer in config */
     for (l = csl.list ; l ; l=l->next) {
	  GqServer *thisserver = l->data;
	  GQTreeWidgetNode *node = server_node_from_server(ctree, thisserver);
	  int found = gq_server_list_contains(gq_server_list_get(), thisserver);

	  /* deleted servers */
	  if (!found) {
	       if (node) gq_tree_remove_node(ctree, node);
	  } else {
	       /* renamed servers ? */
	       char *currtext = gq_tree_get_node_text (ctree,node);
	       if (currtext && strcmp(currtext, gq_server_get_name(thisserver)) != 0) {
		    gq_tree_set_node_text (ctree, node, gq_server_get_name(thisserver));
	       }
	  }
     }

     if (csl.list) {
	  g_list_foreach(csl.list, (GFunc)g_object_unref, NULL);
	  g_list_free(csl.list);
     }
#endif
}

/* GType */
G_DEFINE_TYPE(GqTabBrowse, gq_tab_browse, GQ_TYPE_TAB);

enum {
	PROP_0,
	PROP_INPUT_FORM
};

static void
gq_tab_browse_init(GqTabBrowse* self)
{
#define modeinfo self
	GtkTreeViewColumn* column;
	GtkTreeSelection * selection;
	GtkCellRenderer  * renderer;
	GtkTreeModel     * model;
	GtkWidget *mainpane, *pane1_scrwin;
     GtkWidget *browsemode_vbox, *spacer;
	GqTab            * tab = GQ_TAB(self);

     browsemode_vbox = gtk_vbox_new(FALSE, 0);

     spacer = gtk_hbox_new(FALSE, 0);
     gtk_widget_show(spacer);
     gtk_box_pack_start(GTK_BOX(browsemode_vbox), spacer, FALSE, FALSE, 3);

     mainpane = gtk_hpaned_new();
     gtk_container_border_width(GTK_CONTAINER(mainpane), 2);
     gtk_widget_show(mainpane);
     gtk_box_pack_start(GTK_BOX(browsemode_vbox), mainpane, TRUE, TRUE, 0);
     GQ_TAB_BROWSE(tab)->mainpane = mainpane;
#ifndef USE_TREE_VIEW
     ctreeroot = gq_tree_widget_new ();
     modeinfo->ctreeroot = ctreeroot;

     gq_tree_widget_set_selection_mode(GQ_TREE_WIDGET(ctreeroot), GTK_SELECTION_BROWSE);
     gq_tree_widget_set_column_auto_resize(GQ_TREE_WIDGET(ctreeroot), 0, TRUE);
     if (config->sort_browse) {
	  gtk_clist_set_auto_sort(GTK_CLIST(ctreeroot), TRUE);
     }

     gq_tree_widget_set_select_callback(GQ_TREE_WIDGET(ctreeroot),
					G_CALLBACK(tree_row_selected),
					tab);
     gq_tree_widget_set_expand_callback(GQ_TREE_WIDGET(ctreeroot),
					G_CALLBACK(tree_row_expanded),
					tab);
     g_signal_connect(ctreeroot, "button_press_event",
			G_CALLBACK(button_press_on_tree_item), tab);

#ifdef BROWSER_DND
     browse_dnd_setup(GTK_WIDGET(ctreeroot), tab);
#endif /* BROWSER_DND */

     add_all_servers(GQ_TREE_WIDGET(ctreeroot));
#else
	/* set up the tree view */
	modeinfo->ctreeroot = GTK_TREE_VIEW(gq_tree_view_new());
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(modeinfo->ctreeroot), FALSE);

	column = gtk_tree_view_column_new();
	renderer = gtk_cell_renderer_pixbuf_new();
	gtk_tree_view_column_pack_start(column, renderer, FALSE);
	gtk_tree_view_column_add_attribute(column, renderer,
					   "icon-name", GQ_BROWSER_MODEL_COL_TYPE);
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(column, renderer, TRUE);
	gtk_tree_view_column_add_attribute(column, renderer,
					   "text", GQ_BROWSER_MODEL_COL_NAME);
	gtk_tree_view_append_column(GTK_TREE_VIEW(modeinfo->ctreeroot), column);
	g_signal_connect(modeinfo->ctreeroot, "button-press-event",
			 G_CALLBACK(button_press_on_tree_item), tab);

	/* set up the selection */
	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(modeinfo->ctreeroot));
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
	g_signal_connect(selection, "changed",
			 G_CALLBACK(browser_tree_selection_changed), tab);

	/* set up the model */
#warning "FIXME: add a sort model"
	model = gq_browser_model_new();
	gtk_tree_view_set_model(GTK_TREE_VIEW(modeinfo->ctreeroot), model);
	g_object_unref(model);
#endif
	gtk_widget_show(GTK_WIDGET(modeinfo->ctreeroot));

     pane1_scrwin = gtk_scrolled_window_new(NULL, NULL);
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pane1_scrwin),
				    GTK_POLICY_AUTOMATIC,
				    GTK_POLICY_ALWAYS);
#ifdef USE_TREE_VIEW
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(pane1_scrwin),
					    GTK_SHADOW_IN);
#endif
     gtk_widget_show(pane1_scrwin);

#warning "FIXME: calculate a good default from the window size; load and save to config"
     gtk_paned_set_position(GTK_PANED(mainpane), 300);
     gtk_paned_pack1(GTK_PANED(mainpane), pane1_scrwin, FALSE, FALSE);
	gtk_container_add(GTK_CONTAINER(pane1_scrwin), GTK_WIDGET(modeinfo->ctreeroot));

     // FIXME: remove this scrolled window, we only keep it for API compatibility
     P(self)->pane2_scrwin = gtk_scrolled_window_new(NULL, NULL);
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(P(self)->pane2_scrwin),
				    GTK_POLICY_NEVER,
				    GTK_POLICY_NEVER);
     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(P(self)->pane2_scrwin),
					 GTK_SHADOW_NONE);

	gtk_widget_show(P(self)->pane2_scrwin);

	gtk_paned_pack2(GTK_PANED(mainpane), P(self)->pane2_scrwin, TRUE, FALSE);

     gtk_widget_show(browsemode_vbox);

     /* prepare for proper cleanup */
     g_signal_connect_swapped(browsemode_vbox, "destroy",
			      G_CALLBACK(g_object_unref), tab);

     tab->content = browsemode_vbox;
     gtk_object_set_data(GTK_OBJECT(tab->content), "tab", tab);
#undef modeinfo
}

static void
tab_dispose(GObject* object)
{
	GqTabBrowse* self = GQ_TAB_BROWSE(object);

	gq_tab_browse_set_input_form(self, NULL);

	G_OBJECT_CLASS(gq_tab_browse_parent_class)->dispose(object);
}

static void
tab_finalize(GObject* object)
{
	GqTabBrowse* self = GQ_TAB_BROWSE(object);

	while(self->cur_path) {
		g_free(self->cur_path->data);
		self->cur_path = g_list_delete_link(self->cur_path, self->cur_path);
	}

	G_OBJECT_CLASS(gq_tab_browse_parent_class)->finalize(object);
}

static void
tab_get_property(GObject   * object,
		 guint       prop_id,
		 GValue    * value,
		 GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_INPUT_FORM:
		g_value_set_object(value, P(object)->input_form);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
tab_set_property(GObject     * object,
		 guint         prop_id,
		 GValue const* value,
		 GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_INPUT_FORM:
		gq_tab_browse_set_input_form(GQ_TAB_BROWSE(object), g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
gq_tab_browse_class_init(GqTabBrowseClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);
	GqTabClass* tab_class = GQ_TAB_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose      = tab_dispose;
	object_class->finalize     = tab_finalize;
	object_class->get_property = tab_get_property;
	object_class->set_property = tab_set_property;

	g_object_class_install_property(object_class,
					PROP_INPUT_FORM,
					g_param_spec_object("input-form",
							    _("Input Form"),
							    _("The input form to be used for display"),
							    GQ_TYPE_INPUT_FORM,
							    G_PARAM_READWRITE));

	tab_class->save_snapshot    = browse_save_snapshot;
	tab_class->restore_snapshot = browse_restore_snapshot;

	/* GqTabBrowseClass */
	g_type_class_add_private(self_class, sizeof(struct GqTabBrowsePrivate));
}

