/*
    GQ -- a GTK-based LDAP client
    Copyright (C) 1998-2003 Bert Vermeulen
    Copyright (C) 2002-2003 Peter Stamfest
    Copyright (C) 2006      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-search.h"

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

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

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

#include "common.h"
#include "configfile.h"
#include "encode.h"
#include "errorchain.h"
#include "filter.h"
#include "gq-constants.h"
#include "gq-enumerations.h"
#include "gq-export-dialog.h"
#include "gq-input-form.h"
#include "gq-server-model.h"
#include "gq-tab-browse.h"
#include "gq-window.h"
#include "schema.h"
#include "state.h"
#include "syntax.h"
#include "template.h"
#include "gq-utilities.h"

#define MAX_NUM_ATTRIBUTES		256

struct attrs {
	gchar *name;
	int column;
	struct attrs *next;
};

struct GqTabSearchPrivate {
	GtkWidget   * searchmode_combo;

	GtkWidget   * results_treeview;
	GtkListStore* results_liststore;

	GtkWidget   * results_display;

	GtkWidget   * search_combo;
	GtkWidget   * serverlist_combo;
	GtkWidget   * searchbase_combo;

	gboolean      populated_searchbase : 1;
	gboolean      search_lock : 1;

	GList       * history;

	int           last_options_tab;

	int scope;
	int chase_ref;
	int max_depth;
	GList *attrs;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_TAB_SEARCH, struct GqTabSearchPrivate))

/* Search Mode Tree Model */
enum {
	MODE_COL_MODE,
	MODE_COL_TEXT,
	MODE_N_COLUMNS
};

/* Search Results Tree Model */
#warning "FIXME: make this a class for its own, so we don't need to keep the DN in memory twice"
enum {
	RES_COL_NAME,
	RES_COL_DN,
	RES_N_COLUMNS
};

static void results_popup_menu(GqTabSearch   * self,
			       GdkEventButton* event,
			       GqServerDn    * set);

static void find_in_browser(GqTab *tab);
static void add_all_to_browser(GqTab *tab);
static void add_selected_to_browser(GqTab *tab);
static void export_search_selected_entry(GqTab* tab);
static void delete_search_selected(GqTab *tab);
static void query(GqTab *tab);

static int column_by_attr(struct attrs **attrlist, const char *attribute);
static int new_attr(struct attrs **attrlist, const char *attr);
static struct attrs *find_attr(struct attrs *attrlist, const char *attr);

static gint searchbase_button_clicked(GtkWidget *widget,
				      GdkEventButton *event,
				      GqTab *tab);
static void findbutton_clicked_callback(GqTab *tab);

static void servername_changed_callback(GqTab *tab);
static void search_edit_entry_callback(GqTabSearch *tab);
static void search_new_from_entry_callback(GtkWidget *w, GqTab *tab);
static void delete_search_entry(GqTab *tab);

gchar const*
gq_tab_search_get_base_dn(GqTabSearch const* self)
{
	g_return_val_if_fail(GQ_IS_TAB_SEARCH(self), NULL);

	return gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(P(self)->searchbase_combo)->entry));
}

void
gq_tab_search_set_base_dn(GqTabSearch* self,
			  gchar const* base_dn)
{
	g_return_if_fail(GQ_IS_TAB_SEARCH(self));
	g_return_if_fail(!base_dn || *base_dn);

	gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(self)->searchbase_combo)->entry),
			   base_dn);

#warning "FIXME: enable"
	//g_object_notify(G_OBJECT(self), "base-dn");
}

GqSearchMode
gq_tab_search_get_mode(GqTabSearch const* self)
{
	GqSearchMode mode;
	GtkTreeIter iter;

	gtk_combo_box_get_active_iter(GTK_COMBO_BOX(P(self)->searchmode_combo), &iter);
	gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(P(self)->searchmode_combo)), &iter,
			   MODE_COL_MODE, &mode,
			   -1);

	return mode;
}

static void
tab_search_set_mode(GqTabSearch* self,
		    GqSearchMode mode)
{
	GtkTreeModel* model;
	GtkComboBox*  combo;
	GtkTreeIter   iter;
	GqSearchMode  mode2;

	combo = GTK_COMBO_BOX(P(self)->searchmode_combo);
	model = gtk_combo_box_get_model(combo);
	gtk_tree_model_iter_nth_child(model, &iter, NULL, mode);
	gtk_tree_model_get(model, &iter,
			   MODE_COL_MODE, &mode2,
			   -1);

	g_return_if_fail(mode == mode2);

	gtk_combo_box_set_active_iter(combo, &iter);
}

GqServer*
gq_tab_search_get_server(GqTabSearch const* self)
{
	GtkTreeIter   iter;

	g_return_val_if_fail(GQ_IS_TAB_SEARCH(self), NULL);
	g_return_val_if_fail(GTK_IS_COMBO_BOX(P(self)->serverlist_combo), NULL);
	g_return_val_if_fail(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(P(self)->serverlist_combo), &iter), NULL);

	return gq_server_model_get_server(GQ_SERVER_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(P(self)->serverlist_combo))), &iter);
}

void
gq_tab_search_set_server(GqTabSearch* self,
			 GqServer   * server)
{
	g_return_if_fail(GQ_IS_TAB_SEARCH(self));
	g_return_if_fail(!server || GQ_IS_SERVER(server));

#warning "FIXME: this connection could be better (so that the entry gets updated when the server gets renamed etc.)"
	gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(self)->serverlist_combo)->entry),
			   gq_server_get_name(server));

#warning "FIXME: enable"
	//g_object_notify(G_OBJECT(self), "server");
}

static void
search_save_snapshot(int error_context G_GNUC_UNUSED,
		     char *state_name,
		     GqTab *tab)
{
	GtkTreeIter  iter;
	GqServer   * server;
	GList      * hist;

	hist = P(tab)->history;
	if (hist) {
		state_value_set_list(state_name, "history", hist);
	}

	state_value_set_list(state_name, "attributes", P(tab)->attrs);
	state_value_set_int(state_name, "chase", P(tab)->chase_ref);
	state_value_set_int(state_name, "max-depth", P(tab)->max_depth);
	state_value_set_int(state_name, "scope", P(tab)->scope);
	state_value_set_enum(state_name, "search-mode",
			     gq_tab_search_get_mode(GQ_TAB_SEARCH(tab)),
			     GQ_TYPE_SEARCH_MODE);

	state_value_set_int(state_name, "last-options-tab", P(tab)->last_options_tab);

	gtk_combo_box_get_active_iter(GTK_COMBO_BOX(P(tab)->serverlist_combo), &iter);
	server = gq_server_model_get_server(GQ_SERVER_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(P(tab)->serverlist_combo))),
					    &iter);
	state_value_set_string(state_name, "lastserver", gq_server_get_name(server));
}

static void search_restore_snapshot(int error_context         G_GNUC_UNUSED,
				    char *state_name,
				    GqTab *tab,
				    struct pbar_win *progress G_GNUC_UNUSED)
{
	GqSearchMode mode;
	GList*       searchhist;
	gchar const* lastserver;

	lastserver = state_value_get_string(state_name, "lastserver", NULL);
	if(lastserver && lastserver[0]) {
		GqServer* server = gq_server_list_get_by_name(gq_server_list_get(), lastserver);
		if(server) {
			GtkTreeModel* model;
			GtkTreeIter   iter;
			model = gtk_combo_box_get_model(GTK_COMBO_BOX(P(tab)->serverlist_combo));
			if(gq_server_model_get_iter(GQ_SERVER_MODEL(model), &iter, server)) {
				gtk_combo_box_set_active_iter(GTK_COMBO_BOX(P(tab)->serverlist_combo), &iter);
			}
		}
	}

	mode = state_value_get_enum(state_name, "search-mode",
				    GQ_SEARCH_MODE_BEGINS_WITH,
				    GQ_TYPE_SEARCH_MODE);
	tab_search_set_mode(GQ_TAB_SEARCH(tab), mode);

     if (config->restore_search_history) {
	  const GList *hist = state_value_get_list(state_name, "history");
	  if (hist) {
	       const GList *I;

	       searchhist = P(tab)->history;
	       for (I = hist ; I ; I = g_list_next(I)) {
		    searchhist = g_list_append(searchhist, g_strdup(I->data));
	       }
	       searchhist = g_list_insert(searchhist, "", 0);
	       gtk_combo_set_popdown_strings(GTK_COMBO(P(tab)->search_combo), searchhist);
	       searchhist = g_list_remove(searchhist, searchhist->data);
	       P(tab)->history = searchhist;
	  }

	  /* independently configurable? */
	  P(tab)->chase_ref = state_value_get_int(state_name, "chase", 1);
	  P(tab)->max_depth = state_value_get_int(state_name, "max-depth", 7);
	  P(tab)->scope = state_value_get_int(state_name, "scope", LDAP_SCOPE_SUBTREE);

	  P(tab)->attrs = free_list_of_strings(P(tab)->attrs);
	  P(tab)->attrs = 
	       copy_list_of_strings(state_value_get_list(state_name,
							 "attributes"));

	  P(tab)->last_options_tab = state_value_get_int(state_name, "last-options-tab", 0);
     }
}

static GtkWidget *current_search_options_window = NULL;

static void
search_options_destroyed(void)
{
	current_search_options_window = NULL;  
}

struct so_windata {
     GtkWidget *win;
     GtkWidget *notebook;
     GtkWidget *chase_ref;
     GtkWidget *show_ref;
     GtkWidget *onelevel;
     GtkWidget *subtree;
     GtkWidget *max;
     GtkWidget *list;

     GqTab *tab;
     GqServer *server;
     struct server_schema *ss;
};

static void
free_so_windata(struct so_windata *so)
{
	g_return_if_fail(so);

	g_object_unref(so->server);
	g_free(so);
}

const char *lastoptions = "global.search.lastoptions";

static void
so_okbutton_clicked(struct so_windata *so)
{
     GqTab *tab = so->tab;
     GList *I;

     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(so->chase_ref))) {
	  P(tab)->chase_ref = 1;
     } else {
	  P(tab)->chase_ref = 0;
     }

     P(tab)->max_depth =
	  gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(so->max));

     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(so->onelevel))) {
	  P(tab)->scope = LDAP_SCOPE_ONELEVEL;
     } else {
	  P(tab)->scope = LDAP_SCOPE_SUBTREE;
     }

     P(tab)->attrs = free_list_of_strings(P(tab)->attrs);

     for( I = GTK_CLIST(so->list)->selection ; I ; I = g_list_next(I) ) {
	  int row = GPOINTER_TO_INT(I->data);
	  char *t;

	  gtk_clist_get_text(GTK_CLIST(so->list), row, 0, &t);

	  P(tab)->attrs = g_list_append(P(tab)->attrs,
						g_strdup(t));
     }

     P(tab)->last_options_tab = gtk_notebook_get_current_page(GTK_NOTEBOOK(so->notebook));

     state_value_set_int(lastoptions, "chase", P(tab)->chase_ref);
     state_value_set_int(lastoptions, "max-depth", P(tab)->max_depth);
     state_value_set_int(lastoptions, "scope", P(tab)->scope);
     state_value_set_list(lastoptions, "attributes", P(tab)->attrs);

     state_value_set_int(lastoptions, "last-options-tab", P(tab)->last_options_tab);

     gtk_widget_destroy(so->win);
}

static void
so_attrs_clear_clicked(struct so_windata *so) 
{
     gtk_clist_unselect_all( GTK_CLIST(so->list));
}

struct so_select_by_oc_cb_data
{
     struct so_windata *so;
     LDAPObjectClass *oc;
};

static void
so_select_by_oc(struct so_select_by_oc_cb_data *cbd) 
{
     int i, j;
     g_assert(cbd);

     if (cbd->oc->oc_at_oids_must) {
	  for ( i = 0 ; cbd->oc->oc_at_oids_must[i] ; i++ ) {
	       char *t;
	       for(j = 0 ; 
		   gtk_clist_get_text(GTK_CLIST(cbd->so->list), j, 0, &t) ;
		   j++) {
		    if (strcasecmp(t, cbd->oc->oc_at_oids_must[i]) == 0) {
			 gtk_clist_select_row(GTK_CLIST(cbd->so->list), j, 0);
			 break;
		    }
	       }
	  }
     }
     if (cbd->oc->oc_at_oids_may) {
	  for ( i = 0 ; cbd->oc->oc_at_oids_may[i] ; i++ ) {
	       char *t;
	       for(j = 0 ; 
		   gtk_clist_get_text(GTK_CLIST(cbd->so->list), j, 0, &t) ;
		   j++) {
		    if (strcasecmp(t, cbd->oc->oc_at_oids_may[i]) == 0) {
			 gtk_clist_select_row(GTK_CLIST(cbd->so->list), j, 0);
			 break;
		    }
	       }
	  }
     }

}

/*
 * 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 so_button_press_on_tree_item(GtkWidget *clist,
					     GdkEventButton *event,
					     struct so_windata *so)
{
     GtkWidget *root_menu, *menu, *menu_item, *label, *submenu;

     if (event->type == GDK_BUTTON_PRESS && event->button == 3
	 && event->window == GTK_CLIST(clist)->clist_window) {

	  root_menu = gtk_menu_item_new_with_label("Root");
	  gtk_widget_show(root_menu);
	  menu = gtk_menu_new();

	  gtk_menu_item_set_submenu(GTK_MENU_ITEM(root_menu), menu);

	  label = gtk_menu_item_new_with_label(_("Attributes"));
	  gtk_widget_set_sensitive(label, FALSE);
	  gtk_widget_show(label);

	  gtk_menu_append(GTK_MENU(menu), label);
	  gtk_menu_set_title(GTK_MENU(menu), _("Attributes"));

	  menu_item = gtk_separator_menu_item_new();
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  gtk_widget_set_sensitive(menu_item, FALSE);
	  gtk_widget_show(menu_item);
	  
/* 	  /\* The common Refresh item *\/ */
/* 	  menu_item = gtk_menu_item_new_with_label(_("Refresh")); */
/* 	  gtk_menu_append(GTK_MENU(menu), menu_item); */
/* 	  gtk_widget_show(menu_item); */

/* 	  g_signal_connect(menu_item, "activate", */
/* 			     G_CALLBACK(tree_row_refresh), */
/* 			     tab); */


	  /* Clear */
	  menu_item = gtk_menu_item_new_with_label(_("Clear"));
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  gtk_widget_show(menu_item);

	  g_signal_connect_swapped(menu_item, "activate",
			     G_CALLBACK(so_attrs_clear_clicked),
			     so);


	  /* "Select by objectClass" submenu */
	  menu_item = gtk_menu_item_new_with_label(_("Select by objectclass"));
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  submenu = gtk_menu_new();
	  gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
	  gtk_widget_show(menu_item);

	  if (so->ss) {
	       GList *I;
	       int i;

	       LDAPObjectClass *oc;

	       for ( I = so->ss->oc ; I ; I = g_list_next(I) ) {
		    oc = (LDAPObjectClass *) I->data;

		    if(oc->oc_names && oc->oc_names[0]) {
			 for(i = 0; oc->oc_names[i]; i++) {
			      struct so_select_by_oc_cb_data *cbd =
				   (struct so_select_by_oc_cb_data*) g_malloc0(sizeof(struct so_select_by_oc_cb_data));
			      cbd->so = so;
			      cbd->oc = oc;

			      menu_item = 
				   gtk_menu_item_new_with_label(oc->oc_names[i]);
			      /* attach for destruction */
			      gtk_object_set_data_full(GTK_OBJECT(menu_item),
						       "cbd",
						       cbd,
						       (GtkDestroyNotify)g_free);
			      gtk_menu_append(GTK_MENU(submenu), menu_item);
			      g_signal_connect_swapped(menu_item, "activate",
						 G_CALLBACK(so_select_by_oc),
						 cbd);
			      gtk_widget_show(menu_item);
			 }
		    }
	       }
	  }

/* 	  g_signal_connect(menu_item, "activate", */
/* 			     G_CALLBACK(so_select_by_oc), */
/* 			     so); */

	  /*  *****  */

	  
	  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
			 event->button, event->time);
	  
	  g_signal_stop_emission_by_name(clist, 
				       "button_press_event");
	  return(TRUE);
     }

     return(FALSE);
}

static void chase_toggled(GtkToggleButton *togglebutton,
			  struct so_windata *so)
{
     gboolean st = gtk_toggle_button_get_active(togglebutton);
     gtk_widget_set_sensitive(so->max, st);
}

static void create_search_options_window(GqTab *tab)
{
     GtkWidget *window, *vbox0, *vbox1, *frame, *radio, 
	  *radio_chase, *radio_show;
     GtkWidget *table, *label, *entry, *bbox, *button, *list, *w;
     GtkWidget *notebook;
     GSList *radio_group;
     struct so_windata *so;
     GtkTooltips *tips;

     struct server_schema *ss = NULL;

     g_assert(tab);

     if(current_search_options_window) {
	  gtk_window_present(GTK_WINDOW(current_search_options_window));
	  return;
     }

     tips = gtk_tooltips_new();

     window = stateful_gtk_window_new(GTK_WINDOW_TOPLEVEL,
					  "search-options", 300, 350);
     g_assert(tab->win && tab->win->mainwin);
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
     gtk_window_set_transient_for(GTK_WINDOW(window),
				  GTK_WINDOW(tab->win->mainwin));

     gtk_widget_realize(window);

     so = g_malloc0(sizeof(struct so_windata));
     gtk_object_set_data_full(GTK_OBJECT(window), "so",
			      so, (GtkDestroyNotify) free_so_windata);

     so->tab = tab;
     so->win = window;

     g_signal_connect(window, "destroy",
			G_CALLBACK(search_options_destroyed), NULL);
     g_signal_connect(window, "key_press_event",
			G_CALLBACK(close_on_esc),
			window);

     current_search_options_window = window;
     gtk_window_set_title(GTK_WINDOW(window), _("Search Options"));
     gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);

     vbox0 = gtk_vbox_new(FALSE, 0);
     gtk_container_border_width(GTK_CONTAINER(vbox0), 
				CONTAINER_BORDER_WIDTH);
     gtk_widget_show(vbox0);
     gtk_container_add(GTK_CONTAINER(window), vbox0);

     so->notebook = notebook = gtk_notebook_new();
     gtk_widget_show(notebook);
     gtk_box_pack_start(GTK_BOX(vbox0), notebook, TRUE, TRUE, 5);

     /* General Tab */
     vbox1 = gtk_vbox_new(FALSE, 0);
     gtk_widget_show(vbox1);
     gtk_container_border_width(GTK_CONTAINER(vbox1), 
				CONTAINER_BORDER_WIDTH);

     label = gq_label_new(_("_General"));
     gtk_widget_show(label);
     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox1, label);

     /* Referrals Frame */
     frame = gtk_frame_new(_("Referrals"));
     gtk_widget_show(frame);
     gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, TRUE, 5);

     table = gtk_table_new(2, 3, FALSE);
     gtk_widget_show(table);

     gtk_container_add(GTK_CONTAINER(frame), table);

     radio_chase = gq_radio_button_new_with_label(NULL, _("C_hase"));
     so->chase_ref = radio_chase;

/*      gtk_box_pack_start(GTK_BOX(vbox2), radio, TRUE, TRUE, 0); */
     gtk_table_attach(GTK_TABLE(table), radio_chase,
		      0, 1,
		      0, 1, 
		      0, 0,
		      0, 0);
     gtk_widget_show(radio_chase);
     g_signal_connect(radio_chase, "toggled", 
			G_CALLBACK(chase_toggled),
			so);

     radio_group = gtk_radio_button_group(GTK_RADIO_BUTTON(radio_chase));
     radio_show = gq_radio_button_new_with_label(radio_group, _("_Show"));
     so->show_ref = radio_show;
/*      gtk_box_pack_start(GTK_BOX(vbox2), radio, TRUE, TRUE, 0); */
     gtk_table_attach(GTK_TABLE(table), radio_show,
		      1, 2, 
		      0, 1, 
		      0, 0,
		      0, 0);
     gtk_widget_show(radio_show);

     /* Max. Depth label & spin button */

     label = gq_label_new(_("Max. _Depth"));
     gtk_table_attach(GTK_TABLE(table), label,
		      0, 1, 
		      1, 2, 
		      0, 0,
		      0, 0);
     gtk_widget_show(label);

     entry = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(P(tab)->max_depth,
								   0.0, 999.0,
								   1.0, 5.0,
								   1.0)),
				 1.0, 0);
     so->max = entry;

     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_chase), 
				  P(tab)->chase_ref);
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_show),
				  !P(tab)->chase_ref);

     gtk_table_attach(GTK_TABLE(table), entry,
		      1, 2, 
		      1, 2, 
		      GTK_SHRINK, GTK_SHRINK,
		      2, 0);
     gtk_widget_show(entry);

     gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);

     /* Search Scope Frame */
     frame = gtk_frame_new(_("Search scope"));
     gtk_widget_show(frame);
     gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, TRUE, 5);

     table = gtk_table_new(2, 3, FALSE);
     gtk_widget_show(table);

     gtk_container_add(GTK_CONTAINER(frame), table);

     radio = gq_radio_button_new_with_label(NULL, _("_1 level"));
     so->onelevel = radio;

     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio),
				  P(tab)->scope == LDAP_SCOPE_ONELEVEL);

/*      gtk_box_pack_start(GTK_BOX(vbox2), radio, TRUE, TRUE, 0); */
     gtk_table_attach(GTK_TABLE(table), radio,
		      0, 1,
		      0, 1, 
		      0, 0,
		      0, 0);
     gtk_widget_show(radio);

     radio_group = gtk_radio_button_group(GTK_RADIO_BUTTON(radio));
     radio = gq_radio_button_new_with_label(radio_group, _("Sub_tree"));
     so->subtree = radio;

     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio),
				  P(tab)->scope != LDAP_SCOPE_ONELEVEL);

/*      gtk_box_pack_start(GTK_BOX(vbox2), radio, TRUE, TRUE, 0); */
     gtk_table_attach(GTK_TABLE(table), radio,
		      1, 2, 
		      0, 1, 
		      0, 0,
		      0, 0);
     gtk_widget_show(radio);

     /* well, they aren't */

/*      label = gtk_label_new(_("The search options are global search settings. They are common to all search tabs")); */
     
/*      gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); */
/*      gtk_box_pack_start(GTK_BOX(vbox1), label, FALSE, FALSE, 0); */
/*      gtk_widget_show(label); */

     /* attributes Tab */
     vbox1 = gtk_vbox_new(FALSE, 0);
     gtk_widget_show(vbox1);
     gtk_container_border_width(GTK_CONTAINER(vbox1), 
				CONTAINER_BORDER_WIDTH);

     label = gq_label_new(_("_Attributes"));
     gtk_widget_show(label);
     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox1, label);

     w = gtk_scrolled_window_new(NULL, NULL);
     gtk_widget_show(w);
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
                                    GTK_POLICY_AUTOMATIC,
				    GTK_POLICY_AUTOMATIC);
     gtk_box_pack_start(GTK_BOX(vbox1), w, TRUE, TRUE, 0);

     so->list = list = gtk_clist_new(1);
     gtk_widget_show(list);

     g_signal_connect(list, "button_press_event",
			G_CALLBACK(so_button_press_on_tree_item),
			so);

     gtk_container_add(GTK_CONTAINER(w), list);
     gtk_clist_set_selection_mode(GTK_CLIST(list),
				  GTK_SELECTION_MULTIPLE);

     gtk_tooltips_set_tip(tips, list,
			  _("The attributes to show in the search result. Selecting NO attributs will show ALL."),
			  Q_("tooltip|")
			  );

     /* fill list with attributes from schema */
     {
	  GqServer *server;
	  char *cur_servername;
	  int opt;

	  cur_servername = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(P(tab)->serverlist_combo)->entry), 0, -1);
	  server = gq_server_list_get_by_name(gq_server_list_get(), cur_servername);
	  g_free(cur_servername);

	  if (server) {
	       int context = error_new_context(_("Creating search option window"), tab->win->mainwin);

	       so->ss = ss = get_server_schema(context, server);
	       so->server = g_object_ref(server);

	       error_flush(context);
	  }
	  if (ss) {
	       GList *I;
	       int i;
	       char *t[2] = { NULL, NULL };
	       int row;

	       LDAPAttributeType *at;
	       
	       for ( I = ss->at ; I ; I = g_list_next(I) ) {
		    at = (LDAPAttributeType *) I->data;

		    if(at->at_names && at->at_names[0]) {
			 for(i = 0; at->at_names[i]; i++) {
			      t[0] = (char*) at->at_names[i];
			      row = gtk_clist_append(GTK_CLIST(list), t);
			      if (g_list_find_custom(P(tab)->attrs,
						     t[0], 
						     (GCompareFunc) strcasecmp)) { /*UTF-8?*/
				   gtk_clist_select_row(GTK_CLIST(list), row, 0);
			      }
			 }
		    }
	       }
	  }
	  opt = gtk_clist_optimal_column_width(GTK_CLIST(list), 0);
	  gtk_clist_set_column_width(GTK_CLIST(list), 0, opt);
     }

     /* Global Buttons */

     bbox = gtk_hbutton_box_new();
     gtk_widget_show(bbox);

     gtk_box_pack_end(GTK_BOX(vbox0), bbox, FALSE, FALSE, 0);

     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
     gtk_widget_show(button);
     g_signal_connect_swapped(button, "clicked",
			G_CALLBACK(so_okbutton_clicked),
			so);
     gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, TRUE, 10);
     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
     gtk_widget_grab_default(button);

     button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
     gtk_widget_show(button);
     g_signal_connect_swapped(button, "clicked",
			       G_CALLBACK(gtk_widget_destroy),
			       window);

     gtk_box_pack_end(GTK_BOX(bbox), button, FALSE, TRUE, 10);

     gtk_notebook_set_page(GTK_NOTEBOOK(notebook), P(tab)->last_options_tab);

     gtk_widget_show(window);
}



static void optbutton_clicked_callback(GqTab *tab)
{
     create_search_options_window(tab);
}

static void
set_selected(GtkTreeModel* model,
	     GtkTreePath * path G_GNUC_UNUSED,
	     GtkTreeIter * iter,
	     gpointer      search_tab)
{
	GqServerDn* sdn = NULL;
	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &sdn,
			   -1);
	g_object_set_data_full(search_tab,
			       "selected-entry",
			       g_object_ref(sdn),
			       g_object_unref);
}

static void
results_selection_changed(GtkTreeSelection* selection,
			  GqTabSearch     * self)
{
	GqServerDn* entry = NULL;

	switch(gtk_tree_selection_count_selected_rows(selection)) {
	case 1:
		gtk_tree_selection_selected_foreach(selection, set_selected, self);
		entry = g_object_get_data(G_OBJECT(self), "selected-entry");
		break;
	default:
		break;
	}
	gq_input_form_set_entry(GQ_INPUT_FORM(P(self)->results_display), entry);
	g_object_set_data(G_OBJECT(self), "selected-entry", NULL);
}

static gboolean
results_button_pressed(GtkWidget     * treeview,
		       GdkEventButton* event,
		       GqTabSearch   * self)
{
	if(event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
		search_edit_entry_callback(self);

		return TRUE;
	}
	else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
		GtkTreeView* view = GTK_TREE_VIEW(treeview);
		GtkTreePath* path = NULL;
		GqServerDn * set  = NULL;

		if(gtk_tree_view_get_path_at_pos(view, event->x, event->y,
						 &path, NULL, NULL, NULL))
		{
			GtkTreeModel* model = gtk_tree_view_get_model(view);
			GtkTreeIter iter;
			gtk_tree_model_get_iter(model, &iter, path);
			gtk_tree_model_get(model, &iter,
					   RES_COL_DN, &set,
					   -1);
			gtk_tree_path_free(path);
			path = NULL;

			results_popup_menu(self, event, set);

			return TRUE;
		}
	}

	return FALSE;
}

GqTab*
gq_tab_search_new(void)
{
	return g_object_new(GQ_TYPE_TAB_SEARCH, NULL);
}


static void
servername_changed_callback(GqTab *tab)
{
     GList *searchbase_list;
	GqServer *server;
	GtkTreeIter iter;

	if(!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(P(tab)->serverlist_combo), &iter)) {
		// no selection
		return;
	}
	server = gq_server_model_get_server(GQ_SERVER_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(P(tab)->serverlist_combo))),
					    &iter);

	/* make sure searchbase gets refreshed next time the searchbase combo button is
	   pressed. Just insert the server's default base DN for now */
	P(tab)->populated_searchbase = 0;
	if (server) {
		gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(tab)->searchbase_combo)->entry), server->basedn);
		searchbase_list = g_list_append(NULL, server->basedn);
		gtk_combo_set_popdown_strings(GTK_COMBO(P(tab)->searchbase_combo), searchbase_list);
		g_list_free(searchbase_list);
	}
}


static gint searchbase_button_clicked(GtkWidget *widget,
				      GdkEventButton *event, GqTab *tab)
{
     GList *searchbase_list;
     GList *suffixes_list, *temp;
     GqServer *server;
     int found_default_searchbase;
     char *cur_servername;
     int error_context;

     found_default_searchbase = 0;

	if(!P(tab)->populated_searchbase && event->button == 1) {
	  cur_servername = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(P(tab)->serverlist_combo)->entry), 0, -1);
	  server = gq_server_list_get_by_name(gq_server_list_get(), cur_servername);
	  g_free(cur_servername);
	  if (!server)
	       return(FALSE);

	  error_context = error_new_context(_("Looking for search-bases"),
					    widget);

	  searchbase_list = NULL;
 	  suffixes_list = get_suffixes(error_context, server);
    
	  temp = suffixes_list;
	  while (temp) {
	       if (g_strcasecmp(server->basedn, temp->data) == 0)
		    found_default_searchbase = 1;
	       searchbase_list = g_list_append(searchbase_list, temp->data);
      	       temp = g_list_next(temp);
	  }

	  if (found_default_searchbase == 0)
	       searchbase_list = g_list_prepend(searchbase_list, server->basedn);

	  gtk_combo_set_popdown_strings(GTK_COMBO(P(tab)->searchbase_combo), searchbase_list);

	  temp = suffixes_list;
	  while (temp) {
	       g_free(temp->data);
	       temp = g_list_next(temp);
	  }
	  if (suffixes_list)
	       g_list_free(suffixes_list);
	  g_list_free(searchbase_list);

	  P(tab)->populated_searchbase = 1;

	  error_flush(error_context);
	}

     return FALSE;
}


void findbutton_clicked_callback(GqTab *tab)
{
     GtkWidget *focusbox;

     set_busycursor();
     query(tab);
     set_normalcursor();

     focusbox = tab->focus;
     gtk_widget_grab_focus(focusbox);
     gtk_editable_select_region(GTK_EDITABLE(focusbox), 0, -1);

}

static int column_by_attr(struct attrs **attrlist, const char *attribute)
{
     struct attrs *attr;

     attr = find_attr(*attrlist, attribute);

     if(!attr)
	  return(new_attr(attrlist, attribute));
     else
	  return(attr->column);
}


static int new_attr(struct attrs **attrlist, const char *attr)
{
     struct attrs *n_attr, *alist;
     int cnt;

     n_attr = g_malloc(sizeof(struct attrs));
     n_attr->name = g_strdup(attr);
     n_attr->next = NULL;

     cnt = 0;
     if(*attrlist) {
	  cnt++;
	  alist = *attrlist;
	  while(alist->next) {
	       cnt++;
	       alist = alist->next;
	  }
	  alist->next = n_attr;
     }
     else
	  *attrlist = n_attr;

     n_attr->column = cnt;

     return(cnt);
}


static struct attrs *find_attr(struct attrs *attrlist, const char *attribute)
{

     while(attrlist) {
	  if(!strcasecmp(attrlist->name, attribute))
	       return(attrlist);
	  attrlist = attrlist->next;
     }

     return(NULL);
}


static void
free_attrlist(struct attrs *attrlist)
{
	g_free(attrlist->name);
	attrlist->name = NULL;

	// FIXME: Try to do this iteratively
	if(G_LIKELY(attrlist->next)) {
		free_attrlist(attrlist->next);
		attrlist->next = NULL;
	}

	g_free(attrlist);
}


char*
make_filter(GqServer const* server,
	    gchar const*    querystring,
	    GqSearchMode    mode)
{
     char *filter = NULL;

     if(*querystring == '(') {
	  filter = g_strdup(querystring);
     }
     else if(g_utf8_strchr(querystring, -1, '=')) {
	  filter = g_strdup_printf("(%s)", querystring);
     }
     else {
		gchar const* format = NULL;
		gchar const* attribute = NULL;

	  switch(mode) {
	  case GQ_SEARCH_MODE_BEGINS_WITH:
	       format = "(%s=%s*)";
	       break;
	  case GQ_SEARCH_MODE_ENDS_WITH:
	       format = "(%s=*%s)";
	       break;
	  case GQ_SEARCH_MODE_CONTAINS:
	       format = "(%s=*%s*)";
	       break;
	  case GQ_SEARCH_MODE_EQUALS:
	       format = "(%s=%s)";
	       break;
	  default:
	       g_assert_not_reached();
	       return NULL;
	  };

		attribute = gq_server_get_search_attribute(server);

		if(!attribute) {
#warning "FIXME: log this into the status bar"
			g_message(_("The server doesn't have a search attribute set. Using the default one (cn) now. "));
			attribute = "cn";
		}
		filter = g_strdup_printf(format, attribute, querystring);
     }

     return filter;
}


static void add_to_search_history(GqTab *tab)
{
     gchar *searchterm;
     GList *last, *item;

     searchterm = gtk_editable_get_chars(GTK_EDITABLE(tab->focus), 0, -1);
     if(P(tab)->history && !strcmp(P(tab)->history->data, searchterm)) {
	  g_free(searchterm);
     }
     else {
	  /* if this searchterm is already in history, delete it first */
	  item = g_list_find_custom(P(tab)->history, searchterm, (GCompareFunc) strcasecmp);
	  if(item) {
	       g_free(item->data);
	       P(tab)->history = g_list_remove(P(tab)->history, item->data);
	  }

	  P(tab)->history = g_list_insert(P(tab)->history, searchterm, 0);
	  if(g_list_length(P(tab)->history) > MAX_SEARCH_HISTORY_LENGTH) {
	       last = g_list_last(P(tab)->history);
	       g_free(last->data);
	       P(tab)->history = g_list_remove(P(tab)->history, last->data);
	  }
	  gtk_combo_set_popdown_strings(GTK_COMBO(tab->focus->parent), P(tab)->history);
     }

}

static void
fill_one_row(int           query_context G_GNUC_UNUSED,
	     GqServer    * server,
	     LDAP        * ld,
	     LDAPMessage * e,
	     GString     **tolist,
	     int         * columns_done  G_GNUC_UNUSED,
	     struct attrs* attrlist      G_GNUC_UNUSED, // may we'll need it again
	     GqTabSearch * self)
{
	GtkTreeIter iter;
     int i;
     gchar *cl[MAX_NUM_ATTRIBUTES];
     char *dn, *attr G_GNUC_UNUSED, **vals G_GNUC_UNUSED;
     GqServerDn *set;

     /* not every attribute necessarily comes back for
      * every entry, so clear this every time */
     for(i = 0; i < MAX_NUM_ATTRIBUTES; i++) {
	  cl[i] = NULL;
	  g_string_truncate(tolist[i], 0);
     }

     dn = ldap_get_dn(ld, e);
     /* store for later reference */
     set = gq_server_dn_new(dn, server);

#warning "FIXME: use this to find the matching attribute"
#if 0
     if(config->showdn) {
	  g_string_append(tolist[0], dn);
	  cl[0] = tolist[0]->str;
     }
     ldap_memfree(dn);

     for(attr = ldap_first_attribute(ld, e, &berptr); attr != NULL;
	 attr = ldap_next_attribute(ld, e, berptr)) {
	  if (!show_in_search(query_context, server, attr)) {
	       ldap_memfree(attr);
	       continue;
	  }

	  /* This should now work for ;binary as well */
	  cur_col = column_by_attr(&attrlist, attr);
	  if(cur_col == MAX_NUM_ATTRIBUTES) {
	       ldap_memfree(attr);
	       break;
	  }

	  if(!columns_done[cur_col]) {
	       char *c = attr_strip(attr);
	       gtk_clist_set_column_title(GTK_CLIST(clist), cur_col, c);
	       /* setting the width somehow causes my gtk2 to not show
		  the title correctly - BUG */
/*	       gtk_clist_set_column_width(GTK_CLIST(clist), cur_col, 120); */
	       gtk_clist_set_column_resizeable(GTK_CLIST(clist), cur_col,
					       TRUE);
	       if (c) g_free(c);
	       columns_done[cur_col] = 1;
	  }

	  vals = ldap_get_values(ld, e, attr);
	  if(vals) {
	       for(i = 0; vals[i] != NULL; i++) {
		    if(i == 0) {
			 g_string_assign(tolist[cur_col], vals[i]);
		    } else {
			 g_string_append(tolist[cur_col], " ");
			 g_string_append(tolist[cur_col], vals[i]);
		    }
	       }
	       ldap_value_free(vals);
	       if (g_utf8_validate(tolist[cur_col]->str, -1, NULL)) {
		    cl[cur_col] = tolist[cur_col]->str;
	       } else {
		    cl[cur_col] = "";
	       }
	  }
	  ldap_memfree(attr);
     }
#ifndef HAVE_OPENLDAP12
     if(berptr)
	  ber_free(berptr, 0);
#endif


     for(i = MAX_NUM_ATTRIBUTES ; i >= 0 ; i--) {
	  if (cl[i]) {
	       for ( ; i >= 0 ; i-- ) {
		    if (!cl[i]) {
			 cl[i] = "";
		    }
	       }
	       break;
	  }
     }
#endif

	/* insert row into result model */
	gtk_list_store_append(P(self)->results_liststore, &iter);
	gtk_list_store_set(P(self)->results_liststore, &iter,
			   RES_COL_NAME, gq_server_dn_get_dn(set),
			   RES_COL_DN,   set,
			   -1);
}


struct chasing {
     GqServer *server;
     char *base;
};

static struct chasing *new_chasing(GqServer *server,
				   const char *base)
{
	struct chasing *n = g_malloc0(sizeof(struct chasing));
	n->base = g_strdup(base);
	if(server) {
		n->server = g_object_ref(server);
	} else {
		n->server = NULL;
	}
	return n;
}

static void
free_chasing(struct chasing *ch)
{
	if(ch->server) {
		g_object_unref(ch->server);
		ch->server = NULL;
	}
	g_free(ch->base);
	g_free(ch);
}


static void add_referral(int error_context,
			 GqServer *server,
			 const char *referral, GList **nextlevel)
{
     LDAPURLDesc *desc = NULL;

     if (ldap_url_parse(referral, &desc) == 0) {
/*	  GString *new_uri = g_string_sized_new(strlen(referral)); */
	  GqServer *newserver;

	  newserver = get_referral_server(error_context, server, referral);

/*	  g_string_sprintf(new_uri, "%s://%s:%d/",  */
/*			   desc->lud_scheme, */
/*			   desc->lud_host, */
/*			   desc->lud_port); */

/*	  newserver = new_ldapserver(); */

/*	  /\* some sensible settings for the "usual" case: */
/*	     Anonymous bind. Also show referrals *\/ */
/*	  newserver->ask_pw   = 0; */
/*	  newserver->show_ref = 1; */

/* #warning "ADD CONFIG FOR EXTENDED REFERENCE CHASING" */
/*	  /\* check: do we have this server around already??? *\/ */
/*	  s = server_by_canon_name(new_uri->str, TRUE); */

/*	  if (!s) { */
/*	       s = server; */
/*	  } */

/*	  if (s) { */
/*	       copy_ldapserver(newserver, s); */
/*	       statusbar_msg(_("Initialized temporary server-definition '%1$s' from existing server '%2$s'"), new_uri->str, server->name); */
/*	  } else { */
/*	       statusbar_msg(_("Created temporary server-definition '%1$s' with no pre-set values."), new_uri->str); */
/*	  } */

/*	  g_free_and_dup(newserver->name,     new_uri->str); */
/*	  g_free_and_dup(newserver->ldaphost, new_uri->str); */
/*	  g_free_and_dup(newserver->basedn,   desc->lud_dn); */

	  newserver->quiet = 1;

	  canonicalize_ldapserver(newserver);

	  transient_add_server(newserver);

	  *nextlevel = g_list_append(*nextlevel,
				     new_chasing(newserver, desc->lud_dn));

	  ldap_free_urldesc(desc);
/*	  g_string_free(new_uri, TRUE); */
     }
}

struct list_click_info {
     int last_col;
     int last_type;
};


/* static gint result_compare_func(GtkCList *clist, */
/*				gconstpointer ptr1, */
/*				gconstpointer ptr2) */
/* { */

/*      gtk_clist_get_sort_column(clist); */


/* } */

#if 0
static void click_column (GtkCList *clist,
			 gint column,
			 struct list_click_info *lci)
{
     gtk_clist_set_sort_column(clist, column);
     if (lci->last_col != column) {
	  lci->last_type = GTK_SORT_ASCENDING;
     } else {
	  lci->last_type = (lci->last_type == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
     }
     lci->last_col = column;

     gtk_clist_set_sort_type(clist, lci->last_type);
     gtk_clist_sort(clist);
}
#endif

static void
query(GqTab *tab)
{
	GtkTreeIter iter;
     LDAP *ld;
     LDAPMessage *res, *e;
	GtkWidget *focusbox;
     GqServer *server;
     gchar *cur_searchbase, *enc_searchbase, *querystring;
     GString *tolist[MAX_NUM_ATTRIBUTES];
     char *filter, *searchterm;
     int msg, rc, i, row, l;
     int oc_col, columns_done[MAX_NUM_ATTRIBUTES];
     int want_oc = 1;
     struct attrs *attrlist;
     LDAPControl c;
     LDAPControl *ctrls[2] = { NULL, NULL } ;
     char *base;
     const char **attrs = NULL;

     GList *thislevel = NULL, *nextlevel = NULL;
     int level = 0;
     struct chasing *ch = NULL;

	if(P(tab)->search_lock) {
		return;
	}

	P(tab)->search_lock = 1;

     focusbox = tab->focus;
     searchterm = gtk_editable_get_chars(GTK_EDITABLE(focusbox), 0, -1);
     querystring = encoded_string(searchterm);
     g_free(searchterm);

     if(querystring[0] == 0) {
	  error_push(0, _("Please enter a valid search filter"));
	  goto done;
     }

#warning "FIXME: memset()"
     for(i = 0; i < MAX_NUM_ATTRIBUTES; i++)
	  columns_done[i] = 0;

	if(!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(P(tab)->serverlist_combo), &iter)) {
#warning "FIXME: we should realize this earlier"
		error_push(0, _("Oops! No Server selected!"));
		goto done;
	}
	server = gq_server_model_get_server(GQ_SERVER_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(P(tab)->serverlist_combo))),
					    &iter);

     filter = make_filter(server, querystring, gq_tab_search_get_mode(GQ_TAB_SEARCH(tab)));
     g_free(querystring);

     statusbar_msg(_("Searching for %s"), filter);

     cur_searchbase = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(P(tab)->searchbase_combo)->entry), 0, -1);

     enc_searchbase = encoded_string(cur_searchbase);
     g_free(cur_searchbase);

	/* setup results - clear model */
	gtk_list_store_clear(P(tab)->results_liststore);

     /* prepare attrs list for searches */
     l = g_list_length(P(tab)->attrs);

     want_oc = 1;
     if (l) {
	  /* fill attrs with pointers belonging to the
	     list. The attrs[i] MUST NOT be free'd */
	  const GList *I;

	  want_oc = 0;
	  attrs = g_malloc0(sizeof(const char *) * (l + 1));
	  for ( i = 0, I = P(tab)->attrs ; I ; i++, I = g_list_next(I)) {
	       attrs[i] = I->data;
	       if (strcasecmp(attrs[i], "objectclass") == 0) {
		    want_oc = 1;
	       }
	  }
     }

     attrlist = NULL;

     if (want_oc) {
	  oc_col = column_by_attr(&attrlist, "objectClass");
#warning "FIXME: check and enable object class information"
#if 0
	  gtk_clist_set_column_title(GTK_CLIST(new_main_clist), oc_col,
				     "objectClass");
	  gtk_clist_set_column_width(GTK_CLIST(new_main_clist), oc_col, 120);
	  columns_done[oc_col] = 1;

	  gtk_clist_set_column_resizeable(GTK_CLIST(new_main_clist),
					  oc_col, TRUE);

/*	  gtk_clist_set_column_visibility(GTK_CLIST(new_main_clist), */
/*					  oc_col, 0); */
#endif
     }

     for(i = 0; i < MAX_NUM_ATTRIBUTES; i++) {
	  tolist[i] = g_string_new("");
     }

     /* prepare ManageDSAit in case we should show referrals */
     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;

     thislevel = NULL;
     nextlevel = NULL;
     level = 0;

     ch = new_chasing(server, enc_searchbase);
     thislevel = g_list_append(thislevel, ch);

     if(enc_searchbase) {
		g_free(enc_searchbase);
		enc_searchbase = NULL;
     }

     row = 0;

     /* do the searching */
     set_busycursor();

     while (thislevel || nextlevel) {
	  if (thislevel == NULL) {
	       level++;
	       thislevel = nextlevel;
	       nextlevel = NULL;
	  }
	  if (level > P(tab)->max_depth) {
	       statusbar_msg(_("Reached maximum recursion depth"));

	       g_list_foreach(thislevel, (GFunc) free_chasing, NULL);
	       g_list_free(thislevel);
	       break;
	  }

	  ch = thislevel->data;
	  thislevel = g_list_remove(thislevel, ch);

	  server = ch->server;
	  base = ch->base;

	  if( (ld = open_connection(0, server)) != NULL) {
	       statusbar_msg(_("Searching on server '%1$s' below '%2$s'"),
			     gq_server_get_name(server), base);
	       rc = ldap_search_ext(ld, base,
				    P(tab)->scope,
				    filter,
				    (char **)attrs,	/* attrs & API bug*/
				    0,			/* attrsonly */
				    P(tab)->chase_ref ? NULL : ctrls,
/*				    server->show_ref ? ctrls : NULL, */
				    /* serverctrls */
				    NULL,		/* clientctrls */
				    NULL,		/* timeout */
				    LDAP_NO_LIMIT,	/* sizelimit */
				    &msg);

	       if(rc == -1) {
		    server->server_down++;
		    error_push(0,
			       _("Error searching below '%1$s': %2$s"),
			       enc_searchbase, ldap_err2string(rc));
/*		    close_connection(server, FALSE); */
/*		    GQ_TAB_SEARCH(tab)->search_lock = 0; */
/*		    return; */
		    goto cont;
	       }

	       for (rc = 1 ; rc ; ) {
		    int code = ldap_result(ld, msg, 0, NULL, &res);
		    if (code == -1) {
			 /* error */
			 error_push(0,
				    _("Unspecified error searching below '%1$s'"),
				    enc_searchbase);
			 break;
		    }
		    for( e = ldap_first_message(ld, res) ; e != NULL ;
			 e = ldap_next_message(ld, e) ) {
			 switch (ldap_msgtype(e)) {
			 case LDAP_RES_SEARCH_ENTRY:
			      fill_one_row(0,
					   server, ld, e,
					   tolist,
					   columns_done,
					   attrlist,
					   GQ_TAB_SEARCH(tab));
			      row++;

			      rc = 1;
			      break; /* OK */
			 case LDAP_RES_SEARCH_REFERENCE: {
			      char **refs = NULL;
			      rc = ldap_parse_reference(ld, e, &refs, NULL, 0);

			      if (rc == LDAP_SUCCESS) {
				   for (i = 0 ; refs[i] ; i++) {
					add_referral(0,
						     server,
						     refs[i], &nextlevel);
				   }
				   rc = 1;
			      }
			      ldap_value_free(refs);
			      break;
			 }
			 case LDAP_RES_SEARCH_RESULT:
			      rc = 0;
			      break;
			 default:
			      rc = 0;
			      break;
			 }
		    }
		    if (res) ldap_msgfree(res);
	       }
	  cont:
	       close_connection(server, FALSE);
	  } else {
	       /* ld == NULL */
	  }

	  free_chasing(ch);
     }

     set_normalcursor();

     if (attrs) g_free(attrs);
     free(filter);

     for(i = 0; i < MAX_NUM_ATTRIBUTES; i++) {
	  g_string_free(tolist[i], TRUE);
     }

     if(row > 0) {
/*	  statusbar_msg("%s", ldap_err2string(msg)); */
/*      else { */
	  statusbar_msg(ngettext("One entry found", "%d entries found", row),
			row);
     }

     free_attrlist(attrlist);
     add_to_search_history(tab);

done:
	P(tab)->search_lock = 0;
}

static void
results_popup_menu(GqTabSearch   * self,
		   GdkEventButton* event,
		   GqServerDn    * set)
{
#define tab self
	GtkTreeSelection* selection;
     GtkWidget *root_menu, *menu, *menu_item, *label;
     GtkWidget *submenu;
     int transient = is_transient_server(gq_server_dn_get_server(set));
     char **exploded_dn = NULL;
     gchar const *name;
	int selected;

	/* Check if several entries it should be sensitive */
	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(self)->results_treeview));
	selected = gtk_tree_selection_count_selected_rows(selection);

     root_menu = gtk_menu_item_new_with_label("Root");
     gtk_widget_show(root_menu);

     menu = gtk_menu_new();
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(root_menu), menu);

     exploded_dn = gq_ldap_explode_dn(gq_server_dn_get_dn(set), FALSE);

     if (exploded_dn) {
	  name = exploded_dn[0];
     } else {
	  name = gq_server_dn_get_dn(set);
     }

     label = gtk_menu_item_new_with_label(name);
     gtk_widget_set_sensitive(label, FALSE);
     gtk_widget_show(label);

     gtk_menu_append(GTK_MENU(menu), label);
     gtk_menu_set_title(GTK_MENU(menu), name);

     if (exploded_dn) gq_exploded_free(exploded_dn);

     menu_item = gtk_separator_menu_item_new();
     gtk_menu_append(GTK_MENU(menu), menu_item);
     gtk_widget_set_sensitive(menu_item, FALSE);
     gtk_widget_show(menu_item);

     /* Selection submenu */
     menu_item = gtk_menu_item_new_with_label(_("Selection"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     submenu = gtk_menu_new();
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
     gtk_widget_show(menu_item);

     /* Select All */
     menu_item = gtk_menu_item_new_with_label(_("Select All"));
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			      G_CALLBACK(gtk_tree_selection_select_all),
			      selection);
     gtk_widget_show(menu_item);

     /* Unselect All */
     menu_item = gtk_menu_item_new_with_label(_("Unselect All"));
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			      G_CALLBACK(gtk_tree_selection_unselect_all),
			      selection);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(menu_item, 0 < selected);

     /* separator */
     menu_item = gtk_menu_item_new();
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     gtk_widget_show(menu_item);

	/* Export to LDIF*/
	menu_item = gtk_image_menu_item_new_with_label(_("Export to LDIF"));
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
				      gtk_image_new_from_stock(GTK_STOCK_SAVE_AS, GTK_ICON_SIZE_MENU));
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(export_search_selected_entry),
			       tab);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(menu_item, 0 < selected);

     /* Add to Browser*/
     menu_item = gtk_menu_item_new_with_label(_("Add to Browser"));
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(add_selected_to_browser),
			       tab);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(menu_item, 0 < selected);

     /* separator */
     menu_item = gtk_menu_item_new();
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     gtk_widget_show(menu_item);

     /* Delete */
     menu_item = gtk_menu_item_new_with_label(_("Delete"));
     gtk_menu_append(GTK_MENU(submenu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(delete_search_selected),
			       tab);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(menu_item, 0 < selected);
     /*** End of Selected submenu ***/

     /* Edit */
     menu_item = gtk_menu_item_new_with_label(_("Edit"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(search_edit_entry_callback),
			       tab);
     gtk_widget_show(menu_item);

     /* Use as template */
     menu_item = gtk_menu_item_new_with_label(_("Use as template"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     g_signal_connect(menu_item, "activate",
			G_CALLBACK(search_new_from_entry_callback),
			tab);
     gtk_widget_show(menu_item);

     /* Find in Browser */
     menu_item = gtk_menu_item_new_with_label(_("Find in browser"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(find_in_browser),
			       tab);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(GTK_WIDGET(menu_item), !transient);

     /* Add all to Browser */
     menu_item = gtk_menu_item_new_with_label(_("Add all to browser"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(add_all_to_browser),
			       tab);
     gtk_widget_show(menu_item);
     gtk_widget_set_sensitive(GTK_WIDGET(menu_item), !transient);

     /* separator */
     menu_item = gtk_menu_item_new();
     gtk_menu_append(GTK_MENU(menu), menu_item);
     gtk_widget_show(menu_item);

     /* Delete */
     menu_item = gtk_menu_item_new_with_label(_("Delete"));
     gtk_menu_append(GTK_MENU(menu), menu_item);
     g_signal_connect_swapped(menu_item, "activate",
			       G_CALLBACK(delete_search_entry),
			       tab);
     gtk_widget_show(menu_item);

     gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
		    event->button, event->time);
#undef tab
}

static void
find_in_browser_foreach(GtkTreeModel* model,
			GtkTreePath * path G_GNUC_UNUSED,
			GtkTreeIter * iter,
			gpointer      data G_GNUC_UNUSED)
{
	GqTab      * browsetab;
	GqServerDn * sdn;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &sdn,
			   -1);

	/* find last used browser... */
	browsetab = gq_window_get_last_tab(&mainwin, GQ_TYPE_TAB_BROWSE);
	if(browsetab) {
		GtkWidget* ctree;
		int ctx;
		ctree = GQ_TAB_BROWSE(browsetab)->ctreeroot;
		ctx = error_new_context(_("Finding entry in browser"),
				  GTK_WIDGET(ctree));
		show_server_dn(ctx, model, gq_server_dn_get_server(sdn),
			 gq_server_dn_get_dn(sdn), TRUE);
		go_to_page(browsetab);
		error_flush(ctx);
	} else {
#warning "FIXME: just open a browser"
		single_warning_popup(_("No browser available"));
	}
}

void
find_in_browser(GqTab *tab)
{
#warning "FIXME: store the already used browsers and open new ones if necessary"
	GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(tab)->results_treeview));
	gtk_tree_selection_selected_foreach(sel, find_in_browser_foreach, tab);
}


struct ServerDnShowData {
	gint       context;
	GtkWidget* treeview;
};

static gboolean
server_dn_show_b(GtkTreeModel* model,
	         GtkTreePath * path G_GNUC_UNUSED,
	         GtkTreeIter * iter,
	         gpointer      pdata)
{
	struct ServerDnShowData* data = pdata;
	GqServerDn* dn = NULL;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &dn,
			   -1);

	show_server_dn(data->context, model,
		       gq_server_dn_get_server(dn),
		       gq_server_dn_get_dn(dn), FALSE);

	return FALSE;
}

void
add_all_to_browser(GqTab *tab)
{
	GqTab *browsetab;
	struct ServerDnShowData data = {
		0, NULL
	};

	/* find last used browser... */
     browsetab = gq_window_get_last_tab(&mainwin, GQ_TYPE_TAB_BROWSE);
	if(!browsetab) {
#warning "FIXME: just open a fucking browser"
		single_warning_popup(_("No browser available"));
		return;
	}

	data.context = error_new_context(_("Adding all to browser"), P(tab)->results_treeview);
	data.treeview = GTK_WIDGET(GQ_TAB_BROWSE(browsetab)->ctreeroot);
	gtk_tree_model_foreach(GTK_TREE_MODEL(P(tab)->results_liststore),
			       server_dn_show_b,
			       &data);
	error_flush(data.context);
	go_to_page(browsetab);
}

static void
server_dn_show(GtkTreeModel* model,
	       GtkTreePath * path,
	       GtkTreeIter * iter,
	       gpointer      data)
{
	server_dn_show_b(model, path,
			 iter, data);
}

static void
add_selected_to_browser(GqTab *tab)
{
	GqTab* browsetab;
	struct ServerDnShowData data = {
		0, NULL
	};

	/* find last used browser... */
	browsetab = gq_window_get_last_tab(&mainwin, GQ_TYPE_TAB_BROWSE);
	if(!browsetab) {
#warning "FIXME: just open a fucking browser"
		single_warning_popup(_("No browser available"));
		return;
	}

	data.context = error_new_context(_("Adding selected entries to browser"),
					 P(tab)->results_treeview);
	data.treeview = GTK_WIDGET(GQ_TAB_BROWSE(browsetab)->ctreeroot);

	gtk_clist_freeze(GTK_CLIST(GQ_TAB_BROWSE(browsetab)->ctreeroot));
	gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(GTK_TREE_VIEW(P(tab)->results_treeview)),
					    server_dn_show,
					    &data);
	gtk_clist_thaw(GTK_CLIST(GQ_TAB_BROWSE(browsetab)->ctreeroot));
	go_to_page(browsetab);

	error_flush(data.context);
}

static void
collect_selected(GtkTreeModel* model,
		 GtkTreePath * path G_GNUC_UNUSED,
		 GtkTreeIter * iter,
		 gpointer      data)
{
	GqServerDn* entry;
	GList     **list = data;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &entry,
			   -1);
	*list = g_list_prepend(*list, g_object_ref(entry));
}

static void
export_search_selected_entry(GqTab* tab)
{
     GqTab *browsetab;

     /* find last used browser... */

     browsetab = gq_window_get_last_tab(&mainwin, GQ_TYPE_TAB_BROWSE);
     if(browsetab) {
		GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(tab)->results_treeview));
		gint              error_context;
		GList           * list = NULL;

		gtk_tree_selection_selected_foreach(sel, collect_selected, &list);

		error_context = error_new_context(_("Exporting selected entries to LDIF"),
					    tab->win->mainwin);

		gq_export_server_dns(error_context, GTK_WINDOW(tab->win->mainwin), list);

		error_flush(error_context);
     } else {
#warning "FIXME: just open a fucking browser"
	  single_warning_popup(_("No browser available"));
     }
}

static void
server_dn_queue_for_delete(GtkTreeModel* model,
			   GtkTreePath * path G_GNUC_UNUSED,
			   GtkTreeIter * iter,
			   gpointer data)
{
	GqServerDn* entry = NULL;
	GList     **to_delete = data;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &entry,
			   -1);

	*to_delete = g_list_prepend(*to_delete, g_object_ref(entry));
}

static void
delete_search_selected(GqTab *tab)
{
	GtkTreeSelection* sel;
	GqServerDn      * entry;
	GtkWidget       * treeview = P(tab)->results_treeview;
	GList           * to_delete = NULL,
			* iterator;
	gint              context;

	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

#warning "FIXME: we should realize this earlier"
	g_return_if_fail(!gtk_tree_selection_count_selected_rows(sel));

#warning "FIXME: start working on undelete support so this question gets obsolete"
#warning "FIXME: set a parent window"
	if(!question_popup(NULL,
			   _("Do you really want to delete the selected entries?"),
			   _("Do you really want to delete the selected entries?")))
	{
		return;
	}

	// TRANSLATORS: there is a unicode symbold for the ellipsis (...): u2026
	context = error_new_context(_("Preparing deletion..."),
				    treeview);
	gtk_tree_selection_selected_foreach(sel,
					    server_dn_queue_for_delete,
					    &to_delete);
	error_flush(context);

	/* FIXME: sort by ldapserver and keep connection open across
	 * deletions  */
	context = error_new_context(_("Deleting selected entries"),
				    treeview);

	for (iterator = g_list_last(to_delete) ; iterator ; iterator = g_list_previous(iterator)) {
		entry = iterator->data;

		if(delete_entry(context, gq_server_dn_get_server(entry),
		   gq_server_dn_get_dn(entry)))
		{
			//deld = g_list_append(deld, I->data);
		}
		else {
#warning "FIXME: cancel deletion, revert the beginning and return to a clean directory state"
		}
	}

#warning "FIXME: remove the deleted entries from the search list"
	for (iterator = g_list_first(to_delete) ; iterator ; iterator = g_list_next(iterator)) {
#if 0
		gtk_clist_remove(GTK_CLIST(clist),
				 GPOINTER_TO_INT(I->data));
#endif
		g_object_unref(iterator->data);
		iterator->data = NULL;
	}
	g_list_free(to_delete);

	error_flush(context);
}

static void
search_tab_new_from_entry_foreach(GtkTreeModel* model,
				  GtkTreePath * path G_GNUC_UNUSED,
				  GtkTreeIter * iter,
				  gpointer      data)
{
	GqServerDn *set;
	int error_context;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &set,
			   -1);

	error_context = error_new_context(_("Creating new entry from search result"), data);

#warning "FIXME: set the parent window somehow"
	new_from_entry(gq_server_dn_get_server(set),
		       gq_server_dn_get_dn(set));

	error_flush(error_context);
}

#warning "FIXME: check whether the widget is necessary if the tab is a widget"
static void
search_new_from_entry_callback(GtkWidget* w,
			       GqTab    * tab G_GNUC_UNUSED)
{
	GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(tab)->results_treeview));
	gtk_tree_selection_selected_foreach(sel, search_tab_new_from_entry_foreach, w);
}

static void
search_tab_edit_entry_foreach(GtkTreeModel* model,
			      GtkTreePath * path G_GNUC_UNUSED,
			      GtkTreeIter * iter,
			      gpointer      data)
{
	G_GNUC_UNUSED GqTabSearch* self = GQ_TAB_SEARCH(data);
	GqServerDn * set;

	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &set,
			   -1);

	edit_entry(gq_server_dn_get_server(set),
		   gq_server_dn_get_dn(set));
}

static void
search_edit_entry_callback(GqTabSearch *self)
{
	GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(self)->results_treeview));
	gtk_tree_selection_selected_foreach(sel, search_tab_edit_entry_foreach, self);
}

static void
delete_search_entry_foreach(GtkTreeModel* model,
			    GtkTreePath * path G_GNUC_UNUSED,
			    GtkTreeIter * iter,
			    gpointer      data)
{
	GqTabSearch* self = GQ_TAB_SEARCH(data);
	GqServerDn *set;
	int ctx;

#warning "FIXME: add some way to visualize deleted items and/or deletion problems in the tree"
#warning "FIXME: queue items for deletion, delete async and remove items from the search results"
	gtk_tree_model_get(model, iter,
			   RES_COL_DN, &set,
			   -1);

	ctx = error_new_context(_("Deleting entry"),
			        P(self)->results_treeview);

	delete_entry(ctx, gq_server_dn_get_server(set),
		     gq_server_dn_get_dn(set));

	error_flush(ctx);
}

static void
delete_search_entry(GqTab *tab)
{
	GtkTreeSelection* sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(P(tab)->results_treeview));
	gtk_tree_selection_selected_foreach(sel, delete_search_entry_foreach, tab);
}

void
fill_out_search(GqTab      * tab,
		GqServer   * server,
		gchar const* search_base_dn)
{
#warning "FIXME: take a GqTabSearch as the first parameter"
	g_return_if_fail(GQ_IS_TAB_SEARCH(tab));
	g_return_if_fail(GQ_IS_SERVER(server));
	g_return_if_fail(!search_base_dn || *search_base_dn);

	if (!search_base_dn) {
		search_base_dn = server->basedn;
	}

	gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(tab)->serverlist_combo)->entry), gq_server_get_name(server));
	gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(tab)->searchbase_combo)->entry), search_base_dn);

	gtk_editable_set_position(GTK_EDITABLE(GTK_COMBO(P(tab)->serverlist_combo)->entry), 0);
	gtk_editable_set_position(GTK_EDITABLE(GTK_COMBO(P(tab)->searchbase_combo)->entry), 0);

	/* make tab visible */
	go_to_page(tab);
}

/* GType */
G_DEFINE_TYPE(GqTabSearch, gq_tab_search, GQ_TYPE_TAB);

static void
gq_tab_search_init(GqTabSearch* self)
{
	GtkWidget *searchmode_vbox, *table, *scrwin;
     GtkWidget *findbutton, *optbutton;
	GtkWidget* label;
	GtkWidget* paned;
#define modeinfo self
     const GList *last_attr;
	GtkCellRenderer* renderer;
	GtkTreeModel* model;
	GqTab          * tab = GQ_TAB(self);
	gchar* label_text;
	gint   i;


	P(modeinfo)->scope     = state_value_get_int(lastoptions, "scope",
						  LDAP_SCOPE_SUBTREE);
	P(modeinfo)->chase_ref = state_value_get_int(lastoptions, "chase", 1);
	P(self)->max_depth = state_value_get_int(lastoptions, "max-depth", 7);
	P(self)->last_options_tab = state_value_get_int(lastoptions, "last-options-tab", 0);

     /* copy attribute list */
     last_attr = state_value_get_list(lastoptions, "attributes");

	P(self)->attrs = free_list_of_strings(P(self)->attrs);
	P(self)->attrs = copy_list_of_strings(last_attr);

     /* setup widgets */
     searchmode_vbox = gtk_vbox_new(FALSE, 0);
     table = gtk_table_new(2, 5, FALSE);
	gtk_container_set_border_width(GTK_CONTAINER(table), 6);
	gtk_table_set_row_spacings(GTK_TABLE(table), 6);
	gtk_table_set_col_spacings(GTK_TABLE(table), 6);
     gtk_widget_show(table);
     gtk_box_pack_start(GTK_BOX(searchmode_vbox), table, FALSE, FALSE, 3);

	/* search label */
	label = gtk_label_new(NULL);
	label_text = g_strdup_printf("<b>%s</b>", _("Search:"));
	gtk_label_set_markup(GTK_LABEL(label), label_text);
	g_free(label_text);
	label_text = NULL;
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_widget_show(label);
	gtk_table_attach(GTK_TABLE(table), label,
			 0,1, 0,1,
			 GTK_FILL, GTK_EXPAND,
			 0,0);

	model = GTK_TREE_MODEL(gtk_list_store_new(MODE_N_COLUMNS, G_TYPE_INT, G_TYPE_STRING));
	for(i = 0; ; i++) {
		GtkTreeIter  iter;
		gchar const* text = NULL;

		switch(i) {
		case GQ_SEARCH_MODE_BEGINS_WITH:
			text = _("Attribute begins with");
			break;
		case GQ_SEARCH_MODE_ENDS_WITH:
			text = _("Attribute ends with");
			break;
		case GQ_SEARCH_MODE_CONTAINS:
			text = _("Attribute matches");
			break;
		case GQ_SEARCH_MODE_EQUALS:
			text = _("Attribute matches exactly");
			break;
		default:
			break;
		}

		if(text) {
			gtk_list_store_append(GTK_LIST_STORE(model), &iter);
			gtk_list_store_set(GTK_LIST_STORE(model), &iter,
					   MODE_COL_MODE, i,
					   MODE_COL_TEXT, text,
					   -1);
		} else {
			break;
		}
	}
	P(tab)->searchmode_combo = gtk_combo_box_new_with_model(model);
	gtk_combo_box_set_active(GTK_COMBO_BOX(P(tab)->searchmode_combo), 0);
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(P(tab)->searchmode_combo),
				 renderer,
				 TRUE);
	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(P(tab)->searchmode_combo),
				       renderer,
				       "text", 1,
				       NULL);
	g_object_unref(model);
	gtk_widget_show(P(tab)->searchmode_combo);
	gtk_table_attach(GTK_TABLE(table), P(tab)->searchmode_combo,
			 1,2, 0,1,
			 GTK_FILL, GTK_EXPAND | GTK_FILL,
			 0,0);

	/* searchterm combo box */
	P(tab)->search_combo = gtk_combo_new();
	gtk_combo_disable_activate(GTK_COMBO(P(tab)->search_combo));
	if(P(self)->history) {
		gtk_combo_set_popdown_strings(GTK_COMBO(P(tab)->search_combo), P(self)->history);
	}
	gtk_widget_show(P(tab)->search_combo);
	GTK_WIDGET_SET_FLAGS(GTK_COMBO(P(tab)->search_combo)->entry, GTK_CAN_FOCUS);
	GTK_WIDGET_SET_FLAGS(GTK_COMBO(P(tab)->search_combo)->entry, GTK_RECEIVES_DEFAULT);
	gtk_table_attach(GTK_TABLE(table), P(tab)->search_combo,
			 2,4, 0,1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND,
			 0,0);
	g_signal_connect_swapped(GTK_COMBO(P(tab)->search_combo)->entry, "activate",
			         G_CALLBACK(findbutton_clicked_callback), tab);
	tab->focus = GTK_COMBO(P(tab)->search_combo)->entry;

	/* server label */
	label = gtk_label_new(_("Server"));
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_widget_show(label);
	gtk_table_attach(GTK_TABLE(table), label,
			 0,1, 1,2,
			 GTK_FILL, GTK_EXPAND,
			 0,0);

	/* LDAP server combo box */
	P(self)->serverlist_combo = gtk_combo_box_new();
	{
		GtkCellRenderer* renderer;
		GtkTreeModel* model = gq_server_model_new(gq_server_list_get());
		gtk_combo_box_set_model(GTK_COMBO_BOX(P(self)->serverlist_combo),
					model);
		g_object_unref(model);
		gtk_combo_box_set_active(GTK_COMBO_BOX(P(self)->serverlist_combo), 0);

		renderer = gtk_cell_renderer_pixbuf_new();
		gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(P(self)->serverlist_combo),
					   renderer, FALSE);
		gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(P(self)->serverlist_combo), renderer,
					       "icon-name", GQ_SERVER_MODEL_COL_STATUS,
					       NULL);
		renderer = gtk_cell_renderer_text_new();
		gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(P(self)->serverlist_combo),
					   renderer, TRUE);
		gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(P(self)->serverlist_combo),
					       renderer,
					       "text", GQ_SERVER_MODEL_COL_NAME,
					       NULL);
	}
	gtk_table_attach(GTK_TABLE(table), P(self)->serverlist_combo,
		         1,2, 1,2,
		         GTK_FILL, GTK_EXPAND,
		         0,0);
	g_signal_connect_swapped(P(self)->serverlist_combo, "changed",
				 G_CALLBACK(servername_changed_callback), tab);
	gtk_widget_show(P(self)->serverlist_combo);

	/* base dn label */
	label = gtk_label_new(_("Base DN"));
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_widget_show(label);
	gtk_table_attach(GTK_TABLE(table), label,
			 2,3, 1,2,
			 GTK_FILL, GTK_EXPAND,
			 0,0);

	/* search base combo box */
	P(self)->searchbase_combo = gtk_combo_new();

	if(gq_server_list_n_servers(gq_server_list_get())) {
		gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(P(self)->searchbase_combo)->entry),
				   gq_server_list_get_server(gq_server_list_get(), 0)->basedn);
	}

	gtk_table_attach(GTK_TABLE(table), P(self)->searchbase_combo,
		      3,4, 1,2,
		      GTK_EXPAND | GTK_FILL, GTK_EXPAND,
		      0,0);
	g_signal_connect(GTK_COMBO(P(self)->searchbase_combo)->button, "button_press_event",
			G_CALLBACK(searchbase_button_clicked), tab);
	gtk_widget_show(P(self)->searchbase_combo);

     /* find button */
     findbutton = gq_button_new_with_label(_("_Find"));
     gtk_widget_show(findbutton);
     gtk_table_attach(GTK_TABLE(table), findbutton,
		      4,5, 0,1,
		      GTK_FILL, GTK_EXPAND | GTK_FILL,
		      0,0);
     gtk_container_border_width(GTK_CONTAINER (findbutton), 0);
     g_signal_connect_swapped(findbutton, "clicked",
			       G_CALLBACK(findbutton_clicked_callback),
			       tab);

     /* Options button */
     optbutton = gq_button_new_with_label(_("_Options"));
     gtk_widget_show(optbutton);
     gtk_table_attach(GTK_TABLE(table), optbutton,
		      4,5, 1,2,
		      GTK_FILL, GTK_EXPAND | GTK_FILL,
		      0,0);
     gtk_container_border_width(GTK_CONTAINER (optbutton), 0);
     g_signal_connect_swapped(optbutton, "clicked",
			       G_CALLBACK(optbutton_clicked_callback),
			       tab);

	/* results paned */
#warning "FIXME: save the paned position"
	paned = gtk_hpaned_new();
	gtk_widget_show(paned);
	gtk_box_pack_start(GTK_BOX(searchmode_vbox), paned, TRUE, TRUE, 0);

	/* search result display */
	/* this is added before the tree, because setting the tree model causes
	 * a "changed" event on the tree selection wich requires the results
	 * display to be set */
	P(self)->results_display = gq_input_form_new();
	gtk_widget_show(P(self)->results_display);
	gtk_paned_pack2(GTK_PANED(paned), P(self)->results_display,
			FALSE, FALSE);
	gq_input_form_set_editable(GQ_INPUT_FORM(P(self)->results_display), TRUE);

	/* results treeview */
	scrwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_paned_pack1(GTK_PANED(paned), scrwin,
			TRUE, FALSE);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin),
				       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin),
					    GTK_SHADOW_IN);
	P(self)->results_treeview = gtk_tree_view_new();
	gtk_container_add(GTK_CONTAINER(scrwin), P(self)->results_treeview);
	gtk_widget_show_all(scrwin);
	g_signal_connect(P(self)->results_treeview, "button-press-event",
			 G_CALLBACK(results_button_pressed), self);
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(P(self)->results_treeview)),
				    GTK_SELECTION_MULTIPLE);
	g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(P(self)->results_treeview)), "changed",
			 G_CALLBACK(results_selection_changed), self);
	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(P(self)->results_treeview), -1,
						    _("Distinguished Name (DN)"), gtk_cell_renderer_text_new(),
						    "text", RES_COL_NAME,
						    NULL);
#warning "FIXME: add attribute and match columns"
#if 0
#warning "FIXME: let the tree view support sort columns"
	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(P(self)->results_treeview), -1,
						    _("Attribute"), gtk_cell_renderer_text_new(),
#warning "FIXME: add the attribute column"
						    // "text", 0,
						    NULL);
	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(P(self)->results_treeview), -1,
						    _("Match"), gtk_cell_renderer_text_new(),
#warning "FIXME: add the match column"
						    // "text", 0,
						    NULL);
#endif

	P(self)->results_liststore = gtk_list_store_new(RES_N_COLUMNS, G_TYPE_STRING, GQ_TYPE_SERVER_DN);
	gtk_tree_view_set_model(GTK_TREE_VIEW(P(self)->results_treeview),
				GTK_TREE_MODEL(P(self)->results_liststore));
	g_object_unref(P(self)->results_liststore);

	gtk_widget_show(searchmode_vbox);

     g_signal_connect_swapped(searchmode_vbox, "destroy",
			      G_CALLBACK(g_object_unref), tab);

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

static void
tab_finalize(GObject* object)
{
	GqTabSearch* self = GQ_TAB_SEARCH(object);

	g_list_foreach(P(self)->history, (GFunc)g_free, NULL);
	g_list_free(P(self)->history);

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


static void
gq_tab_search_class_init(GqTabSearchClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);
	GqTabClass* tab_class = GQ_TAB_CLASS(self_class);

	object_class->finalize = tab_finalize;

	tab_class->save_snapshot    = search_save_snapshot;
	tab_class->restore_snapshot = search_restore_snapshot;

	g_type_class_add_private(self_class, sizeof(struct GqTabSearchPrivate));
}

