Rework the spreadsheet import feature of the grapic user interface
[pspp] / src / ui / gui / psppire-import-assistant.c
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 #include "psppire-import-assistant.h"
19
20 #include <gtk/gtk.h>
21
22 #include "data/casereader.h"
23 #include "data/data-in.h"
24 #include "data/format-guesser.h"
25 #include "data/gnumeric-reader.h"
26 #include "data/ods-reader.h"
27 #include "data/value-labels.h"
28 #include "data/casereader-provider.h"
29
30 #include "libpspp/i18n.h"
31
32 #include "builder-wrapper.h"
33
34 #include "psppire-data-sheet.h"
35 #include "psppire-data-store.h"
36 #include "psppire-dialog.h"
37 #include "psppire-encoding-selector.h"
38 #include "psppire-variable-sheet.h"
39
40 #include "psppire-import-spreadsheet.h"
41 #include "psppire-import-textfile.h"
42
43 #include "ui/syntax-gen.h"
44
45 #include <gettext.h>
46 #define _(msgid) gettext (msgid)
47 #define N_(msgid) msgid
48
49 typedef void page_func (PsppireImportAssistant *, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir);
50
51
52 static void formats_page_create (PsppireImportAssistant *ia);
53
54 static void psppire_import_assistant_init            (PsppireImportAssistant      *act);
55 static void psppire_import_assistant_class_init      (PsppireImportAssistantClass *class);
56
57 G_DEFINE_TYPE (PsppireImportAssistant, psppire_import_assistant, GTK_TYPE_ASSISTANT);
58
59
60 /* Properties */
61 enum
62   {
63     PROP_0,
64   };
65
66 static void
67 psppire_import_assistant_set_property (GObject         *object,
68                                        guint            prop_id,
69                                        const GValue    *value,
70                                        GParamSpec      *pspec)
71 {
72   //   PsppireImportAssistant *act = PSPPIRE_IMPORT_ASSISTANT (object);
73
74   switch (prop_id)
75     {
76     default:
77       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
78       break;
79     };
80 }
81
82
83 static void
84 psppire_import_assistant_get_property (GObject    *object,
85                                        guint            prop_id,
86                                        GValue          *value,
87                                        GParamSpec      *pspec)
88 {
89   //  PsppireImportAssistant *assistant = PSPPIRE_IMPORT_ASSISTANT (object);
90
91   switch (prop_id)
92     {
93     default:
94       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
95       break;
96     };
97 }
98
99 static GObjectClass * parent_class = NULL;
100
101
102 static void
103 psppire_import_assistant_finalize (GObject *object)
104 {
105   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (object);
106
107   if (ia->spreadsheet)
108     spreadsheet_unref (ia->spreadsheet);
109
110   ds_destroy (&ia->quotes);
111
112   dict_unref (ia->dict);
113   dict_unref (ia->casereader_dict);
114
115   g_object_unref (ia->text_builder);
116   g_object_unref (ia->spread_builder);
117
118   ia->response = -1;
119   g_main_loop_unref (ia->main_loop);
120
121   if (G_OBJECT_CLASS (parent_class)->finalize)
122     G_OBJECT_CLASS (parent_class)->finalize (object);
123 }
124
125
126 static void
127 psppire_import_assistant_class_init (PsppireImportAssistantClass *class)
128 {
129   GObjectClass *object_class = G_OBJECT_CLASS (class);
130
131   parent_class = g_type_class_peek_parent (class);
132
133   object_class->set_property = psppire_import_assistant_set_property;
134   object_class->get_property = psppire_import_assistant_get_property;
135
136   object_class->finalize = psppire_import_assistant_finalize;
137 }
138
139
140 /* Causes the assistant to close, returning RESPONSE for
141    interpretation by text_data_import_assistant. */
142 static void
143 close_assistant (PsppireImportAssistant *ia, int response)
144 {
145   ia->response = response;
146   g_main_loop_quit (ia->main_loop);
147   gtk_widget_hide (GTK_WIDGET (ia));
148 }
149
150
151 /* Called when the Paste button on the last page of the assistant
152    is clicked. */
153 static void
154 on_paste (GtkButton *button, PsppireImportAssistant *ia)
155 {
156   close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
157 }
158
159
160 /* /\* Clears the set of user-modified variables from IA's formats */
161 /*    substructure.  This discards user modifications to variable */
162 /*    formats, thereby causing formats to revert to their */
163 /*    defaults.  *\/ */
164 /* static void */
165 /* reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page) */
166 /* { */
167 /* } */
168
169 static void prepare_formats_page (PsppireImportAssistant *ia);
170
171 /* Called when the Reset button is clicked.
172    This function marshalls the callback to the relevant page.  */
173 static void
174 on_reset (GtkButton *button, PsppireImportAssistant *ia)
175 {
176   gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia));
177   {
178     GtkWidget *page =  gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
179
180     page_func *xon_reset = g_object_get_data (G_OBJECT (page), "on-reset");
181
182     if (xon_reset)
183       xon_reset (ia, page, 0);
184   }
185 }
186
187
188 static gint
189 next_page_func (gint old_page, gpointer data)
190 {
191   return old_page + 1;
192 }
193
194
195 /* Called just before PAGE is displayed as the current page of
196    IMPORT_ASSISTANT, this updates IA content according to the new
197    page. */
198 static void
199 on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia)
200 {
201   gtk_widget_show (ia->reset_button);
202   gtk_widget_hide (ia->paste_button);
203
204   gint pn = gtk_assistant_get_current_page (assistant);
205   gint previous_page_index = ia->previous_page;
206   g_assert (pn != previous_page_index);
207
208   if (previous_page_index >= 0)
209     {
210       GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
211
212         page_func *on_leaving = g_object_get_data (G_OBJECT (closing_page), "on-leaving");
213         if (on_leaving)
214           on_leaving (ia, closing_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
215     }
216
217     GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
218
219     page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
220     if (on_entering)
221       on_entering (ia, new_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
222
223   ia->previous_page = pn;
224 }
225
226 /* Called when the Cancel button in the assistant is clicked. */
227 static void
228 on_cancel (GtkAssistant *assistant, PsppireImportAssistant *ia)
229 {
230   close_assistant (ia, GTK_RESPONSE_CANCEL);
231 }
232
233 /* Called when the Apply button on the last page of the assistant
234    is clicked. */
235 static void
236 on_close (GtkAssistant *assistant, PsppireImportAssistant *ia)
237 {
238   close_assistant (ia, GTK_RESPONSE_APPLY);
239 }
240
241
242 static void
243 on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
244 {
245   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
246   gchar *f = gtk_file_chooser_get_filename (fc);
247   int i;
248
249   for(i = gtk_assistant_get_n_pages (GTK_ASSISTANT (ia)); i > 0; --i)
250     gtk_assistant_remove_page (GTK_ASSISTANT (ia), i);
251
252   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), FALSE);
253
254   if (f && g_file_test (f, G_FILE_TEST_IS_REGULAR))
255     {
256       gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE);
257
258       if (ia->spreadsheet)
259         spreadsheet_unref (ia->spreadsheet);
260
261       ia->spreadsheet = gnumeric_probe (f, FALSE);
262
263       if (!ia->spreadsheet)
264         ia->spreadsheet = ods_probe (f, FALSE);
265
266       if (ia->spreadsheet)
267         {
268           sheet_spec_page_create (ia);
269         }
270       else
271         {
272           intro_page_create (ia);
273           first_line_page_create (ia);
274           separators_page_create (ia);
275         }
276
277       formats_page_create (ia);
278     }
279
280   g_free (f);
281 }
282
283 /* This has to be done on a map signal callback,
284    because GtkFileChooserWidget resets everything when it is mapped. */
285 static void
286 on_map (PsppireImportAssistant *ia, GtkWidget *page)
287 {
288 #if TEXT_FILE
289   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
290
291   if (ia->file_name)
292     gtk_file_chooser_set_filename (fc, ia->file_name);
293 #endif
294
295   on_chosen (ia, page);
296 }
297
298
299
300 static void
301 chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
302 {
303 }
304
305 static void
306 chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
307 {
308   if (dir != IMPORT_ASSISTANT_FORWARDS)
309     return;
310
311   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
312
313   g_free (ia->file_name);
314   ia->file_name = gtk_file_chooser_get_filename (fc);
315
316   /* Add the chosen file to the recent manager.  */
317   {
318     gchar *uri = gtk_file_chooser_get_uri (fc);
319     GtkRecentManager * manager = gtk_recent_manager_get_default ();
320     gtk_recent_manager_add_item (manager, uri);
321     g_free (uri);
322   }
323
324   if (!ia->spreadsheet)
325     {
326       g_print ("%s:%d Where does this belong?\n", __FILE__, __LINE__);
327       gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
328       ia->text_file = psppire_text_file_new (ia->file_name, encoding);
329       gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
330                                GTK_TREE_MODEL (ia->text_file));
331
332       g_free (encoding);
333     }
334 }
335
336 static void
337 chooser_page_reset (PsppireImportAssistant *ia, GtkWidget *page)
338 {
339   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
340
341   gtk_file_chooser_set_filter (fc, ia->default_filter);
342   gtk_file_chooser_unselect_all (fc);
343
344   on_chosen (ia, page);
345 }
346
347
348 static void
349 on_file_activated (GtkFileChooser *chooser, PsppireImportAssistant *ia)
350 {
351   gtk_assistant_next_page (GTK_ASSISTANT (ia));
352 }
353
354 static void
355 chooser_page_create (PsppireImportAssistant *ia)
356 {
357   GtkFileFilter *filter = NULL;
358
359   GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
360
361   g_signal_connect (chooser, "file-activated", G_CALLBACK (on_file_activated), ia);
362
363   g_object_set_data (G_OBJECT (chooser), "on-leaving", chooser_page_leave);
364   g_object_set_data (G_OBJECT (chooser), "on-reset",   chooser_page_reset);
365   g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
366
367   g_object_set (chooser, "local-only", FALSE, NULL);
368
369
370   ia->default_filter = gtk_file_filter_new ();
371   gtk_file_filter_set_name (ia->default_filter, _("All Files"));
372   gtk_file_filter_add_pattern (ia->default_filter, "*");
373   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
374
375   filter = gtk_file_filter_new ();
376   gtk_file_filter_set_name (filter, _("Text Files"));
377   gtk_file_filter_add_mime_type (filter, "text/*");
378   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
379
380   filter = gtk_file_filter_new ();
381   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
382   gtk_file_filter_add_pattern (filter, "*.txt");
383   gtk_file_filter_add_pattern (filter, "*.TXT");
384   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
385
386   filter = gtk_file_filter_new ();
387   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
388   gtk_file_filter_add_mime_type (filter, "text/plain");
389   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
390
391   filter = gtk_file_filter_new ();
392   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
393   gtk_file_filter_add_mime_type (filter, "text/csv");
394   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
395
396   /* I've never encountered one of these, but it's listed here:
397      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
398   filter = gtk_file_filter_new ();
399   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
400   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
401   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
402
403   filter = gtk_file_filter_new ();
404   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
405   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
406   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
407
408   filter = gtk_file_filter_new ();
409   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
410   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
411   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
412
413   filter = gtk_file_filter_new ();
414   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
415   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
416   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
417   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
418
419   ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
420   gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
421
422   add_page_to_assistant (ia, chooser,
423                          GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
424
425   g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
426   g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
427 }
428
429
430
431 static void
432 psppire_import_assistant_init (PsppireImportAssistant *ia)
433 {
434   ia->text_builder = builder_new ("text-data-import.ui");
435   ia->spread_builder = builder_new ("spreadsheet-import.ui");
436
437   ia->previous_page = -1 ;
438   ia->file_name = NULL;
439
440   ia->spreadsheet = NULL;
441   ia->updating_selection = FALSE;
442   ia->dict = NULL;
443   ia->casereader_dict = NULL;
444
445   ia->main_loop = g_main_loop_new (NULL, TRUE);
446
447   g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
448   g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
449   g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
450
451   ia->paste_button = gtk_button_new_with_label (_("Paste"));
452   ia->reset_button = gtk_button_new_with_label (_("Reset"));
453
454   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
455
456   g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
457   g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
458
459   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
460
461   gtk_window_set_title (GTK_WINDOW (ia),
462                         _("Importing Delimited Text Data"));
463
464   gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp");
465
466   chooser_page_create (ia);
467
468   gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
469
470   gtk_window_maximize (GTK_WINDOW (ia));
471 }
472
473
474 /* Appends a page of the given TYPE, with PAGE as its content, to
475    the GtkAssistant encapsulated by IA.  Returns the GtkWidget
476    that represents the page. */
477 GtkWidget *
478 add_page_to_assistant (PsppireImportAssistant *ia,
479                        GtkWidget *page, GtkAssistantPageType type, const gchar *title)
480 {
481   GtkWidget *content = page;
482
483   gtk_assistant_append_page (GTK_ASSISTANT (ia), content);
484   gtk_assistant_set_page_type (GTK_ASSISTANT(ia), content, type);
485   gtk_assistant_set_page_title (GTK_ASSISTANT(ia), content, title);
486   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), content, TRUE);
487
488   return content;
489 }
490
491
492 GtkWidget *
493 psppire_import_assistant_new (GtkWindow *toplevel)
494 {
495   return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_IMPORT_ASSISTANT,
496                                    /* Some window managers (notably ratpoison)
497                                       ignore the maximise command when a window is
498                                       transient.  This causes problems for this
499                                       window. */
500                                    /* "transient-for", toplevel, */
501                                    NULL));
502 }
503
504
505
506 \f
507
508
509 /* Called just before the formats page of the assistant is
510    displayed. */
511 static void
512 prepare_formats_page (PsppireImportAssistant *ia)
513 {
514 /* Set the data model for both the data sheet and the variable sheet.  */
515   if (ia->spreadsheet)
516     spreadsheet_set_data_models (ia);
517   else
518     textfile_set_data_models (ia);
519
520   /* Show half-half the data sheet and the variable sheet.  */
521   gint pmax;
522   g_object_get (get_widget_assert (ia->text_builder, "vpaned1"),
523                 "max-position", &pmax, NULL);
524
525   g_object_set (get_widget_assert (ia->text_builder, "vpaned1"),
526                 "position", pmax / 2, NULL);
527
528   gtk_widget_show (ia->paste_button);
529 }
530
531 static void
532 formats_page_create (PsppireImportAssistant *ia)
533 {
534   GtkBuilder *builder = ia->text_builder;
535
536   GtkWidget *w = get_widget_assert (builder, "Formats");
537   g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
538   //  g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
539
540   ia->data_sheet = get_widget_assert (builder, "data-sheet");
541   ia->var_sheet = get_widget_assert (builder, "variable-sheet");
542
543   add_page_to_assistant (ia, w,
544                          GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
545 }
546
547
548 \f
549
550 static void
551 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
552 {
553   int i;
554
555   ds_put_cstr (s, "  /DELIMITERS=\"");
556
557   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
558     ds_put_cstr (s, "\\t");
559   for (i = 0; i < SEPARATOR_CNT; i++)
560     {
561       const struct separator *seps = &separators[i];
562       GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
563       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
564         {
565           if (seps->c == '\t')
566             continue;
567
568           ds_put_byte (s, seps->c);
569         }
570     }
571   ds_put_cstr (s, "\"\n");
572
573   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
574     {
575       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
576       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
577       if (quotes && *quotes)
578         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
579       free (quotes);
580     }
581 }
582
583 static void
584 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
585 {
586   int i;
587   int var_cnt;
588
589   g_return_if_fail (ia->dict);
590
591   ds_put_cstr (s, "  /VARIABLES=\n");
592
593   var_cnt = dict_get_var_cnt (ia->dict);
594   for (i = 0; i < var_cnt; i++)
595     {
596       struct variable *var = dict_get_var (ia->dict, i);
597       char format_string[FMT_STRING_LEN_MAX + 1];
598       fmt_to_string (var_get_print_format (var), format_string);
599       ds_put_format (s, "    %s %s%s\n",
600                      var_get_name (var), format_string,
601                      i == var_cnt - 1 ? "." : "");
602     }
603 }
604
605 static void
606 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
607 {
608   gint first_case = 0;
609   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
610
611   if (first_case > 0)
612     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
613 }
614
615 static void
616 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
617 {
618   gint first_line = 0;
619   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
620
621   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
622     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
623                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
624   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
625     ds_put_format (s, "SAMPLE %.4g.\n",
626                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
627 }
628
629
630 /* Emits PSPP syntax to S that applies the dictionary attributes
631    (such as missing values and value labels) of the variables in
632    DICT.  */
633 static void
634 apply_dict (const struct dictionary *dict, struct string *s)
635 {
636   size_t var_cnt = dict_get_var_cnt (dict);
637   size_t i;
638
639   for (i = 0; i < var_cnt; i++)
640     {
641       struct variable *var = dict_get_var (dict, i);
642       const char *name = var_get_name (var);
643       enum val_type type = var_get_type (var);
644       int width = var_get_width (var);
645       enum measure measure = var_get_measure (var);
646       enum var_role role = var_get_role (var);
647       enum alignment alignment = var_get_alignment (var);
648       const struct fmt_spec *format = var_get_print_format (var);
649
650       if (var_has_missing_values (var))
651         {
652           const struct missing_values *mv = var_get_missing_values (var);
653           size_t j;
654
655           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
656           for (j = 0; j < mv_n_values (mv); j++)
657             {
658               if (j)
659                 ds_put_cstr (s, ", ");
660               syntax_gen_value (s, mv_get_value (mv, j), width, format);
661             }
662
663           if (mv_has_range (mv))
664             {
665               double low, high;
666               if (mv_has_value (mv))
667                 ds_put_cstr (s, ", ");
668               mv_get_range (mv, &low, &high);
669               syntax_gen_num_range (s, low, high, format);
670             }
671           ds_put_cstr (s, ").\n");
672         }
673       if (var_has_value_labels (var))
674         {
675           const struct val_labs *vls = var_get_value_labels (var);
676           const struct val_lab **labels = val_labs_sorted (vls);
677           size_t n_labels = val_labs_count (vls);
678           size_t i;
679
680           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
681           for (i = 0; i < n_labels; i++)
682             {
683               const struct val_lab *vl = labels[i];
684               ds_put_cstr (s, "\n  ");
685               syntax_gen_value (s, &vl->value, width, format);
686               ds_put_byte (s, ' ');
687               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
688             }
689           free (labels);
690           ds_put_cstr (s, ".\n");
691         }
692       if (var_has_label (var))
693         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
694                          name, var_get_label (var));
695       if (measure != var_default_measure (type))
696         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
697                          name, measure_to_syntax (measure));
698       if (role != ROLE_INPUT)
699         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
700                          var_role_to_syntax (role), name);
701       if (alignment != var_default_alignment (type))
702         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
703                          name, alignment_to_syntax (alignment));
704       if (var_get_display_width (var) != var_default_display_width (width))
705         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
706                          name, var_get_display_width (var));
707     }
708 }
709
710
711
712 static void
713 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
714 {
715   GtkBuilder *builder = ia->spread_builder;
716   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
717   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
718   GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
719   const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
720   int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
721   gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
722
723
724   char *filename;
725   if (ia->spreadsheet)
726     filename = ia->spreadsheet->file_name;
727   else
728     g_object_get (ia->text_file, "file-name", &filename, NULL);
729   syntax_gen_pspp (s,
730                    "GET DATA"
731                    "\n  /TYPE=%ss"
732                    "\n  /FILE=%sq"
733                    "\n  /SHEET=index %d"
734                    "\n  /READNAMES=%ss",
735                    ia->spreadsheet->type,
736                    filename,
737                    sheet_index,
738                    read_names ? "ON" : "OFF");
739
740   if (range && 0 != strcmp ("", range))
741     {
742       syntax_gen_pspp (s,
743                        "\n  /CELLRANGE=RANGE %sq", range);
744     }
745   else
746     {
747       syntax_gen_pspp (s,
748                        "\n  /CELLRANGE=FULL");
749     }
750
751
752   syntax_gen_pspp (s, ".\n");
753 }
754
755
756 gchar *
757 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
758 {
759   struct string s = DS_EMPTY_INITIALIZER;
760
761   if (!ia->spreadsheet)
762     {
763       gchar *file_name = NULL;
764       gchar *encoding = NULL;
765       g_object_get (ia->text_file,
766                     "file-name", &file_name,
767                     "encoding", &encoding,
768                     NULL);
769
770       if (file_name == NULL)
771         return NULL;
772
773       syntax_gen_pspp (&s,
774                        "GET DATA"
775                        "\n  /TYPE=TXT"
776                        "\n  /FILE=%sq\n",
777                        file_name);
778       if (encoding && strcmp (encoding, "Auto"))
779         syntax_gen_pspp (&s, "  /ENCODING=%sq\n", encoding);
780
781       ds_put_cstr (&s,
782                    "  /ARRANGEMENT=DELIMITED\n"
783                    "  /DELCASE=LINE\n");
784
785       first_line_append_syntax (ia, &s);
786       separators_append_syntax (ia, &s);
787
788       formats_append_syntax (ia, &s);
789       apply_dict (ia->dict, &s);
790       intro_append_syntax (ia, &s);
791     }
792   else
793     {
794       sheet_spec_gen_syntax (ia, &s);
795     }
796
797   return ds_cstr (&s);
798 }
799
800
801 int
802 psppire_import_assistant_run (PsppireImportAssistant *asst)
803 {
804   g_main_loop_run (asst->main_loop);
805   return asst->response;
806 }