1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2015, 2016, 2017, 2018, 2020 Free Software Foundation
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.
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.
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/>. */
18 #include "psppire-import-assistant.h"
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"
30 #include "libpspp/i18n.h"
32 #include "builder-wrapper.h"
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"
40 #include "psppire-import-spreadsheet.h"
41 #include "psppire-import-textfile.h"
43 #include "ui/syntax-gen.h"
46 #define _(msgid) gettext (msgid)
47 #define N_(msgid) msgid
49 typedef void page_func (PsppireImportAssistant *, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir);
52 static void formats_page_create (PsppireImportAssistant *ia);
54 static void psppire_import_assistant_init (PsppireImportAssistant *act);
55 static void psppire_import_assistant_class_init (PsppireImportAssistantClass *class);
57 G_DEFINE_TYPE (PsppireImportAssistant, psppire_import_assistant, GTK_TYPE_ASSISTANT);
67 psppire_import_assistant_set_property (GObject *object,
72 // PsppireImportAssistant *act = PSPPIRE_IMPORT_ASSISTANT (object);
77 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
84 psppire_import_assistant_get_property (GObject *object,
89 // PsppireImportAssistant *assistant = PSPPIRE_IMPORT_ASSISTANT (object);
94 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99 static GObjectClass * parent_class = NULL;
103 psppire_import_assistant_finalize (GObject *object)
105 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (object);
108 spreadsheet_unref (ia->spreadsheet);
110 ds_destroy (&ia->quotes);
112 dict_unref (ia->dict);
113 dict_unref (ia->casereader_dict);
115 g_object_unref (ia->text_builder);
116 g_object_unref (ia->spread_builder);
119 g_main_loop_unref (ia->main_loop);
121 if (G_OBJECT_CLASS (parent_class)->finalize)
122 G_OBJECT_CLASS (parent_class)->finalize (object);
127 psppire_import_assistant_class_init (PsppireImportAssistantClass *class)
129 GObjectClass *object_class = G_OBJECT_CLASS (class);
131 parent_class = g_type_class_peek_parent (class);
133 object_class->set_property = psppire_import_assistant_set_property;
134 object_class->get_property = psppire_import_assistant_get_property;
136 object_class->finalize = psppire_import_assistant_finalize;
140 /* Causes the assistant to close, returning RESPONSE for
141 interpretation by text_data_import_assistant. */
143 close_assistant (PsppireImportAssistant *ia, int response)
145 ia->response = response;
146 g_main_loop_quit (ia->main_loop);
147 gtk_widget_hide (GTK_WIDGET (ia));
151 /* Called when the Paste button on the last page of the assistant
154 on_paste (GtkButton *button, PsppireImportAssistant *ia)
156 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
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 */
165 /* reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page) */
169 static void prepare_formats_page (PsppireImportAssistant *ia);
171 /* Called when the Reset button is clicked.
172 This function marshalls the callback to the relevant page. */
174 on_reset (GtkButton *button, PsppireImportAssistant *ia)
176 gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia));
178 GtkWidget *page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
180 page_func *xon_reset = g_object_get_data (G_OBJECT (page), "on-reset");
183 xon_reset (ia, page, 0);
189 next_page_func (gint old_page, gpointer data)
195 /* Called just before PAGE is displayed as the current page of
196 IMPORT_ASSISTANT, this updates IA content according to the new
199 on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia)
201 gtk_widget_show (ia->reset_button);
202 gtk_widget_hide (ia->paste_button);
204 gint pn = gtk_assistant_get_current_page (assistant);
205 gint previous_page_index = ia->previous_page;
206 g_assert (pn != previous_page_index);
208 if (previous_page_index >= 0)
210 GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
212 page_func *on_leaving = g_object_get_data (G_OBJECT (closing_page), "on-leaving");
214 on_leaving (ia, closing_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
217 GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
219 page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
221 on_entering (ia, new_page, (pn > previous_page_index) ? IMPORT_ASSISTANT_FORWARDS : IMPORT_ASSISTANT_BACKWARDS);
223 ia->previous_page = pn;
226 /* Called when the Cancel button in the assistant is clicked. */
228 on_cancel (GtkAssistant *assistant, PsppireImportAssistant *ia)
230 close_assistant (ia, GTK_RESPONSE_CANCEL);
233 /* Called when the Apply button on the last page of the assistant
236 on_close (GtkAssistant *assistant, PsppireImportAssistant *ia)
238 close_assistant (ia, GTK_RESPONSE_APPLY);
243 on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
245 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
246 gchar *f = gtk_file_chooser_get_filename (fc);
249 for(i = gtk_assistant_get_n_pages (GTK_ASSISTANT (ia)); i > 0; --i)
250 gtk_assistant_remove_page (GTK_ASSISTANT (ia), i);
252 gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), FALSE);
254 if (f && g_file_test (f, G_FILE_TEST_IS_REGULAR))
256 gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE);
259 spreadsheet_unref (ia->spreadsheet);
261 ia->spreadsheet = gnumeric_probe (f, FALSE);
263 if (!ia->spreadsheet)
264 ia->spreadsheet = ods_probe (f, FALSE);
268 sheet_spec_page_create (ia);
272 intro_page_create (ia);
273 first_line_page_create (ia);
274 separators_page_create (ia);
277 formats_page_create (ia);
283 /* This has to be done on a map signal callback,
284 because GtkFileChooserWidget resets everything when it is mapped. */
286 on_map (PsppireImportAssistant *ia, GtkWidget *page)
289 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
292 gtk_file_chooser_set_filename (fc, ia->file_name);
295 on_chosen (ia, page);
301 chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
306 chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
308 if (dir != IMPORT_ASSISTANT_FORWARDS)
311 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
313 g_free (ia->file_name);
314 ia->file_name = gtk_file_chooser_get_filename (fc);
316 /* Add the chosen file to the recent manager. */
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);
324 if (!ia->spreadsheet)
326 gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
327 ia->text_file = psppire_text_file_new (ia->file_name, encoding);
328 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
329 GTK_TREE_MODEL (ia->text_file));
336 chooser_page_reset (PsppireImportAssistant *ia, GtkWidget *page)
338 GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
340 gtk_file_chooser_set_filter (fc, ia->default_filter);
341 gtk_file_chooser_unselect_all (fc);
343 on_chosen (ia, page);
348 on_file_activated (GtkFileChooser *chooser, PsppireImportAssistant *ia)
350 gtk_assistant_next_page (GTK_ASSISTANT (ia));
354 chooser_page_create (PsppireImportAssistant *ia)
356 GtkFileFilter *filter = NULL;
358 GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
360 g_signal_connect (chooser, "file-activated", G_CALLBACK (on_file_activated), ia);
362 g_object_set_data (G_OBJECT (chooser), "on-leaving", chooser_page_leave);
363 g_object_set_data (G_OBJECT (chooser), "on-reset", chooser_page_reset);
364 g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
366 g_object_set (chooser, "local-only", FALSE, NULL);
369 ia->default_filter = gtk_file_filter_new ();
370 gtk_file_filter_set_name (ia->default_filter, _("All Files"));
371 gtk_file_filter_add_pattern (ia->default_filter, "*");
372 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
374 filter = gtk_file_filter_new ();
375 gtk_file_filter_set_name (filter, _("Text Files"));
376 gtk_file_filter_add_mime_type (filter, "text/*");
377 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
379 filter = gtk_file_filter_new ();
380 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
381 gtk_file_filter_add_pattern (filter, "*.txt");
382 gtk_file_filter_add_pattern (filter, "*.TXT");
383 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
385 filter = gtk_file_filter_new ();
386 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
387 gtk_file_filter_add_mime_type (filter, "text/plain");
388 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
390 filter = gtk_file_filter_new ();
391 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
392 gtk_file_filter_add_mime_type (filter, "text/csv");
393 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
395 /* I've never encountered one of these, but it's listed here:
396 http://www.iana.org/assignments/media-types/text/tab-separated-values */
397 filter = gtk_file_filter_new ();
398 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
399 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
400 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
402 filter = gtk_file_filter_new ();
403 gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
404 gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
405 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
407 filter = gtk_file_filter_new ();
408 gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
409 gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
410 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
412 filter = gtk_file_filter_new ();
413 gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
414 gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
415 gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
416 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
418 ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
419 gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
421 add_page_to_assistant (ia, chooser,
422 GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
424 g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
425 g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
431 psppire_import_assistant_init (PsppireImportAssistant *ia)
433 ia->text_builder = builder_new ("text-data-import.ui");
434 ia->spread_builder = builder_new ("spreadsheet-import.ui");
436 ia->previous_page = -1 ;
437 ia->file_name = NULL;
439 ia->spreadsheet = NULL;
440 ia->updating_selection = FALSE;
442 ia->casereader_dict = NULL;
444 ia->main_loop = g_main_loop_new (NULL, TRUE);
446 g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
447 g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
448 g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
450 ia->paste_button = gtk_button_new_with_label (_("Paste"));
451 ia->reset_button = gtk_button_new_with_label (_("Reset"));
453 gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
455 g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
456 g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
458 gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
460 gtk_window_set_title (GTK_WINDOW (ia),
461 _("Importing Delimited Text Data"));
463 gtk_window_set_icon_name (GTK_WINDOW (ia), "org.gnu.pspp");
464 gtk_window_set_modal (GTK_WINDOW(ia), TRUE);
466 chooser_page_create (ia);
468 gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
470 gtk_window_maximize (GTK_WINDOW (ia));
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. */
478 add_page_to_assistant (PsppireImportAssistant *ia,
479 GtkWidget *page, GtkAssistantPageType type, const gchar *title)
481 GtkWidget *content = page;
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);
493 psppire_import_assistant_new (GtkWindow *toplevel)
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
500 /* "transient-for", toplevel, */
509 /* Called just before the formats page of the assistant is
512 prepare_formats_page (PsppireImportAssistant *ia)
514 /* Set the data model for both the data sheet and the variable sheet. */
516 spreadsheet_set_data_models (ia);
518 textfile_set_data_models (ia);
520 /* Show half-half the data sheet and the variable sheet. */
522 g_object_get (get_widget_assert (ia->text_builder, "vpaned1"),
523 "max-position", &pmax, NULL);
525 g_object_set (get_widget_assert (ia->text_builder, "vpaned1"),
526 "position", pmax / 2, NULL);
528 gtk_widget_show (ia->paste_button);
532 formats_page_create (PsppireImportAssistant *ia)
534 GtkBuilder *builder = ia->text_builder;
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);
540 ia->data_sheet = get_widget_assert (builder, "data-sheet");
541 ia->var_sheet = get_widget_assert (builder, "variable-sheet");
543 add_page_to_assistant (ia, w,
544 GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
551 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
553 GtkBuilder *builder = ia->spread_builder;
554 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
555 GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
556 GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
557 const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
558 int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
559 gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
564 filename = ia->spreadsheet->file_name;
566 g_object_get (ia->text_file, "file-name", &filename, NULL);
573 ia->spreadsheet->type,
576 read_names ? "ON" : "OFF");
578 if (range && 0 != strcmp ("", range))
581 "\n /CELLRANGE=RANGE %sq", range);
586 "\n /CELLRANGE=FULL");
590 syntax_gen_pspp (s, ".\n");
595 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
597 struct string s = DS_EMPTY_INITIALIZER;
599 if (!ia->spreadsheet)
601 text_spec_gen_syntax (ia, &s);
605 sheet_spec_gen_syntax (ia, &s);
613 psppire_import_assistant_run (PsppireImportAssistant *asst)
615 g_main_loop_run (asst->main_loop);
616 return asst->response;