/*
    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-input-form.h"

#include <string.h>
#include <stdio.h>		/* perror() */
#include <stdlib.h>		/* free() */
#include <ctype.h>		/* tolower() */

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

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

#include "common.h"
#include "configfile.h"
#include "encode.h"
#include "errorchain.h"
#include "gq-browser-model.h"
#include "gq-tab-browse.h" // for USE_TREE_VIEW
#include "gq-tab-schema.h"
#include "gq-utilities.h"
#include "gq-window.h"
#include "ldif.h" /* for b64_decode */
#include "schema.h"
#include "state.h"
#include "syntax.h"
#include "tinput.h"

struct GqInputFormPrivate {
	GList     * formlist;
	GList     * oldlist;
	GqServerDn* entry;
	gchar     * olddn;

	GtkWidget  * toolbar;
	GtkTooltips* tips;
	GtkWidget  * table;
	GtkWidget  * dn_label;
	GtkWidget  * buttons;
	GtkWidget  * button_apply;
	GtkWidget  * button_add_new;
	GtkWidget  * button_refresh;
	GtkWidget  * button_close;
	GtkWidget  * button_add;
	GtkWidget  * button_cancel;
	GtkWidget  * scwin;

#ifndef USE_TREE_VIEW
	GQTreeWidget *ctreeroot;
#else
	GtkTreeModel* model;
#endif
	GQTreeWidgetNode *ctree_refresh;

	gboolean edit;
	gboolean hide_status;
	gboolean close_window;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_INPUT_FORM, struct GqInputFormPrivate))

enum {
	SIG_UPDATED,
	N_SIGNALS
};
static guint input_form_signals[N_SIGNALS] = {0};

void refresh_inputform(GqInputForm *form);

static void add_entry_from_formlist(GqInputForm *iform);
static void add_entry_from_formlist_and_select(GqInputForm *iform);
static int add_entry_from_formlist_no_close(int add_context, 
					    GqInputForm *iform);
static void hide_empty_attributes(GtkToggleToolButton *button, GqInputForm *iform);

static void create_new_attr(GtkButton *button, GqInputForm *iform);

static void change_displaytype(GtkWidget* button,
			       GqInputForm *iform,
			       int wanted_dt);
static void build_inputform   (int          error_context,
			       GqInputForm* iform);


GtkWidget*
gq_input_form_new(void)
{
	return g_object_new(GQ_TYPE_INPUT_FORM, NULL);
}

static void
input_form_set_close_window(GqInputForm* self,
			    gboolean     close_window)
{
	if(P(self)->close_window == close_window) {
		return;
	}

	if((P(self)->close_window = close_window)) {
		gtk_widget_show(P(self)->button_close);
	}
	else {
		gtk_widget_hide(P(self)->button_close);
	}
}

GqTreeWidgetNode*
gq_input_form_get_ctree_refresh(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->ctree_refresh;
}

void
gq_input_form_set_ctree_refresh(GqInputForm     * self,
				GqTreeWidgetNode* node)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->ctree_refresh == node) {
		return;
	}

	P(self)->ctree_refresh = node;

	g_object_notify(G_OBJECT(self), "ctree-refresh");
}
#ifndef USE_TREE_VIEW
GqTreeWidget*
gq_input_form_get_ctree_root(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->ctreeroot;
}

void
gq_input_form_set_ctree_root(GqInputForm * self,
			     GqTreeWidget* widget)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));
	g_return_if_fail(!widget || GTK_IS_CTREE(widget));

	if(P(self)->ctreeroot == widget) {
		return;
	}

	P(self)->ctreeroot = NULL;

	g_object_notify(G_OBJECT(self), "ctree-root");
}
#else
GtkTreeModel*
gq_input_form_get_model(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->model;
}

void
gq_input_form_set_model(GqInputForm * self,
			GtkTreeModel* model)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));
	g_return_if_fail(!model || GQ_IS_BROWSER_MODEL(model));

	if(P(self)->model == model) {
		return;
	}

	if(P(self)->model) {
		g_object_unref(P(self)->model);
		P(self)->model = NULL;
	}

	if(model) {
		P(self)->model = g_object_ref(model);
	}

	g_object_notify(G_OBJECT(self), "model");
}
#endif

gchar const*
gq_input_form_get_dn(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->entry ? gq_server_dn_get_dn(P(self)->entry) : NULL;
}

gboolean
gq_input_form_get_editable(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), FALSE);

	return P(self)->edit;
}

void
gq_input_form_set_editable(GqInputForm* self,
			   gboolean     editable)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->edit == editable) {
		return;
	}

	P(self)->edit = editable;

	if(P(self)->edit) {
		gtk_widget_show(P(self)->button_apply);
		gtk_widget_show(P(self)->button_add_new);
		gtk_widget_show(P(self)->button_refresh);

		if(P(self)->close_window) {
			gtk_widget_show(P(self)->button_close);
		}
		/* this will be used as the callback to set on "activate" on
		   inputfields (i.e. hitting return in an entry box). */
		self->activate = mod_entry_from_formlist;
		gtk_widget_hide(P(self)->button_add);
		gtk_widget_hide(P(self)->button_cancel);
		if(GTK_IS_WINDOW(gtk_widget_get_toplevel(P(self)->button_apply))) {
			gtk_widget_grab_default(P(self)->button_apply);
		}
	}
	else {
		gtk_widget_hide(P(self)->button_apply);
		gtk_widget_hide(P(self)->button_add_new);
		gtk_widget_hide(P(self)->button_refresh);
		gtk_widget_hide(P(self)->button_close);
		self->activate = add_entry_from_formlist;
		gtk_widget_show(P(self)->button_add);
		gtk_widget_show(P(self)->button_cancel);
		gtk_widget_grab_default(P(self)->button_add);
	}

	g_object_notify(G_OBJECT(self), "editable");
}

GqServerDn*
gq_input_form_get_entry(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->entry;
}

static void
input_form_set_entry_full(GqInputForm* self,
			  GqServerDn * entry,
			  gboolean     rebuild)
{
	GList* formlist = NULL;

	g_return_if_fail(GQ_IS_INPUT_FORM(self));
	g_return_if_fail(!entry || GQ_IS_SERVER_DN(entry));

	if(entry == P(self)->entry) {
		return;
	}

	if(P(self)->entry) {
		gq_input_form_set_oldlist(self, NULL);
		gq_input_form_set_old_dn(self, NULL);
		g_object_unref(P(self)->entry);
		P(self)->entry = NULL;
	}

	if(entry) {
		P(self)->entry = g_object_ref(entry);
		formlist = formlist_from_entry(0, gq_server_dn_get_server(P(self)->entry),
					       gq_server_dn_get_dn(P(self)->entry), 0);
		gq_input_form_set_old_dn(self, NULL);
	}

	gq_input_form_set_formlist(self, formlist);
	gq_input_form_set_oldlist(self, dup_formlist(formlist));

	if(rebuild && P(self)->table) {
		// when destroying the widget, this gets invalid
		build_inputform(0, self);
	}

	g_object_notify(G_OBJECT(self), "entry");
}

void
gq_input_form_set_entry(GqInputForm* self,
			GqServerDn * entry)
{
	input_form_set_entry_full(self, entry, TRUE);
}

GList*
gq_input_form_get_formlist(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->formlist;
}

void
gq_input_form_set_formlist(GqInputForm* self,
			   GList      * formlist)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->formlist == formlist) {
		return;
	}

	if(P(self)->formlist) {
		free_formlist(P(self)->formlist);
		P(self)->formlist = NULL;
	}

	P(self)->formlist = formlist;

	g_object_notify(G_OBJECT(self), "formlist");
}

gboolean
gq_input_form_get_hide(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), FALSE);

	return P(self)->hide_status;
}

void
gq_input_form_set_hide(GqInputForm* self,
		       gboolean     hide)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->hide_status == hide) {	
		return;
	}

	P(self)->hide_status = hide;

	g_object_notify(G_OBJECT(self), "hide");
}

gchar const*
gq_input_form_get_old_dn(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->olddn;
}

void
gq_input_form_set_old_dn(GqInputForm* self,
			 gchar const* old_dn)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->olddn == old_dn ||
	   (old_dn && P(self)->olddn && !strcmp(old_dn, P(self)->olddn)))
	{
		return;
	}

	if(P(self)->olddn) {
		g_free(P(self)->olddn);
	}

	P(self)->olddn = g_strdup(old_dn);

	g_object_notify(G_OBJECT(self), "old-dn");
}

GList*
gq_input_form_get_oldlist(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->oldlist;
}

void
gq_input_form_set_oldlist(GqInputForm* self,
			  GList      * oldlist)
{
	g_return_if_fail(GQ_IS_INPUT_FORM(self));

	if(P(self)->oldlist == oldlist) {
		return;
	}

	if(P(self)->oldlist) {
		free_formlist(P(self)->oldlist);
		P(self)->oldlist = NULL;
	}

	P(self)->oldlist = oldlist;

	g_object_notify(G_OBJECT(self), "oldlist");
}

GqServer*
gq_input_form_get_server(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return gq_server_dn_get_server(P(self)->entry);
}

GtkWidget*
gq_input_form_get_scrolled_window(GqInputForm const* self)
{
	g_return_val_if_fail(GQ_IS_INPUT_FORM(self), NULL);

	return P(self)->scwin;
}

void save_input_snapshot(int error_context G_GNUC_UNUSED,
			 GqInputForm *iform, const char *state_name)
{
     int hide;
     GtkWidget *w;

     g_assert(iform);
     g_assert(state_name);

     hide = gq_input_form_get_hide(iform);
     state_value_set_int(state_name, "hide-empty-attributes", hide);

     w = P(iform)->scwin;
     if (w) {
	  GtkAdjustment *adj;
	  int percent;

	  /* saving and restoring the "upper" value works around a
	     problem due to the fact that when restoring the inputfrom
	     in the usual case (during mainwin init) the widgets are
	     not yet shown and the upper value of the adjustments are
	     not set yet (= still zero). In order to be able to
	     restore a value it must be between the lower and upper
	     values. If both are zero this cannot work. */
	  adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w));
	  percent = adj->value / adj->upper * 100.0;
	  state_value_set_int(state_name, "scrolled-window-x", percent);
/* 	  state_value_set_int(state_name, "scrolled-window-x-upper", adj->upper); */

	  adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
	  percent = adj->value / adj->upper * 100.0;
	  state_value_set_int(state_name, "scrolled-window-y", percent);
/* 	  state_value_set_int(state_name, "scrolled-window-y-upper", adj->upper); */
     }
}

struct snapshot_info {
     GqInputForm *iform;
     double x, y;
};

static void vp_pos(GtkWidget *w, struct snapshot_info *si)
{
     GtkAdjustment *adj;
     GtkWidget *vp = GTK_BIN(P(si->iform)->scwin)->child;

     adj = gtk_viewport_get_hadjustment(GTK_VIEWPORT(vp));
     gtk_adjustment_set_value(adj, si->x * adj->upper);

     adj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(vp));
     gtk_adjustment_set_value(adj, si->y * adj->upper);

     if (w) {
	  g_signal_handlers_disconnect_by_func(w, 
					G_CALLBACK(vp_pos), si);
     }

     g_free(si);
}


void restore_input_snapshot(int error_context G_GNUC_UNUSED,
			    GqInputForm *iform, const char *state_name)
{
     int hide;
     GtkWidget *w;

     g_assert(iform);
     g_assert(state_name);

     hide = state_value_get_int(state_name, "hide-empty-attributes", 0);

     if (iform->hide_attr_button) {
	  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(iform->hide_attr_button), hide);
     }
     w = P(iform)->scwin;
     if (w) {
	  GtkAdjustment *adj;
	  int x, y;
	  struct snapshot_info *si;

	  w = GTK_BIN(w)->child;
	  adj = gtk_viewport_get_hadjustment(GTK_VIEWPORT(w));
	  x = state_value_get_int(state_name, "scrolled-window-x", 0);

	  adj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(w));
	  y = state_value_get_int(state_name, "scrolled-window-y", 0);

	  si = g_malloc0(sizeof(struct snapshot_info));
	  si->iform = iform;
	  si->x = x / 100.0;
	  si->y = y / 100.0;

	  g_signal_connect(w, "realize", 
			     G_CALLBACK(vp_pos), si);
     }
}

void create_form_window(GqInputForm *iform)
{
     GtkWidget *edit_window, *vbox;

	g_return_if_fail(GQ_IS_INPUT_FORM(iform));

     edit_window = stateful_gtk_window_new(GTK_WINDOW_TOPLEVEL,
					   "inputform", 500, 450);
     gtk_window_set_title(GTK_WINDOW(edit_window), _("New entry"));

     gtk_widget_show(edit_window);
     g_signal_connect_swapped(edit_window, "key_press_event",
                               G_CALLBACK(close_on_esc),
                               edit_window);

     vbox = GTK_WIDGET(iform);
     gtk_container_border_width(GTK_CONTAINER(vbox), 5);
     gtk_container_add(GTK_CONTAINER(edit_window), vbox);
     gtk_widget_show(vbox);

     iform->parent_window = edit_window;
}

static void linebutton_clicked(GtkToolButton *button, GqInputForm *iform)
{
     change_displaytype(GTK_WIDGET(button), iform, DISPLAYTYPE_ENTRY);
}

static void textareabutton_clicked(GtkToolButton *button, GqInputForm *iform)
{
     change_displaytype(GTK_WIDGET(button), iform, DISPLAYTYPE_TEXT);
}

GdkWindow *get_any_gdk_window(GtkWidget *w)
{
     while (w) {
	  if (GTK_WIDGET(w)->window) {
	       return GTK_WIDGET(w)->window;
	  }
	  w = GTK_WIDGET(w)->parent;
     }
     g_error("No GDK window in widget hierarchy");
     return NULL;
}

static void
change_dt(GtkWidget *menu_item, gpointer data)
{
	GqFormfill* form = GQ_FORMFILL(gtk_object_get_data(GTK_OBJECT(menu_item), "formfill"));
	gq_formfill_change_display_type(form, GPOINTER_TO_INT(data));
}


static void schema_show_attr(GtkMenuItem *menuitem, GqFormfill *form)
{
     struct server_schema *ss = NULL;
     int error_context;

     if (gq_formfill_get_server(form) == NULL) return;

     error_context = error_new_context(_("Showing schema information"),
				       GTK_WIDGET(menuitem));

     ss = get_schema(error_context, gq_formfill_get_server(form));

     popup_detail(SCHEMA_TYPE_AT, gq_formfill_get_server(form), 
		  find_canonical_at_by_at(ss, gq_formfill_get_attrname(form)));

     error_flush(error_context);
}

static void remove_dt_default_for_attr(GtkMenuItem *menuitem,
				       GqFormfill *form)
{
     gpointer okey;
     gpointer val;
     struct attr_settings *as = lookup_attr_settings(gq_formfill_get_attrname(form));
     int old_ddt;
     int do_free = 0;

     if (!as) return;

     old_ddt = as->defaultDT;

     as->defaultDT = -1;
     if (is_default_attr_settings(as)) {
	  do_free = 1;
     }

     if (do_free) {
	  if (g_hash_table_lookup_extended(config->attrs,
					   as->name,
					   &okey, &val)) {
	       g_hash_table_remove(config->attrs, okey);
	  }
     }

     if (save_config(GTK_WIDGET(menuitem))) {
	  if (do_free) {
	       if (okey) g_free(okey);
	       if (val)  free_attr_settings(val);
	  }
     } else {
	  as->defaultDT = old_ddt;

	  if (do_free) {
	       /* save failed, undo changes - re-insert into hash */
	       g_hash_table_insert(config->attrs, 
				   okey, val);
	  }
     }
}

static void make_dt_default_for_attr(GtkMenuItem *menuitem, 
				     GqFormfill *form)
{
     gpointer nkey = NULL;

     struct attr_settings *as = lookup_attr_settings(gq_formfill_get_attrname(form));
     int old_ddt = -1;
     int is_new = 0;

     if (!as) {
	  char *t;
	  as = new_attr_settings();
	  as->name = g_strdup(gq_formfill_get_attrname(form));
	  for (t = as->name ; *t ; t++) *t = tolower(*t);
	  is_new = 1;

	  g_hash_table_insert(config->attrs, 
			      nkey = g_strdup(as->name),
			      as);
     }

     old_ddt = as->defaultDT;
     as->defaultDT = get_dt_from_handler(gq_formfill_get_dt_handler(form));

     if (!save_config(GTK_WIDGET(menuitem))) {
	  /* save failed, undo changes */
	  if (is_new) {
	       g_hash_table_remove(config->attrs, nkey);
	       free_attr_settings(as);
	       g_free(nkey);
	  } else {
	       as->defaultDT = old_ddt;
	  }
     }
}

/* quick and dirty callback data */
struct iform_form {
     GqInputForm *iform;
     GqFormfill  *form;
};

static struct iform_form *new_iform_form() {
     struct iform_form *i = g_malloc0(sizeof(struct iform_form));
     return i;
}

static void free_iform_form(struct iform_form *iff) {
     g_free(iff);
}

static void remove_user_friendly_for_attr(GtkMenuItem *menuitem,
					  struct iform_form *iff)
{
     gpointer okey;
     gpointer val;
     struct attr_settings *as = lookup_attr_settings(gq_formfill_get_attrname(iff->form));
     char *old_uf = NULL;
     int do_free = 0;

     if (!as) return;

     old_uf = as->user_friendly;
     as->user_friendly = NULL;

     if (is_default_attr_settings(as)) {
	  do_free = 1;
     }

     if (do_free) {
	  if (g_hash_table_lookup_extended(config->attrs,
					   as->name,
					   &okey, &val)) {
	       g_hash_table_remove(config->attrs, okey);
	  }
     }

     if (save_config(GTK_WIDGET(menuitem))) {
	  if (do_free) {
	       g_free(okey);
	       g_free(old_uf);
	       if (val) free_attr_settings(val);
	  }
	  refresh_inputform(iff->iform);
     } else {
	  as->user_friendly = old_uf;

	  if (do_free) {
	       /* save failed, undo changes - re-insert into hash */
	       g_hash_table_insert(config->attrs, 
				   okey, val);
	  }
     }
}

static void make_user_friendly_for_attr(GtkMenuItem *menuitem, 
					struct iform_form *iff)
{
     gchar *ret = NULL;
     struct attr_settings *as = lookup_attr_settings(gq_formfill_get_attrname(iff->form));
     GString *msg = g_string_sized_new(150);

     if (as) ret = as->user_friendly;

     g_string_printf(msg, _("User friendly name for LDAP attribute '%s'"),
		     gq_formfill_get_attrname(iff->form));


     if (query_popup(msg->str, &ret, FALSE, GTK_WIDGET(menuitem))) {
	  gpointer nkey = NULL;
	  
	  char *old_uf = NULL;
	  int is_new = 0;
	  
	  if (!as) {
	       char *t;

	       is_new = 1;

	       as = new_attr_settings();
	       as->name = g_strdup(gq_formfill_get_attrname(iff->form));
	       for (t = as->name ; *t ; t++) *t = tolower(*t);
	       
	       g_hash_table_insert(config->attrs, 
				   nkey = g_strdup(as->name),
				   as);
	  }
	  
	  old_uf = as->user_friendly;
	  if ((ret && strlen(ret) == 0)  ||
	      (strcmp(ret, gq_formfill_get_attrname(iff->form)) == 0)) {
	       g_free(ret);
	       ret = NULL;
	  }

	  as->user_friendly = ret;
	  
	  if (!save_config(GTK_WIDGET(menuitem))) {
	       /* save failed, undo changes */
	       if (is_new) {
		    g_hash_table_remove(config->attrs, nkey);
		    free_attr_settings(as);
		    g_free(nkey);
	       } else {
		    as->user_friendly = old_uf;
	       }
	  } else {
	       g_free(old_uf);
	       ret = NULL;
	       refresh_inputform(iff->iform);
	  }
     }
     g_free(ret);
     g_string_free(msg, TRUE);
}


static gboolean
widget_button_press(struct iform_form* iff,
		    GdkEventButton   * event)
{
     if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
	  GtkWidget *menu_item, *menu, *label, *submenu;
	  GtkWidget *root_menu;
	  GList *dt_list;
	  char buf[40];
	  struct attr_settings *as;

	  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);

	  g_snprintf(buf, sizeof(buf), _("Attribute %s"), gq_formfill_get_attrname(iff->form));

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

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

	  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);

	  menu_item = gtk_menu_item_new_with_label(_("Schema information"));
/*	  gtk_object_set_data(GTK_OBJECT(menu_item), "formfill", form); */
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  g_signal_connect(menu_item, "activate",
			     G_CALLBACK(schema_show_attr),
			     iff->form);
	  gtk_widget_show(menu_item);

	  /* Change display type submenu */
	  menu_item = gtk_menu_item_new_with_label(_("Change display type"));
	  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);

	  label = gtk_menu_item_new_with_label(_("Select display type"));
	  gtk_widget_set_sensitive(label, FALSE);
	  gtk_widget_show(label);
	  gtk_menu_append(GTK_MENU(submenu), label);

	  menu_item = gtk_separator_menu_item_new();

	  gtk_menu_append(GTK_MENU(submenu), menu_item);
	  gtk_widget_set_sensitive(menu_item, FALSE);
	  gtk_widget_show(menu_item);

	  for(dt_list = get_selectable_displaytypes(); dt_list; dt_list = dt_list->next) {
	       GQTypeDisplayClass *h = g_type_class_ref(GPOINTER_TO_INT(dt_list->data));

	       if(!h) {
		       g_warning("Couldn't get class for %s", g_type_name(GPOINTER_TO_INT(dt_list->data)));
		       continue;
	       }

	       menu_item = gtk_menu_item_new_with_label(h->name);
	       gtk_object_set_data(GTK_OBJECT(menu_item), "formfill",
				   iff->form);
/*	       gtk_object_set_data(GTK_OBJECT(menu_item), "entry", entry); */
	       gtk_menu_append(GTK_MENU(submenu), menu_item);
	       g_signal_connect(menu_item, "activate",
				  G_CALLBACK(change_dt),
				  dt_list->data);
	       gtk_widget_show(menu_item);
	       g_type_class_unref(h);
	  }

	  as = lookup_attr_settings(gq_formfill_get_attrname(iff->form));

	  menu_item = gtk_menu_item_new_with_label(_("Make current display type the default for this attribute"));
/*	  gtk_object_set_data(GTK_OBJECT(menu_item), "formfill", form); */
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  g_signal_connect(menu_item, "activate",
			     G_CALLBACK(make_dt_default_for_attr),
			     iff->form);
	  gtk_widget_show(menu_item);

	  menu_item = gtk_menu_item_new_with_label(_("Remove user-defined default display type setting"));
/*	  gtk_object_set_data(GTK_OBJECT(menu_item), "formfill", form); */
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  g_signal_connect(menu_item, "activate",
			     G_CALLBACK(remove_dt_default_for_attr),
			     iff->form);
	  gtk_widget_show(menu_item);

	  gtk_widget_set_sensitive(menu_item,
				   as != NULL && as->defaultDT != -1);

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

	  menu_item = gtk_menu_item_new_with_label(_("Set user-friendly name"));
	  gtk_menu_append(GTK_MENU(menu), menu_item);

	  g_signal_connect(menu_item, "activate",
			     G_CALLBACK(make_user_friendly_for_attr),
			     iff);
	  gtk_widget_show(menu_item);

	  menu_item = gtk_menu_item_new_with_label(_("Clear user-friendly name"));
	  gtk_menu_append(GTK_MENU(menu), menu_item);
	  g_signal_connect(menu_item, "activate",
			     G_CALLBACK(remove_user_friendly_for_attr),
			     iff);
	  gtk_widget_show(menu_item);

	  gtk_widget_set_sensitive(menu_item,
				   as != NULL && as->user_friendly);

	  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
			 event->button, event->time);

	  return(TRUE);
     }
     return FALSE;
}


static void
dn_changed(GtkEditable *editable,
	   GqInputForm *iform)
{
#warning "FIXME: this could be gtk_entry_get_text() to not dup the memory"
	if(P(iform)->button_add_new) {
		gchar      * val = gtk_editable_get_chars(editable, 0, -1);
		gchar const* dn  = gq_input_form_get_dn(iform);
		gtk_widget_set_sensitive(P(iform)->button_add_new, strcmp(dn, val) != 0);
		g_free(val);
	}
}


void
build_or_update_inputform(int          error_context,
			  GqInputForm* iform,
			  gboolean     build)
{
     GList *values, *formlist, *widgetlist;

     GtkWidget *vbox;
     GtkWidget *widget = NULL;
     GqFormfill *ff;
     int row, row_arrow, vcnt, currcnt;
     GByteArray *gb;

	g_return_if_fail(GTK_IS_WIDGET(P(iform)->table));

	if(gq_input_form_get_dn(iform)) {
		gtk_entry_set_text(GTK_ENTRY(iform->dn_widget), gq_input_form_get_dn(iform));
	}

	if(build) {
		for(widgetlist = gtk_container_children(GTK_CONTAINER(P(iform)->table));
		    widgetlist;
		    widgetlist = g_list_delete_link(widgetlist, widgetlist))
		{
			if(widgetlist->data != P(iform)->dn_label &&
			   widgetlist->data != iform->dn_widget)
			{
				gtk_container_remove(GTK_CONTAINER(P(iform)->table),
						     widgetlist->data);
			}
		}
	}

     row = 1;
     /* remember that form->num_inputfields and g_list_length(values) may not
      * be the same as a result of multiple input widgets */
     for(formlist = gq_input_form_get_formlist(iform); formlist; formlist = formlist->next) {
	  ff = GQ_FORMFILL(formlist->data);

	  if (build || gq_formfill_get_label(ff) == NULL) {
	       GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));
	       /* attribute name */

	       if (config->browse_use_user_friendly) {
		    gq_formfill_set_label(ff, gtk_label_new(human_readable_attrname(gq_formfill_get_attrname(ff))));
	       } else {
		    gq_formfill_set_label(ff, gtk_label_new(gq_formfill_get_attrname(ff)));
	       }

	       gtk_misc_set_alignment(GTK_MISC(gq_formfill_get_label(ff)),
				      LABEL_JUSTIFICATION, .5);
	       gtk_widget_show(gq_formfill_get_label(ff));

	       gq_formfill_set_event_box(ff, gtk_event_box_new());
	       gtk_container_add(GTK_CONTAINER(gq_formfill_get_event_box(ff)), gq_formfill_get_label(ff));

	       if (klass && klass->selectable) {
		    struct iform_form *iff = new_iform_form();
		    iff->iform = iform;
		    iff->form  = ff;

		    g_signal_connect_swapped(gq_formfill_get_event_box(ff), "button_press_event",
				             G_CALLBACK(widget_button_press), iff);
		    gtk_object_set_data_full(GTK_OBJECT(gq_formfill_get_event_box(ff)),
					     "cb-data", iff,
					     (GtkDestroyNotify)free_iform_form);
	       }

	       gtk_widget_show(gq_formfill_get_event_box(ff));
	       gtk_table_attach(GTK_TABLE(P(iform)->table), gq_formfill_get_event_box(ff),
				0, 1, row, row + 1,
				GTK_FILL,
				GTK_FILL,
				0, 0);

	       row_arrow = row;

	       vbox = gtk_vbox_new(FALSE, 1);
	       gq_formfill_set_vbox(ff, vbox);
	       gtk_widget_show(vbox);

	       gtk_table_attach(GTK_TABLE(P(iform)->table), vbox,
				1, 2, row, row + 1,
				GTK_FILL|GTK_EXPAND,
				GTK_FILL|GTK_EXPAND, 0, 0);
	       g_type_class_unref(klass);
	  }

	  gtk_widget_restore_default_style(gq_formfill_get_label(ff));

	  if(gq_formfill_get_flags(ff) & FLAG_NOT_IN_SCHEMA) {
		GdkColor color = {0, 0xa0a0, 0x0000, 0x0000};
		if(!gtk_style_lookup_color(gtk_widget_get_style(GTK_WIDGET(iform)), "additional-color", &color)) {
			gdk_color_alloc(gdk_screen_get_default_colormap(gtk_widget_get_screen(GTK_WIDGET(iform))), &color);
		}
	       gtk_widget_modify_fg(gq_formfill_get_label(ff), GTK_STATE_NORMAL, &color);
	  }
	  if(gq_formfill_get_flags(ff) & FLAG_MUST_IN_SCHEMA) {
		GdkColor color = {0, 0x2020, 0x4a4a, 0x8787};
		if(!gtk_style_lookup_color(gtk_widget_get_style(GTK_WIDGET(iform)), "required-color", &color)) {
			gdk_color_alloc(gdk_screen_get_default_colormap(gtk_widget_get_screen(GTK_WIDGET(iform))), &color);
		}
		gtk_widget_modify_fg(gq_formfill_get_label(ff), GTK_STATE_NORMAL, &color);
	  }
	  if(gq_formfill_get_flags(ff) & FLAG_DEL_ME) {
		GdkColor color = {0, 0x8f8f, 0x5959, 0x0202};
		if(!gtk_style_lookup_color(gtk_widget_get_style(GTK_WIDGET(iform)), "delete-color", &color)) {
			gdk_color_alloc(gdk_screen_get_default_colormap(gtk_widget_get_screen(GTK_WIDGET(iform))), &color);
		}
	       gtk_widget_modify_fg(gq_formfill_get_label(ff), GTK_STATE_NORMAL, &color);
	  }
	  if(gq_formfill_get_flags(ff) & FLAG_EXTENSIBLE_OBJECT_ATTR) {
		GdkColor color = {0, 0x5c5c, 0x3535, 0x6666};
		if(!gtk_style_lookup_color(gtk_widget_get_style(GTK_WIDGET(iform)), "extensible-color", &color)) {
			gdk_color_alloc(gdk_screen_get_default_colormap(gtk_widget_get_screen(GTK_WIDGET(iform))), &color);
		}
	       gtk_widget_modify_fg(gq_formfill_get_label(ff), GTK_STATE_NORMAL, &color);
	  }

	  widgetlist = gq_formfill_get_widgets(ff);
	  currcnt = g_list_length(widgetlist);
	  values = gq_formfill_get_values(ff);

	  for(vcnt = 0; vcnt < gq_formfill_get_n_inputfields(ff); vcnt++) {
	       GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));
	       gb = NULL;
	       if (values) {
		    gb = (GByteArray*) values->data;
	       }

/*  if (gb) { char *z = g_malloc(gb->len+1); */
/*    strncpy(z, gb->data, gb->len); */
/*    z[gb->len] = 0; */
/*    printf("attr=%s data=%s\n", ff->attrname, z); */
/*    g_free(z); */
/*  } */
	       if (klass && klass->get_widget) {
		    if (vcnt >= currcnt) {
			 widget =
			      klass->get_widget(error_context,
							 ff, gb,
							 G_CALLBACK(iform->activate),
							 iform);

			 gtk_widget_show(widget);

			 gtk_object_set_data(GTK_OBJECT(widget),
					     "formfill", ff);

			 gtk_box_pack_start(GTK_BOX(gq_formfill_get_vbox(ff)), widget, TRUE, TRUE, 0);
			 if(ff == iform->focusform)
			      gtk_widget_grab_focus(widget);

			 gq_formfill_add_widget(ff, widget);

			 if (gq_formfill_get_flags(ff) & FLAG_NO_USER_MOD) {
			      gtk_widget_set_sensitive(widget, 0);
			 }
		    } else {
			 widget = widgetlist ? widgetlist->data : NULL;
		    }
	       }

	       if (values) values = values->next;
	       if (widgetlist) widgetlist = widgetlist->next;

	       g_type_class_unref(klass);
	  }
	  
	  row_arrow = row++;

	  if ((build || gq_formfill_get_morebutton(ff) == NULL) &&
	      ((gq_formfill_get_flags(ff) & FLAG_SINGLE_VALUE) == 0)) {
	       GtkWidget* align = gtk_alignment_new(0.5, 1, 0, 0);
	       gtk_widget_show(align);

	       gq_formfill_set_morebutton(ff, gq_new_arrowbutton(iform));
	       gtk_object_set_data(GTK_OBJECT(gq_formfill_get_morebutton(ff)), "formfill", ff);
	       gtk_container_add(GTK_CONTAINER(align), gq_formfill_get_morebutton(ff));

	       gtk_table_attach(GTK_TABLE(P(iform)->table), align,
				2, 3, row_arrow, row_arrow + 1,
				GTK_SHRINK, GTK_FILL, 0, 0);

	       if(G_UNLIKELY(!iform->tooltips)) {
			iform->tooltips = gtk_tooltips_new();
	       }

	       gtk_tooltips_set_tip(iform->tooltips,
				    gq_formfill_get_morebutton(ff),
				    _("Extend Attribute"),
				    _("Extend this Attribute by appending one more field"));

	       if (gq_formfill_get_flags(ff) & FLAG_NO_USER_MOD) {
		    gtk_widget_set_sensitive(gq_formfill_get_morebutton(ff), 0);
	       }
	  }
     }

     /* restore the hide-status from a previous LDAP object... */
#warning "FIXME: shouldn't this be done in set_hide()"
     set_hide_empty_attributes(gq_input_form_get_hide(iform), iform);

	g_signal_emit(iform, input_form_signals[SIG_UPDATED], 0);
}


static void
build_inputform(int error_context, GqInputForm *iform)
{
	build_or_update_inputform(error_context, iform, TRUE);
}


void refresh_inputform(GqInputForm *iform)
{
     GList *oldlist, *newlist;
     double frac_x = 0.0, frac_y = 0.0;
     GtkWidget *w;
     int error_context;

     w = P(iform)->scwin;
     if (w) {
	  GtkAdjustment *adj;

	  w = GTK_BIN(w)->child;
	  adj = gtk_viewport_get_hadjustment(GTK_VIEWPORT(w));
	  frac_x = adj->value / adj->upper;

	  adj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(w));
	  frac_y = adj->value / adj->upper;
     }

	gq_input_form_set_oldlist(iform, NULL);
	gq_input_form_set_formlist(iform, NULL);

     error_context = error_new_context(_("Refreshing entry"), 
				       iform->parent_window);

	oldlist = formlist_from_entry(error_context,
				      gq_input_form_get_server(iform),
				      gq_input_form_get_dn(iform), 0);

     if(oldlist) {
	  newlist = dup_formlist(oldlist);
	  gq_input_form_set_formlist(iform, newlist);
	  gq_input_form_set_oldlist(iform, oldlist);

	  build_inputform(error_context, iform);
     }

     /* let the main loop run to get all the geometry sorted out for
	the new inputform, afterwards restore the previously set
	viewport position */
	gq_main_loop_flush();

     if (P(iform)->scwin) {
	  struct snapshot_info *si;

	  si = g_malloc0(sizeof(struct snapshot_info));
	  si->iform = iform;
	  si->x = frac_x;
	  si->y = frac_y;

	  vp_pos(NULL, si); 
     }

     error_flush(error_context);
}

#warning "FIXME: this should be a GqServerDn"
void edit_entry(GqServer *server, const char *dn)
{
     GList *oldlist;
     GtkWidget *vbox;
     GqInputForm *iform;
     GtkWidget* edit_window;
     int error_context;

     edit_window = stateful_gtk_window_new(GTK_WINDOW_TOPLEVEL,
					   "entry-window", 500, 450); 

     gtk_window_set_title(GTK_WINDOW(edit_window), dn);
     
     gtk_widget_show(edit_window);
     g_signal_connect_swapped(edit_window, "key_press_event",
                               G_CALLBACK(close_on_esc),
                               edit_window);

     error_context = error_new_context("", edit_window);

	iform = GQ_INPUT_FORM(gq_input_form_new());
	vbox = GTK_WIDGET(iform);
     gtk_container_border_width(GTK_CONTAINER(iform), 5);
     gtk_container_add(GTK_CONTAINER(edit_window), vbox);
     gtk_widget_show(vbox);

     iform->parent_window = edit_window;

     oldlist = formlist_from_entry(error_context, server, dn, 0);

	if(oldlist) {
		GqServerDn* entry = gq_server_dn_new(dn, server);
		gq_input_form_set_editable(iform, TRUE);
		input_form_set_close_window(iform, TRUE);
		gq_input_form_set_entry(iform, entry);
		gq_input_form_set_old_dn(iform, dn);
		gq_input_form_set_oldlist(iform, oldlist);
	}

	error_flush(error_context);
}

#warning "FIXME: this should be a GqServerDn"
void
new_from_entry(GqServer   * server,
	       gchar const* dn)
{
	GqInputForm* iform;
	GqServerDn * entry;

	entry = gq_server_dn_new(dn, server);

	iform = GQ_INPUT_FORM(gq_input_form_new());
	gq_input_form_set_entry(iform, entry);

	create_form_window(iform);
}


static char*
get_new_dn(GqInputForm *iform)
{
     char *content, *content_enc;

     if(iform->dn_widget) {
	  content = gtk_editable_get_chars(GTK_EDITABLE(iform->dn_widget), 0, -1);
	  content_enc = encoded_string(content);
	  g_free(content);

	  return content_enc;
     }

     return NULL;
}


/*
 * update formlist from on-screen table
 */
void
update_formlist(GqInputForm *iform)
{
     GList *formlist, *fl, *children = NULL;
     GtkWidget *child;
     GqFormfill *ff;
     int displaytype;

     formlist = gq_input_form_get_formlist(iform);
     if(formlist == NULL)
	  return;

     /* walk the formlist and check the inputList for each */
     /* clear any values in formlist, they'll be overwritten shortly
	with more authorative (newer) data anyway */

     for (fl = formlist ; fl ; fl = fl->next ) {
	  ff = GQ_FORMFILL(fl->data);
	  free_formfill_values(ff);

	  displaytype = gq_formfill_get_display_type(ff);

	  for (children = gq_formfill_get_widgets(ff); children ;
	       children = children->next) {
	       child = GTK_WIDGET(children->data);

	       if(ff && displaytype) {
		    GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));
		    GByteArray *ndata = NULL;

#warning "FIXME: use a function and not directly the pointer"
		    if (klass && klass->get_data) {
			    ndata = klass->get_data(ff, child);
			    /* don't bother adding in empty fields */
			    if(ndata) {
				 gq_formfill_add_value(ff, ndata);
			    }
		    }
		    g_type_class_unref(klass);
	       }
	  }
     }

	/* take care of the dn input widget */
	if(iform->dn_widget) {
		gchar const* dn = gq_input_form_get_dn(iform);
		gchar const* new_dn = gtk_entry_get_text(GTK_ENTRY(iform->dn_widget));

		if(dn != new_dn && dn && new_dn && strcmp(dn, new_dn)) {
			GqServerDn* entry;
			gchar *content_enc = encoded_string(new_dn);
			entry = gq_server_dn_new(content_enc, gq_server_dn_get_server(P(iform)->entry));
			input_form_set_entry_full(iform, entry, FALSE);
			g_free(content_enc);
		}
	}
}


void clear_table(GqInputForm *iform)
{
     GtkWidget *vbox, *old_table, *new_table;
     GList *formlist;
     GqFormfill *ff;

#warning "FIXME: HACKS HACKS HACKS, DIRTY HACKS"
     vbox = P(iform)->table->parent;
     old_table = P(iform)->table;
     formlist = gq_input_form_get_formlist(iform);

     gtk_container_remove(GTK_CONTAINER(vbox), old_table);
/*       gtk_widget_destroy(old_table); */

     /* table inside vbox, will self-expand */
     new_table = gtk_table_new(3, 2, FALSE);
     gtk_container_border_width(GTK_CONTAINER(new_table), 5);
     gtk_table_set_row_spacings(GTK_TABLE(new_table), 1);
     gtk_table_set_col_spacings(GTK_TABLE(new_table), 10);
     gtk_widget_show(new_table);
     gtk_box_pack_start(GTK_BOX(vbox), new_table, FALSE, TRUE, 0);
     P(iform)->table = new_table;

     /* should use destroy signals on the widgets to remove widget
        references */

     for ( ; formlist ; formlist = formlist->next ) {
	  ff = GQ_FORMFILL(formlist->data);
	  gq_formfill_set_label(ff, NULL);
	  gq_formfill_set_morebutton(ff, NULL);
	  gq_formfill_set_vbox(ff, NULL);
	  gq_formfill_clear_widgets(ff);
     }
     
}


static void add_entry_from_formlist(GqInputForm *iform)
{
     int ctx = error_new_context(_("Adding entry"), iform->parent_window);
     if (add_entry_from_formlist_no_close(ctx, iform)) {
	  destroy_editwindow(iform);
     }
     error_flush(ctx);
}


static void add_entry_from_formlist_and_select(GqInputForm *iform)
{
     int rc;
     char *dn;

     int ctx = error_new_context(_("Adding entry"), iform->parent_window);

     dn = get_new_dn(iform);
     rc = add_entry_from_formlist_no_close(ctx, iform);
#ifndef USE_TREE_VIEW
     if (rc && dn && P(iform)->ctreeroot) {
	  show_dn(ctx, P(iform)->ctreeroot, NULL, dn, TRUE);
     }
#else
     if(rc && dn && P(iform)->model) {
	     show_dn(ctx, P(iform)->model, NULL, NULL, dn, TRUE);
     }
#endif
     if (dn) g_free(dn);

     error_flush(ctx);
}


static int add_entry_from_formlist_no_close(int add_context,
					    GqInputForm *iform)
{
     LDAP *ld;
     LDAPMod **mods, *mod;
     GQTreeWidget *ctree;
     GQTreeWidgetNode *node;
     GList *formlist;
     GqServer *server;
     GqFormfill *ff;
     GqTab *tab;
     int res, cmod;
     int i;
     char const* dn;
     char *parentdn, **rdn;
     LDAPControl c, *ctrls[2] = { NULL, NULL } ;

     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;

     formlist = gq_input_form_get_formlist(iform);
     g_assert(formlist);

     /* handle impossible errors first - BTW: no need for I18N here*/
     if(!formlist) {
	  error_push(add_context,
		     "Hmmm, no formlist to build entry!");
	  return 0;
     }

     server = gq_input_form_get_server(iform);
     g_assert(server);

     if(!server) {
	  error_push(add_context,
		     "Hmmm, no server!");
	  return 0;
     }

     update_formlist(iform);

     dn = gq_input_form_get_dn(iform);
     if(!dn || dn[0] == 0) {
#warning "FIXME: recognize this earlier"
	  error_push(add_context, _("You must enter a DN for a new entry."));
	  return 0;
     }

     set_busycursor();

     ld = open_connection(add_context, server);
     if(!ld) {
	  set_normalcursor();
	  return 0;
     }

     /* build LDAPMod from formlist */
     cmod = 0;
     mods = g_malloc(sizeof(void *) * (g_list_length(formlist) + 1));
     while(formlist) {
	  ff = GQ_FORMFILL(formlist->data);

	  if (! (gq_formfill_get_flags(ff) & FLAG_NO_USER_MOD)) {
	       if (gq_formfill_get_values(ff)) {
		    GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));
		    mod = klass->buildLDAPMod(ff, LDAP_MOD_ADD, gq_formfill_get_values(ff));
		    mods[cmod++] = mod;
		    g_type_class_unref(klass);
	       }
	  }

	  formlist = formlist->next;
     }
     mods[cmod] = NULL;

     res = ldap_add_ext_s(ld, dn, mods, ctrls, NULL);

     if(res == LDAP_NOT_SUPPORTED) {
	  res = ldap_add_s(ld, dn, mods);
     }

     ldap_mods_free(mods, 1);

     if (res == LDAP_SERVER_DOWN) {
	  server->server_down++;
     }
     if (res == LDAP_REFERRAL) {
	  /* FIXME */
     }
     if(res != LDAP_SUCCESS) {
	  error_push(add_context, _("Error adding new entry '%1$s': '%2$s'"),
		     dn,
		     ldap_err2string(res));
	  push_ldap_addl_error(ld, add_context);
	  set_normalcursor();
	  close_connection(server, FALSE);
	  return 0;
     }

     /* Walk the list of browser tabs and refresh the parent
        node of the newly added entry to give visual feedback */

     /* construct parent DN */


     /* don't need the RDN of the current entry */
     parentdn = g_malloc(strlen(dn));
     parentdn[0] = 0;
     rdn = gq_ldap_explode_dn(dn, 0);

     /* FIXME: think about using openldap 2.1 specific stuff if
        available... */
     for(i = 1; rdn[i]; i++) {
	  if (parentdn[0]) strcat(parentdn, ","); /* Flawfinder: ignore */
	  strcat(parentdn, rdn[i]);		  /* Flawfinder: ignore */
     }
     gq_exploded_free(rdn);

     for( i = 0 ; (tab = mainwin_get_tab_nth(&mainwin, i)) != NULL ; i++) {
/*      for (tabs = g_list_first(mainwin.tablist) ; tabs ;  */
/*	  tabs = g_list_next(tabs)) { */
/*	  tab = GQ_TAB(tabs->data); */
	  if(GQ_IS_TAB_BROWSE(tab)) {
	       ctree = GQ_TAB_BROWSE(tab)->ctreeroot;
	       if(ctree == NULL)
		    continue;

	       node = tree_node_from_server_dn(ctree, server, parentdn);
	       if(node == NULL)
		    continue;

	       /* refresh the parent DN node... */
	       refresh_subtree(add_context, ctree, node);
	  }
     }

     g_free(parentdn);

     set_normalcursor();

     close_connection(server, FALSE);

     return 1;
}


void
mod_entry_from_formlist(GqInputForm *iform)
{
     GList *newlist, *oldlist;
     LDAPMod **mods;
     LDAP *ld;
	GqServerDn* entry = NULL;
     int mod_context, res;
	gchar      * olddn = NULL;
     GQTreeWidgetNode *node = NULL;
     int do_modrdn = 0;
     int error = 0;

     mod_context = error_new_context(_("Problem modifying entry"), 
				     iform->parent_window);

     /* store  olddn to use with message later on - in case we change the DN */
     olddn = g_strdup(gq_input_form_get_old_dn(iform));

     /* obtain all needed stuff from the iform ASAP, as it might be
	destroyed during refresh_subtree (through reselection of a
	CTreeRow and the selection callback). */

	entry     = g_object_ref(gq_input_form_get_entry(iform));
	oldlist   = dup_formlist(gq_input_form_get_oldlist(iform));
	newlist   = gq_input_form_get_formlist(iform);

	update_formlist(iform);

	do_modrdn = olddn && gq_server_dn_get_dn(entry) && strcasecmp(olddn, gq_server_dn_get_dn(entry));

     if( (ld = open_connection(mod_context, gq_server_dn_get_server(entry))) == NULL) {
	  goto done;
     }

     if (do_modrdn) {
	  int rc;

#if HAVE_LDAP_CLIENT_CACHE
	  ldap_uncache_entry(ld, olddn);
#endif
	  
#ifndef USE_TREE_VIEW
	  /* find node now, olddn will change during change_rdn */
	  if(gq_input_form_get_ctree_refresh(iform)) {
/*  	       printf("refresh %s to become %s\n", olddn, dn); */
	       node = tree_node_from_server_dn(gq_input_form_get_ctree_root(iform),
			       		       gq_server_dn_get_server(entry), olddn);
	  }	       
#endif

	  if((rc = change_rdn(iform, mod_context)) != 0) {
/*  	       printf("error %d\n", rc); */

	       error = 1;
	       goto done;
	  }
     }

     mods = formdiff_to_ldapmod(oldlist, newlist);
	if(mods != NULL && mods[0] != NULL) {
	  LDAPControl ct, *ctrls[2] = { NULL, NULL } ;
	  ct.ldctl_oid		= LDAP_CONTROL_MANAGEDSAIT;
	  ct.ldctl_value.bv_val	= NULL;
	  ct.ldctl_value.bv_len	= 0;
	  ct.ldctl_iscritical	= 1;
	  
	  ctrls[0] = &ct;

	  res = ldap_modify_ext_s(ld, gq_server_dn_get_dn(entry), mods, ctrls, NULL);

	  if(res == LDAP_NOT_SUPPORTED) {
	       res = ldap_modify_s(ld, gq_server_dn_get_dn(entry), mods);
	  }

	  if (res == LDAP_SERVER_DOWN) {
	       gq_server_dn_get_server(entry)->server_down++;
	  }
	  
	  if(res == LDAP_SUCCESS) {
		gq_input_form_set_oldlist(iform, dup_formlist(newlist));
	  } else {
	       error_push(mod_context, _("Error modifying entry '%1$s': %2$s"),
			  gq_server_dn_get_dn(entry),
			  ldap_err2string(res));
	       push_ldap_addl_error(ld, mod_context);
	  } 
	  ldap_mods_free(mods, 1);
	}


     statusbar_msg(_("Modified %s"), olddn);
     /* free memory */

	if(P(iform)->close_window) {
	  destroy_editwindow(iform);
	}

#if HAVE_LDAP_CLIENT_CACHE
     ldap_uncache_entry(ld, gq_server_dn_get_dn(entry));
#endif

 done:
	free_formlist(oldlist);
	g_object_unref(entry);
     g_free(olddn);
     close_connection(gq_server_dn_get_server(entry), FALSE);

#ifndef USE_TREE_VIEW
     /* refresh visual if requested by browse mode */
     if (do_modrdn && node && !error) {
		refresh_subtree_new_dn(mod_context,
				       gq_input_form_get_ctree_root(iform),
				       node,
				       gq_server_dn_get_dn(entry), 0);
     }
#endif

     error_flush(mod_context);
}

int change_rdn(GqInputForm *iform, int context)
{
     GString *message = NULL;
     LDAP *ld;
     GqServer *server;
     int error, rc, i, remove_flag = 0;
	gchar const* olddn,
	           * dn;
     char **oldrdn, **rdn;
     char *noattrs[] = { LDAP_NO_ATTRS, NULL };
     LDAPMessage *res = NULL;

#if defined(HAVE_LDAP_RENAME)
     LDAPControl cc, *ctrls[2] = { NULL, NULL } ;

     /* prepare ManageDSAit in case we deal with referrals */
     cc.ldctl_oid		= LDAP_CONTROL_MANAGEDSAIT;
     cc.ldctl_value.bv_val	= NULL;
     cc.ldctl_value.bv_len	= 0;
     cc.ldctl_iscritical	= 1;
     
     ctrls[0] = &cc;
#endif

     server = gq_input_form_get_server(iform);
     if( (ld = open_connection(context, server)) == NULL)
	  return(1);

     olddn = gq_input_form_get_old_dn(iform);
     dn    = gq_input_form_get_dn(iform);

     oldrdn = gq_ldap_explode_dn(olddn, 0);
     rdn = gq_ldap_explode_dn(dn, 0);

     if (rdn == NULL) {
	  /* parsing error */

	  error_push(context, _("Cannot explode DN '%s'. Maybe problems with quoting or special characters. See RFC 2253 for details of DN syntax."),
		     dn);

	  error = 1;
     } else {
	  /* parsing OK */

	  message = g_string_sized_new(256);
	  
	  /* check if user really only attemps to change the RDN and not
	     any other parts of the DN */
	  error = 0;
	  for(i = 1; rdn[i]; i++) {
	       if(oldrdn[i] == NULL || rdn[i] == NULL || 
		  (strcasecmp(oldrdn[i], rdn[i]) != 0)) {
		    error_push(context,
			       _("You can only change the RDN of the DN (%s)"),
			       oldrdn[0]);
		    error = 1;
		    break;
	       }
	  }
     }

     if(!error) {
	  statusbar_msg(_("Modifying RDN to %s"), rdn[0]);

	  /* check to see if the rdn exists as an attribute. If it
             does set the remove flag. If it does not do not set the
             remove flag. This is due to the fact that in the latter
             case a set remove flag actually removes the object (at
             least from openldap 2.0 servers. This strange behaviour
             was pointed out by <gwu@acm.org>. */

	  rc = ldap_search_s(ld, olddn, LDAP_SCOPE_BASE, 
			     oldrdn[0], noattrs, 0, &res);
	  if (rc == LDAP_SUCCESS) {
	       LDAPMessage *e = ldap_first_entry(ld, res);
	       if (e) {
		    remove_flag = 1;
	       }
	  }
	  if (res) ldap_msgfree(res);
/*  	  printf("oldrdn[0]=%s, remove=%d\n", oldrdn[0], remove_flag); */

#if defined(HAVE_LDAP_RENAME)
	  /* see draft-ietf-ldapext-ldap-c-api-xx.txt for details */
	  rc = ldap_rename_s(ld,
			     olddn,		/* dn */
			     rdn[0],		/* newrdn */
			     NULL,		/* newparent */
			     remove_flag,	/* deleteoldrdn */
			     ctrls,		/* serverctrls */
			     NULL		/* clientctrls */
			     );

#else
	  rc = ldap_modrdn2_s(ld, olddn, rdn[0], remove_flag);
#endif
	  if(rc == LDAP_SUCCESS) {
	        /* get ready for subsequent DN changes */
		gq_input_form_set_old_dn(iform, dn);
	  } else {
	       if (rc == LDAP_SERVER_DOWN) {
		    server->server_down++;
	       }
	       error_push(context, _("Error renaming entry '%1$s': %2$s"),
			  olddn,
			  ldap_err2string(rc));
	       push_ldap_addl_error(ld, context);
	       error = 2;
	  }
     }

     if (oldrdn) gq_exploded_free(oldrdn);
     if (rdn) gq_exploded_free(rdn);
     if (message) g_string_free(message, TRUE);

     close_connection(server, FALSE);

     return(error);
}


/*
 * convert the differences between oldlist and newlist into
 * LDAPMod structures
 */
LDAPMod**
formdiff_to_ldapmod(GList *oldlist, GList *newlist)
{
     GList *oldlist_tmp, *newlist_tmp, *values, *deleted_values, *added_values;
     LDAPMod **mods, *mod;
     GqFormfill *oldform, *newform;
     int old_l, new_l, modcnt;

	if(!oldlist && !newlist) {
		return NULL;
	}

     oldlist_tmp = oldlist;
     newlist_tmp = newlist;

     old_l = g_list_length(oldlist);
     new_l = g_list_length(newlist);
     mods = malloc( sizeof(void *) * (MAX(old_l, new_l) + 1) * 4);
     if (mods == NULL) {
	  perror("formdiff_to_ldapmod");
	  exit(1);
     }
     mods[0] = NULL;
     modcnt = 0;

     /* pass 0: Actually delete those values marked with FLAG_DEL_ME. These
        will be picked up be the following passes */

     for (; newlist ; newlist = g_list_next(newlist)) {
	  newform = GQ_FORMFILL(newlist->data);
	  if (gq_formfill_get_flags(newform) & FLAG_DEL_ME) {
	       free_formfill_values(newform);
	       gq_formfill_unset_flag(newform, FLAG_DEL_ME);
	  }
     }

     newlist = newlist_tmp;

     /* pass 1: deleted attributes, and deleted/added values */
     for( ; oldlist ; oldlist = oldlist->next ) {
	  oldform = GQ_FORMFILL(oldlist->data);

/*  	  if (oldform->flags & FLAG_NO_USER_MOD) continue; */
	  newform = lookup_attribute(newlist, gq_formfill_get_attrname(oldform));
	  /* oldform->values can come up NULL if the attribute was in
	     the form only because add_attrs_by_oc() added it. Not a
	     delete in this case... */
	  if(gq_formfill_get_values(oldform) != NULL && 
	     (newform == NULL || gq_formfill_get_values(newform) == NULL)) {
	       /* attribute deleted */
	       mod = malloc(sizeof(LDAPMod));
	       if (mod == NULL) {
		    perror("formdiff_to_ldapmod");
		    exit(2);
	       }
	       mod->mod_op = LDAP_MOD_DELETE;

	       if (gq_formfill_get_syntax(oldform) && gq_formfill_get_syntax(oldform)->must_binary) {
		    mod->mod_type = g_strdup_printf("%s;binary", gq_formfill_get_attrname(oldform));
	       }
	       else {
		    mod->mod_type = g_strdup(gq_formfill_get_attrname(oldform));
	       }
	       mod->mod_values = NULL;
	       mods[modcnt++] = mod;
	  }
	  else {
	       GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(oldform));

	       /* pass 1.1: deleted values */
	       deleted_values = NULL;
	       values = gq_formfill_get_values(oldform);
	       while(values) {
		    if(!find_value(gq_formfill_get_values(newform), (GByteArray *) values->data)) {
			 deleted_values = g_list_append(deleted_values, values->data);
		    }
		    values = values->next;
	       }

	       /* pass 1.2: added values */
	       added_values = NULL;
	       values = gq_formfill_get_values(newform);
	       while(values) {
		    if(!find_value(gq_formfill_get_values(oldform), (GByteArray *) values->data)) {
			 added_values = g_list_append(added_values, values->data);
		    }
		    values = values->next;
	       }

	       if(deleted_values && added_values) {
		    /* values deleted and added -- likely a simple edit.
		       Optimize this into a MOD_REPLACE. This could be
		       more work for the server in case of a huge number
		       of values, but who knows...
		    */
		    mod = klass->buildLDAPMod(oldform,
					      LDAP_MOD_REPLACE,
					      gq_formfill_get_values(newform));
		    mods[modcnt++] = mod;
	       }
	       else {
		    if(deleted_values) {
			 mod = klass->buildLDAPMod(oldform,
								 LDAP_MOD_DELETE,
								 deleted_values);
			 mods[modcnt++] = mod;
		    } else if(added_values) {
			 mod = klass->buildLDAPMod(oldform,
								 LDAP_MOD_ADD,
								 added_values);
			 mods[modcnt++] = mod;
		    }
	       }

	       g_list_free(deleted_values);
	       g_list_free(added_values);
	       g_type_class_unref(klass);
	  }
     }
     oldlist = oldlist_tmp;

     /* pass 2: added attributes */
     while(newlist) {
	  newform = GQ_FORMFILL(newlist->data);
	  if(lookup_attribute(oldlist, gq_formfill_get_attrname(newform)) == NULL) {
		  GList* values = gq_formfill_get_values(newform);
	       /* new attribute was added */

	       if(values) {
		    GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(newform));
		    mod = klass->buildLDAPMod(newform, LDAP_MOD_ADD, values);

		    mods[modcnt++] = mod;
		    g_type_class_unref(klass);
	       }
	  }

	  newlist = newlist->next;
     }

     mods[modcnt] = NULL;

     return(mods);
}


char **glist_to_mod_values(GList *values)
{
     int valcnt;
     char **od_values;

     valcnt = 0;
     od_values = g_malloc(sizeof(char *) * (g_list_length(values) + 1));
     while(values) {
	  if(values->data)
	       od_values[valcnt++] = g_strdup(values->data);
	  values = values->next;
     }
     od_values[valcnt] = NULL;

     return(od_values);
}


struct berval **glist_to_mod_bvalues(GList *values)
{
     int valcnt = 0;
     struct berval **od_bvalues;

     od_bvalues = g_malloc(sizeof(struct berval *) * (g_list_length(values) + 1));

     while(values) {
	  if(values->data) {
	       int l = strlen(values->data);
	       int approx = (l / 4) * 3 + 10;
	       GByteArray *gb = g_byte_array_new();
	       if (gb == NULL) break; /* FIXME */
	       g_byte_array_set_size(gb, approx);
	       b64_decode(gb, values->data, l);
	       
	       od_bvalues[valcnt] = (struct berval*) g_malloc(sizeof(struct berval));
	       od_bvalues[valcnt]->bv_len = gb->len;
	       od_bvalues[valcnt]->bv_val = (gchar*)gb->data;
	       
	       g_byte_array_free(gb, FALSE);
	       valcnt++;
	  }
	  values = values->next;
     }
     od_bvalues[valcnt] = NULL;

     return(od_bvalues);
}


int find_value(GList *list, GByteArray *value)
{
     GByteArray *gb;

     while(list) {
	  gb = (GByteArray *) list->data;
	  if( gb->len == value->len && 
	      memcmp(gb->data, value->data, gb->len) == 0)
	       return(1);

	  list = list->next;
     }

     return(0);
}


void
destroy_editwindow(GqInputForm *iform)
{
     gtk_widget_destroy(iform->parent_window); /* OK, window is a toplevel widget */
}


void
add_row(GtkWidget  * button,
	GqInputForm* iform)
{
	GqFormfill *ff;
	int error_context;

	ff = g_object_get_data(G_OBJECT(button), "formfill");
	g_return_if_fail(GQ_IS_FORMFILL(ff));
	g_object_ref(ff);

#warning "FIXME: pass a gboolean to update_formlist() to avoid re-creation of the current formlist; then get rid of ref/unref here"
	update_formlist(iform);

	gq_formfill_set_n_inputfields(ff, 1 + gq_formfill_get_n_inputfields(ff));

	error_context = error_new_context(_("Adding attribute value field"),
					  iform->parent_window);
/*	clear_table(iform); */
	build_or_update_inputform(error_context, iform, FALSE);
	error_flush(error_context);
	g_object_unref(ff);
}

GtkWidget*
gq_new_arrowbutton(GqInputForm *iform)
{
     GtkWidget *newabutton, *arrow;

     newabutton = gtk_button_new();
     GTK_WIDGET_UNSET_FLAGS(newabutton, GTK_CAN_FOCUS);
     g_signal_connect(newabutton, "clicked",
			G_CALLBACK(add_row),
			iform);
     arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
     gtk_widget_show(arrow);
     gtk_container_add(GTK_CONTAINER(newabutton), arrow);
     gtk_widget_show(newabutton);

     return(newabutton);
}

static void check_focus(GtkWidget *w, GtkWidget **focus) 
{
     if (GTK_WIDGET_HAS_FOCUS(w)) {
	  *focus = w;
     }
}


GtkWidget *find_focusbox(GList *formlist)
{
     GList *widgets;
     GqFormfill *ff;

     if(!formlist)
	  return(NULL);

     for ( ; formlist ; formlist = formlist->next ) {
	  ff = GQ_FORMFILL(formlist->data);
	  if (ff && gq_formfill_get_widgets(ff)) {
	       for (widgets = gq_formfill_get_widgets(ff); widgets ; 
		    widgets = widgets->next ) {
		    
		    if (GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(widgets->data))) {
			 return GTK_WIDGET(widgets->data);
		    }

		    /* check children as well */
		    if (GTK_IS_CONTAINER(GTK_WIDGET(widgets->data))) {
			 GtkWidget *focus = NULL;
			 gtk_container_foreach(GTK_CONTAINER(GTK_WIDGET(widgets->data)),
					       (GtkCallback) check_focus,
					       &focus);
			 if (focus) return widgets->data;
		    }
	       }
	  }
     }

     return NULL;
}


/*
 * callback for entry or textbox buttons
 */
static void change_displaytype(GtkWidget* button, GqInputForm *iform, int wanted_dt)
{
     GtkWidget *focusbox;
     GqFormfill *ff, *focusform = NULL;
     int error_context;

     g_assert(wanted_dt == DISPLAYTYPE_ENTRY || wanted_dt == DISPLAYTYPE_TEXT);

     error_context = error_new_context(_("Changing display type"), button);

     update_formlist(iform);

     focusbox = find_focusbox(gq_input_form_get_formlist(iform));

     if(focusbox == NULL) {
	  /* nothing focused */
	  goto done;
     }

     focusform = GQ_FORMFILL(gtk_object_get_data(GTK_OBJECT(focusbox),
							 "formfill"));

     if(focusform == NULL)
	  /* field can't be resized anyway */
	  return;

     iform->focusform = focusform;

     ff = GQ_FORMFILL(gtk_object_get_data(GTK_OBJECT(focusbox),
						  "formfill"));
     if(!ff) goto done;

     if (wanted_dt == DISPLAYTYPE_ENTRY) {
	  gq_formfill_set_display_type(ff, DISPLAYTYPE_ENTRY);
	  gq_formfill_set_dt_handler(ff, get_dt_handler(gq_formfill_get_display_type(ff)));
     } else if(wanted_dt == DISPLAYTYPE_TEXT) {
	  gq_formfill_set_display_type(ff, DISPLAYTYPE_TEXT);
	  gq_formfill_set_dt_handler(ff, get_dt_handler(gq_formfill_get_display_type(ff)));
     }

     /* redraw */
     clear_table(iform);

     gtk_widget_hide(P(iform)->table);
     build_inputform(error_context, iform);
     gtk_widget_show(P(iform)->table);

 done:
     error_flush(error_context);
}


static void do_hide_empty_attributes(int hidden, GqInputForm *iform)
{
     GList *formlist;
     GList *children;
     GtkWidget *child;
     GqFormfill *ff;
     int i = 0;
     int displaytype;
     int hideme;

     for (formlist = gq_input_form_get_formlist(iform); formlist ; formlist = formlist->next ) {
	  ff = GQ_FORMFILL(formlist->data);
	  hideme = 1;
	  i++;

	  displaytype = gq_formfill_get_display_type(ff);

	  for (children = gq_formfill_get_widgets(ff); children ; children = children->next) {
	       child = GTK_WIDGET(children->data);

	       if (hidden) {
		    if (ff && displaytype) {
			 GQTypeDisplayClass* klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));
			 GByteArray *ndata = NULL;

			 if (klass && klass->get_data) {
			      ndata = klass->get_data(ff, child);
			 }
			 /* don't bother adding in empty fields */
			 if (ndata) {
			      hideme = 0;
			      g_byte_array_free(ndata, 1);
			 }
			 g_type_class_unref(klass);
		    }
	       } else {
		    if (child) gtk_widget_show(child);
	       }
	  }

#warning "FIXME: just hide the formfill"
	  if (hidden && hideme) {
	       if (gq_formfill_get_event_box(ff)) {
		       gtk_widget_hide(gq_formfill_get_event_box(ff));
	       }
	       if (gq_formfill_get_label(ff)) {
		       gtk_widget_hide(gq_formfill_get_label(ff));
	       }
	       if (gq_formfill_get_vbox(ff)) gtk_widget_hide(gq_formfill_get_vbox(ff));
	       if (gq_formfill_get_morebutton(ff)) {
		       gtk_widget_hide(gq_formfill_get_morebutton(ff));
	       }
	       for (children = gq_formfill_get_widgets(ff); children ; 
		    children = children->next) {
		    child = GTK_WIDGET(children->data);
		    if (child) gtk_widget_hide(child);
	       }
	  } else {
	       if (gq_formfill_get_event_box(ff)) {
		       gtk_widget_show(gq_formfill_get_event_box(ff));
	       }
	       if (gq_formfill_get_label(ff)) {
		       gtk_widget_show(gq_formfill_get_label(ff));
	       }
	       if (gq_formfill_get_vbox(ff)) gtk_widget_show(gq_formfill_get_vbox(ff));
	       if (gq_formfill_get_morebutton(ff)) {
		       gtk_widget_show(gq_formfill_get_morebutton(ff));
	       }
	  }
     }
}


static void
hide_empty_attributes(GtkToggleToolButton *button, GqInputForm *iform) {
     int hidden;

     hidden = gtk_toggle_tool_button_get_active(button);
     do_hide_empty_attributes(hidden, iform);
     /* store hide status, to be able to keep this info for
      * the next LDAP object to be shown in this browser */
#warning "FIXME: the function should be called from the set function"
	gq_input_form_set_hide(iform, hidden);
}


void set_hide_empty_attributes(int hidden, GqInputForm *iform)
{

     if(iform->hide_attr_button) {
	  gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(iform->hide_attr_button), hidden);
     }
     do_hide_empty_attributes(hidden, iform);
}


/* Datastructure used to communicate between the functions making up the
   add attribute dialog */

typedef struct {
     int breakloop;
     int rc;
     int destroyed;
     gchar **outbuf;
     GtkWidget *combo;
     GList *entries;
} attr_dialog_comm;


/* The set of functions implementing the dialog for the new attribute
   button for extensibleObjects */

/* gtk2 checked (multiple destroy callbacks safety), confidence 1 */
static void
attr_destroy(attr_dialog_comm* comm)
{
	if (comm) {
		comm->breakloop = 1;
		comm->destroyed = 1;
	}
}

static void
attr_ok(attr_dialog_comm* comm)
{
     *(comm->outbuf) = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(comm->combo)->entry), 0, -1);

     if (g_list_find_custom(comm->entries,
			    *(comm->outbuf),
			    (GCompareFunc) strcmp)) {
	  comm->breakloop = 1;
	  comm->rc = 1;
     } else {
	  *(comm->outbuf) = NULL;
     }

}

static void
attr_cancel(attr_dialog_comm* comm)
{
	comm->breakloop = 1;
	comm->rc = 0;
}

/* pops up a dialog to select an attribute type via a GtkCombo. This
   functions waits for the data and puts it into outbuf. */

static int attr_popup(int error_context, 
		      const char *title,
		      GqServer *server,
		      gchar **outbuf,
		      GtkWidget *modal_for)
{
     GtkWidget *window, *vbox0, *vbox1, *vbox2, *label, *button, *hbox0;
     GtkWidget *f = gtk_grab_get_current();
     GList *gl;
     struct server_schema *ss;

     attr_dialog_comm comm = { 0, 0, 0, NULL, NULL, NULL };
     comm.outbuf = outbuf;
     *outbuf = NULL;

     if (modal_for) {
	  modal_for = gtk_widget_get_toplevel(modal_for);
     }

     ss = get_schema(error_context, server);

     if (!ss) {
	  error_push(error_context, _("Server schema not available."));
	  goto done;
     }
     
     /* This is a BAD hack - it solves a problem with the query popup
        dialog that locks up focus handling with all the
        window-managers I have been able to test this with. Actually,
        it should be sufficient to let go of the focus, but
        hiding/showing seems to work... (as I do not know how to
        release the focus in gtk) - Any gtk Hackers around? */
     if (f != NULL) {
	 gtk_widget_hide(f);
	 gtk_widget_show(f);
     }

     window = gtk_dialog_new();
/*       gtk_container_border_width(GTK_CONTAINER(window), 0); */
     gtk_window_set_title(GTK_WINDOW(window), title);
     gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);
     g_signal_connect_swapped(window, "destroy",
			G_CALLBACK(attr_destroy),
			&comm);
     g_signal_connect_swapped(window, "key_press_event",
                               G_CALLBACK(close_on_esc),
                               window);

     vbox0 = GTK_DIALOG(window)->vbox;
     gtk_widget_show(vbox0);

     vbox1 = gtk_vbox_new(FALSE, 0);
     gtk_widget_show(vbox1);
     gtk_container_border_width(GTK_CONTAINER(vbox1), CONTAINER_BORDER_WIDTH);
     gtk_box_pack_start(GTK_BOX(vbox0), vbox1, TRUE, TRUE, 0);
     
     label = gtk_label_new(title);
     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
     gtk_widget_show(label);
     gtk_box_pack_start(GTK_BOX(vbox1), label, TRUE, TRUE, 0);

     comm.combo = gtk_combo_new();
     gtk_combo_set_value_in_list(GTK_COMBO(comm.combo), TRUE, TRUE);

     comm.entries = NULL;
     
     for (gl = ss->at ; gl ; gl = gl->next) {
	  LDAPAttributeType *at = (LDAPAttributeType *) gl->data;
	  
	  if (at && at->at_names) {
	       int i;
	       for (i = 0 ; at->at_names[i] ; i++) {
		    comm.entries = g_list_append(comm.entries, at->at_names[i]);
	       }
	  }
     }

     comm.entries = g_list_sort(comm.entries, (GCompareFunc) strcmp);
     comm.entries = g_list_insert(comm.entries, "", 0);
	  
     gtk_combo_set_popdown_strings(GTK_COMBO(comm.combo),
				   comm.entries);
    
     GTK_WIDGET_SET_FLAGS(comm.combo, GTK_CAN_FOCUS);
     GTK_WIDGET_SET_FLAGS(comm.combo, GTK_CAN_DEFAULT);


     gtk_widget_set_sensitive(GTK_COMBO(comm.combo)->entry, FALSE);

     gtk_widget_show(comm.combo);
     gtk_box_pack_end(GTK_BOX(vbox1), comm.combo, TRUE, TRUE, 0);

     vbox2 = GTK_DIALOG(window)->action_area;
/*       gtk_container_border_width(GTK_CONTAINER(vbox2), CONTAINER_BORDER_WIDTH); */
     gtk_widget_show(vbox2);

     hbox0 = gtk_hbutton_box_new();
     gtk_widget_show(hbox0);
     gtk_box_pack_start(GTK_BOX(vbox2), hbox0, TRUE, TRUE, 0);

     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);

     g_signal_connect_swapped(button, "clicked",
			G_CALLBACK(attr_ok), &comm);
     gtk_box_pack_start(GTK_BOX(hbox0), button, FALSE, FALSE, 0);
     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
     GTK_WIDGET_SET_FLAGS(button, GTK_RECEIVES_DEFAULT);
     gtk_widget_grab_default(button);
     gtk_widget_show(button);

     button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);

     g_signal_connect_swapped(button, "clicked",
			G_CALLBACK(attr_cancel),
			&comm);

     gtk_box_pack_end(GTK_BOX(hbox0), button, FALSE, FALSE, 0);
     gtk_widget_show(button);

/*       gtk_window_set_transient_for(GTK_WINDOW(window),  */
/*  				  GTK_WINDOW(getMainWin())); */


     gtk_widget_grab_focus(GTK_WIDGET(window));
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);

     gtk_widget_show(window);
     gtk_widget_grab_focus(comm.combo);

     while (!comm.breakloop) {
#warning "FIXME: use gtk_dialog_run()"
	  gtk_main_iteration();
     }

     if (!comm.destroyed) {
	  gtk_widget_destroy(window);
     }

     if (comm.entries) g_list_free(comm.entries);
     comm.entries = NULL;

 done:
     return comm.rc;
}

/* Checks if the objectClass attribute contains "extensibleObject" */
static gboolean
is_extensible_object(GqInputForm *iform)
{
     GList *f, *wl;
     GtkWidget *w;
     GByteArray *ndata = NULL;
     GqFormfill *ff;

     for (f = gq_input_form_get_formlist(iform); f ; f = f->next) {
	  ff = GQ_FORMFILL(f->data);

	  if (strcasecmp(gq_formfill_get_attrname(ff), "objectClass") == 0) {
	       GQTypeDisplayClass* klass = NULL;
	       for (wl = gq_formfill_get_widgets(ff); wl ; wl = wl->next) {
		    w = GTK_WIDGET(wl->data);

		    klass = g_type_class_ref(gq_formfill_get_dt_handler(ff));

		    if (klass && klass->get_data) {
			 ndata = klass->get_data(ff, w);

			 if (ndata) {
			      if (strncasecmp((gchar*)ndata->data,
					      "extensibleObject",
					      ndata->len) == 0) {

				   g_byte_array_free(ndata, TRUE);
				   return TRUE;
			      }
			      g_byte_array_free(ndata, TRUE);
			 }
		    }
		    g_type_class_unref(klass);
	       }
	  }
     }

     return FALSE;
}


static void create_new_attr(GtkButton *button, GqInputForm *iform)
{
     LDAPAttributeType *at;
     GqFormfill *ff;
     GqServer *server;
     int rc;
     char *outbuf;
     int error_context;

     error_context = error_new_context(_("Creating new attribute"),
				       GTK_WIDGET(button));

     if(!is_extensible_object(iform)) {
	  error_push(error_context, _("Not an 'extensibleObject'"));
	  goto done;
     }

     server = gq_input_form_get_server(iform);
     rc = attr_popup(error_context,
		     _("Select name of new attribute"),
		     server,
		     &outbuf,
		     iform->parent_window);

     if (rc && strlen(outbuf) > 0) {
	  at = find_canonical_at_by_at(get_schema(error_context, server), outbuf);
	  if (at) {
	       ff = new_formfill();
	       g_assert(ff);

	       gq_formfill_set_server(ff, server);
	       gq_formfill_set_attrname(ff, outbuf);
	       gq_formfill_set_flag(ff, FLAG_EXTENSIBLE_OBJECT_ATTR);
	       if (at->at_single_value) {
		    gq_formfill_set_flag(ff, FLAG_SINGLE_VALUE);
	       }
	       set_displaytype(error_context, server, ff);
#warning "FIXME: this is not very nice"
	       P(iform)->formlist = formlist_append(P(iform)->formlist, ff);

	       build_or_update_inputform(error_context, iform, FALSE);
	  }
     }

     if (outbuf) g_free(outbuf);

 done:
     error_flush(error_context);
}

/* GType */
G_DEFINE_TYPE(GqInputForm, gq_input_form, GTK_TYPE_VBOX);

enum {
	PROP_0,
	PROP_CTREE_REFRESH,
#ifndef USE_TREE_VIEW
	PROP_CTREE_ROOT,
#endif
	PROP_DN,
	PROP_EDITABLE,
	PROP_ENTRY,
	PROP_FORMLIST,
	PROP_HIDE,
	PROP_OLD_DN,
	PROP_OLDLIST,
	PROP_SERVER
};

static void
input_form_activate(GqInputForm* self,
		    GtkWidget  * dn_entry)
{
	if(self->activate) {
		self->activate(self, dn_entry);
	}
}

static void
gq_input_form_init(GqInputForm* self G_GNUC_UNUSED)
{
#define iform self
	GtkToolItem* tool_item = NULL;
	GtkWidget  * pixmap;
	GtkWidget  * vbox;

	P(self)->tips = gtk_tooltips_new();

	P(self)->toolbar = gtk_toolbar_new();
	gtk_toolbar_set_style(GTK_TOOLBAR(P(self)->toolbar), GTK_TOOLBAR_ICONS);
	gtk_widget_show(P(self)->toolbar);
	gtk_box_pack_start(GTK_BOX(self), P(self)->toolbar,
			   FALSE, FALSE, 0);

	/* line item */
	pixmap = gtk_image_new_from_file(PACKAGE_PREFIX "/share/pixmaps/gq/entry.png");
	gtk_widget_show(pixmap);
	tool_item = gtk_tool_button_new(pixmap, "Single Line");
	gtk_tool_item_set_tooltip(tool_item, P(self)->tips,
			          _("Turn into one-line entry field"),
			          Q_("tooltip|Changes the display type of the current "
				  "attribute into 'Entry', thus makes the input "
				  "field a one-line text box."));
	g_signal_connect(tool_item, "clicked",
			 G_CALLBACK(linebutton_clicked), iform);
	gtk_widget_show(GTK_WIDGET(tool_item));
	gtk_toolbar_insert(GTK_TOOLBAR(P(self)->toolbar), tool_item, -1);

	/* textarea item */
	pixmap = gtk_image_new_from_file(PACKAGE_PREFIX "/share/pixmaps/gq/textview.png");
	gtk_widget_show(pixmap);
	tool_item = gtk_tool_button_new(pixmap, "Multiline");
	gtk_tool_item_set_tooltip(tool_item, P(self)->tips,
			          _("Turn into multi-line entry field"),
				  Q_("tooltip|Changes the display type of the current "
				     "attribute into 'Multi-line text', thus makes "
				     "the input field a multi-line text box."));
	g_signal_connect(tool_item, "clicked",
			 G_CALLBACK(textareabutton_clicked), iform);
	gtk_widget_show(GTK_WIDGET(tool_item));
	gtk_toolbar_insert(GTK_TOOLBAR(P(self)->toolbar), tool_item, -1);

	/* new attribute item */
	pixmap = gtk_image_new_from_file(PACKAGE_PREFIX "/share/pixmaps/gq/new.xpm");
	gtk_widget_show(pixmap);
	tool_item = gtk_tool_button_new(pixmap, "New Attribute");
	gtk_tool_item_set_tooltip(tool_item, P(self)->tips,
			  _("Adds an attribute to an object of class "
			    "'extensibleObject'"),
			  Q_("tooltip|Adds an attribute to an object of "
			     "class 'extensibleObject'"));
	g_signal_connect(tool_item, "clicked",
			 G_CALLBACK(create_new_attr), iform);
	gtk_widget_show(GTK_WIDGET(tool_item));
	gtk_toolbar_insert(GTK_TOOLBAR(P(self)->toolbar), tool_item, -1);

	/* hide empty attributes item */
	pixmap = gtk_image_new_from_file(PACKAGE_PREFIX "/share/pixmaps/gq/hide.xpm");
	gtk_widget_show(pixmap);
	tool_item = gtk_toggle_tool_button_new();
	gtk_tool_button_set_label(GTK_TOOL_BUTTON(tool_item), "Hide Empty");
	gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(tool_item), pixmap);
	gtk_tool_item_set_tooltip(tool_item, P(self)->tips,
				  _("Hide/show empty attributes"),
				  Q_("tooltip|Hides or shows all attributes without "
				     "values. "
				     "This is a good way to see immediately what "
				     "attributes the object really has."));
	g_signal_connect(tool_item, "clicked",
			 G_CALLBACK(hide_empty_attributes), iform);
	gtk_widget_show(GTK_WIDGET(tool_item));
	gtk_toolbar_insert(GTK_TOOLBAR(P(self)->toolbar), tool_item, -1);

	iform->hide_attr_button = GTK_WIDGET(tool_item);

	/* scrolled window with vbox2 inside */
	P(self)->scwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_container_border_width(GTK_CONTAINER(P(self)->scwin), 0);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(P(self)->scwin),
	                               GTK_POLICY_AUTOMATIC,
	                               GTK_POLICY_ALWAYS);
	gtk_widget_show(P(self)->scwin);
	gtk_box_pack_start(GTK_BOX(iform),
			   P(self)->scwin, TRUE, TRUE, 0);

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_widget_show(vbox);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(P(iform)->scwin), vbox);

	/* table inside vbox2, will self-expand */
	P(self)->table = gtk_table_new(3, 2, FALSE);
	gtk_container_border_width(GTK_CONTAINER(P(self)->table), 5);
	gtk_table_set_row_spacings(GTK_TABLE(P(self)->table), 0);
	gtk_table_set_col_spacings(GTK_TABLE(P(self)->table), 10);
	gtk_widget_show(P(self)->table);
	gtk_box_pack_start(GTK_BOX(vbox), P(self)->table, FALSE, TRUE, 0);

	/* DN input box */
	P(iform)->dn_label = gtk_label_new(_("Distinguished Name (DN)"));
	gtk_misc_set_alignment(GTK_MISC(P(self)->dn_label), LABEL_JUSTIFICATION, .5);

#warning "FIXME: enable this one"
//	gtk_widget_modify_fg(P(self)->dn_label, GTK_STATE_NORMAL, &mustcol);
	gtk_widget_show(P(self)->dn_label);
	gtk_table_attach(GTK_TABLE(P(iform)->table), P(self)->dn_label, 0, 1, 0, 1,
			   GTK_FILL, GTK_FILL|GTK_EXPAND, 0, 0);

	iform->dn_widget = gtk_entry_new();
	gtk_widget_show(iform->dn_widget);

	gtk_table_attach(GTK_TABLE(P(iform)->table), iform->dn_widget, 1, 2, 0, 1,
			   GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
	g_signal_connect_swapped(iform->dn_widget, "activate",
				 G_CALLBACK(input_form_activate), iform);
	g_signal_connect(iform->dn_widget, "changed",
			   G_CALLBACK(dn_changed), iform);

	/* button box on the bottom */
	P(self)->buttons = gtk_hbutton_box_new();
	gtk_container_set_border_width(GTK_CONTAINER(P(self)->buttons), 6);
	gtk_widget_show(P(self)->buttons);
	gtk_box_pack_end(GTK_BOX(iform),
			 P(self)->buttons, FALSE, TRUE, 0);

	P(self)->button_apply = gtk_button_new_from_stock(GTK_STOCK_APPLY);
        gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_apply, FALSE, FALSE, 0);
        g_signal_connect_swapped(P(self)->button_apply, "clicked",
                                 G_CALLBACK(mod_entry_from_formlist), iform);
        GTK_WIDGET_SET_FLAGS(P(self)->button_apply, GTK_CAN_DEFAULT);
        GTK_WIDGET_UNSET_FLAGS(P(self)->button_apply, GTK_CAN_FOCUS);

	P(self)->button_add_new = gq_button_new_with_label(_("Add as _new"));
	gtk_widget_set_sensitive(P(self)->button_add_new, FALSE);
        gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_add_new, FALSE, FALSE, 0);
        g_signal_connect_swapped(P(self)->button_add_new, "clicked",
                                    G_CALLBACK(add_entry_from_formlist_and_select),
                                    iform);
        GTK_WIDGET_UNSET_FLAGS(P(self)->button_add_new, GTK_CAN_FOCUS);

	P(self)->button_refresh = gtk_button_new_from_stock(GTK_STOCK_REFRESH);
/*  	gtk_widget_set_sensitive(P(self)->button_refresh, FALSE); */
/*      GTK_WIDGET_UNSET_FLAGS(P(self)->button_refresh, GTK_CAN_FOCUS); */
        gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_refresh, FALSE, FALSE, 0);
        g_signal_connect_swapped(P(self)->button_refresh, "clicked",
                                    G_CALLBACK(refresh_inputform),
                                    iform);

	P(self)->button_close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
	gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_close, FALSE, FALSE, 0);
	g_signal_connect_swapped(P(self)->button_close, "clicked",
					 G_CALLBACK(destroy_editwindow),
					 iform);


	P(self)->button_add = gtk_button_new_from_stock(GTK_STOCK_ADD);
        gtk_widget_show(P(self)->button_add);
        gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_add, FALSE, FALSE, 0);
        g_signal_connect_swapped(P(self)->button_add, "clicked",
                                    G_CALLBACK(add_entry_from_formlist),
                                    iform);
        GTK_WIDGET_SET_FLAGS(P(self)->button_add, GTK_CAN_DEFAULT);

	P(self)->button_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
        gtk_widget_show(P(self)->button_cancel);
        gtk_box_pack_end(GTK_BOX(P(self)->buttons), P(self)->button_cancel, FALSE, FALSE, 0);
        g_signal_connect_swapped(P(self)->button_cancel, "clicked",
                                    G_CALLBACK(destroy_editwindow),
                                    iform);
#undef iform
}

static void
input_form_dispose(GObject* object)
{
	gq_input_form_set_entry(GQ_INPUT_FORM(object), NULL);

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

static void
input_form_finalize(GObject* object)
{
	GqInputForm* self = GQ_INPUT_FORM(object);

	gq_input_form_set_old_dn(self, NULL);
	gq_input_form_set_oldlist(self, NULL);

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

static void
input_form_get_property(GObject   * object,
			guint       prop_id,
			GValue    * value,
			GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_CTREE_REFRESH:
		g_value_set_object(value, gq_input_form_get_ctree_refresh(GQ_INPUT_FORM(object)));
		break;
#ifndef USE_TREE_VIEW
	case PROP_CTREE_ROOT:
		g_value_set_object(value, gq_input_form_get_ctree_root(GQ_INPUT_FORM(object)));
		break;
#endif
	case PROP_DN:
		g_value_set_string(value, gq_input_form_get_dn(GQ_INPUT_FORM(object)));
		break;
	case PROP_ENTRY:
		g_value_set_object(value, gq_input_form_get_entry(GQ_INPUT_FORM(object)));
		break;
	case PROP_FORMLIST:
		g_value_set_pointer(value, gq_input_form_get_formlist(GQ_INPUT_FORM(object)));
		break;
	case PROP_OLD_DN:
		g_value_set_string(value, gq_input_form_get_old_dn(GQ_INPUT_FORM(object)));
		break;
	case PROP_SERVER:
		g_value_set_object(value, gq_input_form_get_server(GQ_INPUT_FORM(object)));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
input_form_set_property(GObject     * object,
			guint         prop_id,
			GValue const* value,
			GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_CTREE_REFRESH:
		gq_input_form_set_ctree_refresh(GQ_INPUT_FORM(object),
						g_value_get_object(value));
		break;
#ifndef USE_TREE_VIEW
	case PROP_CTREE_ROOT:
		gq_input_form_set_ctree_root(GQ_INPUT_FORM(object),
					     g_value_get_object(value));
		break;
#endif
	case PROP_EDITABLE:
		gq_input_form_set_editable(GQ_INPUT_FORM(object),
					   g_value_get_boolean(value));
		break;
	case PROP_ENTRY:
		gq_input_form_set_entry(GQ_INPUT_FORM(object),
					g_value_get_object(value));
		break;
	case PROP_FORMLIST:
		gq_input_form_set_formlist(GQ_INPUT_FORM(object),
					   g_value_get_pointer(value));
		break;
	case PROP_HIDE:
		gq_input_form_set_hide(GQ_INPUT_FORM(object),
				       g_value_get_boolean(value));
		break;
	case PROP_OLD_DN:
		gq_input_form_set_old_dn(GQ_INPUT_FORM(object),
					 g_value_get_string(value));
		break;
	case PROP_OLDLIST:
		gq_input_form_set_oldlist(GQ_INPUT_FORM(object),
					  g_value_get_pointer(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
input_form_destroy(GtkObject* object)
{
	P(object)->table = NULL;
	GQ_INPUT_FORM(object)->hide_attr_button = NULL;

	GTK_OBJECT_CLASS(gq_input_form_parent_class)->destroy(object);
}

static void
gq_input_form_class_init(GqInputFormClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);
	GtkObjectClass* gtk_object_class = GTK_OBJECT_CLASS(self_class);
	GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose      = input_form_dispose;
	object_class->finalize     = input_form_finalize;
	object_class->get_property = input_form_get_property;
	object_class->set_property = input_form_set_property;

	g_object_class_install_property(object_class,
					PROP_CTREE_REFRESH,
					g_param_spec_pointer("ctree-refresh",
							     _("CTree Refresh"),
							     _("Tree Node to be refreshed"),
							     G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_DN,
					g_param_spec_string("dn",
							    _("Distinguished Name (DN)"),
							    _("The DN displayed by this input form"),
							    NULL,
							    G_PARAM_READABLE));
	g_object_class_install_property(object_class,
					PROP_EDITABLE,
					g_param_spec_boolean("editable",
							     _("Editable"),
							     _("Are attributes editable in this form"),
							     FALSE,
							     G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_ENTRY,
					g_param_spec_object("entry",
							    _("Entry"),
							    _("The LDAP entry displayed by this form"),
							    GQ_TYPE_SERVER_DN,
							    G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_FORMLIST,
					g_param_spec_pointer("formlist",
							     _("Form List"),
							     _("The formlist of this input form"),
							     G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_HIDE,
					g_param_spec_boolean("hide",
							     _("Hide"),
							     _("Hide empty attributes"),
							     TRUE,
							     G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_OLD_DN,
					g_param_spec_string("old-dn",
							    _("Forger Distinguished Name (DN)"),
							    _("The DN displayed by this input form before the current one"),
							    NULL,
							    G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_OLDLIST,
					g_param_spec_pointer("oldlist",
							     _("Old List"),
							     _("The list that had been displayed"),
							     G_PARAM_READWRITE));
	g_object_class_install_property(object_class,
					PROP_SERVER,
					g_param_spec_object("server",
							    _("Server"),
							    _("The server belonging to the input form"),
							    GQ_TYPE_SERVER,
							    G_PARAM_READABLE));

	/* GtkObjectClass */
	gtk_object_class->destroy = input_form_destroy;

	/* GtkWidgetClass */
	gtk_widget_class_install_style_property(widget_class,
						g_param_spec_boxed("required-color",
								   _("Required Color"),
								   _("Color for labels of required attributes"),
								   GDK_TYPE_COLOR,
								   G_PARAM_READWRITE));
	gtk_widget_class_install_style_property(widget_class,
						g_param_spec_boxed("additional-color",
								   _("Additional Color"),
								   _("Color for labels of attributes which are not in the schema"),
								   GDK_TYPE_COLOR,
								   G_PARAM_READWRITE));
	gtk_widget_class_install_style_property(widget_class,
						g_param_spec_boxed("delete-color",
								   _("Delete Color"),
								   _("Color for labels of attributes which are queued for deletion"),
								   GDK_TYPE_COLOR,
								   G_PARAM_READWRITE));
	gtk_widget_class_install_style_property(widget_class,
						g_param_spec_boxed("extensible-color",
								   _("Extensible Color"),
								   _("Color for labels of extensible attributes"),
								   GDK_TYPE_COLOR,
								   G_PARAM_READWRITE));

	/* GqInputFormClass */
	g_type_class_add_private(self_class, sizeof(struct GqInputFormPrivate));

	input_form_signals[SIG_UPDATED] = g_signal_new("updated", GQ_TYPE_INPUT_FORM,
						       0, 0,
						       NULL, NULL,
						       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
						       0);
}
