psppire-import-spreadsheet.c: Fix typo
[pspp] / src / ui / gui / psppire-import-spreadsheet.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
19
20 #include "psppire-import-assistant.h"
21 #include "psppire-import-spreadsheet.h"
22 #include "builder-wrapper.h"
23
24 #include "libpspp/misc.h"
25 #include "psppire-spreadsheet-model.h"
26 #include "psppire-spreadsheet-data-model.h"
27 #include "psppire-data-store.h"
28
29 #include <gettext.h>
30 #define _(msgid) gettext (msgid)
31 #define N_(msgid) msgid
32
33 static void
34 set_column_header_label (GtkWidget *button, int i, gpointer user_data)
35 {
36   gchar *x = int_to_ps26 (i);
37   gtk_button_set_label (GTK_BUTTON (button), x);
38   g_free (x);
39 }
40
41 static void do_selection_update (PsppireImportAssistant *ia);
42
43 static void
44 on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
45 {
46   GtkBuilder *builder = ia->spread_builder;
47   gint sheet_number = gtk_combo_box_get_active (cb);
48
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;
51
52   {
53     /* Now set the spin button upper limits according to the size of the selected sheet.  */
54
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");
59
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);
65
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);
73
74
75     GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb0));
76     gtk_adjustment_set_upper (adj, coli);
77
78     adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb1));
79     gtk_adjustment_set_upper (adj, rowi);
80
81     adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb2));
82     gtk_adjustment_set_upper (adj, coli);
83
84     adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (sb3));
85     gtk_adjustment_set_upper (adj, rowi);
86   }
87
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,
92                 "editable", FALSE,
93                 NULL);
94   g_object_unref (data_model);
95
96   GObject *hmodel = NULL;
97   g_object_get (ia->preview_sheet, "hmodel", &hmodel, NULL);
98   
99   g_object_set (hmodel,
100                 "post-button-create-func", set_column_header_label,
101                 NULL);
102
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);
107 }
108
109 /* Ensure that PARTNER is never less than than SUBJECT.  */
110 static void
111 on_value_change_lower (GtkSpinButton *subject, GtkSpinButton *partner)
112 {
113   gint p = gtk_spin_button_get_value_as_int (partner);
114   gint s = gtk_spin_button_get_value_as_int (subject);
115
116   if (s > p)
117     gtk_spin_button_set_value (partner, s);
118 }
119
120 /* Ensure that PARTNER is never greater than to SUBJECT.  */
121 static void
122 on_value_change_upper (GtkSpinButton *subject, GtkSpinButton *partner)
123 {
124   gint p = gtk_spin_button_get_value_as_int (partner);
125   gint s = gtk_spin_button_get_value_as_int (subject);
126
127   if (s < p)
128     gtk_spin_button_set_value (partner, s);
129 }
130
131
132 /* Sets SB to use 1 based display instead of 0 based.  */
133 static gboolean
134 row_output (GtkSpinButton *sb, gpointer unused)
135 {
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);
139   free (text);
140
141   return TRUE;
142 }
143
144 /* Sets SB to use text like A, B, C instead of 0, 1, 2 etc.  */
145 static gboolean
146 column_output (GtkSpinButton *sb, gpointer unused)
147 {
148   gint value = gtk_spin_button_get_value_as_int (sb);
149   char *text = int_to_ps26 (value);
150   if (text == NULL)
151     return FALSE;
152
153   gtk_entry_set_text (GTK_ENTRY (sb), text);
154   free (text);
155
156   return TRUE;
157 }
158
159 /* Interprets the SBs text as 1 based instead of zero based.  */
160 static gint
161 row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
162 {
163   const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
164   gdouble value = g_strtod (text, NULL) - 1;
165
166   if (value < 0)
167     return FALSE;
168
169   memcpy (new_value, &value, sizeof (value));
170
171   return TRUE;
172 }
173
174
175 /* Interprets the SBs text of the form A, B, C etc and
176    sets NEW_VALUE as a double.  */
177 static gint
178 column_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
179 {
180   const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
181   double value = ps26_to_int (text);
182
183   if (value < 0)
184     return FALSE;
185
186   memcpy (new_value, &value, sizeof (value));
187
188   return TRUE;
189 }
190
191 static void
192 reset_page (PsppireImportAssistant *ia)
193 {
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);
197
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);
201
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;
204
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);
209 }
210
211 /* Prepares IA's sheet_spec page. */
212 static void
213 prepare_sheet_spec_page (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
214 {
215   if (dir != IMPORT_ASSISTANT_FORWARDS)
216     return;
217
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");
221
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);
225
226   gint items = gtk_tree_model_iter_n_children (model, NULL);
227   gtk_widget_set_sensitive (sheet_entry, items > 1);
228
229   gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
230   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
231
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);
234
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");
238
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);
241
242   GtkWidget *sb1 = get_widget_assert (builder, "sb1");
243   GtkWidget *sb3 = get_widget_assert (builder, "sb3");
244
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);
247
248
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);
253
254   g_signal_connect (sb2, "output", G_CALLBACK (column_output), NULL);
255   g_signal_connect (sb2, "input", G_CALLBACK (column_input), NULL);
256
257   g_signal_connect (sb1, "output", G_CALLBACK (row_output), NULL);
258   g_signal_connect (sb1, "input", G_CALLBACK (row_input), NULL);
259
260   g_signal_connect (sb3, "output", G_CALLBACK (row_output), NULL);
261   g_signal_connect (sb3, "input", G_CALLBACK (row_input), NULL);
262 }
263
264 static void
265 do_selection_update (PsppireImportAssistant *ia)
266 {
267   GtkBuilder *builder = ia->spread_builder;
268
269   /* Stop this function re-entering itself.  */
270   if (ia->updating_selection)
271     return;
272   ia->updating_selection = TRUE;
273
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;
277
278   g_object_set (ia->preview_sheet, "selection", &sel, NULL);
279
280   char *range = create_cell_range (sel.start_x, sel.start_y, sel.end_x, sel.end_y);
281
282   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
283   if (range)
284     gtk_entry_set_text (GTK_ENTRY (range_entry), range);
285   free (range);
286
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");
291
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);
294
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);
297
298   ia->updating_selection = FALSE;
299 }
300
301 static void
302 on_preview_selection_changed (SswSheet *sheet, gpointer selection,
303                               PsppireImportAssistant *ia)
304 {
305   memcpy (&ia->selection, selection, sizeof (ia->selection));
306   do_selection_update (ia);
307 }
308
309 static void
310 entry_update_selected_range (GtkEntry *entry, PsppireImportAssistant *ia)
311 {
312   const char *text = gtk_entry_get_text (entry);
313
314   if (convert_cell_ref (text,
315                         &ia->selection.start_x, &ia->selection.start_y,
316                         &ia->selection.end_x, &ia->selection.end_y))
317     {
318       do_selection_update (ia);
319     }
320 }
321
322 /* On change of any spinbutton, update the selected range accordingly.   */
323 static void
324 sb_update_selected_range (PsppireImportAssistant *ia)
325 {
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");
331
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));
334
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));
337
338   do_selection_update (ia);
339 }
340
341
342 /* Initializes IA's sheet_spec substructure. */
343 void
344 sheet_spec_page_create (PsppireImportAssistant *ia)
345 {
346   GtkBuilder *builder = ia->spread_builder;
347   GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
348
349   ia->preview_sheet = get_widget_assert (builder, "preview-sheet");
350
351   g_signal_connect (ia->preview_sheet, "selection-changed",
352                     G_CALLBACK (on_preview_selection_changed), ia);
353
354   gtk_widget_show (ia->preview_sheet);
355
356   {
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,
362                                     "text", 0,
363                                     NULL);
364
365     g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
366   }
367
368   {
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);
371
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);
380   }
381
382
383   add_page_to_assistant (ia, page,
384                          GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
385
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);
388 }
389
390
391 /* Set the data model for both the data sheet and the variable sheet.  */
392 void
393 spreadsheet_set_data_models (PsppireImportAssistant *ia)
394 {
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");
399
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)));
405   opts.asw = 8;
406
407   struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
408
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);
414 }
415
416