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/>. */
20 #include "psppire-import-assistant.h"
21 #include "psppire-import-spreadsheet.h"
22 #include "builder-wrapper.h"
24 #include "libpspp/misc.h"
25 #include "psppire-spreadsheet-model.h"
26 #include "psppire-spreadsheet-data-model.h"
27 #include "psppire-data-store.h"
30 #define _(msgid) gettext (msgid)
31 #define N_(msgid) msgid
34 set_column_header_label (GtkWidget *button, uint i, gpointer user_data)
36 gchar *x = int_to_ps26 (i);
37 gtk_button_set_label (GTK_BUTTON (button), x);
41 static void do_selection_update (PsppireImportAssistant *ia);
44 on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
46 GtkBuilder *builder = ia->spread_builder;
47 gint sheet_number = gtk_combo_box_get_active (cb);
49 gint coli = spreadsheet_get_sheet_n_columns (ia->spreadsheet, sheet_number) - 1;
50 gint rowi = spreadsheet_get_sheet_n_rows (ia->spreadsheet, sheet_number) - 1;
53 /* Now set the spin button upper limits according to the size of the selected sheet. */
55 GtkWidget *sb0 = get_widget_assert (builder, "sb0");
56 GtkWidget *sb1 = get_widget_assert (builder, "sb1");
57 GtkWidget *sb2 = get_widget_assert (builder, "sb2");
58 GtkWidget *sb3 = get_widget_assert (builder, "sb3");
60 /* The row spinbuttons contain decimal digits. So there should be
61 enough space to display them. */
62 int digits = (rowi > 0) ? intlog10 (rowi + 1): 1;
63 gtk_entry_set_max_width_chars (GTK_ENTRY (sb1), digits);
64 gtk_entry_set_max_width_chars (GTK_ENTRY (sb3), digits);
66 /* The column spinbuttons are pseudo-base-26 digits. The
67 exact formula for the number required is complicated. However
68 3 is a reasonable amount. It's not too large, and anyone importing
69 a spreadsheet with more than 3^26 columns is likely to experience
70 other problems anyway. */
71 gtk_entry_set_max_width_chars (GTK_ENTRY (sb0), 3);
72 gtk_entry_set_max_width_chars (GTK_ENTRY (sb2), 3);
75 GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb0));
76 gtk_adjustment_set_upper (adj, coli);
78 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb1));
79 gtk_adjustment_set_upper (adj, rowi);
81 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb2));
82 gtk_adjustment_set_upper (adj, coli);
84 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb3));
85 gtk_adjustment_set_upper (adj, rowi);
88 GtkTreeModel *data_model =
89 psppire_spreadsheet_data_model_new (ia->spreadsheet, sheet_number);
90 g_object_set (ia->preview_sheet,
91 "data-model", data_model,
94 g_object_unref (data_model);
96 GObject *hmodel = NULL;
97 g_object_get (ia->preview_sheet, "hmodel", &hmodel, NULL);
100 "post-button-create-func", set_column_header_label,
103 ia->selection.start_x = ia->selection.start_y = 0;
104 ia->selection.end_x = coli;
105 ia->selection.end_y = rowi;
106 do_selection_update (ia);
109 /* Ensure that PARTNER is never less than than SUBJECT. */
111 on_value_change_lower (GtkSpinButton *subject, GtkSpinButton *partner)
113 gint p = gtk_spin_button_get_value_as_int (partner);
114 gint s = gtk_spin_button_get_value_as_int (subject);
117 gtk_spin_button_set_value (partner, s);
120 /* Ensure that PARTNER is never greater than to SUBJECT. */
122 on_value_change_upper (GtkSpinButton *subject, GtkSpinButton *partner)
124 gint p = gtk_spin_button_get_value_as_int (partner);
125 gint s = gtk_spin_button_get_value_as_int (subject);
128 gtk_spin_button_set_value (partner, s);
132 /* Sets SB to use 1 based display instead of 0 based. */
134 row_output (GtkSpinButton *sb, gpointer unused)
136 gint value = gtk_spin_button_get_value_as_int (sb);
137 char *text = g_strdup_printf ("%d", value + 1);
138 gtk_entry_set_text (GTK_ENTRY (sb), text);
144 /* Sets SB to use text like A, B, C instead of 0, 1, 2 etc. */
146 column_output (GtkSpinButton *sb, gpointer unused)
148 gint value = gtk_spin_button_get_value_as_int (sb);
149 char *text = int_to_ps26 (value);
153 gtk_entry_set_text (GTK_ENTRY (sb), text);
159 /* Interprets the SBs text as 1 based instead of zero based. */
161 row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
163 const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
164 gdouble value = g_strtod (text, NULL) - 1;
169 memcpy (new_value, &value, sizeof (value));
175 /* Interprets the SBs text of the form A, B, C etc and
176 sets NEW_VALUE as a double. */
178 column_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
180 const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
181 double value = ps26_to_int (text);
186 memcpy (new_value, &value, sizeof (value));
192 reset_page (PsppireImportAssistant *ia)
194 GtkBuilder *builder = ia->spread_builder;
195 GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
196 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
198 gint sheet_number = 0;
199 GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
200 gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), sheet_number);
202 gint coli = spreadsheet_get_sheet_n_columns (ia->spreadsheet, sheet_number) - 1;
203 gint rowi = spreadsheet_get_sheet_n_rows (ia->spreadsheet, sheet_number) - 1;
205 ia->selection.start_x = ia->selection.start_y = 0;
206 ia->selection.end_x = coli;
207 ia->selection.end_y = rowi;
208 do_selection_update (ia);
211 /* Prepares IA's sheet_spec page. */
213 prepare_sheet_spec_page (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
215 if (dir != IMPORT_ASSISTANT_FORWARDS)
218 GtkBuilder *builder = ia->spread_builder;
219 GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
220 GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
222 GtkTreeModel *model = psppire_spreadsheet_model_new (ia->spreadsheet);
223 gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), model);
224 g_object_unref (model);
226 gint items = gtk_tree_model_iter_n_children (model, NULL);
227 gtk_widget_set_sensitive (sheet_entry, items > 1);
229 gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
230 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
232 GtkWidget *file_name_label = get_widget_assert (builder, "file-name-label");
233 gtk_label_set_text (GTK_LABEL (file_name_label), ia->file_name);
235 /* Gang the increment/decrement buttons, so that the upper always exceeds the lower. */
236 GtkWidget *sb0 = get_widget_assert (builder, "sb0");
237 GtkWidget *sb2 = get_widget_assert (builder, "sb2");
239 g_signal_connect (sb0, "value-changed", G_CALLBACK (on_value_change_lower), sb2);
240 g_signal_connect (sb2, "value-changed", G_CALLBACK (on_value_change_upper), sb0);
242 GtkWidget *sb1 = get_widget_assert (builder, "sb1");
243 GtkWidget *sb3 = get_widget_assert (builder, "sb3");
245 g_signal_connect (sb1, "value-changed", G_CALLBACK (on_value_change_lower), sb3);
246 g_signal_connect (sb3, "value-changed", G_CALLBACK (on_value_change_upper), sb1);
249 /* Set the column spinbuttons to display as A, B, C notation,
250 and the row spinbuttons to display as 1 based instead of zero based. */
251 g_signal_connect (sb0, "output", G_CALLBACK (column_output), NULL);
252 g_signal_connect (sb0, "input", G_CALLBACK (column_input), NULL);
254 g_signal_connect (sb2, "output", G_CALLBACK (column_output), NULL);
255 g_signal_connect (sb2, "input", G_CALLBACK (column_input), NULL);
257 g_signal_connect (sb1, "output", G_CALLBACK (row_output), NULL);
258 g_signal_connect (sb1, "input", G_CALLBACK (row_input), NULL);
260 g_signal_connect (sb3, "output", G_CALLBACK (row_output), NULL);
261 g_signal_connect (sb3, "input", G_CALLBACK (row_input), NULL);
265 do_selection_update (PsppireImportAssistant *ia)
267 GtkBuilder *builder = ia->spread_builder;
269 /* Stop this function re-entering itself. */
270 if (ia->updating_selection)
272 ia->updating_selection = TRUE;
274 /* We must take a copy of the selection. A pointer will not suffice,
275 because the selection can change under us. */
276 SswRange sel = ia->selection;
278 g_object_set (ia->preview_sheet, "selection", &sel, NULL);
280 char *range = create_cell_range (sel.start_x, sel.start_y, sel.end_x, sel.end_y);
282 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
284 gtk_entry_set_text (GTK_ENTRY (range_entry), range);
287 GtkWidget *sb0 = get_widget_assert (builder, "sb0");
288 GtkWidget *sb1 = get_widget_assert (builder, "sb1");
289 GtkWidget *sb2 = get_widget_assert (builder, "sb2");
290 GtkWidget *sb3 = get_widget_assert (builder, "sb3");
292 gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb0), sel.start_x);
293 gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb1), sel.start_y);
295 gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb2), sel.end_x);
296 gtk_spin_button_set_value (GTK_SPIN_BUTTON (sb3), sel.end_y);
298 ia->updating_selection = FALSE;
302 on_preview_selection_changed (SswSheet *sheet, gpointer selection,
303 PsppireImportAssistant *ia)
305 memcpy (&ia->selection, selection, sizeof (ia->selection));
306 do_selection_update (ia);
310 entry_update_selected_range (GtkEntry *entry, PsppireImportAssistant *ia)
312 const char *text = gtk_entry_get_text (entry);
314 if (convert_cell_ref (text,
315 &ia->selection.start_x, &ia->selection.start_y,
316 &ia->selection.end_x, &ia->selection.end_y))
318 do_selection_update (ia);
322 /* On change of any spinbutton, update the selected range accordingly. */
324 sb_update_selected_range (PsppireImportAssistant *ia)
326 GtkBuilder *builder = ia->spread_builder;
327 GtkWidget *sb0 = get_widget_assert (builder, "sb0");
328 GtkWidget *sb1 = get_widget_assert (builder, "sb1");
329 GtkWidget *sb2 = get_widget_assert (builder, "sb2");
330 GtkWidget *sb3 = get_widget_assert (builder, "sb3");
332 ia->selection.start_x = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb0));
333 ia->selection.start_y = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb1));
335 ia->selection.end_x = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb2));
336 ia->selection.end_y = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sb3));
338 do_selection_update (ia);
342 /* Initializes IA's sheet_spec substructure. */
344 sheet_spec_page_create (PsppireImportAssistant *ia)
346 GtkBuilder *builder = ia->spread_builder;
347 GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
349 ia->preview_sheet = get_widget_assert (builder, "preview-sheet");
351 g_signal_connect (ia->preview_sheet, "selection-changed",
352 G_CALLBACK (on_preview_selection_changed), ia);
354 gtk_widget_show (ia->preview_sheet);
357 GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
358 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
359 gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
360 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
361 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
365 g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
369 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
370 g_signal_connect (range_entry, "changed", G_CALLBACK (entry_update_selected_range), ia);
372 GtkWidget *sb0 = get_widget_assert (builder, "sb0");
373 g_signal_connect_swapped (sb0, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
374 GtkWidget *sb1 = get_widget_assert (builder, "sb1");
375 g_signal_connect_swapped (sb1, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
376 GtkWidget *sb2 = get_widget_assert (builder, "sb2");
377 g_signal_connect_swapped (sb2, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
378 GtkWidget *sb3 = get_widget_assert (builder, "sb3");
379 g_signal_connect_swapped (sb3, "value-changed", G_CALLBACK (sb_update_selected_range), ia);
383 add_page_to_assistant (ia, page,
384 GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
386 g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
387 g_object_set_data (G_OBJECT (page), "on-reset", reset_page);
391 /* Set the data model for both the data sheet and the variable sheet. */
393 spreadsheet_set_data_models (PsppireImportAssistant *ia)
395 GtkBuilder *builder = ia->spread_builder;
396 GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
397 GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
398 GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
400 struct spreadsheet_read_options opts;
401 opts.sheet_name = NULL;
402 opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
403 opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
404 opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
407 struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
409 PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
410 PsppireDataStore *store = psppire_data_store_new (dict);
411 psppire_data_store_set_reader (store, reader);
412 g_object_set (ia->data_sheet, "data-model", store, NULL);
413 g_object_set (ia->var_sheet, "data-model", dict, NULL);