1 /*
2  * Copyright (C) 2010 David King <davidk@openismus.com>
3  * Copyright (C) 2010 - 2011 Vivien Malerba <malerba@gnome-db.org>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 
21 #include <glib/gi18n-lib.h>
22 #include <string.h>
23 #include "data-widget.h"
24 #include "../browser-connection.h"
25 #include "../browser-spinner.h"
26 #include "../common/ui-formgrid.h"
27 #include "../browser-window.h"
28 #include "../support.h"
29 #include "data-source-editor.h"
30 #include "analyser.h"
31 
32 /*
33  * The DataPart structure represents the execution of a single DataSource
34  */
35 typedef struct {
36 	DataWidget *dwid;
37 	DataSource *source;
38 
39 	GtkWidget *top;
40 	GtkNotebook *nb; /* page 0: spinner
41 			    page 1 or 2, depends on @data_widget_page, @error_widget_page and @edit_widget_page*/
42 	gint data_widget_page;
43 	gint error_widget_page;
44 	gint edit_widget_page;
45 	gint edit_widget_previous_page;
46 
47 	BrowserSpinner *spinner;
48 	guint spinner_show_timer_id;
49 	GtkWidget *data_widget;
50 	GtkWidget *error_widget;
51 	GtkWidget *edit_widget;
52 	GdaSet *export_data;
53 
54 	GSList *dep_parts; /* list of DataPart which need to be re-run when anything in @export_data
55 			    * changes */
56 	GtkWidget *menu;
57 } DataPart;
58 #define DATA_PART(x) ((DataPart*)(x))
59 
60 static DataPart *data_part_find (DataWidget *dwid, DataSource *source);
61 static void data_part_free (DataPart *part, GSList *all_parts);
62 static void data_part_show_error (DataPart *part, GError *error);
63 
64 struct _DataWidgetPrivate {
65 	DataSourceManager *mgr;
66 	GtkWidget *top_nb; /* page 0 => error, page 1 => contents */
67 	GtkWidget *info_label;
68 	GtkWidget *contents_page_vbox;
69 	GtkWidget *contents_page;
70 	GSList *parts;
71 };
72 
73 static void data_widget_class_init (DataWidgetClass *klass);
74 static void data_widget_init       (DataWidget *dwid, DataWidgetClass *klass);
75 static void data_widget_dispose    (GObject *object);
76 static gboolean compute_sources_dependencies (DataPart *part, GError **error);
77 static void mgr_list_changed_cb (DataSourceManager *mgr, DataWidget *dwid);
78 
79 static GObjectClass *parent_class = NULL;
80 
81 /*
82  * DataWidget class implementation
83  */
84 static void
data_widget_class_init(DataWidgetClass * klass)85 data_widget_class_init (DataWidgetClass *klass)
86 {
87 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
88 
89 	parent_class = g_type_class_peek_parent (klass);
90 
91 	object_class->dispose = data_widget_dispose;
92 }
93 
94 
95 static void
data_widget_init(DataWidget * dwid,G_GNUC_UNUSED DataWidgetClass * klass)96 data_widget_init (DataWidget *dwid, G_GNUC_UNUSED DataWidgetClass *klass)
97 {
98 	g_return_if_fail (IS_DATA_WIDGET (dwid));
99 
100 	/* allocate private structure */
101 	dwid->priv = g_new0 (DataWidgetPrivate, 1);
102 
103 	gtk_orientable_set_orientation (GTK_ORIENTABLE (dwid), GTK_ORIENTATION_VERTICAL);
104 
105 	/* init Widgets's structure */
106 	dwid->priv->top_nb = gtk_notebook_new ();
107 	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (dwid->priv->top_nb), FALSE);
108 	gtk_notebook_set_show_border (GTK_NOTEBOOK (dwid->priv->top_nb), FALSE);
109 	gtk_box_pack_start (GTK_BOX (dwid), dwid->priv->top_nb, TRUE, TRUE, 0);
110 
111 	/* error page */
112 	GtkWidget *info;
113 	info = gtk_info_bar_new ();
114 	gtk_notebook_append_page (GTK_NOTEBOOK (dwid->priv->top_nb), info, NULL);
115 	dwid->priv->info_label = gtk_label_new ("");
116 	gtk_misc_set_alignment (GTK_MISC (dwid->priv->info_label), 0., -1);
117 	gtk_label_set_ellipsize (GTK_LABEL (dwid->priv->info_label), PANGO_ELLIPSIZE_END);
118 	gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (info))),
119 			   dwid->priv->info_label);
120 
121 	/* contents page */
122 	GtkWidget *vbox;
123 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
124 	gtk_notebook_append_page (GTK_NOTEBOOK (dwid->priv->top_nb), vbox, NULL);
125 	dwid->priv->contents_page_vbox = vbox;
126 
127 	gtk_widget_show_all (dwid->priv->top_nb);
128 }
129 
130 GType
data_widget_get_type(void)131 data_widget_get_type (void)
132 {
133 	static GType type = 0;
134 
135 	if (G_UNLIKELY (type == 0)) {
136 		static const GTypeInfo info = {
137 			sizeof (DataWidgetClass),
138 			(GBaseInitFunc) NULL,
139 			(GBaseFinalizeFunc) NULL,
140 			(GClassInitFunc) data_widget_class_init,
141 			NULL,
142 			NULL,
143 			sizeof (DataWidget),
144 			0,
145 			(GInstanceInitFunc) data_widget_init,
146 			0
147 		};
148 		type = g_type_register_static (GTK_TYPE_BOX, "DataWidget", &info, 0);
149 	}
150 	return type;
151 }
152 
153 static void
data_part_free_func(DataPart * part)154 data_part_free_func (DataPart *part)
155 {
156 	data_part_free (part, NULL);
157 }
158 
159 static void
data_widget_dispose(GObject * object)160 data_widget_dispose (GObject *object)
161 {
162 	DataWidget *dwid = (DataWidget*) object;
163 	if (dwid->priv) {
164 		if (dwid->priv->mgr) {
165 			g_signal_handlers_disconnect_by_func (dwid->priv->mgr,
166 							      G_CALLBACK (mgr_list_changed_cb), dwid);
167 			g_object_unref (dwid->priv->mgr);
168 		}
169 		if (dwid->priv->parts) {
170 			g_slist_foreach (dwid->priv->parts, (GFunc) data_part_free_func, NULL);
171 			g_slist_free (dwid->priv->parts);
172 		}
173 		g_free (dwid->priv);
174 		dwid->priv = NULL;
175 	}
176 	parent_class->dispose (object);
177 }
178 
179 static void source_exec_started_cb (DataSource *source, DataPart *part);
180 static void source_exec_finished_cb (DataSource *source, GError *error, DataPart *part);
181 static void data_source_menu_clicked_cb (GtkButton *button, DataPart *part);
182 
183 static DataPart *
create_or_reuse_part(DataWidget * dwid,DataSource * source,gboolean * out_reused)184 create_or_reuse_part (DataWidget *dwid, DataSource *source, gboolean *out_reused)
185 {
186 
187 	DataPart *part;
188 	*out_reused = FALSE;
189 
190 	part = data_part_find (dwid, source);
191 	if (part) {
192 		GtkWidget *parent;
193 		parent = gtk_widget_get_parent (part->top);
194 		if (parent) {
195 			g_object_ref ((GObject*) part->top);
196 			gtk_container_remove (GTK_CONTAINER (parent), part->top);
197 		}
198 		*out_reused = TRUE;
199 		return part;
200 	}
201 
202 	part = g_new0 (DataPart, 1);
203 	part->dwid = dwid;
204 	part->source = g_object_ref (source);
205 	part->edit_widget_previous_page = -1;
206 	g_signal_connect (source, "execution-started",
207 			  G_CALLBACK (source_exec_started_cb), part);
208 	g_signal_connect (source, "execution-finished",
209 			  G_CALLBACK (source_exec_finished_cb), part);
210 
211 	dwid->priv->parts = g_slist_append (dwid->priv->parts, part);
212 
213 	GtkWidget *vbox;
214 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
215 	part->top = vbox;
216 	g_object_ref_sink ((GObject*) part->top);
217 
218 	GtkWidget *header, *label, *button, *image;
219 	const gchar *cstr;
220 
221 	header = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
222 	gtk_box_pack_start (GTK_BOX (vbox), header, FALSE, FALSE, 0);
223 
224 	label = gtk_label_new ("");
225 	cstr = data_source_get_title (source);
226 	if (cstr) {
227 		gchar *tmp;
228 		tmp = g_markup_printf_escaped ("<b><small>%s</small></b>", cstr);
229 		gtk_label_set_markup (GTK_LABEL (label), tmp);
230 		g_free (tmp);
231 	}
232 	else
233 		gtk_label_set_markup (GTK_LABEL (label), "<b><small> </small></b>");
234 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
235 	gtk_widget_set_size_request (label, 150, -1);
236 	gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
237 	gtk_box_pack_start (GTK_BOX (header), label, TRUE, TRUE, 0);
238 
239 	image = gtk_image_new_from_pixbuf (browser_get_pixbuf_icon (BROWSER_ICON_MENU_INDICATOR));
240 	button = gtk_button_new ();
241 	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
242 	gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
243 	gtk_widget_set_name (button, "browser-tab-close-button");
244 	gtk_widget_set_tooltip_text (button, _("Link to other data"));
245 	g_signal_connect (button, "clicked",
246 			  G_CALLBACK (data_source_menu_clicked_cb), part);
247 
248 	gtk_container_add (GTK_CONTAINER (button), image);
249 	gtk_container_set_border_width (GTK_CONTAINER (button), 0);
250 	gtk_box_pack_start (GTK_BOX (header), button, FALSE, FALSE, 0);
251 
252 	GtkWidget *nb, *page;
253 	nb = gtk_notebook_new ();
254 	gtk_notebook_set_show_border (GTK_NOTEBOOK (nb), FALSE);
255 	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
256 	part->nb = GTK_NOTEBOOK (nb);
257 
258 	part->spinner = BROWSER_SPINNER (browser_spinner_new ());
259 	browser_spinner_set_size ((BrowserSpinner*) part->spinner, GTK_ICON_SIZE_LARGE_TOOLBAR);
260 	page = gtk_alignment_new (0.5, 0.5, 0., 0.);
261 	gtk_container_add (GTK_CONTAINER (page), (GtkWidget*) part->spinner);
262 	gtk_notebook_append_page (GTK_NOTEBOOK (nb), page, NULL);
263 	part->data_widget = NULL;
264 
265 	gtk_box_pack_start (GTK_BOX (vbox), nb, TRUE, TRUE, 0);
266 
267 	gtk_widget_show_all (vbox);
268 	if (data_source_execution_going_on (source))
269 		source_exec_started_cb (source, part);
270 
271 	return part;
272 }
273 
274 /* make a super-container to contain @size items: the list
275  * will have @size-2 paned widgets */
276 GSList *
make_paned_list(gint size,gboolean horiz)277 make_paned_list (gint size, gboolean horiz)
278 {
279 	GSList *list = NULL;
280 	gint i;
281 	GtkWidget *paned;
282 
283 	g_assert (size >= 2);
284 	paned = gtk_paned_new (horiz ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
285 	list = g_slist_prepend (list, paned);
286 
287 	for (i = 2; i < size; i++) {
288 		GtkWidget *paned2;
289 		paned2 = gtk_paned_new (horiz ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
290 		gtk_paned_add2 (GTK_PANED (paned), paned2);
291 		list = g_slist_prepend (list, paned2);
292 		paned = paned2;
293 	}
294 	return g_slist_reverse (list);
295 }
296 
297 static void
pack_in_paned_list(GSList * paned_list,gint length,gint pos,GtkWidget * wid)298 pack_in_paned_list (GSList *paned_list, gint length, gint pos, GtkWidget *wid)
299 {
300 	GtkPaned *paned;
301 	if (pos < length - 1) {
302 		paned = g_slist_nth_data (paned_list, pos);
303 		gtk_paned_add1 (paned, wid);
304 	}
305 	else {
306 		paned = g_slist_nth_data (paned_list, pos - 1);
307 		gtk_paned_add2 (paned, wid);
308 	}
309 }
310 
311 static void
remove_data_source_mitem_activated_cb(G_GNUC_UNUSED GtkMenuItem * mitem,DataPart * part)312 remove_data_source_mitem_activated_cb (G_GNUC_UNUSED GtkMenuItem *mitem, DataPart *part)
313 {
314 	data_source_manager_remove_source (part->dwid->priv->mgr, part->source);
315 }
316 
317 static void
data_source_props_activated_cb(GtkCheckMenuItem * mitem,DataPart * part)318 data_source_props_activated_cb (GtkCheckMenuItem *mitem, DataPart *part)
319 {
320 	if (gtk_check_menu_item_get_active (mitem)) {
321 		part->edit_widget_previous_page = gtk_notebook_get_current_page (part->nb);
322 		if (! part->edit_widget) {
323 			part->edit_widget = data_source_editor_new ();
324 			data_source_editor_set_readonly (DATA_SOURCE_EDITOR (part->edit_widget));
325 			part->edit_widget_page = gtk_notebook_append_page (part->nb, part->edit_widget,
326 									   NULL);
327 			gtk_widget_show (part->edit_widget);
328 		}
329 		data_source_editor_display_source (DATA_SOURCE_EDITOR (part->edit_widget),
330 						   part->source);
331 		gtk_notebook_set_current_page (part->nb, part->edit_widget_page);
332 	}
333 	else
334 		gtk_notebook_set_current_page (part->nb, part->edit_widget_previous_page);
335 }
336 
337 static void
data_source_menu_clicked_cb(G_GNUC_UNUSED GtkButton * button,DataPart * part)338 data_source_menu_clicked_cb (G_GNUC_UNUSED GtkButton *button, DataPart *part)
339 {
340 	if (! part->menu) {
341 		GtkWidget *menu, *mitem;
342 		GSList *added_list;
343 		menu = gtk_menu_new ();
344 		part->menu = menu;
345 
346 		mitem = gtk_menu_item_new_with_label (_("Remove data source"));
347 		g_signal_connect (mitem, "activate",
348 				  G_CALLBACK (remove_data_source_mitem_activated_cb), part);
349 		gtk_widget_show (mitem);
350 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
351 
352 		mitem = gtk_check_menu_item_new_with_label (_("Show data source's properties"));
353 		g_signal_connect (mitem, "activate",
354 				  G_CALLBACK (data_source_props_activated_cb), part);
355 		gtk_widget_show (mitem);
356 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
357 
358 		added_list = data_manager_add_proposals_to_menu (menu, part->dwid->priv->mgr,
359 								 part->source, GTK_WIDGET (part->dwid));
360 		if (added_list)
361 			g_slist_free (added_list);
362 	}
363 
364 	gtk_menu_popup (GTK_MENU (part->menu), NULL, NULL,
365                         NULL, NULL, 0,
366                         gtk_get_current_event_time ());
367 }
368 
369 static void
update_layout(DataWidget * dwid)370 update_layout (DataWidget *dwid)
371 {
372 	GSList *newparts_list = NULL;
373 	GArray *sources_array;
374 	GError *lerror = NULL;
375 	GtkWidget *new_contents;
376 
377 	new_contents = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
378 
379 	sources_array = data_source_manager_get_sources_array (dwid->priv->mgr, &lerror);
380 	if (!sources_array) {
381 		gchar *str;
382 		if (lerror && lerror->message)
383 			str = g_strdup_printf (_("Error: %s"), lerror->message);
384 		else {
385 			const GSList *list;
386 			list = data_source_manager_get_sources (dwid->priv->mgr);
387 			if (list)
388 				str = g_strdup_printf (_("Error: %s"), _("No detail"));
389 			else
390 				str = g_strdup_printf (_("Error: %s"), _("No data source defined"));
391 		}
392                 g_clear_error (&lerror);
393                 gtk_label_set_text (GTK_LABEL (dwid->priv->info_label), str);
394                 g_free (str);
395 		gtk_notebook_set_current_page (GTK_NOTEBOOK (dwid->priv->top_nb), 0);
396 		goto cleanups;
397 	}
398 
399 	if (sources_array->len == 1) {
400 		GArray *subarray = g_array_index (sources_array, GArray*, 0);
401 		if (subarray->len == 1) {
402 			DataPart *part;
403 			gboolean reused;
404 			DataSource *source;
405 			source = g_array_index (subarray, DataSource*, 0);
406 			part = create_or_reuse_part (dwid, source, &reused);
407 			gtk_box_pack_start (GTK_BOX (new_contents), part->top, TRUE, TRUE, 0);
408 			g_object_unref ((GObject*) part->top);
409 			newparts_list = g_slist_prepend (newparts_list, part);
410 			if (!reused) {
411 				if (compute_sources_dependencies (part, &lerror))
412 					data_source_execute (source, NULL);
413 				else {
414 					data_part_show_error (part, lerror);
415 					g_clear_error (&lerror);
416 				}
417 			}
418 		}
419 		else {
420 			GSList *paned_list;
421 			gsize i;
422 			paned_list = make_paned_list (subarray->len, FALSE);
423 			gtk_box_pack_start (GTK_BOX (new_contents),
424 					    GTK_WIDGET (paned_list->data), TRUE, TRUE, 0);
425 			for (i = 0; i < subarray->len; i++) {
426 				DataPart *part;
427 				gboolean reused;
428 				DataSource *source;
429 				source = g_array_index (subarray, DataSource*, i);
430 				part = create_or_reuse_part (dwid, source, &reused);
431 				pack_in_paned_list (paned_list, subarray->len, i, part->top);
432 				g_object_unref ((GObject*) part->top);
433 				newparts_list = g_slist_prepend (newparts_list, part);
434 				if (!reused) {
435 					if (compute_sources_dependencies (part, &lerror))
436 						data_source_execute (source, NULL);
437 					else {
438 						data_part_show_error (part, lerror);
439 						g_clear_error (&lerror);
440 					}
441 				}
442 			}
443 			g_slist_free (paned_list);
444 		}
445 	}
446 	else {
447 		GSList *top_paned_list;
448 		gsize j;
449 
450 		top_paned_list = make_paned_list (sources_array->len, TRUE);
451 		gtk_box_pack_start (GTK_BOX (new_contents),
452 				    GTK_WIDGET (top_paned_list->data), TRUE, TRUE, 0);
453 		for (j = 0; j < sources_array->len; j++) {
454 			GArray *subarray = g_array_index (sources_array, GArray*, j);
455 			DataSource *source;
456 
457 			if (subarray->len == 1) {
458 				DataPart *part;
459 				gboolean reused;
460 				source = g_array_index (subarray, DataSource*, 0);
461 				part = create_or_reuse_part (dwid, source, &reused);
462 				pack_in_paned_list (top_paned_list, sources_array->len, j, part->top);
463 				g_object_unref ((GObject*) part->top);
464 				newparts_list = g_slist_prepend (newparts_list, part);
465 				if (!reused) {
466 					if (compute_sources_dependencies (part, &lerror))
467 						data_source_execute (source, NULL);
468 					else {
469 						data_part_show_error (part, lerror);
470 						g_clear_error (&lerror);
471 					}
472 				}
473 			}
474 			else {
475 				GSList *paned_list;
476 				gsize i;
477 				paned_list = make_paned_list (subarray->len, FALSE);
478 				pack_in_paned_list (top_paned_list, sources_array->len, j,
479 						    GTK_WIDGET (paned_list->data));
480 				for (i = 0; i < subarray->len; i++) {
481 					DataPart *part;
482 					gboolean reused;
483 					source = g_array_index (subarray, DataSource*, i);
484 					part = create_or_reuse_part (dwid, source, &reused);
485 					pack_in_paned_list (paned_list, subarray->len, i, part->top);
486 					g_object_unref ((GObject*) part->top);
487 					newparts_list = g_slist_prepend (newparts_list, part);
488 					if (!reused) {
489 						if (compute_sources_dependencies (part, &lerror))
490 							data_source_execute (source, NULL);
491 						else {
492 							data_part_show_error (part, lerror);
493 							g_clear_error (&lerror);
494 						}
495 					}
496 				}
497 				g_slist_free (paned_list);
498 			}
499 		}
500 		g_slist_free (top_paned_list);
501 	}
502 	data_source_manager_destroy_sources_array (sources_array);
503 	gtk_notebook_set_current_page (GTK_NOTEBOOK (dwid->priv->top_nb), 1);
504 
505  cleanups:
506 	{
507 		GSList *list, *nlist = NULL;
508 		for (list = dwid->priv->parts; list; list = list->next) {
509 			if (!g_slist_find (newparts_list, list->data)) {
510 				/* useless part */
511 				data_part_free ((DataPart *) list->data, dwid->priv->parts);
512 			}
513 			else
514 				nlist = g_slist_prepend (nlist, list->data);
515 		}
516 		g_slist_free (newparts_list);
517 		g_slist_free (dwid->priv->parts);
518 		dwid->priv->parts = g_slist_reverse (nlist);
519 	}
520 
521 	gtk_box_pack_start (GTK_BOX (dwid->priv->contents_page_vbox), new_contents, TRUE, TRUE, 0);
522 	gtk_widget_show_all (new_contents);
523 	if (dwid->priv->contents_page)
524 		gtk_widget_destroy (dwid->priv->contents_page);
525 	dwid->priv->contents_page = new_contents;
526 }
527 
528 static void
mgr_list_changed_cb(G_GNUC_UNUSED DataSourceManager * mgr,DataWidget * dwid)529 mgr_list_changed_cb (G_GNUC_UNUSED DataSourceManager *mgr, DataWidget *dwid)
530 {
531 	update_layout (dwid);
532 }
533 
534 /**
535  * data_widget_new
536  *
537  * Returns: the newly created widget.
538  */
539 GtkWidget *
data_widget_new(DataSourceManager * mgr)540 data_widget_new (DataSourceManager *mgr)
541 {
542 	DataWidget *dwid;
543 
544 	g_return_val_if_fail (IS_DATA_SOURCE_MANAGER (mgr), NULL);
545 
546 	dwid = g_object_new (DATA_WIDGET_TYPE, NULL);
547 	dwid->priv->mgr = DATA_SOURCE_MANAGER (mgr);
548 	g_object_ref ((GObject*) dwid->priv->mgr);
549 	g_signal_connect (mgr, "list_changed",
550 			  G_CALLBACK (mgr_list_changed_cb), dwid);
551 
552 	update_layout (dwid);
553 	return GTK_WIDGET (dwid);
554 }
555 
556 static void
data_part_free(DataPart * part,GSList * all_parts)557 data_part_free (DataPart *part, GSList *all_parts)
558 {
559 	if (part->spinner_show_timer_id) {
560 		g_source_remove (part->spinner_show_timer_id);
561 		part->spinner_show_timer_id = 0;
562 	}
563 
564 	if (all_parts) {
565 		GSList *list;
566 		for (list = all_parts; list; list = list->next) {
567 			DataPart *apart = (DataPart *) list->data;
568 			if (apart == part)
569 				continue;
570 			apart->dep_parts = g_slist_remove_all (apart->dep_parts, part);
571 		}
572 	}
573 
574 	if (part->top)
575 		gtk_widget_destroy (part->top);
576 
577 	if (part->source) {
578 		g_signal_handlers_disconnect_by_func (part->source,
579 						      G_CALLBACK (source_exec_started_cb), part);
580 		g_signal_handlers_disconnect_by_func (part->source,
581 						      G_CALLBACK (source_exec_finished_cb), part);
582 		g_object_unref (part->source);
583 	}
584 
585 	if (part->export_data)
586 		g_object_unref (part->export_data);
587 	if (part->dep_parts)
588 		g_slist_free (part->dep_parts);
589 	if (part->menu)
590 		gtk_widget_destroy (part->menu);
591 	g_free (part);
592 }
593 
594 static DataPart *
data_part_find(DataWidget * dwid,DataSource * source)595 data_part_find (DataWidget *dwid, DataSource *source)
596 {
597 	GSList *list;
598 	for (list = dwid->priv->parts; list; list = list->next) {
599 		if (DATA_PART (list->data)->source == source)
600 			return DATA_PART (list->data);
601 	}
602 	return NULL;
603 }
604 
605 static void
data_part_show_error(DataPart * part,GError * error)606 data_part_show_error (DataPart *part, GError *error)
607 {
608 	gchar *tmp;
609 	g_assert (part);
610 	tmp = g_markup_printf_escaped ("\n<b>Error:\n</b>%s",
611 				       error && error->message ? error->message : _("no detail"));
612 	if (! part->error_widget) {
613 		part->error_widget = gtk_label_new ("");
614 		gtk_misc_set_alignment (GTK_MISC (part->error_widget), 0., 0.);
615 		part->error_widget_page = gtk_notebook_append_page (part->nb, part->error_widget,
616 								    NULL);
617 		gtk_widget_show (part->error_widget);
618 	}
619 	gtk_label_set_markup (GTK_LABEL (part->error_widget), tmp);
620 	g_free (tmp);
621 	gtk_notebook_set_current_page (part->nb, part->error_widget_page);
622 }
623 
624 static gboolean
source_exec_started_cb_timeout(DataPart * part)625 source_exec_started_cb_timeout (DataPart *part)
626 {
627 	gtk_notebook_set_current_page (part->nb, 0);
628 	browser_spinner_start (part->spinner);
629 	part->spinner_show_timer_id = 0;
630 	return FALSE; /* remove timer */
631 }
632 
633 static void
source_exec_started_cb(G_GNUC_UNUSED DataSource * source,DataPart * part)634 source_exec_started_cb (G_GNUC_UNUSED DataSource *source, DataPart *part)
635 {
636 	if (! part->spinner_show_timer_id)
637 		part->spinner_show_timer_id = g_timeout_add (300,
638 							     (GSourceFunc) source_exec_started_cb_timeout,
639 							     part);
640 }
641 
642 /*
643  * creates a new string where double underscores '__' are replaced by a single underscore '_'
644  */
645 static gchar *
replace_double_underscores(const gchar * str)646 replace_double_underscores (const gchar *str)
647 {
648         gchar **arr;
649         gchar *ret;
650 
651         arr = g_strsplit (str, "__", 0);
652         ret = g_strjoinv ("_", arr);
653         g_strfreev (arr);
654 
655         return ret;
656 }
657 
658 static void
customize_form_grid(UiFormGrid * cwid)659 customize_form_grid (UiFormGrid *cwid)
660 {
661 	GdauiRawGrid *grid;
662 	grid = ui_formgrid_get_grid_widget (UI_FORMGRID (cwid));
663 
664 	GList *columns, *list;
665 	columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (grid));
666 	for (list = columns; list; list = list->next) {
667 		/* reduce column's title */
668 		const gchar *title;
669 		GtkWidget *header;
670 		title = gtk_tree_view_column_get_title (GTK_TREE_VIEW_COLUMN (list->data));
671 		header = gtk_label_new ("");
672 		if (title) {
673 			gchar *tmp, *str;
674 			str = replace_double_underscores (title);
675 			tmp = g_markup_printf_escaped ("<small>%s</small>", str);
676 			g_free (str);
677 			gtk_label_set_markup (GTK_LABEL (header), tmp);
678 			g_free (tmp);
679 		}
680 		else
681 			gtk_label_set_markup (GTK_LABEL (header), "<small></small>");
682 		gtk_widget_show (header);
683 		gtk_tree_view_column_set_widget (GTK_TREE_VIEW_COLUMN (list->data),
684 						 header);
685 	}
686 
687 	/*if (!columns || !columns->next)*/
688 		gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (grid), FALSE);
689 	g_list_free (columns);
690 }
691 
692 
693 static void formgrid_data_set_changed_cb (UiFormGrid *cwid, DataPart *part);
694 static void data_part_selection_changed_cb (GdauiDataSelector *gdauidataselector, DataPart *part);
695 static void
source_exec_finished_cb(G_GNUC_UNUSED DataSource * source,GError * error,DataPart * part)696 source_exec_finished_cb (G_GNUC_UNUSED DataSource *source, GError *error, DataPart *part)
697 {
698 	if (part->spinner_show_timer_id) {
699 		g_source_remove (part->spinner_show_timer_id);
700 		part->spinner_show_timer_id = 0;
701 	}
702 	else
703 		browser_spinner_stop (part->spinner);
704 
705 #ifdef GDA_DEBUG_NO
706 	g_print ("==== Execution of source [%s] finished\n", data_source_get_title (part->source));
707 #endif
708 	if (error) {
709 		data_part_show_error (part, error);
710 		return;
711 	}
712 
713 	if (! part->data_widget) {
714 		GtkWidget *cwid, *wid;
715 		BrowserConnection *bcnc;
716 		bcnc = browser_window_get_connection ((BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) part->dwid));
717 		cwid = (GtkWidget*) data_source_create_grid (part->source);
718 		ui_formgrid_handle_user_prefs (UI_FORMGRID (cwid), bcnc,
719 					       data_source_get_statement (part->source));
720 		g_signal_connect (cwid, "data-set-changed",
721 				  G_CALLBACK (formgrid_data_set_changed_cb), part);
722 
723 		wid = (GtkWidget*) ui_formgrid_get_grid_widget (UI_FORMGRID (cwid));
724 		part->data_widget = wid;
725 		part->data_widget_page = gtk_notebook_append_page (part->nb, cwid, NULL);
726 		g_signal_connect (part->data_widget, "selection-changed",
727 				  G_CALLBACK (data_part_selection_changed_cb), part);
728 		gtk_widget_show (cwid);
729 
730 #ifdef GDA_DEBUG_NO
731 		g_print ("Creating data widget for source [%s]\n",
732 			 data_source_get_title (part->source));
733 #endif
734 
735 		/* compute part->export_data */
736 		formgrid_data_set_changed_cb (UI_FORMGRID (cwid), part);
737 	}
738 	else {
739 		GError *lerror = NULL;
740 		if (! compute_sources_dependencies (part, &lerror)) {
741 			data_part_show_error (part, lerror);
742 			g_clear_error (&lerror);
743 		}
744 	}
745 	gtk_notebook_set_current_page (part->nb, part->data_widget_page);
746 }
747 
748 static void
formgrid_data_set_changed_cb(UiFormGrid * cwid,DataPart * part)749 formgrid_data_set_changed_cb (UiFormGrid *cwid, DataPart *part)
750 {
751 	GtkWidget *wid;
752 	GArray *export_names;
753 
754 	customize_form_grid (cwid);
755 	wid = (GtkWidget*) ui_formgrid_get_grid_widget (UI_FORMGRID (cwid));
756 
757 	if (part->export_data) {
758 		g_object_unref (part->export_data);
759 		part->export_data = NULL;
760 	}
761 
762 #ifdef GDA_DEBUG_NO
763 	g_print ("+++++ Reset export_data for data source [%s]\n", data_source_get_id (part->source));
764 #endif
765 
766 	export_names = data_source_get_export_names (part->source);
767 	if (export_names && (export_names->len > 0)) {
768 		GSList *holders = NULL;
769 		GdaDataModel *model;
770 		GHashTable *export_columns;
771 		gsize i;
772 		GdaDataModelIter *iter;
773 
774 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (wid));
775 		g_object_get (wid, "model", &model, NULL);
776 
777 		export_columns = data_source_get_export_columns (part->source);
778 		for (i = 0; i < export_names->len; i++) {
779 			gint col;
780 			GdaHolder *bindto;
781 			col = GPOINTER_TO_INT (g_hash_table_lookup (export_columns,
782 								    g_array_index (export_names,
783 										   gchar*, i))) - 1;
784 			bindto = gda_data_model_iter_get_holder_for_field (iter, col);
785 			if (bindto) {
786 				GdaHolder *holder;
787 				holder = gda_holder_copy (bindto);
788 				g_object_set ((GObject*) holder, "id",
789 					      g_array_index (export_names, gchar*, i), NULL);
790 				holders = g_slist_prepend (holders, holder);
791 #ifdef GDA_DEBUG_NO
792 				g_print ("HOLDER [%s::%s]\n",
793 					 gda_holder_get_id (holder),
794 					 g_type_name (gda_holder_get_g_type (holder)));
795 #endif
796 				g_assert (gda_holder_set_bind (holder, bindto, NULL));
797 			}
798 		}
799 
800 		g_object_unref (model);
801 		if (holders) {
802 			part->export_data = gda_set_new (holders);
803 			g_slist_foreach (holders, (GFunc) g_object_unref, NULL);
804 			g_slist_free (holders);
805 		}
806 	}
807 
808 	GError *lerror = NULL;
809 	if (! compute_sources_dependencies (part, &lerror)) {
810 		data_part_show_error (part, lerror);
811 		g_clear_error (&lerror);
812 		lerror = NULL;
813 	}
814 
815 	if (part->dep_parts) {
816 		GSList *list;
817 		for (list = part->dep_parts; list; list = list->next) {
818 			if (! compute_sources_dependencies ((DataPart*) list->data, &lerror)) {
819 				data_part_show_error (part, lerror);
820 				g_clear_error (&lerror);
821 				lerror = NULL;
822 			}
823 		}
824 	}
825 }
826 
827 static void
data_part_selection_changed_cb(G_GNUC_UNUSED GdauiDataSelector * gdauidataselector,DataPart * part)828 data_part_selection_changed_cb (G_GNUC_UNUSED GdauiDataSelector *gdauidataselector, DataPart *part)
829 {
830 	if (part->export_data) {
831 		GSList *list;
832 #ifdef GDA_DEBUG_NO
833 		for (list = part->export_data->holders; list; list = list->next) {
834 			GdaHolder *holder = GDA_HOLDER (list->data);
835 			gchar *tmp;
836 			tmp = gda_value_stringify (gda_holder_get_value (holder));
837 			if (strlen (tmp) > 10)
838 				tmp [15] = 0;
839 			g_print ("%s=>[%s]\n", gda_holder_get_id (holder), tmp);
840 			g_free (tmp);
841 		}
842 #endif
843 
844 		for (list = part->dep_parts; list; list = list->next) {
845 			DataPart *spart = DATA_PART (list->data);
846 			data_source_execute (spart->source, NULL);
847 		}
848 	}
849 }
850 
851 static gboolean
compute_sources_dependencies(DataPart * part,GError ** error)852 compute_sources_dependencies (DataPart *part, GError **error)
853 {
854 	GdaSet *import;
855 	gboolean retval = TRUE;
856 
857 	import = data_source_get_import (part->source);
858 	if (!import)
859 		return TRUE;
860 
861 	GSList *holders;
862 	for (holders = import->holders; holders; holders = holders->next) {
863 		GdaHolder *holder = (GdaHolder*) holders->data;
864 		const gchar *hid = gda_holder_get_id (holder);
865 		GSList *list;
866 
867 		for (list = part->dwid->priv->parts; list; list = list->next) {
868 			DataPart *opart = DATA_PART (list->data);
869 			if (opart == part)
870 				continue;
871 
872 			GdaSet *export;
873 			GdaHolder *holder2;
874 			opart->dep_parts = g_slist_remove (opart->dep_parts, part);
875 			export = data_widget_get_export (part->dwid, opart->source);
876 			if (!export)
877 				continue;
878 
879 			holder2 = gda_set_get_holder (export, hid);
880 			if (holder2) {
881 				GError *lerror = NULL;
882 				if (! gda_holder_set_bind (holder, holder2, &lerror)) {
883 					if (retval) {
884 						if (lerror &&
885 						    (lerror->domain == GDA_HOLDER_ERROR) &&
886 						    (lerror->code == GDA_HOLDER_VALUE_TYPE_ERROR)) {
887 							g_set_error (error, GDA_HOLDER_ERROR,
888 								     GDA_HOLDER_VALUE_TYPE_ERROR,
889 								     _("Can't bind parameter '%s' of type '%s' "
890 								       "to a parameter of type '%s'"),
891 								     gda_holder_get_id (holder),
892 								     gda_g_type_to_string (gda_holder_get_g_type (holder)),
893 								     gda_g_type_to_string (gda_holder_get_g_type (holder2)));
894 							g_clear_error (&lerror);
895 						}
896 						else
897 							g_propagate_error (error, lerror);
898 						retval = FALSE;
899 					}
900 					else
901 						g_clear_error (&lerror);
902 				}
903 #ifdef GDA_DEBUG_NO
904 				else {
905 					g_print ("[%s].[%s] bound to [%s].[%s]\n",
906 						 data_source_get_id (part->source),
907 						 hid,
908 						 data_source_get_id (opart->source),
909 						 gda_holder_get_id (holder2));
910 				}
911 #endif
912 				opart->dep_parts = g_slist_append (opart->dep_parts, part);
913 				continue;
914 			}
915 		}
916 	}
917 
918 	return retval;
919 }
920 
921 /**
922  * data_widget_get_export
923  *
924  * Returns: the #GdaSet or %NULL, no ref held to it by the caller
925  */
926 GdaSet *
data_widget_get_export(DataWidget * dwid,DataSource * source)927 data_widget_get_export (DataWidget *dwid, DataSource *source)
928 {
929 	DataPart *part;
930 	g_return_val_if_fail (IS_DATA_WIDGET (dwid), NULL);
931 	g_return_val_if_fail (IS_DATA_SOURCE (source), NULL);
932 	part = data_part_find (dwid, source);
933 	if (!part) {
934 		g_warning ("Can't find DataPart for DataSource");
935 		return NULL;
936 	}
937 	return part->export_data;
938 }
939 
940 /**
941  * data_widget_rerun
942  */
943 void
data_widget_rerun(DataWidget * dwid)944 data_widget_rerun (DataWidget *dwid)
945 {
946 	GSList *parts;
947 	g_return_if_fail (IS_DATA_WIDGET (dwid));
948 
949 	for (parts = dwid->priv->parts; parts; parts = parts->next) {
950 		DataPart *part;
951 		part = (DataPart*) parts->data;
952 		data_source_execute (part->source, NULL);
953 	}
954 }
955