1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301, USA.
20  */
21 
text_get(node)22 #include <stdlib.h>
23 #include <string.h>
24 #include <gtk/gtk.h>
25 #include <glib/gi18n-lib.h>
26 #include "gdaui-server-operation.h"
27 #include "gdaui-basic-form.h"
28 #include "gdaui-raw-grid.h"
29 #include "gdaui-raw-form.h"
30 #include "gdaui-data-store.h"
31 #include "gdaui-data-proxy.h"
32 #include "gdaui-data-selector.h"
33 #include "gdaui-data-proxy-info.h"
34 #include <libgda/gda-debug-macros.h>
35 
36 static void gdaui_server_operation_class_init (GdauiServerOperationClass *class);
unit_get(size)37 static void gdaui_server_operation_init (GdauiServerOperation *wid);
38 static void gdaui_server_operation_dispose (GObject *object);
39 
40 static void gdaui_server_operation_set_property (GObject *object,
41 						 guint param_id,
42 						 const GValue *value,
43 						 GParamSpec *pspec);
44 static void gdaui_server_operation_get_property (GObject *object,
tuple_int_get(node, attr_name, default=None)45 						 guint param_id,
46 						 GValue *value,
47 						 GParamSpec *pspec);
48 
49 static void gdaui_server_operation_fill (GdauiServerOperation *form);
50 
bool_get(value)51 /* properties */
52 enum {
53 	PROP_0,
54 	PROP_SERVER_OP_OBJ,
55 	PROP_OPT_HEADER
56 };
57 
58 typedef struct _WidgetData {
59 	struct _WidgetData    *parent;
60 	gchar                 *path_name; /* NULL if for SEQUENCE_ITEM */
61 	GSList                *children;
62 	GtkWidget             *widget;
63 } WidgetData;
64 #define WIDGET_DATA(x) ((WidgetData*)(x))
65 
66 struct _GdauiServerOperationPriv
67 {
68 	GdaServerOperation     *op;
69 	GSList                 *widget_data; /* list of WidgetData structures */
70 #ifdef HAVE_LIBGLADE
71 	GladeXML               *glade;
72 #endif
73 	gboolean                opt_header;
74 };
75 
76 WidgetData *widget_data_new (WidgetData *parent, const gchar *path_name);
77 void        widget_data_free (WidgetData *wd);
78 WidgetData *widget_data_find (GdauiServerOperation *form, const gchar *path);
79 
80 WidgetData *
81 widget_data_new (WidgetData *parent, const gchar *path_name)
82 {
83 	WidgetData *wd;
84 
85 	wd = g_new0 (WidgetData, 1);
86 	wd->parent = parent;
87 	if (path_name)
88 		wd->path_name = g_strdup (path_name);
89 	if (parent)
90 		parent->children = g_slist_append (parent->children, wd);
91 	return wd;
92 }
93 
94 void
95 widget_data_free (WidgetData *wd)
96 {
97 	g_free (wd->path_name);
98 	g_slist_foreach (wd->children, (GFunc) widget_data_free, NULL);
99 	g_slist_free (wd->children);
100 	g_free (wd);
101 }
102 
103 WidgetData *
104 widget_data_find (GdauiServerOperation *form, const gchar *path)
105 {
106 	gchar **array;
107 	gint i, index;
108 	WidgetData *wd = NULL;
109 	GSList *list;
110 
111 	if (!path)
112 		return NULL;
113 	g_assert (*path == '/');
114 
115 	array = g_strsplit (path, "/", 0);
116 	if (!array [1]) {
117 		g_strfreev (array);
118 		return NULL;
119 	}
120 
121 	list = form->priv->widget_data;
122 	while (list && !wd) {
123 		if (WIDGET_DATA (list->data)->path_name &&
124 		    !strcmp (WIDGET_DATA (list->data)->path_name, array[1]))
125 			wd = WIDGET_DATA (list->data);
126 		list = list->next;
127 	}
128 
129 	i = 2;
130 	while (array[i] && wd) {
131 		char *end;
132 		list = wd->children;
133 
134 		index = strtol (array[i], &end, 10);
135 		if (end && *end)
136 			index = -1; /* could not convert array[i] to an int */
137 
138 		if ((index >= 0) && wd->children && !WIDGET_DATA (wd->children->data)->path_name)
139 			wd = g_slist_nth_data (wd->children, index);
140 		else {
141 			wd = NULL;
142 			while (list && !wd) {
143 				if (WIDGET_DATA (list->data)->path_name &&
144 				    !strcmp (WIDGET_DATA (list->data)->path_name, array[i]))
145 					wd = WIDGET_DATA (list->data);
146 				list = list->next;
147 			}
148 		}
149 		i++;
150 	}
151 
152 	/*g_print ("## %s (%s): %p\n", __FUNCTION__, path, wd);*/
153 	g_strfreev (array);
154 	return wd;
155 }
156 
157 /* get a pointer to the parents to be able to call their destructor */
158 static GObjectClass *parent_class = NULL;
159 
160 GType
161 gdaui_server_operation_get_type (void)
162 {
163 	static GType type = 0;
164 
165 	if (G_UNLIKELY (type == 0)) {
166 		static const GTypeInfo info = {
167 			sizeof (GdauiServerOperationClass),
168 			(GBaseInitFunc) NULL,
169 			(GBaseFinalizeFunc) NULL,
170 			(GClassInitFunc) gdaui_server_operation_class_init,
171 			NULL,
172 			NULL,
173 			sizeof (GdauiServerOperation),
174 			0,
175 			(GInstanceInitFunc) gdaui_server_operation_init,
176 			0
177 		};
178 
179 		type = g_type_register_static (GTK_TYPE_BOX, "GdauiServerOperation", &info, 0);
180 	}
181 
182 	return type;
183 }
184 
185 static void
186 gdaui_server_operation_class_init (GdauiServerOperationClass *class)
187 {
188 	GObjectClass *object_class = G_OBJECT_CLASS (class);
189 
190 	parent_class = g_type_class_peek_parent (class);
191 
192 	object_class->dispose = gdaui_server_operation_dispose;
193 
194 	/* Properties */
195         object_class->set_property = gdaui_server_operation_set_property;
196         object_class->get_property = gdaui_server_operation_get_property;
197 	g_object_class_install_property (object_class, PROP_SERVER_OP_OBJ,
198 					 g_param_spec_object ("server-operation",
199 							      _("The specification of the operation to implement"),
200 							      NULL, GDA_TYPE_SERVER_OPERATION,
201 							      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
202 							      G_PARAM_READABLE));
203 	g_object_class_install_property (object_class, PROP_OPT_HEADER,
204 					 g_param_spec_boolean ("hide-single-header",
205 							       _("Request section header to be hidden if there is only one section"),
206 							       NULL, FALSE, G_PARAM_CONSTRUCT | G_PARAM_READABLE |
207 							       G_PARAM_WRITABLE));
208 }
209 
210 static void
211 gdaui_server_operation_init (GdauiServerOperation * wid)
212 {
213 	wid->priv = g_new0 (GdauiServerOperationPriv, 1);
214 	wid->priv->op = NULL;
215 	wid->priv->widget_data = NULL;
216 #ifdef HAVE_LIBGLADE
217 	wid->priv->glade = NULL;
218 #endif
219 	wid->priv->opt_header = FALSE;
220 
221 	gtk_orientable_set_orientation (GTK_ORIENTABLE (wid), GTK_ORIENTATION_VERTICAL);
222 }
223 
224 
225 /**
226  * gdaui_server_operation_new:
227  * @op: a #GdaServerOperation structure
228  *
229  * Creates a new #GdauiServerOperation widget using all the parameters provided in @paramlist.
230  *
231  * The global layout is rendered using a table (a #GtkTable), and an entry is created for each
232  * node of @paramlist.
233  *
234  * Returns: the new widget
235  *
236  * Since: 4.2
237  */
238 GtkWidget *
239 gdaui_server_operation_new (GdaServerOperation *op)
240 {
241 	GObject *obj;
242 
243 	obj = g_object_new (GDAUI_TYPE_SERVER_OPERATION, "server-operation", op, NULL);
244 
245 	return (GtkWidget *) obj;
246 }
247 
248 static void sequence_item_added_cb (GdaServerOperation *op, const gchar *seq_path, gint item_index, GdauiServerOperation *form);
249 static void sequence_item_remove_cb (GdaServerOperation *op, const gchar *seq_path, gint item_index, GdauiServerOperation *form);
250 
251 static void
252 gdaui_server_operation_dispose (GObject *object)
253 {
254 	GdauiServerOperation *form;
255 
256 	g_return_if_fail (object != NULL);
257 	g_return_if_fail (GDAUI_IS_SERVER_OPERATION (object));
258 	form = GDAUI_SERVER_OPERATION (object);
259 
260 	if (form->priv) {
261 		/* paramlist */
262 		if (form->priv->op) {
263 			g_signal_handlers_disconnect_by_func (G_OBJECT (form->priv->op),
264 							      G_CALLBACK (sequence_item_added_cb), form);
265 			g_signal_handlers_disconnect_by_func (G_OBJECT (form->priv->op),
266 							      G_CALLBACK (sequence_item_remove_cb), form);
267 			g_object_unref (form->priv->op);
268 		}
269 
270 		if (form->priv->widget_data) {
271 			g_slist_foreach (form->priv->widget_data, (GFunc) widget_data_free, NULL);
272 			g_slist_free (form->priv->widget_data);
273 			form->priv->widget_data = NULL;
274 		}
275 
276 #ifdef HAVE_LIBGLADE
277 		if (form->priv->glade)
278 			g_object_unref (form->priv->glade);
279 #endif
280 
281 		/* the private area itself */
282 		g_free (form->priv);
283 		form->priv = NULL;
284 	}
285 
286 	/* for the parent class */
287 	parent_class->dispose (object);
288 }
289 
290 
291 static void
292 gdaui_server_operation_set_property (GObject *object,
293 				     guint param_id,
294 				     const GValue *value,
295 				     GParamSpec *pspec)
296 {
297 	GdauiServerOperation *form;
298 
299         form = GDAUI_SERVER_OPERATION (object);
300         if (form->priv) {
301                 switch (param_id) {
302 		case PROP_SERVER_OP_OBJ:
303 			if (form->priv->op) {
304 				TO_IMPLEMENT;
305 				g_assert_not_reached ();
306 			}
307 
308 			form->priv->op = GDA_SERVER_OPERATION(g_value_get_object (value));
309 			if (form->priv->op) {
310 				g_return_if_fail (GDA_IS_SERVER_OPERATION (form->priv->op));
311 
312 				g_object_ref (form->priv->op);
313 
314 				gdaui_server_operation_fill (form);
315 				g_signal_connect (G_OBJECT (form->priv->op), "sequence-item-added",
316 						  G_CALLBACK (sequence_item_added_cb), form);
317 				g_signal_connect (G_OBJECT (form->priv->op), "sequence-item-remove",
318 						  G_CALLBACK (sequence_item_remove_cb), form);
319 			}
320 			break;
321 		case PROP_OPT_HEADER:
322 			form->priv->opt_header = g_value_get_boolean (value);
323 			break;
324 		default:
325 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
326 			break;
327 		}
328 	}
329 }
330 
331 static void
332 gdaui_server_operation_get_property (GObject *object,
333 				     guint param_id,
334 				     GValue *value,
335 				     GParamSpec *pspec)
336 {
337 	GdauiServerOperation *form;
338 
339         form = GDAUI_SERVER_OPERATION (object);
340         if (form->priv) {
341                 switch (param_id) {
342 		case PROP_SERVER_OP_OBJ:
343 			g_value_set_object (value, form->priv->op);
344 			break;
345 		case PROP_OPT_HEADER:
346 			g_value_set_boolean (value, form->priv->opt_header);
347 			break;
348 		default:
349 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
350 			break;
351                 }
352         }
353 }
354 
355 /*
356  * create the entries in the widget
357  */
358 
359 static GtkWidget *fill_create_widget (GdauiServerOperation *form, const gchar *path,
360 				      gchar **section_str, GSList **label_widgets);
361 static void seq_add_item (GtkButton *button, GdauiServerOperation *form);
362 static void seq_del_item (GtkButton *button, GdauiServerOperation *form);
363 
364 
365 /*
366  * @path is like "/SEQ", DOES NOT contain the index of the item to add, which is also in @index
367  */
368 static void
369 sequence_grid_attach_widget (GdauiServerOperation *form, GtkWidget *grid, GtkWidget *wid,
370 			     const gchar *path, gint index)
371 {
372 	GtkWidget *image;
373 	guint min, size;
374 
375 	min = gda_server_operation_get_sequence_min_size (form->priv->op, path);
376 	size = gda_server_operation_get_sequence_size (form->priv->op, path);
377 
378 	/* new widget */
379 	gtk_grid_attach (GTK_GRID (grid), wid, 0, index, 1, 1);
380 	gtk_widget_show (wid);
381 
382 	/* "-" button */
383 	image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
384 	wid = gtk_button_new ();
385 	gtk_button_set_image (GTK_BUTTON (wid), image);
386 	gtk_grid_attach (GTK_GRID (grid), wid, 1, index, 1, 1);
387 	gtk_widget_show (wid);
388 	g_object_set_data_full (G_OBJECT (wid), "_seq_path", g_strdup (path), g_free);
389 	g_object_set_data (G_OBJECT (wid), "_index", GINT_TO_POINTER (index+1));
390 	g_signal_connect (G_OBJECT (wid), "clicked",
391 			  G_CALLBACK (seq_del_item), form);
392 	if (size <= min)
393 		gtk_widget_set_sensitive (wid, FALSE);
394 }
395 
396 static GtkWidget *create_table_fields_array_create_widget (GdauiServerOperation *form, const gchar *path,
397 							   gchar **section_str, GSList **label_widgets);
398 static GtkWidget *
399 fill_create_widget (GdauiServerOperation *form, const gchar *path, gchar **section_str, GSList **label_widgets)
400 {
401 	GdaServerOperationNode *info_node;
402 	GtkWidget *plwid = NULL;
403 
404 	info_node = gda_server_operation_get_node_info (form->priv->op, path);
405 	g_assert (info_node);
406 
407 	if (label_widgets)
408 		*label_widgets = NULL;
409 	if (section_str)
410 		*section_str = NULL;
411 
412 	/* very custom widget rendering goes here */
413 	if ((gda_server_operation_get_op_type (form->priv->op) == GDA_SERVER_OPERATION_CREATE_TABLE) &&
414 	    !strcmp (path, "/FIELDS_A"))
415 		return create_table_fields_array_create_widget (form, path, section_str, label_widgets);
416 
417 	/* generic widget rendering */
418 	switch (info_node->type) {
419 	case GDA_SERVER_OPERATION_NODE_PARAMLIST: {
420 		GdaSet *plist;
421 
422 		plist = info_node->plist;
423 		plwid = gdaui_basic_form_new (plist);
424 		gdaui_basic_form_set_unknown_color (GDAUI_BASIC_FORM (plwid), 0., 0., 0., 0.);
425 		g_object_set ((GObject*) plwid, "show-actions", FALSE, NULL);
426 
427 		if (section_str) {
428 			const gchar *name;
429 			name = g_object_get_data (G_OBJECT (plist), "name");
430 			if (name && *name)
431 				*section_str = g_strdup_printf ("<b>%s:</b>", name);
432 			else
433 				*section_str = NULL;
434 		}
435 		if (label_widgets) {
436 			GSList *params;
437 
438 			params = plist->holders;
439 			while (params) {
440 				GtkWidget *label_entry;
441 
442 				label_entry = gdaui_basic_form_get_label_widget (GDAUI_BASIC_FORM (plwid),
443 										 GDA_HOLDER (params->data));
444 				if (label_entry && !g_slist_find (*label_widgets, label_entry))
445 					*label_widgets = g_slist_prepend (*label_widgets, label_entry);
446 				params = params->next;
447 			}
448 			*label_widgets = g_slist_reverse (*label_widgets);
449 		}
450 		break;
451 	}
452 	case GDA_SERVER_OPERATION_NODE_DATA_MODEL: {
453 		GdaDataModel *model;
454 		GtkWidget *winfo;
455 		GtkWidget *box, *grid;
456 
457 		plwid = gtk_scrolled_window_new (NULL, NULL);
458 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (plwid),
459 						GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
460 		gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (plwid),
461 						     GTK_SHADOW_NONE);
462 
463 		model = info_node->model;
464 		grid = gdaui_raw_grid_new (model);
465 		gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (plwid), grid);
466 		gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (plwid))),
467 					      GTK_SHADOW_NONE);
468 		gdaui_data_proxy_set_write_mode (GDAUI_DATA_PROXY (grid),
469 						 GDAUI_DATA_PROXY_WRITE_ON_ROW_CHANGE);
470 		gtk_widget_show (grid);
471 
472 		g_object_set (G_OBJECT (grid), "info-cell-visible", FALSE, NULL);
473 
474 		winfo = gdaui_data_proxy_info_new (GDAUI_DATA_PROXY (grid),
475 						   GDAUI_DATA_PROXY_INFO_ROW_MODIFY_BUTTONS);
476 
477 		box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
478 		gtk_box_pack_start (GTK_BOX (box), plwid, TRUE, TRUE, 0);
479 		gtk_widget_show (plwid);
480 
481 		gtk_box_pack_start (GTK_BOX (box), winfo, FALSE, TRUE, 0);
482 		gtk_widget_show (winfo);
483 
484 		plwid = box;
485 
486 		if (section_str)
487 			*section_str = g_strdup_printf ("<b>%s:</b>",
488 							(gchar*) g_object_get_data (G_OBJECT (model), "name"));
489 
490 		if (label_widgets) {
491 			GtkWidget *label_entry;
492 			gchar *str;
493 
494 			if (info_node->status == GDA_SERVER_OPERATION_STATUS_REQUIRED) {
495 				str = g_strdup_printf ("<b>%s:</b>",
496 						       (gchar*) g_object_get_data (G_OBJECT (model), "name"));
497 				label_entry = gtk_label_new (str);
498 				gtk_label_set_use_markup (GTK_LABEL (label_entry), TRUE);
499 			}
500 			else {
501 				str = g_strdup_printf ("%s:", (gchar*) g_object_get_data (G_OBJECT (model), "name"));
502 				label_entry = gtk_label_new (str);
503 			}
504 			g_free (str);
505 			gtk_misc_set_alignment (GTK_MISC (label_entry), 0., 0.);
506 
507 			gtk_widget_show (label_entry);
508 			str = (gchar *) g_object_get_data (G_OBJECT (model), "descr");
509 			if (str && *str)
510 				gtk_widget_set_tooltip_text (label_entry, str);
511 
512 			*label_widgets = g_slist_prepend (*label_widgets, label_entry);
513 		}
514 
515 		gtk_widget_set_vexpand (plwid, TRUE);
516 		break;
517 	}
518 	case GDA_SERVER_OPERATION_NODE_PARAM: {
519 		GdaSet *plist;
520 		GdaHolder *param;
521 		GSList *list;
522 
523 		param = info_node->param;
524 		list = g_slist_append (NULL, param);
525 		plist = gda_set_new (list);
526 		g_slist_free (list);
527 		plwid = gdaui_basic_form_new (plist);
528 		gdaui_basic_form_set_unknown_color (GDAUI_BASIC_FORM (plwid), 0., 0., 0., 0.);
529 		g_object_set ((GObject*) plwid, "show-actions", FALSE, NULL);
530 		/* we don't need plist anymore */
531 		g_object_unref (plist);
532 
533 		if (section_str)
534 			*section_str = g_strdup_printf ("<b>%s:</b>",
535 							(gchar*) g_object_get_data (G_OBJECT (param), "name"));
536 		if (label_widgets) {
537 			GtkWidget *label_entry;
538 
539 			label_entry = gdaui_basic_form_get_label_widget (GDAUI_BASIC_FORM (plwid), param);
540 			*label_widgets = g_slist_prepend (*label_widgets, label_entry);
541 		}
542 		break;
543 	}
544 	case GDA_SERVER_OPERATION_NODE_SEQUENCE: {
545 		guint n, size;
546 		GtkWidget *grid, *wid, *image;
547 		WidgetData *wdp, *wd;
548 		gchar *parent_path = NULL, *path_name = NULL;
549 		guint max;
550 
551 		max = gda_server_operation_get_sequence_max_size (form->priv->op, path);
552 		if (section_str) {
553 			const gchar *seq_name;
554 			seq_name = gda_server_operation_get_sequence_name (form->priv->op, path);
555 			*section_str = g_strdup_printf ("<b>%s:</b>", seq_name);
556 		}
557 
558 		plwid = gtk_scrolled_window_new (NULL, NULL);
559 
560 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (plwid),
561 						GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
562 		gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (plwid),
563 						     GTK_SHADOW_NONE);
564 
565 		size = gda_server_operation_get_sequence_size (form->priv->op, path);
566 		grid = gtk_grid_new ();
567 		gtk_grid_set_row_spacing (GTK_GRID (grid), 10);
568 		gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (plwid), grid);
569 		gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (plwid))),
570 					      GTK_SHADOW_NONE);
571 		gtk_widget_show (grid);
572 
573 		parent_path = gda_server_operation_get_node_parent (form->priv->op, path);
574 		path_name = gda_server_operation_get_node_path_portion (form->priv->op, path);
575 		wdp = widget_data_find (form, parent_path);
576 		wd = widget_data_new (wdp, path_name);
577 		wd->widget = grid;
578 		if (! wdp)
579 			form->priv->widget_data = g_slist_append (form->priv->widget_data, wd);
580 		g_free (parent_path);
581 		g_free (path_name);
582 
583 		/* existing entries */
584 		for (n = 0; n < size; n++) {
585 			GtkWidget *wid;
586 			gchar *str;
587 
588 			str = g_strdup_printf ("%s/%d", path, n);
589 			wid = fill_create_widget (form, str, NULL, NULL);
590 			sequence_grid_attach_widget (form, grid, wid, path, n);
591 			g_free (str);
592 		}
593 
594 		if (size < max) {
595 			/* last row is for new entries */
596 			wid = gtk_label_new (_("Add"));
597 			gtk_misc_set_alignment (GTK_MISC (wid), .0, -1);
598 			gtk_grid_attach (GTK_GRID (grid), wid, 0, size, 1, 1);
599 			gtk_widget_show (wid);
600 
601 			image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
602 			wid = gtk_button_new ();
603 			gtk_button_set_image (GTK_BUTTON (wid), image);
604 			gtk_grid_attach (GTK_GRID (grid), wid, 1, size, 1, 1);
605 			gtk_widget_show (wid);
606 
607 			g_signal_connect (G_OBJECT (wid), "clicked",
608 					  G_CALLBACK (seq_add_item), form);
609 			g_object_set_data_full (G_OBJECT (wid), "_seq_path", g_strdup (path), g_free);
610 		}
611 
612 		gtk_widget_set_vexpand (plwid, TRUE);
613 		break;
614 	}
615 	case GDA_SERVER_OPERATION_NODE_SEQUENCE_ITEM: {
616 		gchar **node_names;
617 		gint size;
618 		gchar *parent_path;
619 		WidgetData *wdp, *wdi;
620 
621 		node_names = gda_server_operation_get_sequence_item_names (form->priv->op, path);
622 		size = g_strv_length (node_names);
623 		if (size > 1) {
624 			GtkWidget *grid;
625 			gint i, tab_index;
626 
627 			grid = gtk_grid_new ();
628 			for (i = 0, tab_index = 0; i < size; i++) {
629 				GtkWidget *wid;
630 				GSList *lab_list, *list;
631 				gint nb_labels = 0;
632 
633 				wid = fill_create_widget (form, node_names[i], NULL, &lab_list);
634 				for (list = lab_list; list; list = list->next) {
635 					GtkWidget *label_entry = (GtkWidget *) list->data;
636 					GtkWidget *parent;
637 
638 					if (label_entry) {
639 						parent = gtk_widget_get_parent (label_entry);
640 						if (parent) {
641 							g_object_ref (label_entry);
642 							gtk_container_remove (GTK_CONTAINER (parent), label_entry);
643 						}
644 						gtk_grid_attach (GTK_GRID (grid), label_entry,
645 								 0, tab_index, 1, 1);
646 						if (parent)
647 							g_object_unref (label_entry);
648 					}
649 					nb_labels++;
650 					tab_index++;
651 				}
652 				g_slist_free (lab_list);
653 
654 				if (nb_labels > 0)
655 					gtk_grid_attach (GTK_GRID (grid), wid, 1,
656 							 tab_index - nb_labels, 1, nb_labels);
657 				else {
658 					gtk_grid_attach (GTK_GRID (grid), wid, 1, tab_index, 1, 1);
659 					tab_index += 1;
660 				}
661 				gtk_widget_show (wid);
662 			}
663 			plwid = grid;
664 		}
665 		else
666 			plwid = fill_create_widget (form, node_names[0], NULL, NULL);
667 
668 		parent_path = gda_server_operation_get_node_parent (form->priv->op, path);
669 		wdp = widget_data_find (form, parent_path);
670 		g_assert (wdp);
671 		wdi = widget_data_new (wdp, NULL);
672 		wdi->widget = plwid;
673 
674 		g_free (parent_path);
675 		g_strfreev (node_names);
676 		break;
677 	}
678 	default:
679 		g_assert_not_reached ();
680 		break;
681 	}
682 
683 	return plwid;
684 }
685 
686 static void
687 gdaui_server_operation_fill (GdauiServerOperation *form)
688 {
689 	gint i;
690 	gchar **topnodes;
691 #ifdef HAVE_LIBGLADE
692 	gchar *glade_file;
693 #endif
694 
695 	/* parameters list management */
696 	if (!form->priv->op)
697 		/* nothing to do */
698 		return;
699 
700 	/* load Glade file for specific GUI if it exists */
701 #ifdef HAVE_LIBGLADE
702 	glade_file = gdaui_gbr_get_data_dir_path ("server_operation.glade");
703 	form->priv->glade = glade_xml_new (glade_file,
704 					   gda_server_operation_op_type_to_string (gda_server_operation_get_op_type (form->priv->op)),
705 					   NULL);
706 	g_free (glade_file);
707 	if (form->priv->glade) {
708 		GtkWidget *mainw;
709 		mainw = glade_xml_get_widget (form->priv->glade,
710 					      gda_server_operation_op_type_to_string (gda_server_operation_get_op_type (form->priv->op)));
711 		if (mainw) {
712 			gtk_box_pack_start (GTK_BOX (form), mainw, TRUE, TRUE, 0);
713 			gtk_widget_show (mainw);
714 		}
715 		else {
716 			g_object_unref (form->priv->glade);
717 			form->priv->glade = NULL;
718 		}
719 	}
720 #endif
721 
722 	/* user visible widgets */
723 	topnodes = gda_server_operation_get_root_nodes (form->priv->op);
724 	i = 0;
725 	while (topnodes[i]) {
726 		GtkWidget *plwid;
727 		gchar *section_str;
728 		GtkWidget *container = NULL;
729 
730 #ifdef HAVE_LIBGLADE
731 		if (form->priv->glade) {
732 			container = glade_xml_get_widget (form->priv->glade, topnodes[i]);
733 			if (!container) {
734 				i++;
735 				continue;
736 			}
737 		}
738 #endif
739 		if (!container)
740 			container = (GtkWidget *) form;
741 
742 		plwid = fill_create_widget (form, topnodes[i], &section_str, NULL);
743 		if (plwid) {
744 			GdaServerOperationNodeStatus status;
745 			GtkWidget *label = NULL, *hbox = NULL;
746 
747 			if (! (form->priv->opt_header && (g_strv_length (topnodes) == 1)) && section_str) {
748 				GtkWidget *lab;
749 				label = gtk_label_new ("");
750 				gtk_widget_show (label);
751 				gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
752 				gtk_label_set_markup (GTK_LABEL (label), section_str);
753 				g_free (section_str);
754 
755 				hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); /* HIG */
756 				gtk_widget_show (hbox);
757 				lab = gtk_label_new ("    ");
758 				gtk_box_pack_start (GTK_BOX (hbox), lab, FALSE, FALSE, 0);
759 				gtk_widget_show (lab);
760 
761 				gtk_box_pack_start (GTK_BOX (hbox), plwid, TRUE, TRUE, 0);
762 				gtk_widget_show (plwid);
763 			}
764 			else
765 				gtk_widget_show (plwid);
766 
767 
768 			gda_server_operation_get_node_type (form->priv->op, topnodes[i], &status);
769 			switch (status) {
770 			case GDA_SERVER_OPERATION_STATUS_OPTIONAL: {
771 				GtkWidget *exp;
772 				exp = gtk_expander_new ("");
773 				if (!label) {
774 					gchar *str;
775 					label = gtk_label_new ("");
776 					gtk_widget_show (label);
777 					gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
778 					str = g_strdup_printf ("<b>%s:</b>", _("Options"));
779 					gtk_label_set_markup (GTK_LABEL (label), str);
780 					g_free (str);
781 				}
782 
783 				gtk_expander_set_label_widget (GTK_EXPANDER (exp), label);
784 				gtk_box_pack_start (GTK_BOX (container), exp, TRUE, TRUE, 5);
785 				if (hbox)
786 					gtk_container_add (GTK_CONTAINER (exp), hbox);
787 				else
788 					gtk_container_add (GTK_CONTAINER (exp), plwid);
789 				gtk_widget_show (exp);
790 				break;
791 			}
792 			case GDA_SERVER_OPERATION_STATUS_REQUIRED: {
793 				gboolean expand;
794 				expand = gtk_widget_get_vexpand (plwid);
795 
796 				if (label)
797 					gtk_box_pack_start (GTK_BOX (container), label, FALSE, TRUE, 5);
798 				if (hbox)
799 					gtk_box_pack_start (GTK_BOX (container), hbox, expand, TRUE, 0);
800 				else
801 					gtk_box_pack_start (GTK_BOX (container), plwid, expand, TRUE, 0);
802 				break;
803 			}
804 			default:
805 				break;
806 			}
807 		}
808 
809 		i++;
810 	}
811 
812 	/* destroying unused widgets in the Glade description */
813 #ifdef HAVE_LIBGLADE
814 	if (form->priv->glade) {
815 		GList *widgets, *list;
816 
817 		widgets = glade_xml_get_widget_prefix (form->priv->glade, "/");
818 		for (list = widgets; list; list = list->next) {
819 			const gchar *name;
820 
821 			name = glade_get_widget_name ((GtkWidget *) (list->data));
822 			if (!gda_server_operation_get_node_info (form->priv->op, name)) {
823 				GtkWidget *parent;
824 
825 				/* dirty hack to remove a notebook page */
826 				parent = gtk_widget_get_parent ((GtkWidget *) (list->data));
827 				if (GTK_IS_VIEWPORT (parent))
828 					parent = gtk_widget_get_parent (parent);
829 				if (GTK_IS_SCROLLED_WINDOW (parent))
830 					parent = gtk_widget_get_parent (parent);
831 				if (GTK_IS_NOTEBOOK (parent)) {
832 					gint pageno;
833 
834 					pageno = gtk_notebook_page_num (GTK_NOTEBOOK (parent),
835 									(GtkWidget *) (list->data));
836 					gtk_notebook_remove_page (GTK_NOTEBOOK (parent), pageno);
837 				}
838 				else
839 					gtk_widget_destroy ((GtkWidget *) (list->data));
840 			}
841 		}
842 		g_list_free (widgets);
843 	}
844 #endif
845 
846 	g_strfreev (topnodes);
847 
848 }
849 
850 /*
851  * For sequences: adding an item by clicking on the "+" button
852  */
853 static void
854 seq_add_item (GtkButton *button, GdauiServerOperation *form)
855 {
856 	gchar *path;
857 
858 	path = g_object_get_data (G_OBJECT (button), "_seq_path");
859 	gda_server_operation_add_item_to_sequence (form->priv->op, path);
860 }
861 
862 /*
863  * For sequences: removing an item by clicking on the "-" button
864  */
865 static void
866 seq_del_item (GtkButton *button, GdauiServerOperation *form)
867 {
868 	gchar *seq_path, *item_path;
869 	gint index;
870 
871 	seq_path = g_object_get_data (G_OBJECT (button), "_seq_path");
872 	index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "_index")) - 1;
873 	g_assert (index >= 0);
874 	item_path = g_strdup_printf ("%s/%d", seq_path, index);
875 	gda_server_operation_del_item_from_sequence (form->priv->op, item_path);
876 	g_free (item_path);
877 }
878 
879 /*
880  * For sequences: treating the "sequence-item-added" signal
881  */
882 struct MoveChild {
883 	GtkWidget *widget;
884 	guint16    top_attach;
885 };
886 
887 static void
888 sequence_item_added_cb (GdaServerOperation *op, const gchar *seq_path, gint item_index, GdauiServerOperation *form)
889 {
890 	GtkWidget *grid;
891 	GList *children, *list, *to_move = NULL;
892 	GtkWidget *wid;
893 	gchar *str;
894 	WidgetData *wd;
895 	guint max, min, size;
896 
897 	max = gda_server_operation_get_sequence_max_size (op, seq_path);
898 	min = gda_server_operation_get_sequence_min_size (op, seq_path);
899 	size = gda_server_operation_get_sequence_size (op, seq_path);
900 
901 	wd = widget_data_find (form, seq_path);
902 	g_assert (wd);
903 	grid = wd->widget;
904 	g_assert (grid);
905 
906 	/* move children DOWN if necessary */
907 	children = gtk_container_get_children (GTK_CONTAINER (grid));
908 	for (list = children; list; list = list->next) {
909 		GtkWidget *child = GTK_WIDGET (list->data);
910 
911 		if (child) {
912 			guint top_attach, left_attach;
913 			gtk_container_child_get (GTK_CONTAINER (grid), child,
914 						 "top-attach", &top_attach,
915 						 "left-attach", &left_attach, NULL);
916 			/* ADD/REMOVE button sensitivity */
917 			if (left_attach == 1) {
918 				if (top_attach == size-1)
919 					gtk_widget_set_sensitive (child, (size < max) ? TRUE : FALSE);
920 				else
921 					gtk_widget_set_sensitive (child, (size > min) ? TRUE : FALSE);
922 			}
923 
924 			/* move children DOWN if necessary and change the "_index" property */
925 			if (top_attach >= (guint)item_index) {
926 				struct MoveChild *mc;
927 				gint index;
928 
929 				mc = g_new (struct MoveChild, 1);
930 				mc->widget = child;
931 				mc->top_attach = top_attach + 1;
932 				to_move = g_list_append (to_move, mc);
933 
934 				index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "_index"));
935 				if (index > 0)
936 					g_object_set_data (G_OBJECT (child), "_index",
937 							   GINT_TO_POINTER (index + 1));
938 			}
939 		}
940 	}
941 	g_list_free (children);
942 
943 
944 	for (list = to_move; list; list = list->next) {
945 		struct MoveChild *mc;
946 
947 		mc = (struct MoveChild *) (list->data);
948 		gtk_container_child_set (GTK_CONTAINER (grid), mc->widget,
949 					 "top-attach", mc->top_attach,
950 					 "height", 1, NULL);
951 		g_free (list->data);
952 	}
953 	g_list_free (to_move);
954 
955 	/* add widget corresponding to the new sequence item */
956 	str = g_strdup_printf ("%s/%d", seq_path, item_index);
957 	wid = fill_create_widget (form, str, NULL, NULL);
958 	sequence_grid_attach_widget (form, grid, wid, seq_path, item_index);
959 	g_free (str);
960 }
961 
962 /*
963  * For sequences: treating the "sequence-item-remove" signal
964  */
965 static void
966 sequence_item_remove_cb (GdaServerOperation *op, const gchar *seq_path, gint item_index, GdauiServerOperation *form)
967 {
968 	GtkWidget *grid;
969 	GList *children, *list, *to_move = NULL;
970 	gchar *str;
971 	WidgetData *wds, *wdi;
972 	guint min, size;
973 
974 	min = gda_server_operation_get_sequence_min_size (op, seq_path);
975 	size = gda_server_operation_get_sequence_size (op, seq_path);
976 	/* note: size is the size of the sequence _before_ the actual removal of the sequence item */
977 
978 	wds = widget_data_find (form, seq_path);
979 	g_assert (wds);
980 	grid = wds->widget;
981 	g_assert (grid);
982 
983 	/* remove widget */
984 	str = g_strdup_printf ("%s/%d", seq_path, item_index);
985 	wdi = widget_data_find (form, str);
986 	g_free (str);
987 	g_assert (wdi);
988 	gtk_widget_destroy (wdi->widget);
989 	g_assert (wdi->parent == wds);
990 	wds->children = g_slist_remove (wds->children, wdi);
991 	widget_data_free (wdi);
992 
993 	/* remove the widget associated to the sequence item */
994 	children = gtk_container_get_children (GTK_CONTAINER (grid));
995 	for (list = children; list; ) {
996 		GtkWidget *child = GTK_WIDGET (list->data);
997 		if (child) {
998 			guint top_attach;
999 			gtk_container_child_get (GTK_CONTAINER (grid), child,
1000 						 "top-attach", &top_attach, NULL);
1001 			if (top_attach == (guint)item_index) {
1002 				gtk_widget_destroy (child);
1003 				g_list_free (children);
1004 				children = gtk_container_get_children (GTK_CONTAINER (grid));
1005 				list = children;
1006 				continue;
1007 			}
1008 		}
1009 		list = list->next;
1010 	}
1011 	g_list_free (children);
1012 
1013 	/* move children UP if necessary */
1014 	children = gtk_container_get_children (GTK_CONTAINER (grid));
1015 	for (list = children; list; list = list->next) {
1016 		GtkWidget *child = GTK_WIDGET (list->data);
1017 		if (child) {
1018 			guint top_attach, left_attach;
1019 			gtk_container_child_get (GTK_CONTAINER (grid), child,
1020 						 "top-attach", &top_attach,
1021 						 "left-attach", &left_attach, NULL);
1022 			/* ADD/REMOVE button sensitivity */
1023 			if (left_attach == 1) {
1024 				if (top_attach == size)
1025 					gtk_widget_set_sensitive (child, TRUE);
1026 				else
1027 					gtk_widget_set_sensitive (child, (size-1 > min) ? TRUE : FALSE);
1028 			}
1029 
1030 			/* move widgets UP if necessary and change the "_index" property */
1031 			if (top_attach > (guint)item_index) {
1032 				struct MoveChild *mc;
1033 				gint index;
1034 
1035 				mc = g_new (struct MoveChild, 1);
1036 				mc->widget = child;
1037 				mc->top_attach = top_attach - 1;
1038 				to_move = g_list_append (to_move, mc);
1039 
1040 				index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "_index"));
1041 				if (index > 0)
1042 					g_object_set_data (G_OBJECT (child), "_index",
1043 							   GINT_TO_POINTER (index - 1));
1044 			}
1045 		}
1046 	}
1047 	g_list_free (children);
1048 
1049 	for (list = to_move; list; list = list->next) {
1050 		struct MoveChild *mc;
1051 
1052 		mc = (struct MoveChild *) (list->data);
1053 		gtk_container_child_set (GTK_CONTAINER (grid), mc->widget,
1054 					 "top-attach", mc->top_attach,
1055 					 "height", 1, NULL);
1056 		g_free (list->data);
1057 	}
1058 	g_list_free (to_move);
1059 }
1060 
1061 
1062 /**
1063  * gdaui_server_operation_new_in_dialog:
1064  * @op: a #GdaServerOperation object
1065  * @parent: (allow-none): the parent window for the new dialog, or %NULL
1066  * @title: (allow-none): the title of the dialog window, or %NULL
1067  * @header: (allow-none): a helper text displayed at the top of the dialog, or %NULL
1068  *
1069  * Creates a new #GdauiServerOperation widget in the same way as gdaui_server_operation_new()
1070  * and puts it into a #GtkDialog widget. The returned dialog has the "Ok" and "Cancel" buttons
1071  * which respectively return GTK_RESPONSE_ACCEPT and GTK_RESPONSE_REJECT.
1072  *
1073  * The #GdauiServerOperation widget is attached to the dialog using the user property
1074  * "form".
1075  *
1076  * Returns: the new #GtkDialog widget
1077  *
1078  * Since: 4.2
1079  */
1080 GtkWidget *
1081 gdaui_server_operation_new_in_dialog (GdaServerOperation *op, GtkWindow *parent,
1082 				      const gchar *title, const gchar *header)
1083 {
1084 	GtkWidget *form;
1085 	GtkWidget *dlg;
1086 	GtkWidget *dcontents;
1087 	const gchar *rtitle;
1088 
1089 	form = gdaui_server_operation_new (op);
1090 
1091 	rtitle = title;
1092 	if (!rtitle)
1093 		rtitle = _("Server operation specification");
1094 
1095 	dlg = gtk_dialog_new_with_buttons (rtitle, parent,
1096 					   GTK_DIALOG_MODAL,
1097 					   GTK_STOCK_OK,
1098 					   GTK_RESPONSE_ACCEPT,
1099 					   GTK_STOCK_CANCEL,
1100 					   GTK_RESPONSE_REJECT,
1101 					   NULL);
1102 	dcontents = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
1103 
1104 	if (header && *header) {
1105 		GtkWidget *label;
1106 
1107 		label = gtk_label_new (NULL);
1108 		gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1109 		gtk_label_set_markup (GTK_LABEL (label), header);
1110 		gtk_box_pack_start (GTK_BOX (dcontents), label, FALSE, FALSE, 5);
1111 
1112 		gtk_widget_show (label);
1113 	}
1114 	gtk_container_set_border_width (GTK_CONTAINER (dcontents), 4);
1115 	gtk_box_pack_start (GTK_BOX (dcontents), form, TRUE, TRUE, 10);
1116 
1117 	gtk_widget_show_all (form);
1118 
1119 	return dlg;
1120 }
1121 
1122 
1123 /*
1124  * CREATE_TABLE "/FIELDS_A" Custom widgets rendering
1125  */
1126 static void create_table_grid_fields_iter_row_changed_cb (GdaDataModelIter *grid_iter, gint row,
1127 							  GdaDataModelIter *form_iter);
1128 static void create_table_proxy_row_inserted_cb (GdaDataProxy *proxy, gint row, GdauiServerOperation *form);
1129 static GtkWidget *
1130 create_table_fields_array_create_widget (GdauiServerOperation *form, const gchar *path,
1131 					 G_GNUC_UNUSED gchar **section_str,
1132 					 G_GNUC_UNUSED GSList **label_widgets)
1133 {
1134 	GdaServerOperationNode *info_node;
1135 	GtkWidget *hlayout, *sw, *box, *label;
1136 	GtkWidget *grid_fields, *form_props, *winfo;
1137 	GdaDataProxy *proxy;
1138 	gint name_col, col, nbcols;
1139 	GdaDataModelIter *grid_iter, *form_iter;
1140 
1141 	info_node = gda_server_operation_get_node_info (form->priv->op, path);
1142 	g_assert (info_node->type == GDA_SERVER_OPERATION_NODE_DATA_MODEL);
1143 
1144 	hlayout = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
1145 
1146 	/* form for field properties */
1147 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1148 	gtk_paned_pack2 (GTK_PANED (hlayout), box, TRUE, TRUE);
1149 
1150 	label = gtk_label_new (_("<b>Field properties:</b>"));
1151 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1152 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
1153 	gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
1154 
1155 	form_props = gdaui_raw_form_new (GDA_DATA_MODEL (info_node->model));
1156 	proxy = gdaui_data_proxy_get_proxy (GDAUI_DATA_PROXY (form_props));
1157 	gdaui_data_proxy_set_write_mode (GDAUI_DATA_PROXY (form_props),
1158 					 GDAUI_DATA_PROXY_WRITE_ON_VALUE_CHANGE);
1159 	gtk_box_pack_start (GTK_BOX (box), form_props, TRUE, TRUE, 0);
1160 	g_signal_connect (proxy, "row-inserted",
1161 			  G_CALLBACK (create_table_proxy_row_inserted_cb), form);
1162 
1163 	gtk_widget_show_all (box);
1164 
1165 	/* grid for field names */
1166 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1167 	gtk_paned_pack1 (GTK_PANED (hlayout), box, TRUE, TRUE);
1168 
1169 	label = gtk_label_new (_("<b>Fields:</b>"));
1170 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1171 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
1172 	gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
1173 
1174 	sw = gtk_scrolled_window_new (NULL, NULL);
1175 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1176 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1177 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
1178 
1179 	grid_fields = gdaui_raw_grid_new (GDA_DATA_MODEL (proxy));
1180 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (grid_fields), FALSE);
1181 	g_object_set (G_OBJECT (grid_fields), "info-cell-visible", FALSE, NULL);
1182 
1183 	name_col = 0;
1184 	nbcols = gda_data_proxy_get_proxied_model_n_cols (proxy);
1185 	g_assert (name_col < nbcols);
1186 	for (col = 0; col < name_col; col++)
1187 		gdaui_data_selector_set_column_visible (GDAUI_DATA_SELECTOR (grid_fields), col, FALSE);
1188 	for (col = name_col + 1; col < nbcols; col++)
1189 		gdaui_data_selector_set_column_visible (GDAUI_DATA_SELECTOR (grid_fields), col, FALSE);
1190 
1191 	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), grid_fields);
1192 	gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (sw))),
1193 				      GTK_SHADOW_NONE);
1194 	gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0);
1195 
1196 	/* buttons to add/remove fields */
1197 	winfo = gdaui_data_proxy_info_new (GDAUI_DATA_PROXY (form_props),
1198 					   GDAUI_DATA_PROXY_INFO_ROW_MODIFY_BUTTONS);
1199 	gtk_box_pack_start (GTK_BOX (box), winfo, FALSE, FALSE, 0);
1200 
1201 	gtk_widget_show_all (box);
1202 
1203 	/* keep the selections in sync */
1204 	grid_iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (grid_fields));
1205 	form_iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (form_props));
1206 	g_signal_connect (grid_iter, "row-changed",
1207 			  G_CALLBACK (create_table_grid_fields_iter_row_changed_cb), form_iter);
1208 	g_signal_connect (form_iter, "row-changed",
1209 			  G_CALLBACK (create_table_grid_fields_iter_row_changed_cb), grid_iter);
1210 
1211 	gtk_widget_set_vexpand (hlayout, TRUE);
1212 
1213 	{
1214 		GtkActionGroup *group;
1215 		GtkAction *action;
1216 		group = gdaui_data_proxy_get_actions_group (GDAUI_DATA_PROXY (form_props));
1217 		action = gtk_action_group_get_action (group, "ActionNew");
1218 		g_object_set (G_OBJECT (action), "tooltip", _("Add a new field"), NULL);
1219 		action = gtk_action_group_get_action (group, "ActionDelete");
1220 		g_object_set (G_OBJECT (action), "tooltip", _("Remove selected field"), NULL);
1221 		action = gtk_action_group_get_action (group, "ActionCommit");
1222 		gtk_action_set_visible (action, FALSE);
1223 		action = gtk_action_group_get_action (group, "ActionReset");
1224 		gtk_action_set_visible (action, FALSE);
1225 	}
1226 
1227 	return hlayout;
1228 }
1229 
1230 static void
1231 create_table_grid_fields_iter_row_changed_cb (GdaDataModelIter *iter1, gint row, GdaDataModelIter *iter2)
1232 {
1233 	g_signal_handlers_block_by_func (G_OBJECT (iter2),
1234 					 G_CALLBACK (create_table_grid_fields_iter_row_changed_cb), iter1);
1235 	gda_data_model_iter_move_to_row (iter2, row);
1236 	g_signal_handlers_unblock_by_func (G_OBJECT (iter2),
1237 					   G_CALLBACK (create_table_grid_fields_iter_row_changed_cb), iter1);
1238 }
1239 
1240 static void
1241 create_table_proxy_row_inserted_cb (GdaDataProxy *proxy, gint row, GdauiServerOperation *form)
1242 {
1243 	GdaDataModelIter *iter;
1244 	GdaHolder *holder;
1245 	GdaServerProvider *prov;
1246 	GdaConnection *cnc;
1247 	const gchar *type = NULL;
1248 
1249 	iter = gda_data_model_create_iter (GDA_DATA_MODEL (proxy));
1250 	gda_data_model_iter_move_to_row (iter, row);
1251 	holder = gda_set_get_nth_holder (GDA_SET (iter), 0);
1252 	gda_holder_set_value_str (holder, NULL, "fieldname", NULL);
1253 
1254 	g_object_get (form->priv->op, "connection", &cnc, "provider", &prov, NULL);
1255 	if (prov)
1256 		type = gda_server_provider_get_default_dbms_type (prov, cnc, G_TYPE_STRING);
1257 	holder = gda_set_get_nth_holder (GDA_SET (iter), 1);
1258 	gda_holder_set_value_str (holder, NULL, type ? type : "varchar", NULL);
1259 	if (cnc)
1260 		g_object_unref (cnc);
1261 	if (prov)
1262 		g_object_unref (prov);
1263 }
1264