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