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 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 <string.h>
22 #include "gdaui-data-import.h"
23 #include <libgda/libgda.h>
24 #include <glib/gi18n-lib.h>
25 #include <libgda-ui/libgda-ui.h>
26 #include <libgda/binreloc/gda-binreloc.h>
27 
28 static void gdaui_data_import_class_init (GdauiDataImportClass *class);
29 static void gdaui_data_import_init (GdauiDataImport *wid);
30 static void gdaui_data_import_dispose (GObject *object);
31 
32 
33 enum {
34 	SEP_COMMA,
35 	SEP_SEMICOL,
36 	SEP_TAB,
37 	SEP_SPACE,
38 	SEP_PIPE,
39 	SEP_OTHER,
40 	SEP_LAST
41 };
42 
43 struct _GdauiDataImportPriv
44 {
45 	GdaDataModel  *model;
46 
47 	/* spec widgets */
48 	GtkWidget     *file_chooser;
49 	GtkWidget     *encoding_combo;
50 	GtkWidget     *first_line_check;
51 	GtkWidget     *sep_array [SEP_LAST];
52 	GtkWidget     *sep_other_entry;
53 
54 	/* preview widgets */
55 	GtkWidget     *preview_box;
56 	GtkWidget     *no_data_label;
57 	GtkWidget     *preview_grid;
58 };
59 
60 static void spec_changed_cb (GtkWidget *wid, GdauiDataImport *import);
61 
62 /* get a pointer to the parents to be able to call their destructor */
63 static GObjectClass *parent_class = NULL;
64 
65 GType
gdaui_data_import_get_type(void)66 gdaui_data_import_get_type (void)
67 {
68 	static GType type = 0;
69 
70 	if (G_UNLIKELY (type == 0)) {
71 		static const GTypeInfo info = {
72 			sizeof (GdauiDataImportClass),
73 			(GBaseInitFunc) NULL,
74 			(GBaseFinalizeFunc) NULL,
75 			(GClassInitFunc) gdaui_data_import_class_init,
76 			NULL,
77 			NULL,
78 			sizeof (GdauiDataImport),
79 			0,
80 			(GInstanceInitFunc) gdaui_data_import_init,
81 			0
82 		};
83 
84 		type = g_type_register_static (GTK_TYPE_PANED, "GdauiDataImport", &info, 0);
85 	}
86 
87 	return type;
88 }
89 
90 static void
gdaui_data_import_class_init(GdauiDataImportClass * class)91 gdaui_data_import_class_init (GdauiDataImportClass * class)
92 {
93 	GObjectClass   *object_class = G_OBJECT_CLASS (class);
94 
95 	parent_class = g_type_class_peek_parent (class);
96 
97 	object_class->dispose = gdaui_data_import_dispose;
98 }
99 
100 static void
gdaui_data_import_init(GdauiDataImport * import)101 gdaui_data_import_init (GdauiDataImport * import)
102 {
103 	GtkWidget *label, *vbox, *hbox;
104 	gchar *str;
105 	GtkWidget *grid, *entry;
106 	GtkFileFilter *filter;
107 	GdaDataModel *encs;
108 	GSList *encs_errors;
109 
110 	import->priv = g_new0 (GdauiDataImportPriv, 1);
111 	import->priv->model = NULL;
112 
113 	gtk_orientable_set_orientation (GTK_ORIENTABLE (import), GTK_ORIENTATION_VERTICAL);
114 
115 	/*
116 	 * top part: import specs.
117 	 */
118 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
119         gtk_paned_pack1 (GTK_PANED (import), vbox, FALSE, FALSE);
120 
121 	str = g_strdup_printf ("<b>%s:</b>", _("Import specifications"));
122         label = gtk_label_new ("");
123         gtk_label_set_markup (GTK_LABEL (label), str);
124         gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
125         g_free (str);
126         gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
127 
128         hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); /* HIG */
129         gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
130         gtk_widget_show (hbox);
131         label = gtk_label_new ("    ");
132         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
133 
134 	grid = gtk_grid_new ();
135 	gtk_box_pack_start (GTK_BOX (hbox), grid, TRUE, TRUE, 0);
136 	gtk_grid_set_column_spacing (GTK_GRID (grid), 5);
137 	gtk_grid_set_row_spacing (GTK_GRID (grid), 5);
138 
139 	/* file to import from */
140 	label = gtk_label_new (_("File to import from:"));
141 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
142 	gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
143 
144 	entry = gtk_file_chooser_button_new (_("File to import data from"), GTK_FILE_CHOOSER_ACTION_OPEN);
145 	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (entry), gdaui_get_default_path ());
146 	import->priv->file_chooser = entry;
147 	filter = gtk_file_filter_new ();
148 	gtk_file_filter_set_name (filter, _("Comma separated values"));
149 	gtk_file_filter_add_pattern (filter, "*.csv");
150 	gtk_file_filter_add_pattern (filter, "*.txt");
151 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (entry), filter);
152 	filter = gtk_file_filter_new ();
153 	gtk_file_filter_set_name (filter, _("XML exported"));
154 	gtk_file_filter_add_pattern (filter, "*.xml");
155 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (entry), filter);
156 	filter = gtk_file_filter_new ();
157 	gtk_file_filter_set_name (filter, _("All files"));
158 	gtk_file_filter_add_pattern (filter, "*");
159 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (entry), filter);
160 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 0, 3, 1);
161 	g_signal_connect (G_OBJECT (entry), "selection-changed",
162 			  G_CALLBACK (spec_changed_cb), import);
163 
164 	/* Encoding */
165 	label = gtk_label_new (_("Encoding:"));
166 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
167 	gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
168 
169 	gchar *fname = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, "import_encodings.xml", NULL);
170 	encs = gda_data_model_import_new_file (fname, TRUE, NULL);
171 	g_free (fname);
172 	encs_errors = gda_data_model_import_get_errors (GDA_DATA_MODEL_IMPORT (encs));
173 	if (encs_errors) {
174 		for (; encs_errors; encs_errors = g_slist_next (encs_errors)) {
175 			gda_log_message ("Error importing import_encodings.xml: %s\n",
176 					 encs_errors->data && ((GError *) encs_errors->data)->message ?
177 					 ((GError *) encs_errors->data)->message : _("no detail"));
178 		}
179 		import->priv->encoding_combo = NULL;
180 	}
181 	else {
182 		gint cols[] = {0};
183 		entry = gdaui_combo_new_with_model (encs, 1, cols);
184 		import->priv->encoding_combo = entry;
185 	}
186 	g_object_unref (encs);
187 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 1, 3, 1);
188 	g_signal_connect (G_OBJECT (entry), "changed",
189 			  G_CALLBACK (spec_changed_cb), import);
190 
191 	/* first line as title */
192 	label = gtk_label_new (_("First line as title:"));
193 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
194 	gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
195 
196 	entry = gtk_check_button_new ();
197 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (entry), TRUE);
198 	import->priv->first_line_check = entry;
199 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 2, 2, 1);
200 	g_signal_connect (G_OBJECT (entry), "toggled",
201 			  G_CALLBACK (spec_changed_cb), import);
202 
203 	/* separator */
204 	label = gtk_label_new (_("Separator:"));
205 	gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
206 	gtk_grid_attach (GTK_GRID (grid), label, 0, 3, 1, 1);
207 
208 	entry = gtk_radio_button_new_with_label (NULL, _("Comma"));
209 	import->priv->sep_array [SEP_COMMA] = entry;
210 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 3, 1, 1);
211 	g_object_set_data (G_OBJECT (entry), "_sep", ",");
212 	g_signal_connect (G_OBJECT (entry), "toggled",
213 			  G_CALLBACK (spec_changed_cb), import);
214 
215 	entry = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (entry), _("Semi colon"));
216 	import->priv->sep_array [SEP_SEMICOL] = entry;
217 	gtk_grid_attach (GTK_GRID (grid), entry, 2, 3, 1, 1);
218 	g_object_set_data (G_OBJECT (entry), "_sep", ";");
219 	g_signal_connect (G_OBJECT (entry), "toggled",
220 			  G_CALLBACK (spec_changed_cb), import);
221 
222 	entry = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (entry), _("Tab"));
223 	import->priv->sep_array [SEP_TAB] = entry;
224 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 4, 1, 1);
225 	g_object_set_data (G_OBJECT (entry), "_sep", "\t");
226 	g_signal_connect (G_OBJECT (entry), "toggled",
227 			  G_CALLBACK (spec_changed_cb), import);
228 
229 	entry = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (entry), _("Space"));
230 	import->priv->sep_array [SEP_SPACE] = entry;
231 	gtk_grid_attach (GTK_GRID (grid), entry, 2, 4, 1, 1);
232 	g_object_set_data (G_OBJECT (entry), "_sep", " ");
233 	g_signal_connect (G_OBJECT (entry), "toggled",
234 			  G_CALLBACK (spec_changed_cb), import);
235 
236 	entry = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (entry), _("Pipe"));
237 	import->priv->sep_array [SEP_PIPE] = entry;
238 	gtk_grid_attach (GTK_GRID (grid), entry, 1, 5, 1, 1);
239 	g_object_set_data (G_OBJECT (entry), "_sep", "|");
240 	g_signal_connect (G_OBJECT (entry), "toggled",
241 			  G_CALLBACK (spec_changed_cb), import);
242 
243 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
244 	gtk_grid_attach (GTK_GRID (grid), hbox, 2, 5, 1, 1);
245 	entry = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (entry), _("Other:"));
246 	import->priv->sep_array [SEP_OTHER] = entry;
247 	gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
248 	g_object_set_data (G_OBJECT (entry), "_sep", "");
249 	g_signal_connect (G_OBJECT (entry), "toggled",
250 			  G_CALLBACK (spec_changed_cb), import);
251 
252 	entry = gtk_entry_new ();
253 	import->priv->sep_other_entry = entry;
254 	gtk_entry_set_max_length (GTK_ENTRY (entry), 1);
255 	gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
256 	gtk_widget_set_sensitive (entry, FALSE);
257 	g_signal_connect (G_OBJECT (entry), "changed",
258 			  G_CALLBACK (spec_changed_cb), import);
259 
260         gtk_widget_show_all (vbox);
261 
262 
263 	/*
264 	 * bottom part: import preview
265 	 */
266 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
267         gtk_paned_pack2 (GTK_PANED (import), vbox, TRUE, FALSE);
268 
269 	str = g_strdup_printf ("<b>%s:</b>", _("Import preview"));
270         label = gtk_label_new ("");
271         gtk_label_set_markup (GTK_LABEL (label), str);
272         gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
273         g_free (str);
274         gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
275 
276         hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); /* HIG */
277 	import->priv->preview_box = hbox;
278 
279         gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
280         gtk_widget_show (hbox);
281         label = gtk_label_new ("    ");
282         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
283 
284         label = gtk_label_new ("");
285         gtk_label_set_markup (GTK_LABEL (label), _("No data."));
286         gtk_misc_set_alignment (GTK_MISC (label), 0., 0.);
287         gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
288 	import->priv->no_data_label = label;
289 
290 	gtk_widget_show_all (vbox);
291 
292 	gtk_paned_set_position (GTK_PANED (import), 1);
293 }
294 
295 /**
296  * gdaui_data_import_new
297  *
298  * Creates a new #GdauiDataImport widget. After import, a #GdaDataModel will be created.
299  *
300  * Returns: the new widget
301  */
302 GtkWidget *
gdaui_data_import_new(void)303 gdaui_data_import_new (void)
304 {
305 	GdauiDataImport *import;
306 
307 	import = GDAUI_DATA_IMPORT (g_object_new (GDAUI_TYPE_DATA_IMPORT, NULL));
308 
309 	return GTK_WIDGET (import);
310 }
311 
312 
313 static void
gdaui_data_import_dispose(GObject * object)314 gdaui_data_import_dispose (GObject *object)
315 {
316 	GdauiDataImport *import;
317 
318 	g_return_if_fail (object != NULL);
319 	g_return_if_fail (GDAUI_IS_DATA_IMPORT (object));
320 	import = GDAUI_DATA_IMPORT (object);
321 
322 	if (import->priv) {
323 		if (import->priv->model) {
324 			g_object_unref (import->priv->model);
325 			import->priv->model = NULL;
326 		}
327 
328 		/* the private area itself */
329 		g_free (import->priv);
330 		import->priv = NULL;
331 	}
332 
333 	/* for the parent class */
334 	parent_class->dispose (object);
335 }
336 
337 static void
spec_changed_cb(GtkWidget * wid,GdauiDataImport * import)338 spec_changed_cb (GtkWidget *wid, GdauiDataImport *import)
339 {
340 	gchar *file;
341 	GdaSet *options;
342 	gchar *sep;
343 	GdaHolder *psep = NULL;
344 	gint sepno;
345 
346 	if (import->priv->preview_grid) {
347 		gtk_widget_destroy (import->priv->preview_grid);
348 		import->priv->preview_grid = NULL;
349 	}
350 	if (import->priv->model) {
351 		g_object_unref (import->priv->model);
352 		import->priv->model = NULL;
353 	}
354 
355 	sep = g_object_get_data (G_OBJECT (wid), "_sep");
356 	if (sep) {
357 		if (*sep == 0)
358 			gtk_widget_set_sensitive (import->priv->sep_other_entry,
359 						  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wid)));
360 
361 		if (! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wid)))
362 			return;
363 	}
364 
365 	for (sepno = SEP_COMMA; sepno < SEP_LAST; sepno++) {
366 		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (import->priv->sep_array [sepno]))) {
367 			sep = g_object_get_data (G_OBJECT (import->priv->sep_array [sepno]), "_sep");
368 			psep = gda_holder_new (G_TYPE_STRING);
369 			g_object_set (G_OBJECT (psep), "id", "SEPARATOR", NULL);
370 			if (sepno != SEP_OTHER)
371 				gda_holder_set_value_str (psep, NULL, sep, NULL);
372 			else
373 				gda_holder_set_value_str (psep, NULL,
374 							  gtk_entry_get_text (GTK_ENTRY (import->priv->sep_other_entry)),
375 							  NULL);
376 			break;
377 		}
378 	}
379 
380 	options = gda_set_new (NULL);
381 	if (psep) {
382 		gda_set_add_holder (options, psep);
383 		g_object_unref (psep);
384 	}
385 
386 	if (import->priv->encoding_combo) {
387 		GdaDataModelIter *iter;
388 
389 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (import->priv->encoding_combo));;
390 		if (iter) {
391 			GdaHolder *h;
392 			h = g_object_new (GDA_TYPE_HOLDER, "id", "ENCODING", "g-type", G_TYPE_STRING, NULL);
393 
394 			gda_holder_set_value (h, (GValue *) gda_data_model_iter_get_value_at (iter, 0), NULL);
395 			gda_set_add_holder (options, h);
396 			g_object_unref (h);
397 		}
398 	}
399 
400 	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (import->priv->first_line_check))) {
401 			GdaHolder *h;
402 			h = gda_holder_new_inline (G_TYPE_BOOLEAN, "TITLE_AS_FIRST_LINE", TRUE);
403 			gda_set_add_holder (options, h);
404 			g_object_unref (h);
405 	}
406 
407 	file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (import->priv->file_chooser));
408 	if (file) {
409 		import->priv->model = gda_data_model_import_new_file (file, TRUE, options);
410 		g_free (file);
411 	}
412 
413 	if (options)
414 		g_object_unref (options);
415 
416 	if (import->priv->model) {
417 		gtk_widget_hide (import->priv->no_data_label);
418 		import->priv->preview_grid = gdaui_grid_new (import->priv->model);
419 
420 		gdaui_data_proxy_column_show_actions (GDAUI_DATA_PROXY (import->priv->preview_grid), -1, FALSE);
421 		gdaui_grid_set_sample_size (GDAUI_GRID (import->priv->preview_grid), 50);
422 		g_object_set (G_OBJECT (import->priv->preview_grid), "info-flags",
423 			      GDAUI_DATA_PROXY_INFO_CHUNCK_CHANGE_BUTTONS |
424 			      GDAUI_DATA_PROXY_INFO_CURRENT_ROW, NULL);
425 		gtk_box_pack_start (GTK_BOX (import->priv->preview_box), import->priv->preview_grid, TRUE, TRUE, 0);
426 		gtk_widget_show (import->priv->preview_grid);
427 		gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (import->priv->file_chooser)));
428 	}
429 	else
430 		gtk_widget_show (import->priv->no_data_label);
431 }
432 
433 /**
434  * gdaui_data_import_get_model
435  * @import: a #GdauiDataImport widget
436  *
437  * Get the current imported data model. The caller has to reference it if needed.
438  *
439  * Returns: the #GdaDataModel, or %NULL
440  */
441 GdaDataModel *
gdaui_data_import_get_model(GdauiDataImport * import)442 gdaui_data_import_get_model (GdauiDataImport *import)
443 {
444 	g_return_val_if_fail (GDAUI_IS_DATA_IMPORT (import), NULL);
445 	return import->priv->model;
446 }
447