1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2015, 2016, 2017, 2018, 2020 Free Software Foundation
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <gtk/gtk.h>
20
21 #include "data/casereader.h"
22 #include "data/data-in.h"
23 #include "data/data-out.h"
24 #include "data/dictionary.h"
25 #include "data/format-guesser.h"
26 #include "data/format.h"
27 #include "data/gnumeric-reader.h"
28 #include "data/ods-reader.h"
29 #include "data/spreadsheet-reader.h"
30 #include "data/value-labels.h"
31 #include "data/casereader-provider.h"
32
33 #include "libpspp/i18n.h"
34 #include "libpspp/line-reader.h"
35 #include "libpspp/message.h"
36 #include "libpspp/hmap.h"
37 #include "libpspp/hash-functions.h"
38 #include "libpspp/str.h"
39
40 #include "builder-wrapper.h"
41
42 #include "psppire-data-sheet.h"
43 #include "psppire-data-store.h"
44 #include "psppire-dialog.h"
45 #include "psppire-delimited-text.h"
46 #include "psppire-dict.h"
47 #include "psppire-encoding-selector.h"
48 #include "psppire-import-assistant.h"
49 #include "psppire-scanf.h"
50 #include "psppire-spreadsheet-model.h"
51 #include "psppire-text-file.h"
52 #include "psppire-variable-sheet.h"
53
54 #include "ui/syntax-gen.h"
55
56 #include <gettext.h>
57 #define _(msgid) gettext (msgid)
58 #define N_(msgid) msgid
59
60 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
61
62
63 /* Chooses a name for each column on the separators page */
64 static void choose_column_names (PsppireImportAssistant *ia);
65
66 static void intro_page_create (PsppireImportAssistant *ia);
67 static void first_line_page_create (PsppireImportAssistant *ia);
68
69 static void separators_page_create (PsppireImportAssistant *ia);
70 static void formats_page_create (PsppireImportAssistant *ia);
71
72 static void psppire_import_assistant_init (PsppireImportAssistant *act);
73 static void psppire_import_assistant_class_init (PsppireImportAssistantClass *class);
74
75 G_DEFINE_TYPE (PsppireImportAssistant, psppire_import_assistant, GTK_TYPE_ASSISTANT);
76
77
78 /* Properties */
79 enum
80 {
81 PROP_0,
82 };
83
84 static void
psppire_import_assistant_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)85 psppire_import_assistant_set_property (GObject *object,
86 guint prop_id,
87 const GValue *value,
88 GParamSpec *pspec)
89 {
90 // PsppireImportAssistant *act = PSPPIRE_IMPORT_ASSISTANT (object);
91
92 switch (prop_id)
93 {
94 default:
95 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96 break;
97 };
98 }
99
100
101 static void
psppire_import_assistant_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)102 psppire_import_assistant_get_property (GObject *object,
103 guint prop_id,
104 GValue *value,
105 GParamSpec *pspec)
106 {
107 // PsppireImportAssistant *assistant = PSPPIRE_IMPORT_ASSISTANT (object);
108
109 switch (prop_id)
110 {
111 default:
112 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
113 break;
114 };
115 }
116
117 static GObjectClass * parent_class = NULL;
118
119
120 static void
psppire_import_assistant_finalize(GObject * object)121 psppire_import_assistant_finalize (GObject *object)
122 {
123 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (object);
124
125 if (ia->spreadsheet)
126 spreadsheet_unref (ia->spreadsheet);
127
128 ds_destroy (&ia->quotes);
129
130 dict_unref (ia->dict);
131 dict_unref (ia->casereader_dict);
132
133 g_object_unref (ia->builder);
134
135 ia->response = -1;
136 g_main_loop_unref (ia->main_loop);
137
138 if (G_OBJECT_CLASS (parent_class)->finalize)
139 G_OBJECT_CLASS (parent_class)->finalize (object);
140 }
141
142
143 static void
psppire_import_assistant_class_init(PsppireImportAssistantClass * class)144 psppire_import_assistant_class_init (PsppireImportAssistantClass *class)
145 {
146 GObjectClass *object_class = G_OBJECT_CLASS (class);
147
148 parent_class = g_type_class_peek_parent (class);
149
150 object_class->set_property = psppire_import_assistant_set_property;
151 object_class->get_property = psppire_import_assistant_get_property;
152
153 object_class->finalize = psppire_import_assistant_finalize;
154 }
155
156
157 /* Causes the assistant to close, returning RESPONSE for
158 interpretation by text_data_import_assistant. */
159 static void
close_assistant(PsppireImportAssistant * ia,int response)160 close_assistant (PsppireImportAssistant *ia, int response)
161 {
162 ia->response = response;
163 g_main_loop_quit (ia->main_loop);
164 gtk_widget_hide (GTK_WIDGET (ia));
165 }
166
167
168 /* Called when the Paste button on the last page of the assistant
169 is clicked. */
170 static void
on_paste(GtkButton * button,PsppireImportAssistant * ia)171 on_paste (GtkButton *button, PsppireImportAssistant *ia)
172 {
173 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
174 }
175
176
177 /* Revises the contents of the fields tree view based on the
178 currently chosen set of separators. */
179 static void
revise_fields_preview(PsppireImportAssistant * ia)180 revise_fields_preview (PsppireImportAssistant *ia)
181 {
182 choose_column_names (ia);
183 }
184
185
186 struct separator
187 {
188 const char *name; /* Name (for use with get_widget_assert). */
189 gunichar c; /* Separator character. */
190 };
191
192 /* All the separators in the dialog box. */
193 static const struct separator separators[] =
194 {
195 {"space", ' '},
196 {"tab", '\t'},
197 {"bang", '!'},
198 {"colon", ':'},
199 {"comma", ','},
200 {"hyphen", '-'},
201 {"pipe", '|'},
202 {"semicolon", ';'},
203 {"slash", '/'},
204 };
205
206 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
207
208 struct separator_count_node
209 {
210 struct hmap_node node;
211 int occurance; /* The number of times the separator occurs in a line */
212 int quantity; /* The number of lines with this occurance */
213 };
214
215
216 /* Picks the most likely separator and quote characters based on
217 IA's file data. */
218 static void
choose_likely_separators(PsppireImportAssistant * ia)219 choose_likely_separators (PsppireImportAssistant *ia)
220 {
221 gint first_line = 0;
222 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
223
224 gboolean valid;
225 GtkTreeIter iter;
226 int j;
227
228 struct hmap count_map[SEPARATOR_CNT];
229 for (j = 0; j < SEPARATOR_CNT; ++j)
230 hmap_init (count_map + j);
231
232 GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
233
234 for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
235 valid;
236 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
237 {
238 gchar *line_text = NULL;
239 gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
240
241 gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
242
243 struct substring cs = ss_cstr (line_text);
244 for (;
245 UINT32_MAX != ss_first_mb (cs);
246 ss_get_mb (&cs))
247 {
248 ucs4_t character = ss_first_mb (cs);
249
250 int s;
251 for (s = 0; s < SEPARATOR_CNT; ++s)
252 {
253 if (character == separators[s].c)
254 counts[s]++;
255 }
256 }
257
258 int j;
259 for (j = 0; j < SEPARATOR_CNT; ++j)
260 {
261 if (counts[j] > 0)
262 {
263 struct separator_count_node *cn = NULL;
264 unsigned int hash = hash_int (counts[j], 0);
265 HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
266 {
267 if (cn->occurance == counts[j])
268 break;
269 }
270
271 if (cn == NULL)
272 {
273 struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
274 new_cn->occurance = counts[j];
275 new_cn->quantity = 1;
276 hmap_insert (&count_map[j], &new_cn->node, hash);
277 }
278 else
279 cn->quantity++;
280 }
281 }
282
283 free (line_text);
284 free (counts);
285 }
286 gtk_tree_path_free (p);
287
288 if (hmap_count (count_map) > 0)
289 {
290 int most_frequent = -1;
291 int largest = 0;
292 for (j = 0; j < SEPARATOR_CNT; ++j)
293 {
294 struct separator_count_node *cn;
295 HMAP_FOR_EACH (cn, struct separator_count_node, node, &count_map[j])
296 {
297 if (largest < cn->quantity)
298 {
299 largest = cn->quantity;
300 most_frequent = j;
301 }
302 }
303 hmap_destroy (&count_map[j]);
304 }
305
306 g_return_if_fail (most_frequent >= 0);
307
308 GtkWidget *toggle =
309 get_widget_assert (ia->builder, separators[most_frequent].name);
310 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
311 }
312 }
313
314 static void
repopulate_delimiter_columns(PsppireImportAssistant * ia)315 repopulate_delimiter_columns (PsppireImportAssistant *ia)
316 {
317 /* Remove all the columns */
318 while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
319 {
320 GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
321 gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
322 }
323
324 gint n_fields =
325 gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
326
327 /* ... and put them back again. */
328 gint f;
329 for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
330 f < n_fields; f++)
331 {
332 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
333
334 const gchar *title = NULL;
335
336 if (f == 0)
337 title = _("line");
338 else
339 {
340 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
341 {
342 title =
343 psppire_delimited_text_get_header_title
344 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
345 }
346 if (title == NULL)
347 title = _("var");
348 }
349
350 GtkTreeViewColumn *column =
351 gtk_tree_view_column_new_with_attributes (title,
352 renderer,
353 "text", f,
354 NULL);
355 g_object_set (column,
356 "resizable", TRUE,
357 "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
358 NULL);
359
360 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
361 }
362 }
363
364 static void
reset_tree_view_model(PsppireImportAssistant * ia)365 reset_tree_view_model (PsppireImportAssistant *ia)
366 {
367 GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
368 g_object_ref (tm);
369 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
370
371
372 repopulate_delimiter_columns (ia);
373
374 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
375 // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
376
377 g_object_unref (tm);
378 }
379
380 /* Called just before the separators page becomes visible in the
381 assistant, and when the Reset button is clicked. */
382 static void
prepare_separators_page(PsppireImportAssistant * ia,GtkWidget * page)383 prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page)
384 {
385 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
386 GTK_TREE_MODEL (ia->delimiters_model));
387
388 g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
389 G_CALLBACK (reset_tree_view_model), ia);
390
391
392 repopulate_delimiter_columns (ia);
393
394 revise_fields_preview (ia);
395 choose_likely_separators (ia);
396 }
397
398 /* Resets IA's intro page to its initial state. */
399 static void
reset_intro_page(PsppireImportAssistant * ia)400 reset_intro_page (PsppireImportAssistant *ia)
401 {
402 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
403 TRUE);
404 }
405
406
407
408 /* Clears the set of user-modified variables from IA's formats
409 substructure. This discards user modifications to variable
410 formats, thereby causing formats to revert to their
411 defaults. */
412 static void
reset_formats_page(PsppireImportAssistant * ia,GtkWidget * page)413 reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page)
414 {
415 }
416
417 static void prepare_formats_page (PsppireImportAssistant *ia);
418
419 /* Called when the Reset button is clicked. */
420 static void
on_reset(GtkButton * button,PsppireImportAssistant * ia)421 on_reset (GtkButton *button, PsppireImportAssistant *ia)
422 {
423 gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia));
424 {
425 GtkWidget *page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
426
427 page_func *on_reset = g_object_get_data (G_OBJECT (page), "on-reset");
428
429 if (on_reset)
430 on_reset (ia, page);
431 }
432 }
433
434
435 static gint
next_page_func(gint old_page,gpointer data)436 next_page_func (gint old_page, gpointer data)
437 {
438 return old_page + 1;
439 }
440
441
442 /* Called just before PAGE is displayed as the current page of
443 IMPORT_ASSISTANT, this updates IA content according to the new
444 page. */
445 static void
on_prepare(GtkAssistant * assistant,GtkWidget * page,PsppireImportAssistant * ia)446 on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia)
447 {
448 gtk_widget_show (ia->reset_button);
449 gtk_widget_hide (ia->paste_button);
450
451 gint pn = gtk_assistant_get_current_page (assistant);
452 gint previous_page_index = ia->current_page;
453
454 if (previous_page_index >= 0)
455 {
456 GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
457
458 if (pn > previous_page_index)
459 {
460 page_func *on_forward = g_object_get_data (G_OBJECT (closing_page), "on-forward");
461
462 if (on_forward)
463 on_forward (ia, closing_page);
464 }
465 else
466 {
467 page_func *on_back = g_object_get_data (G_OBJECT (closing_page), "on-back");
468
469 if (on_back)
470 on_back (ia, closing_page);
471 }
472 }
473
474 {
475 GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
476
477 page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
478
479 if (on_entering)
480 on_entering (ia, new_page);
481 }
482
483 ia->current_page = pn;
484 }
485
486 /* Called when the Cancel button in the assistant is clicked. */
487 static void
on_cancel(GtkAssistant * assistant,PsppireImportAssistant * ia)488 on_cancel (GtkAssistant *assistant, PsppireImportAssistant *ia)
489 {
490 close_assistant (ia, GTK_RESPONSE_CANCEL);
491 }
492
493 /* Called when the Apply button on the last page of the assistant
494 is clicked. */
495 static void
on_close(GtkAssistant * assistant,PsppireImportAssistant * ia)496 on_close (GtkAssistant *assistant, PsppireImportAssistant *ia)
497 {
498 close_assistant (ia, GTK_RESPONSE_APPLY);
499 }
500
501
502 static GtkWidget *
503 add_page_to_assistant (PsppireImportAssistant *ia,
504 GtkWidget *page, GtkAssistantPageType type, const gchar *);
505
506
507 static void
on_sheet_combo_changed(GtkComboBox * cb,PsppireImportAssistant * ia)508 on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
509 {
510 GtkTreeIter iter;
511 gchar *range = NULL;
512 GtkTreeModel *model = gtk_combo_box_get_model (cb);
513 GtkBuilder *builder = ia->builder;
514 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
515
516 gtk_combo_box_get_active_iter (cb, &iter);
517 gtk_tree_model_get (model, &iter, PSPPIRE_SPREADSHEET_MODEL_COL_RANGE, &range, -1);
518 gtk_entry_set_text (GTK_ENTRY (range_entry), range ? range : "");
519 g_free (range);
520 }
521
522 /* Prepares IA's sheet_spec page. */
523 static void
prepare_sheet_spec_page(PsppireImportAssistant * ia)524 prepare_sheet_spec_page (PsppireImportAssistant *ia)
525 {
526 GtkBuilder *builder = ia->builder;
527 GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
528 GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
529
530 GtkTreeModel *model = psppire_spreadsheet_model_new (ia->spreadsheet);
531 gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), model);
532
533 gint items = gtk_tree_model_iter_n_children (model, NULL);
534 gtk_widget_set_sensitive (sheet_entry, items > 1);
535
536 gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
537
538 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
539 }
540
541
542 /* Initializes IA's sheet_spec substructure. */
543 static void
sheet_spec_page_create(PsppireImportAssistant * ia)544 sheet_spec_page_create (PsppireImportAssistant *ia)
545 {
546 GtkBuilder *builder = ia->builder;
547 GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
548
549 GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
550 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
551 gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
552 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
553 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
554 "text", 0,
555 NULL);
556
557 g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
558
559 add_page_to_assistant (ia, page,
560 GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
561
562 g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
563 }
564
565 static void
on_chosen(PsppireImportAssistant * ia,GtkWidget * page)566 on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
567 {
568 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
569 gchar *f = gtk_file_chooser_get_filename (fc);
570 int i;
571
572 for(i = gtk_assistant_get_n_pages (GTK_ASSISTANT (ia)); i > 0; --i)
573 gtk_assistant_remove_page (GTK_ASSISTANT (ia), i);
574
575 gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), FALSE);
576
577 if (f && g_file_test (f, G_FILE_TEST_IS_REGULAR))
578 {
579 gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE);
580
581 if (ia->spreadsheet)
582 spreadsheet_unref (ia->spreadsheet);
583
584 ia->spreadsheet = gnumeric_probe (f, FALSE);
585
586 if (!ia->spreadsheet)
587 ia->spreadsheet = ods_probe (f, FALSE);
588
589 if (ia->spreadsheet)
590 {
591 sheet_spec_page_create (ia);
592 }
593 else
594 {
595 intro_page_create (ia);
596 first_line_page_create (ia);
597 separators_page_create (ia);
598 }
599
600 formats_page_create (ia);
601 }
602
603 g_free (f);
604 }
605
606 /* This has to be done on a map signal callback,
607 because GtkFileChooserWidget resets everything when it is mapped. */
608 static void
on_map(PsppireImportAssistant * ia,GtkWidget * page)609 on_map (PsppireImportAssistant *ia, GtkWidget *page)
610 {
611 #if TEXT_FILE
612 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
613
614 if (ia->file_name)
615 gtk_file_chooser_set_filename (fc, ia->file_name);
616 #endif
617
618 on_chosen (ia, page);
619 }
620
621
622
623 static void
chooser_page_enter(PsppireImportAssistant * ia,GtkWidget * page)624 chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page)
625 {
626 }
627
628 static void
chooser_page_leave(PsppireImportAssistant * ia,GtkWidget * page)629 chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page)
630 {
631 g_free (ia->file_name);
632 ia->file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page));
633 gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
634
635 if (!ia->spreadsheet)
636 {
637 ia->text_file = psppire_text_file_new (ia->file_name, encoding);
638 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
639 GTK_TREE_MODEL (ia->text_file));
640 }
641
642
643 g_free (encoding);
644 }
645
646 static void
chooser_page_reset(PsppireImportAssistant * ia,GtkWidget * page)647 chooser_page_reset (PsppireImportAssistant *ia, GtkWidget *page)
648 {
649 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
650
651 gtk_file_chooser_set_filter (fc, ia->default_filter);
652 gtk_file_chooser_unselect_all (fc);
653
654 on_chosen (ia, page);
655 }
656
657
658 static void
on_file_activated(GtkFileChooser * chooser,PsppireImportAssistant * ia)659 on_file_activated (GtkFileChooser *chooser, PsppireImportAssistant *ia)
660 {
661 gtk_assistant_next_page (GTK_ASSISTANT (ia));
662 }
663
664 static void
chooser_page_create(PsppireImportAssistant * ia)665 chooser_page_create (PsppireImportAssistant *ia)
666 {
667 GtkFileFilter *filter = NULL;
668
669 GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
670
671 g_signal_connect (chooser, "file-activated", G_CALLBACK (on_file_activated), ia);
672
673 g_object_set_data (G_OBJECT (chooser), "on-forward", chooser_page_leave);
674 g_object_set_data (G_OBJECT (chooser), "on-reset", chooser_page_reset);
675 g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
676
677 g_object_set (chooser, "local-only", FALSE, NULL);
678
679
680 ia->default_filter = gtk_file_filter_new ();
681 gtk_file_filter_set_name (ia->default_filter, _("All Files"));
682 gtk_file_filter_add_pattern (ia->default_filter, "*");
683 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
684
685 filter = gtk_file_filter_new ();
686 gtk_file_filter_set_name (filter, _("Text Files"));
687 gtk_file_filter_add_mime_type (filter, "text/*");
688 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
689
690 filter = gtk_file_filter_new ();
691 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
692 gtk_file_filter_add_pattern (filter, "*.txt");
693 gtk_file_filter_add_pattern (filter, "*.TXT");
694 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
695
696 filter = gtk_file_filter_new ();
697 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
698 gtk_file_filter_add_mime_type (filter, "text/plain");
699 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
700
701 filter = gtk_file_filter_new ();
702 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
703 gtk_file_filter_add_mime_type (filter, "text/csv");
704 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
705
706 /* I've never encountered one of these, but it's listed here:
707 http://www.iana.org/assignments/media-types/text/tab-separated-values */
708 filter = gtk_file_filter_new ();
709 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
710 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
711 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
712
713 filter = gtk_file_filter_new ();
714 gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
715 gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
716 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
717
718 filter = gtk_file_filter_new ();
719 gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
720 gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
721 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
722
723 filter = gtk_file_filter_new ();
724 gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
725 gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
726 gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
727 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
728
729 ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
730 gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
731
732 add_page_to_assistant (ia, chooser,
733 GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
734
735 g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
736 g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
737 }
738
739
740
741 static void
psppire_import_assistant_init(PsppireImportAssistant * ia)742 psppire_import_assistant_init (PsppireImportAssistant *ia)
743 {
744 ia->builder = builder_new ("text-data-import.ui");
745
746 ia->current_page = -1 ;
747 ia->file_name = NULL;
748
749 ia->spreadsheet = NULL;
750 ia->dict = NULL;
751 ia->casereader_dict = NULL;
752
753 ia->main_loop = g_main_loop_new (NULL, TRUE);
754
755 g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
756 g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
757 g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
758
759 ia->paste_button = gtk_button_new_with_label (_("Paste"));
760 ia->reset_button = gtk_button_new_with_label (_("Reset"));
761
762 gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
763
764 g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
765 g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
766
767 gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
768
769 gtk_window_set_title (GTK_WINDOW (ia),
770 _("Importing Delimited Text Data"));
771
772 gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp");
773
774 chooser_page_create (ia);
775
776 gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
777
778 gtk_window_maximize (GTK_WINDOW (ia));
779 }
780
781
782 /* Appends a page of the given TYPE, with PAGE as its content, to
783 the GtkAssistant encapsulated by IA. Returns the GtkWidget
784 that represents the page. */
785 static GtkWidget *
add_page_to_assistant(PsppireImportAssistant * ia,GtkWidget * page,GtkAssistantPageType type,const gchar * title)786 add_page_to_assistant (PsppireImportAssistant *ia,
787 GtkWidget *page, GtkAssistantPageType type, const gchar *title)
788 {
789 GtkWidget *content = page;
790
791 gtk_assistant_append_page (GTK_ASSISTANT (ia), content);
792 gtk_assistant_set_page_type (GTK_ASSISTANT(ia), content, type);
793 gtk_assistant_set_page_title (GTK_ASSISTANT(ia), content, title);
794 gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), content, TRUE);
795
796 return content;
797 }
798
799
800 /* Called when one of the radio buttons is clicked. */
801 static void
on_intro_amount_changed(PsppireImportAssistant * p)802 on_intro_amount_changed (PsppireImportAssistant *p)
803 {
804 gtk_widget_set_sensitive (p->n_cases_spin,
805 gtk_toggle_button_get_active
806 (GTK_TOGGLE_BUTTON (p->n_cases_button)));
807
808 gtk_widget_set_sensitive (p->percent_spin,
809 gtk_toggle_button_get_active
810 (GTK_TOGGLE_BUTTON (p->percent_button)));
811 }
812
813 static void
on_treeview_selection_change(PsppireImportAssistant * ia)814 on_treeview_selection_change (PsppireImportAssistant *ia)
815 {
816 GtkTreeSelection *selection =
817 gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
818 GtkTreeModel *model = NULL;
819 GtkTreeIter iter;
820 if (gtk_tree_selection_get_selected (selection, &model, &iter))
821 {
822 gint max_lines;
823 int n;
824 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
825 gint *index = gtk_tree_path_get_indices (path);
826 n = *index;
827 gtk_tree_path_free (path);
828 g_object_get (model, "maximum-lines", &max_lines, NULL);
829 gtk_widget_set_sensitive (ia->variable_names_cb,
830 (n > 0 && n < max_lines));
831 ia->delimiters_model =
832 psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
833 g_object_set (ia->delimiters_model, "first-line", n, NULL);
834 }
835 }
836
837 static void
render_text_preview_line(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)838 render_text_preview_line (GtkTreeViewColumn *tree_column,
839 GtkCellRenderer *cell,
840 GtkTreeModel *tree_model,
841 GtkTreeIter *iter,
842 gpointer data)
843 {
844 /*
845 Set the text to a "insensitive" state if the row
846 is greater than what the user declared to be the maximum.
847 */
848 GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
849 gint *ii = gtk_tree_path_get_indices (path);
850 gint max_lines;
851 g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
852 g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
853 gtk_tree_path_free (path);
854 }
855
856 /* Initializes IA's first_line substructure. */
857 static void
first_line_page_create(PsppireImportAssistant * ia)858 first_line_page_create (PsppireImportAssistant *ia)
859 {
860 GtkWidget *w = get_widget_assert (ia->builder, "FirstLine");
861
862 g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
863
864 add_page_to_assistant (ia, w,
865 GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
866
867 GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller");
868
869 if (ia->first_line_tree_view == NULL)
870 {
871 ia->first_line_tree_view = gtk_tree_view_new ();
872 g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
873
874 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
875
876 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
877 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
878 "text", 0,
879 NULL);
880
881 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
882 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
883
884 renderer = gtk_cell_renderer_text_new ();
885 column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
886 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
887
888 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
889
890 g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
891 G_CALLBACK (on_treeview_selection_change), ia);
892 gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
893 }
894
895 gtk_widget_show_all (scrolled_window);
896
897 ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names");
898 }
899
900 static void
intro_on_leave(PsppireImportAssistant * ia)901 intro_on_leave (PsppireImportAssistant *ia)
902 {
903 gint lc = 0;
904 g_object_get (ia->text_file, "line-count", &lc, NULL);
905 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
906 {
907 gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
908 g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
909 }
910 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
911 {
912 gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
913 g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
914 }
915 else
916 {
917 g_object_set (ia->text_file, "maximum-lines", lc, NULL);
918 }
919 }
920
921
922 static void
intro_on_enter(PsppireImportAssistant * ia)923 intro_on_enter (PsppireImportAssistant *ia)
924 {
925 GtkBuilder *builder = ia->builder;
926 GtkWidget *table = get_widget_assert (builder, "button-table");
927
928 struct string s;
929
930 ds_init_empty (&s);
931 ds_put_cstr (&s, _("This assistant will guide you through the process of "
932 "importing data into PSPP from a text file with one line "
933 "per case, in which fields are separated by tabs, "
934 "commas, or other delimiters.\n\n"));
935
936 if (ia->text_file)
937 {
938 if (ia->text_file->total_is_exact)
939 {
940 ds_put_format (
941 &s, ngettext ("The selected file contains %'lu line of text. ",
942 "The selected file contains %'lu lines of text. ",
943 ia->text_file->total_lines),
944 ia->text_file->total_lines);
945 }
946 else if (ia->text_file->total_lines > 0)
947 {
948 ds_put_format (
949 &s, ngettext (
950 "The selected file contains approximately %'lu line of text. ",
951 "The selected file contains approximately %'lu lines of text. ",
952 ia->text_file->total_lines),
953 ia->text_file->total_lines);
954 ds_put_format (
955 &s, ngettext (
956 "Only the first %zu line of the file will be shown for "
957 "preview purposes in the following screens. ",
958 "Only the first %zu lines of the file will be shown for "
959 "preview purposes in the following screens. ",
960 ia->text_file->line_cnt),
961 ia->text_file->line_cnt);
962 }
963 }
964
965 ds_put_cstr (&s, _("You may choose below how much of the file should "
966 "actually be imported."));
967
968 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
969 ds_cstr (&s));
970 ds_destroy (&s);
971
972 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
973 {
974 GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
975 gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
976 1, 1,
977 1, 1);
978 }
979
980 GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
981 gtk_adjustment_set_lower (adj, 1.0);
982
983 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
984 {
985 GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
986 &ia->percent_spin);
987
988 gtk_grid_attach (GTK_GRID (table), hbox_percent,
989 1, 2,
990 1, 1);
991 }
992
993 gtk_widget_show_all (table);
994
995 on_intro_amount_changed (ia);
996 }
997
998 /* Initializes IA's intro substructure. */
999 static void
intro_page_create(PsppireImportAssistant * ia)1000 intro_page_create (PsppireImportAssistant *ia)
1001 {
1002 GtkBuilder *builder = ia->builder;
1003
1004 GtkWidget *w = get_widget_assert (builder, "Intro");
1005
1006 ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
1007
1008
1009 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
1010
1011 ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
1012
1013 ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
1014
1015 ia->percent_button = get_widget_assert (builder, "import-percent");
1016
1017 g_signal_connect_swapped (ia->all_cases_button, "toggled",
1018 G_CALLBACK (on_intro_amount_changed), ia);
1019 g_signal_connect_swapped (ia->n_cases_button, "toggled",
1020 G_CALLBACK (on_intro_amount_changed), ia);
1021 g_signal_connect_swapped (ia->percent_button, "toggled",
1022 G_CALLBACK (on_intro_amount_changed), ia);
1023
1024
1025 g_object_set_data (G_OBJECT (w), "on-forward", intro_on_leave);
1026 g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
1027 g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
1028 }
1029
1030
1031 GtkWidget *
psppire_import_assistant_new(GtkWindow * toplevel)1032 psppire_import_assistant_new (GtkWindow *toplevel)
1033 {
1034 return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_IMPORT_ASSISTANT,
1035 /* Some window managers (notably ratpoison)
1036 ignore the maximise command when a window is
1037 transient. This causes problems for this
1038 window. */
1039 /* "transient-for", toplevel, */
1040 NULL));
1041 }
1042
1043
1044
1045
1046
1047 /* Chooses a name for each column on the separators page */
1048 static void
choose_column_names(PsppireImportAssistant * ia)1049 choose_column_names (PsppireImportAssistant *ia)
1050 {
1051 int i;
1052 unsigned long int generated_name_count = 0;
1053 dict_clear (ia->dict);
1054
1055 for (i = 0;
1056 i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
1057 ++i)
1058 {
1059 const gchar *candidate_name = NULL;
1060
1061 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
1062 {
1063 candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
1064 }
1065
1066 char *name = dict_make_unique_var_name (ia->dict,
1067 candidate_name,
1068 &generated_name_count);
1069
1070 dict_create_var_assert (ia->dict, name, 0);
1071 free (name);
1072 }
1073 }
1074
1075 /* Called when the user toggles one of the separators
1076 checkboxes. */
1077 static void
on_separator_toggle(GtkToggleButton * toggle UNUSED,PsppireImportAssistant * ia)1078 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1079 PsppireImportAssistant *ia)
1080 {
1081 int i;
1082 GSList *delimiters = NULL;
1083 for (i = 0; i < SEPARATOR_CNT; i++)
1084 {
1085 const struct separator *s = &separators[i];
1086 GtkWidget *button = get_widget_assert (ia->builder, s->name);
1087 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1088 {
1089 delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c));
1090 }
1091 }
1092
1093 g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
1094
1095 revise_fields_preview (ia);
1096 }
1097
1098
1099 /* Called when the user changes the entry field for custom
1100 separators. */
1101 static void
on_separators_custom_entry_notify(GObject * gobject UNUSED,GParamSpec * arg1 UNUSED,PsppireImportAssistant * ia)1102 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1103 GParamSpec *arg1 UNUSED,
1104 PsppireImportAssistant *ia)
1105 {
1106 revise_fields_preview (ia);
1107 }
1108
1109 /* Called when the user toggles the checkbox that enables custom
1110 separators. */
1111 static void
on_separators_custom_cb_toggle(GtkToggleButton * custom_cb,PsppireImportAssistant * ia)1112 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1113 PsppireImportAssistant *ia)
1114 {
1115 bool is_active = gtk_toggle_button_get_active (custom_cb);
1116 gtk_widget_set_sensitive (ia->custom_entry, is_active);
1117 revise_fields_preview (ia);
1118 }
1119
1120 /* Called when the user changes the selection in the combo box
1121 that selects a quote character. */
1122 static void
on_quote_combo_change(GtkComboBox * combo,PsppireImportAssistant * ia)1123 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
1124 {
1125 // revise_fields_preview (ia);
1126 }
1127
1128 /* Called when the user toggles the checkbox that enables
1129 quoting. */
1130 static void
on_quote_cb_toggle(GtkToggleButton * quote_cb,PsppireImportAssistant * ia)1131 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
1132 {
1133 bool is_active = gtk_toggle_button_get_active (quote_cb);
1134 gtk_widget_set_sensitive (ia->quote_combo, is_active);
1135 revise_fields_preview (ia);
1136 }
1137
1138 /* Initializes IA's separators substructure. */
1139 static void
separators_page_create(PsppireImportAssistant * ia)1140 separators_page_create (PsppireImportAssistant *ia)
1141 {
1142 GtkBuilder *builder = ia->builder;
1143
1144 size_t i;
1145
1146 GtkWidget *w = get_widget_assert (builder, "Separators");
1147
1148 g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
1149 g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page);
1150
1151 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
1152
1153 ia->custom_cb = get_widget_assert (builder, "custom-cb");
1154 ia->custom_entry = get_widget_assert (builder, "custom-entry");
1155 ia->quote_combo = get_widget_assert (builder, "quote-combo");
1156 ia->quote_cb = get_widget_assert (builder, "quote-cb");
1157
1158 gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
1159
1160 if (ia->fields_tree_view == NULL)
1161 {
1162 GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller");
1163 ia->fields_tree_view = gtk_tree_view_new ();
1164 g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
1165 gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
1166 gtk_widget_show_all (scroller);
1167 }
1168
1169 g_signal_connect (ia->quote_combo, "changed",
1170 G_CALLBACK (on_quote_combo_change), ia);
1171 g_signal_connect (ia->quote_cb, "toggled",
1172 G_CALLBACK (on_quote_cb_toggle), ia);
1173 g_signal_connect (ia->custom_entry, "notify::text",
1174 G_CALLBACK (on_separators_custom_entry_notify), ia);
1175 g_signal_connect (ia->custom_cb, "toggled",
1176 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1177 for (i = 0; i < SEPARATOR_CNT; i++)
1178 g_signal_connect (get_widget_assert (builder, separators[i].name),
1179 "toggled", G_CALLBACK (on_separator_toggle), ia);
1180
1181 }
1182
1183
1184
1185
1186
1187
1188 static struct casereader_random_class my_casereader_class;
1189
1190 static struct ccase *
my_read(struct casereader * reader,void * aux,casenumber idx)1191 my_read (struct casereader *reader, void *aux, casenumber idx)
1192 {
1193 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
1194 GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
1195
1196 GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
1197
1198 const struct caseproto *proto = casereader_get_proto (reader);
1199
1200 GtkTreeIter iter;
1201 struct ccase *c = NULL;
1202 if (gtk_tree_model_get_iter (tm, &iter, tp))
1203 {
1204 c = case_create (proto);
1205 int i;
1206 for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
1207 {
1208 GValue value = {0};
1209 gtk_tree_model_get_value (tm, &iter, i + 1, &value);
1210
1211 const struct variable *var = dict_get_var (ia->casereader_dict, i);
1212
1213 const gchar *ss = g_value_get_string (&value);
1214 if (ss)
1215 {
1216 union value *v = case_data_rw (c, var);
1217 /* In this reader we derive the union value from the
1218 string in the tree_model. We retrieve the width and format
1219 from a dictionary which is stored directly after
1220 the reader creation. Changes in ia->dict in the
1221 variable window are not reflected here and therefore
1222 this is always compatible with the width in the
1223 caseproto. See bug #58298 */
1224 char *xx = data_in (ss_cstr (ss),
1225 "UTF-8",
1226 var_get_write_format (var)->type,
1227 v, var_get_width (var), "UTF-8");
1228
1229 /* if (xx) */
1230 /* g_print ("%s:%d Err %s\n", __FILE__, __LINE__, xx); */
1231 free (xx);
1232 }
1233 g_value_unset (&value);
1234 }
1235 }
1236
1237 gtk_tree_path_free (tp);
1238
1239 return c;
1240 }
1241
1242 static void
my_destroy(struct casereader * reader,void * aux)1243 my_destroy (struct casereader *reader, void *aux)
1244 {
1245 g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
1246 }
1247
1248 static void
my_advance(struct casereader * reader,void * aux,casenumber cnt)1249 my_advance (struct casereader *reader, void *aux, casenumber cnt)
1250 {
1251 g_print ("%s:%d\n", __FILE__, __LINE__);
1252 }
1253
1254 static struct casereader *
textfile_create_reader(PsppireImportAssistant * ia)1255 textfile_create_reader (PsppireImportAssistant *ia)
1256 {
1257 int n_vars = dict_get_var_cnt (ia->dict);
1258
1259 int i;
1260
1261 struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *);
1262 for (i = 0 ; i < n_vars; ++i)
1263 {
1264 fg[i] = fmt_guesser_create ();
1265 }
1266
1267 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1268
1269 GtkTreeIter iter;
1270 gboolean ok;
1271 for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
1272 ok;
1273 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
1274 {
1275 for (i = 0 ; i < n_vars; ++i)
1276 {
1277 gchar *s = NULL;
1278 gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
1279 if (s)
1280 fmt_guesser_add (fg[i], ss_cstr (s));
1281 free (s);
1282 }
1283 }
1284
1285 struct caseproto *proto = caseproto_create ();
1286 for (i = 0 ; i < n_vars; ++i)
1287 {
1288 struct fmt_spec fs;
1289 fmt_guesser_guess (fg[i], &fs);
1290
1291 fmt_fix (&fs, FMT_FOR_INPUT);
1292
1293 struct variable *var = dict_get_var (ia->dict, i);
1294
1295 int width = fmt_var_width (&fs);
1296
1297 var_set_width_and_formats (var, width,
1298 &fs, &fs);
1299
1300 proto = caseproto_add_width (proto, width);
1301 fmt_guesser_destroy (fg[i]);
1302 }
1303
1304 free (fg);
1305
1306 struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
1307 /* Store the dictionary at this point when the casereader is created.
1308 my_read depends on the dictionary to interpret the strings in the treeview.
1309 This guarantees that the union value is produced according to the
1310 caseproto in the reader. */
1311 ia->casereader_dict = dict_clone (ia->dict);
1312 caseproto_unref (proto);
1313 return cr;
1314 }
1315
1316 /* When during import the variable type is changed, the reader is reinitialized
1317 based on the new dictionary with a fresh caseprototype. The default behaviour
1318 when a variable type is changed and the column is resized is that the union
1319 value is interpreted with new variable type and an overlay for that column
1320 is generated. Here we reinit to the original reader based on strings.
1321 As a result you can switch from string to numeric to string without loosing
1322 the string information. */
1323 static void
ia_variable_changed_cb(GObject * obj,gint var_num,guint what,const struct variable * oldvar,gpointer data)1324 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
1325 const struct variable *oldvar, gpointer data)
1326 {
1327 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
1328
1329 struct caseproto *proto = caseproto_create();
1330 for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
1331 {
1332 const struct variable *var = dict_get_var (ia->dict, i);
1333 int width = var_get_width (var);
1334 proto = caseproto_add_width (proto, width);
1335 }
1336
1337 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1338
1339 PsppireDataStore *store = NULL;
1340 g_object_get (ia->data_sheet, "data-model", &store, NULL);
1341
1342 struct casereader *cr = casereader_create_random (proto, n_rows,
1343 &my_casereader_class, ia);
1344 psppire_data_store_set_reader (store, cr);
1345 dict_unref (ia->casereader_dict);
1346 ia->casereader_dict = dict_clone (ia->dict);
1347 }
1348
1349 /* Called just before the formats page of the assistant is
1350 displayed. */
1351 static void
prepare_formats_page(PsppireImportAssistant * ia)1352 prepare_formats_page (PsppireImportAssistant *ia)
1353 {
1354 my_casereader_class.read = my_read;
1355 my_casereader_class.destroy = my_destroy;
1356 my_casereader_class.advance = my_advance;
1357
1358 if (ia->spreadsheet)
1359 {
1360 GtkBuilder *builder = ia->builder;
1361 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1362 GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1363 GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
1364
1365 struct spreadsheet_read_options opts;
1366 opts.sheet_name = NULL;
1367 opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
1368 opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1369 opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
1370 opts.asw = 8;
1371
1372 struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
1373
1374 PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
1375 PsppireDataStore *store = psppire_data_store_new (dict);
1376 psppire_data_store_set_reader (store, reader);
1377 g_object_set (ia->data_sheet, "data-model", store, NULL);
1378 g_object_set (ia->var_sheet, "data-model", dict, NULL);
1379 }
1380 else
1381 {
1382 struct casereader *reader = textfile_create_reader (ia);
1383
1384 PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
1385 PsppireDataStore *store = psppire_data_store_new (dict);
1386 psppire_data_store_set_reader (store, reader);
1387 g_signal_connect (dict, "variable-changed",
1388 G_CALLBACK (ia_variable_changed_cb),
1389 ia);
1390
1391 g_object_set (ia->data_sheet, "data-model", store, NULL);
1392 g_object_set (ia->var_sheet, "data-model", dict, NULL);
1393 }
1394
1395 gint pmax;
1396 g_object_get (get_widget_assert (ia->builder, "vpaned1"),
1397 "max-position", &pmax, NULL);
1398
1399
1400 g_object_set (get_widget_assert (ia->builder, "vpaned1"),
1401 "position", pmax / 2, NULL);
1402
1403 gtk_widget_show (ia->paste_button);
1404 }
1405
1406 static void
formats_page_create(PsppireImportAssistant * ia)1407 formats_page_create (PsppireImportAssistant *ia)
1408 {
1409 GtkBuilder *builder = ia->builder;
1410
1411 GtkWidget *w = get_widget_assert (builder, "Formats");
1412 g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
1413 g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
1414
1415 GtkWidget *vars_scroller = get_widget_assert (builder, "vars-scroller");
1416 if (ia->var_sheet == NULL)
1417 {
1418 ia->var_sheet = psppire_variable_sheet_new ();
1419
1420 gtk_container_add (GTK_CONTAINER (vars_scroller), ia->var_sheet);
1421
1422 ia->dict = dict_create (get_default_encoding ());
1423
1424 gtk_widget_show_all (vars_scroller);
1425 }
1426 GtkWidget *data_scroller = get_widget_assert (builder, "data-scroller");
1427 if (ia->data_sheet == NULL)
1428 {
1429 ia->data_sheet = psppire_data_sheet_new ();
1430 g_object_set (ia->data_sheet, "editable", FALSE, NULL);
1431
1432 gtk_container_add (GTK_CONTAINER (data_scroller), ia->data_sheet);
1433
1434 gtk_widget_show_all (data_scroller);
1435 }
1436
1437 add_page_to_assistant (ia, w,
1438 GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
1439 }
1440
1441
1442
1443
1444 static void
separators_append_syntax(const PsppireImportAssistant * ia,struct string * s)1445 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1446 {
1447 int i;
1448
1449 ds_put_cstr (s, " /DELIMITERS=\"");
1450
1451 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, "tab"))))
1452 ds_put_cstr (s, "\\t");
1453 for (i = 0; i < SEPARATOR_CNT; i++)
1454 {
1455 const struct separator *seps = &separators[i];
1456 GtkWidget *button = get_widget_assert (ia->builder, seps->name);
1457 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1458 {
1459 if (seps->c == '\t')
1460 continue;
1461
1462 ds_put_byte (s, seps->c);
1463 }
1464 }
1465 ds_put_cstr (s, "\"\n");
1466
1467 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1468 {
1469 GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1470 gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1471 if (quotes && *quotes)
1472 syntax_gen_pspp (s, " /QUALIFIER=%sq\n", quotes);
1473 free (quotes);
1474 }
1475 }
1476
1477 static void
formats_append_syntax(const PsppireImportAssistant * ia,struct string * s)1478 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1479 {
1480 int i;
1481 int var_cnt;
1482
1483 g_return_if_fail (ia->dict);
1484
1485 ds_put_cstr (s, " /VARIABLES=\n");
1486
1487 var_cnt = dict_get_var_cnt (ia->dict);
1488 for (i = 0; i < var_cnt; i++)
1489 {
1490 struct variable *var = dict_get_var (ia->dict, i);
1491 char format_string[FMT_STRING_LEN_MAX + 1];
1492 fmt_to_string (var_get_print_format (var), format_string);
1493 ds_put_format (s, " %s %s%s\n",
1494 var_get_name (var), format_string,
1495 i == var_cnt - 1 ? "." : "");
1496 }
1497 }
1498
1499 static void
first_line_append_syntax(const PsppireImportAssistant * ia,struct string * s)1500 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1501 {
1502 gint first_case = 0;
1503 g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
1504
1505 if (first_case > 0)
1506 ds_put_format (s, " /FIRSTCASE=%d\n", first_case + 1);
1507 }
1508
1509 static void
intro_append_syntax(const PsppireImportAssistant * ia,struct string * s)1510 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1511 {
1512 gint first_line = 0;
1513 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
1514
1515 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
1516 ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
1517 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
1518 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
1519 ds_put_format (s, "SAMPLE %.4g.\n",
1520 gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
1521 }
1522
1523
1524 /* Emits PSPP syntax to S that applies the dictionary attributes
1525 (such as missing values and value labels) of the variables in
1526 DICT. */
1527 static void
apply_dict(const struct dictionary * dict,struct string * s)1528 apply_dict (const struct dictionary *dict, struct string *s)
1529 {
1530 size_t var_cnt = dict_get_var_cnt (dict);
1531 size_t i;
1532
1533 for (i = 0; i < var_cnt; i++)
1534 {
1535 struct variable *var = dict_get_var (dict, i);
1536 const char *name = var_get_name (var);
1537 enum val_type type = var_get_type (var);
1538 int width = var_get_width (var);
1539 enum measure measure = var_get_measure (var);
1540 enum var_role role = var_get_role (var);
1541 enum alignment alignment = var_get_alignment (var);
1542 const struct fmt_spec *format = var_get_print_format (var);
1543
1544 if (var_has_missing_values (var))
1545 {
1546 const struct missing_values *mv = var_get_missing_values (var);
1547 size_t j;
1548
1549 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
1550 for (j = 0; j < mv_n_values (mv); j++)
1551 {
1552 if (j)
1553 ds_put_cstr (s, ", ");
1554 syntax_gen_value (s, mv_get_value (mv, j), width, format);
1555 }
1556
1557 if (mv_has_range (mv))
1558 {
1559 double low, high;
1560 if (mv_has_value (mv))
1561 ds_put_cstr (s, ", ");
1562 mv_get_range (mv, &low, &high);
1563 syntax_gen_num_range (s, low, high, format);
1564 }
1565 ds_put_cstr (s, ").\n");
1566 }
1567 if (var_has_value_labels (var))
1568 {
1569 const struct val_labs *vls = var_get_value_labels (var);
1570 const struct val_lab **labels = val_labs_sorted (vls);
1571 size_t n_labels = val_labs_count (vls);
1572 size_t i;
1573
1574 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
1575 for (i = 0; i < n_labels; i++)
1576 {
1577 const struct val_lab *vl = labels[i];
1578 ds_put_cstr (s, "\n ");
1579 syntax_gen_value (s, &vl->value, width, format);
1580 ds_put_byte (s, ' ');
1581 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
1582 }
1583 free (labels);
1584 ds_put_cstr (s, ".\n");
1585 }
1586 if (var_has_label (var))
1587 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
1588 name, var_get_label (var));
1589 if (measure != var_default_measure (type))
1590 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
1591 name, measure_to_syntax (measure));
1592 if (role != ROLE_INPUT)
1593 syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
1594 var_role_to_syntax (role), name);
1595 if (alignment != var_default_alignment (type))
1596 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
1597 name, alignment_to_syntax (alignment));
1598 if (var_get_display_width (var) != var_default_display_width (width))
1599 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
1600 name, var_get_display_width (var));
1601 }
1602 }
1603
1604
1605
1606 static void
sheet_spec_gen_syntax(PsppireImportAssistant * ia,struct string * s)1607 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1608 {
1609 GtkBuilder *builder = ia->builder;
1610 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1611 GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
1612 GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1613 const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
1614 int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
1615 gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1616
1617
1618 char *filename;
1619 if (ia->spreadsheet)
1620 filename = ia->spreadsheet->file_name;
1621 else
1622 g_object_get (ia->text_file, "file-name", &filename, NULL);
1623 syntax_gen_pspp (s,
1624 "GET DATA"
1625 "\n /TYPE=%ss"
1626 "\n /FILE=%sq"
1627 "\n /SHEET=index %d"
1628 "\n /READNAMES=%ss",
1629 (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS",
1630 filename,
1631 sheet_index,
1632 read_names ? "ON" : "OFF");
1633
1634 if (range && 0 != strcmp ("", range))
1635 {
1636 syntax_gen_pspp (s,
1637 "\n /CELLRANGE=RANGE %sq", range);
1638 }
1639 else
1640 {
1641 syntax_gen_pspp (s,
1642 "\n /CELLRANGE=FULL");
1643 }
1644
1645
1646 syntax_gen_pspp (s, ".\n");
1647 }
1648
1649
1650 gchar *
psppire_import_assistant_generate_syntax(PsppireImportAssistant * ia)1651 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
1652 {
1653 struct string s = DS_EMPTY_INITIALIZER;
1654
1655 if (!ia->spreadsheet)
1656 {
1657 gchar *file_name = NULL;
1658 gchar *encoding = NULL;
1659 g_object_get (ia->text_file,
1660 "file-name", &file_name,
1661 "encoding", &encoding,
1662 NULL);
1663
1664 if (file_name == NULL)
1665 return NULL;
1666
1667 syntax_gen_pspp (&s,
1668 "GET DATA"
1669 "\n /TYPE=TXT"
1670 "\n /FILE=%sq\n",
1671 file_name);
1672 if (encoding && strcmp (encoding, "Auto"))
1673 syntax_gen_pspp (&s, " /ENCODING=%sq\n", encoding);
1674
1675 ds_put_cstr (&s,
1676 " /ARRANGEMENT=DELIMITED\n"
1677 " /DELCASE=LINE\n");
1678
1679 first_line_append_syntax (ia, &s);
1680 separators_append_syntax (ia, &s);
1681
1682 formats_append_syntax (ia, &s);
1683 apply_dict (ia->dict, &s);
1684 intro_append_syntax (ia, &s);
1685 }
1686 else
1687 {
1688 sheet_spec_gen_syntax (ia, &s);
1689 }
1690
1691 return ds_cstr (&s);
1692 }
1693
1694
1695 int
psppire_import_assistant_run(PsppireImportAssistant * asst)1696 psppire_import_assistant_run (PsppireImportAssistant *asst)
1697 {
1698 g_main_loop_run (asst->main_loop);
1699 return asst->response;
1700 }
1701