Rename myreversefunc -> psppire_data_store_string_to_value
[pspp] / src / ui / gui / psppire-data-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2017  John Darrington
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
18 #include <config.h>
19 #include "psppire-data-sheet.h"
20 #include <math.h>
21
22 #include <gettext.h>
23 #define _(msgid) gettext (msgid)
24 #define P_(X) (X)
25
26 #include "value-variant.h"
27
28 #include "ui/gui/executor.h"
29 #include "psppire-data-window.h"
30 #include "ssw-axis-model.h"
31
32 static void
33 do_sort (PsppireDataSheet *sheet, GtkSortType order)
34 {
35   SswRange *range = SSW_SHEET(sheet)->selection;
36
37   PsppireDataStore *data_store = NULL;
38   g_object_get (sheet, "data-model", &data_store, NULL);
39
40   int n_vars = 0;
41   int i;
42
43   PsppireDataWindow *pdw =
44      psppire_data_window_for_data_store (data_store);
45
46   GString *syntax = g_string_new ("SORT CASES BY");
47   for (i = range->start_x ; i <= range->end_x; ++i)
48     {
49       const struct variable *var = psppire_dict_get_variable (data_store->dict, i);
50       if (var != NULL)
51         {
52           g_string_append_printf (syntax, " %s", var_get_name (var));
53           n_vars++;
54         }
55     }
56   if (n_vars > 0)
57     {
58       if (order == GTK_SORT_DESCENDING)
59         g_string_append (syntax, " (DOWN)");
60       g_string_append_c (syntax, '.');
61       execute_const_syntax_string (pdw, syntax->str);
62     }
63   g_string_free (syntax, TRUE);
64 }
65
66
67 static void
68 sort_ascending (PsppireDataSheet *sheet)
69 {
70   do_sort (sheet, GTK_SORT_ASCENDING);
71
72   gtk_widget_queue_draw (GTK_WIDGET (sheet));
73 }
74
75 static void
76 sort_descending (PsppireDataSheet *sheet)
77 {
78   do_sort (sheet, GTK_SORT_DESCENDING);
79
80   gtk_widget_queue_draw (GTK_WIDGET (sheet));
81 }
82
83 \f
84
85 static void
86 change_data_value (PsppireDataSheet *sheet, gint col, gint row, GValue *value)
87 {
88   PsppireDataStore *store = NULL;
89   g_object_get (sheet, "data-model", &store, NULL);
90
91   const struct variable *var = psppire_dict_get_variable (store->dict, col);
92
93   if (NULL == var)
94     return;
95
96   union value v;
97
98   GVariant *vrnt = g_value_get_variant (value);
99
100   value_variant_get (&v, vrnt);
101
102   psppire_data_store_set_value (store, row, var, &v);
103
104   value_destroy_from_variant (&v, vrnt);
105 }
106
107 \f
108
109 static void
110 show_cases_row_popup (PsppireDataSheet *sheet, int row,
111                       guint button, guint state, gpointer p)
112 {
113   GListModel *vmodel = NULL;
114   g_object_get (sheet, "vmodel", &vmodel, NULL);
115   if (vmodel == NULL)
116     return;
117
118   guint n_items = g_list_model_get_n_items (vmodel);
119
120   if (row >= n_items)
121     return;
122
123   if (button != 3)
124     return;
125
126   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
127                      GINT_TO_POINTER (row));
128
129   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
130 }
131
132
133 static void
134 insert_new_case (PsppireDataSheet *sheet)
135 {
136   PsppireDataStore *data_store = NULL;
137   g_object_get (sheet, "data-model", &data_store, NULL);
138
139   gint posn = GPOINTER_TO_INT (g_object_get_data
140                                 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
141
142   psppire_data_store_insert_new_case (data_store, posn);
143
144   gtk_widget_queue_draw (GTK_WIDGET (sheet));
145 }
146
147 static void
148 delete_cases (PsppireDataSheet *sheet)
149 {
150   SswRange *range = SSW_SHEET(sheet)->selection;
151
152   PsppireDataStore *data_store = NULL;
153   g_object_get (sheet, "data-model", &data_store, NULL);
154
155   psppire_data_store_delete_cases (data_store, range->start_y,
156                                    range->end_y - range->start_y + 1);
157
158   gtk_widget_queue_draw (GTK_WIDGET (sheet));
159 }
160
161 static GtkWidget *
162 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
163 {
164   GtkWidget *menu = gtk_menu_new ();
165
166   GtkWidget *item =
167     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
168
169   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
170   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
171
172   item = gtk_separator_menu_item_new ();
173   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
174
175   sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
176   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
177   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
178   g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
179                             G_CALLBACK (delete_cases), sheet);
180
181   gtk_widget_show_all (menu);
182   return menu;
183 }
184
185
186 static void
187 show_cases_column_popup (PsppireDataSheet *sheet, int column, guint button, guint state,
188                          gpointer p)
189 {
190   GListModel *hmodel = NULL;
191   g_object_get (sheet, "hmodel", &hmodel, NULL);
192   if (hmodel == NULL)
193     return;
194
195   guint n_items = g_list_model_get_n_items (hmodel);
196
197   if (column >= n_items)
198     return;
199
200   if (button != 3)
201     return;
202
203   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
204                      GINT_TO_POINTER (column));
205
206   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
207 }
208
209 static void
210 insert_new_variable (PsppireDataSheet *sheet)
211 {
212   PsppireDataStore *data_store = NULL;
213   g_object_get (sheet, "data-model", &data_store, NULL);
214
215   gint posn = GPOINTER_TO_INT (g_object_get_data
216                                 (G_OBJECT (sheet->data_sheet_cases_column_popup),
217                                  "item"));
218
219   const struct variable *v = psppire_dict_insert_variable (data_store->dict,
220                                                            posn, NULL);
221
222   psppire_data_store_insert_value (data_store, var_get_width(v),
223                                    var_get_case_index (v));
224
225   gtk_widget_queue_draw (GTK_WIDGET (sheet));
226 }
227
228 static void
229 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
230 {
231   SswRange *range = selection;
232
233   PsppireDataStore *data_store = NULL;
234   g_object_get (sheet, "data-model", &data_store, NULL);
235
236
237   gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
238   gint length = psppire_data_store_get_case_count (data_store);
239
240
241   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
242   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
243
244   gboolean whole_column_selected =
245     (range->start_y == 0 && range->end_y == length - 1);
246   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
247                             whole_column_selected);
248   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
249                             whole_column_selected);
250   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
251                             whole_column_selected);
252 }
253
254 static void
255 delete_variables (PsppireDataSheet *sheet)
256 {
257   SswRange *range = SSW_SHEET(sheet)->selection;
258
259   PsppireDataStore *data_store = NULL;
260   g_object_get (sheet, "data-model", &data_store, NULL);
261
262   psppire_dict_delete_variables (data_store->dict, range->start_x,
263                                  (range->end_x - range->start_x + 1));
264
265   gtk_widget_queue_draw (GTK_WIDGET (sheet));
266 }
267
268
269
270 static GtkWidget *
271 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
272 {
273   GtkWidget *menu = gtk_menu_new ();
274
275   GtkWidget *item =
276     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
277   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
278                             sheet);
279   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
280
281   item = gtk_separator_menu_item_new ();
282   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
283
284   sheet->data_clear_variables_menu_item =
285     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
286   g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
287                             G_CALLBACK (delete_variables),
288                             sheet);
289   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
290   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
291
292   item = gtk_separator_menu_item_new ();
293   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
294
295
296   sheet->data_sort_ascending_menu_item =
297     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
298   g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
299                             G_CALLBACK (sort_ascending), sheet);
300   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
301   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
302
303   sheet->data_sort_descending_menu_item =
304     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
305   g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
306                             G_CALLBACK (sort_descending), sheet);
307   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
308   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
309
310   gtk_widget_show_all (menu);
311   return menu;
312 }
313
314
315 \f
316
317 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
318
319 static GObjectClass * parent_class = NULL;
320 static gboolean dispose_has_run = FALSE;
321
322 static void
323 psppire_data_sheet_dispose (GObject *obj)
324 {
325   //  PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
326
327   if (dispose_has_run)
328     return;
329
330   dispose_has_run = TRUE;
331
332   /* Chain up to the parent class */
333   G_OBJECT_CLASS (parent_class)->dispose (obj);
334 }
335
336 static void
337 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
338 {
339   GObjectClass *object_class = G_OBJECT_CLASS (class);
340   object_class->dispose = psppire_data_sheet_dispose;
341
342   parent_class = g_type_class_peek_parent (class);
343 }
344
345 GtkWidget*
346 psppire_data_sheet_new (void)
347 {
348   GObject *obj =
349     g_object_new (PSPPIRE_TYPE_DATA_SHEET,
350                   "forward-conversion", psppire_data_store_value_to_string,
351                   "reverse-conversion", psppire_data_store_string_to_value,
352                   "editable", TRUE,
353                   "horizontal-draggable", TRUE,
354                   NULL);
355
356   return GTK_WIDGET (obj);
357 }
358
359
360 static gboolean
361 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
362 {
363   guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
364
365   if (!psppire_data_store_filtered (store, row))
366     return FALSE;
367
368   /* Draw a diagonal line through the widget */
369   guint width = gtk_widget_get_allocated_width (widget);
370   guint height = gtk_widget_get_allocated_height (widget);
371
372   GtkStyleContext *sc = gtk_widget_get_style_context (widget);
373   gtk_render_line (sc, cr, 0, 0, width, height);
374
375   return FALSE;
376 }
377
378 static void
379 button_post_create (GtkWidget *button, guint i, gpointer user_data)
380 {
381   PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
382
383   g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
384   g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
385 }
386
387
388 static gboolean
389 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
390 {
391   if (pos < 0)
392     return FALSE;
393
394   PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
395   PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
396   PangoLayout *layout = pango_layout_new (context);
397   PangoRectangle rect;
398   
399   pango_layout_set_text (layout, "M", 1);
400   pango_layout_get_extents (layout, NULL, &rect);
401   
402   gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
403   
404   g_object_unref (G_OBJECT (layout));
405   g_object_unref (G_OBJECT (context));
406   
407   gint Ms = round ((size / width_of_M) - 0.25);
408   struct variable *var = psppire_dict_get_variable (dict, pos);
409   g_return_val_if_fail (var, TRUE);
410   var_set_display_width (var, Ms);
411   return TRUE;
412 }
413
414 static void
415 set_dictionary (PsppireDataSheet *sheet)
416 {
417   GtkTreeModel *data_model = NULL;
418   g_object_get (sheet, "data-model", &data_model, NULL);
419
420   PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
421   g_object_set (sheet, "hmodel", store->dict, NULL);
422
423   g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
424                     sheet);
425
426   SswAxisModel *vmodel = NULL;
427   g_object_get (sheet, "vmodel", &vmodel, NULL);
428   g_assert (SSW_IS_AXIS_MODEL (vmodel));
429
430   g_object_set (vmodel,
431                 "post-button-create-func", button_post_create,
432                 "post-button-create-func-data", store,
433                 NULL);
434 }
435
436 static void
437 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
438 {
439   PsppireDataStore *data_store = NULL;
440   g_object_get (sheet, "data-model", &data_store, NULL);
441
442   if (data_store == NULL)
443     return;
444
445   PsppireDict *dict = data_store->dict;
446   struct variable *var = psppire_dict_get_variable (dict, from);
447
448   if (var == NULL)
449     return;
450   gint new_pos = to;
451   /* The index refers to the final position, so if the source
452      is less than the destination, then we must subtract 1, to
453      account for the position vacated by the source */
454   if (from < to)
455     new_pos--;
456   dict_reorder_var (dict->dict, var, new_pos);
457 }
458
459 static void
460 psppire_data_sheet_init (PsppireDataSheet *sheet)
461 {
462   sheet->data_sheet_cases_column_popup =
463     create_data_column_header_popup_menu (sheet);
464
465   sheet->data_sheet_cases_row_popup =
466     create_data_row_header_popup_menu (sheet);
467
468   g_signal_connect (sheet, "selection-changed",
469                     G_CALLBACK (set_menu_items_sensitivity), sheet);
470
471   g_signal_connect (sheet, "column-header-pressed",
472                     G_CALLBACK (show_cases_column_popup), sheet);
473
474   g_signal_connect (sheet, "row-header-pressed",
475                     G_CALLBACK (show_cases_row_popup), sheet);
476
477   g_signal_connect (sheet, "value-changed",
478                     G_CALLBACK (change_data_value), NULL);
479
480   g_signal_connect (sheet, "notify::data-model",
481                     G_CALLBACK (set_dictionary), NULL);
482
483   g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);
484 }