New module: psppire_variable_sheet.c
[pspp] / src / ui / gui / psppire-variable-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-variable-sheet.h"
20
21 #include "ui/gui/psppire-var-sheet-header.h"
22
23 #include "psppire-dict.h"
24 #include "var-type-dialog.h"
25 #include "missing-val-dialog.h"
26 #include "val-labs-dialog.h"
27 #include "var-display.h"
28 #include "data/format.h"
29 #include "data/value-labels.h"
30 #include "helper.h"
31
32 #include <gettext.h>
33 #define _(msgid) gettext (msgid)
34 #define P_(X) (X)
35
36
37 G_DEFINE_TYPE (PsppireVariableSheet, psppire_variable_sheet, JMD_TYPE_SHEET)
38
39 static void
40 set_var_type (GtkCellRenderer *renderer,
41      GtkCellEditable *editable,
42      gchar           *path,
43      gpointer         user_data)
44 {
45   PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (user_data);
46   gint row = -1, col = -1;
47   jmd_sheet_get_active_cell (JMD_SHEET (sheet), &col, &row);
48
49   PsppireDict *dict = NULL;
50   g_object_get (sheet, "data-model", &dict, NULL);
51
52   struct variable *var =
53     psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
54
55   const struct fmt_spec *format = var_get_write_format (var);
56   struct fmt_spec fmt = *format;
57   GtkWindow *win = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet)));
58   if (GTK_RESPONSE_OK == psppire_var_type_dialog_run (win, &fmt))
59     {
60       var_set_width_and_formats (var, fmt_var_width (&fmt), &fmt, &fmt);
61     }
62 }
63
64 static void
65 set_missing_values (GtkCellRenderer *renderer,
66      GtkCellEditable *editable,
67      gchar           *path,
68      gpointer         user_data)
69 {
70   PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (user_data);
71   gint row = -1, col = -1;
72   jmd_sheet_get_active_cell (JMD_SHEET (sheet), &col, &row);
73
74   PsppireDict *dict = NULL;
75   g_object_get (sheet, "data-model", &dict, NULL);
76
77   struct variable *var =
78     psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
79
80   struct missing_values mv;
81   if (GTK_RESPONSE_OK ==
82       psppire_missing_val_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))),
83                                       var, &mv))
84     {
85       var_set_missing_values (var, &mv);
86     }
87
88   mv_destroy (&mv);
89 }
90
91 static void
92 set_value_labels (GtkCellRenderer *renderer,
93      GtkCellEditable *editable,
94      gchar           *path,
95      gpointer         user_data)
96 {
97   PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (user_data);
98   gint row = -1, col = -1;
99   jmd_sheet_get_active_cell (JMD_SHEET (sheet), &col, &row);
100
101   PsppireDict *dict = NULL;
102   g_object_get (sheet, "data-model", &dict, NULL);
103
104   struct variable *var =
105     psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
106
107   struct val_labs *vls =
108     psppire_val_labs_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))), var);
109
110   if (vls)
111     {
112       var_set_value_labels (var, vls);
113       val_labs_destroy (vls);
114     }
115 }
116
117 static GtkCellRenderer *
118 create_spin_renderer (GType type)
119 {
120   GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
121
122   GtkAdjustment *adj = gtk_adjustment_new (0,
123                                            0, G_MAXDOUBLE,
124                                            1, 1,
125                                            0);
126   g_object_set (r,
127                 "adjustment", adj,
128                 NULL);
129
130   return r;
131 }
132
133 static GtkCellRenderer *
134 create_combo_renderer (GType type)
135 {
136   GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
137
138   GEnumClass *ec = g_type_class_ref (type);
139
140   const GEnumValue *ev ;
141   for (ev = ec->values; ev->value_name; ++ev)
142     {
143       GtkTreeIter iter;
144
145       gtk_list_store_append (list_store, &iter);
146
147       gtk_list_store_set (list_store, &iter,
148                           0, ev->value,
149                           1, gettext (ev->value_nick),
150                           -1);
151     }
152
153   GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
154
155   g_object_set (r,
156                 "model", list_store,
157                 "text-column", 1,
158                 "has-entry", TRUE,
159                 NULL);
160
161   return r;
162 }
163
164 static GtkCellRenderer *spin_renderer;
165 static GtkCellRenderer *column_width_renderer;
166 static GtkCellRenderer *measure_renderer;
167 static GtkCellRenderer *alignment_renderer;
168
169 static GtkCellRenderer *
170 select_renderer_func (PsppireVariableSheet *sheet, gint col, gint row, GType type, gpointer ud)
171 {
172   if (!spin_renderer)
173     spin_renderer = create_spin_renderer (type);
174
175   if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
176     column_width_renderer = create_combo_renderer (type);
177
178   if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
179     measure_renderer = create_combo_renderer (type);
180
181   if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
182     alignment_renderer = create_combo_renderer (type);
183
184   switch  (col)
185     {
186     case DICT_TVM_COL_WIDTH:
187     case DICT_TVM_COL_DECIMAL:
188     case DICT_TVM_COL_COLUMNS:
189       return spin_renderer;
190
191     case DICT_TVM_COL_TYPE:
192       return sheet->var_type_renderer;
193
194     case DICT_TVM_COL_VALUE_LABELS:
195       return sheet->value_label_renderer;
196
197     case DICT_TVM_COL_MISSING_VALUES:
198       return sheet->missing_values_renderer;
199
200     case DICT_TVM_COL_ALIGNMENT:
201       return alignment_renderer;
202
203     case DICT_TVM_COL_MEASURE:
204       return measure_renderer;
205
206     case DICT_TVM_COL_ROLE:
207       return column_width_renderer;
208     }
209
210   return NULL;
211 }
212
213 \f
214
215 static void
216 show_variables_row_popup (JmdSheet *sheet, int row, uint button,
217                           uint state, gpointer p)
218 {
219   PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
220   GListModel *vmodel = NULL;
221   g_object_get (sheet, "vmodel", &vmodel, NULL);
222   if (vmodel == NULL)
223     return;
224
225   guint n_items = g_list_model_get_n_items (vmodel);
226
227   if (row >= n_items)
228     return;
229
230   if (button != 3)
231     return;
232
233   g_object_set_data (G_OBJECT (var_sheet->row_popup), "item",
234                      GINT_TO_POINTER (row));
235
236   gtk_menu_popup_at_pointer (GTK_MENU (var_sheet->row_popup), NULL);
237 }
238
239 static void
240 insert_new_variable_var (PsppireVariableSheet *var_sheet)
241 {
242   gint item = GPOINTER_TO_INT (g_object_get_data
243                                 (G_OBJECT (var_sheet->row_popup),
244                                  "item"));
245
246   PsppireDict *dict = NULL;
247   g_object_get (var_sheet, "data-model", &dict, NULL);
248
249   psppire_dict_insert_variable (dict, item, NULL);
250
251   gtk_widget_queue_draw (GTK_WIDGET (var_sheet));
252 }
253
254
255 static void
256 delete_variables (JmdSheet *sheet)
257 {
258   JmdRange *range = sheet->selection;
259
260   PsppireDict *dict = NULL;
261   g_object_get (sheet, "data-model", &dict, NULL);
262
263   psppire_dict_delete_variables (dict, range->start_y,
264                                  (range->end_y - range->start_y + 1));
265
266   gtk_widget_queue_draw (GTK_WIDGET (sheet));
267 }
268
269 static GtkWidget *
270 create_var_row_header_popup_menu (PsppireVariableSheet *var_sheet)
271 {
272   GtkWidget *menu = gtk_menu_new ();
273
274   GtkWidget *item =
275     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
276   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
277                             var_sheet);
278   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
279
280   item = gtk_separator_menu_item_new ();
281   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
282
283   var_sheet->clear_variables_menu_item =
284     gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
285
286   g_signal_connect_swapped (var_sheet->clear_variables_menu_item, "activate",
287                             G_CALLBACK (delete_variables), var_sheet);
288
289   gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item, FALSE);
290   gtk_menu_shell_append (GTK_MENU_SHELL (menu),
291                          var_sheet->clear_variables_menu_item);
292
293   gtk_widget_show_all (menu);
294   return menu;
295 }
296
297
298 static void
299 set_var_popup_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
300 {
301   PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
302   JmdRange *range = selection;
303   gint width = gtk_tree_model_get_n_columns (sheet->data_model);
304
305   gboolean whole_row_selected = (range->start_x == 0 &&
306                                  range->end_x == width - 1 - 1);
307   /*  PsppireDict has an "extra" column: TVM_COL_VAR   ^^^ */
308   gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item,
309                             whole_row_selected);
310 }
311
312 \f
313
314 static void
315 change_var_property (PsppireVariableSheet *var_sheet, gint col, gint row, const GValue *value)
316 {
317   PsppireDict *dict = NULL;
318   g_object_get (var_sheet, "data-model", &dict, NULL);
319
320   /* Return the IDXth variable */
321   struct variable *var =  psppire_dict_get_variable (dict, row);
322
323   if (NULL == var)
324     var = psppire_dict_insert_variable (dict, row, NULL);
325
326   switch (col)
327     {
328     case DICT_TVM_COL_NAME:
329       {
330         const char *name = g_value_get_string (value);
331         if (psppire_dict_check_name (dict, name, FALSE))
332           dict_rename_var (dict->dict, var, g_value_get_string (value));
333       }
334       break;
335     case DICT_TVM_COL_WIDTH:
336       {
337       gint width = g_value_get_int (value);
338       if (var_is_numeric (var))
339         {
340           struct fmt_spec format = *var_get_print_format (var);
341           fmt_change_width (&format, width, FMT_FOR_OUTPUT);
342           var_set_both_formats (var, &format);
343         }
344       else
345         {
346           var_set_width (var, width);
347         }
348       }
349       break;
350     case DICT_TVM_COL_DECIMAL:
351       {
352       gint decimals = g_value_get_int (value);
353       if (decimals >= 0)
354         {
355           struct fmt_spec format = *var_get_print_format (var);
356           fmt_change_decimals (&format, decimals, FMT_FOR_OUTPUT);
357           var_set_both_formats (var, &format);
358         }
359       }
360       break;
361     case DICT_TVM_COL_LABEL:
362       var_set_label (var, g_value_get_string (value));
363       break;
364     case DICT_TVM_COL_COLUMNS:
365       var_set_display_width (var, g_value_get_int (value));
366       break;
367     case DICT_TVM_COL_MEASURE:
368       var_set_measure (var, g_value_get_int (value));
369       break;
370     case DICT_TVM_COL_ALIGNMENT:
371       var_set_alignment (var, g_value_get_int (value));
372       break;
373     case DICT_TVM_COL_ROLE:
374       var_set_role (var, g_value_get_int (value));
375       break;
376     default:
377       g_warning ("Changing unknown column %d of variable sheet column not supported",
378                  col);
379       break;
380     }
381 }
382
383 static gchar *
384 var_sheet_data_to_string (JmdSheet *sheet, GtkTreeModel *m,
385                           gint col, gint row, const GValue *in)
386 {
387   if (col >= n_DICT_COLS - 1) /* -1 because psppire-dict has an extra column */
388     return NULL;
389
390   const struct variable *var = psppire_dict_get_variable (PSPPIRE_DICT (m), row);
391   if (var == NULL)
392     return NULL;
393
394   if (col == DICT_TVM_COL_TYPE)
395     {
396       const struct fmt_spec *print = var_get_print_format (var);
397       return strdup (fmt_gui_name (print->type));
398     }
399   else if (col == DICT_TVM_COL_MISSING_VALUES)
400     return missing_values_to_string (var, NULL);
401   else if (col == DICT_TVM_COL_VALUE_LABELS)
402     {
403       const struct val_labs *vls = var_get_value_labels (var);
404       if (vls == NULL || val_labs_count (vls) == 0)
405         return strdup (_("None"));
406       const struct val_lab **labels = val_labs_sorted (vls);
407       const struct val_lab *vl = labels[0];
408       gchar *vstr = value_to_text (vl->value, var);
409       char *text = xasprintf (_("{%s, %s}..."), vstr,
410                               val_lab_get_escaped_label (vl));
411       free (vstr);
412       free (labels);
413       return text;
414     }
415
416   return jmd_sheet_default_forward_conversion (sheet, m, col, row, in);
417 }
418
419 \f
420
421 static GObjectClass * parent_class = NULL;
422
423 static void
424 psppire_variable_sheet_dispose (GObject *obj)
425 {
426   PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (obj);
427
428   if (sheet->dispose_has_run)
429     return;
430
431   sheet->dispose_has_run = TRUE;
432
433   g_object_unref (sheet->value_label_renderer);
434   g_object_unref (sheet->missing_values_renderer);
435   g_object_unref (sheet->var_type_renderer);
436
437   /* Chain up to the parent class */
438   G_OBJECT_CLASS (parent_class)->dispose (obj);
439 }
440
441 static void
442 psppire_variable_sheet_class_init (PsppireVariableSheetClass *class)
443 {
444   GObjectClass *object_class = G_OBJECT_CLASS (class);
445   object_class->dispose = psppire_variable_sheet_dispose;
446 }
447
448 GtkWidget*
449 psppire_variable_sheet_new (void)
450 {
451   PsppireVarSheetHeader *vsh =
452     g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
453
454   GObject *obj =
455     g_object_new (PSPPIRE_TYPE_VARIABLE_SHEET,
456                   "select-renderer-func", select_renderer_func,
457                   "hmodel", vsh,
458                   "forward-conversion", var_sheet_data_to_string,
459                   NULL);
460
461   return GTK_WIDGET (obj);
462 }
463
464 static void
465 psppire_variable_sheet_init (PsppireVariableSheet *sheet)
466 {
467   sheet->dispose_has_run = FALSE;
468
469   sheet->value_label_renderer = gtk_cell_renderer_text_new ();
470   g_signal_connect (sheet->value_label_renderer,
471                     "editing-started", G_CALLBACK (set_value_labels),
472                     sheet);
473
474   sheet->missing_values_renderer = gtk_cell_renderer_text_new ();
475   g_signal_connect (sheet->missing_values_renderer,
476                     "editing-started", G_CALLBACK (set_missing_values),
477                     sheet);
478
479   sheet->var_type_renderer = gtk_cell_renderer_text_new ();
480   g_signal_connect (sheet->var_type_renderer,
481                     "editing-started", G_CALLBACK (set_var_type),
482                     sheet);
483
484   sheet->row_popup = create_var_row_header_popup_menu (sheet);
485
486
487   g_signal_connect (sheet, "selection-changed",
488                     G_CALLBACK (set_var_popup_sensitivity), sheet);
489
490   g_signal_connect (sheet, "row-header-pressed",
491                     G_CALLBACK (show_variables_row_popup), sheet);
492
493   g_signal_connect_swapped (sheet, "value-changed",
494                             G_CALLBACK (change_var_property), sheet);
495 }