e37d4353028ef9c5611690c1b582ddbbb7d9f2dc
[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, SSW_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   ssw_sheet_get_active_cell (SSW_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   ssw_sheet_get_active_cell (SSW_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   ssw_sheet_get_active_cell (SSW_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 (SswSheet *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 (SswSheet *sheet)
257 {
258   SswRange *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 (SswSheet *sheet, gpointer selection, gpointer p)
300 {
301   PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
302   SswRange *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 (SswSheet *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 ssw_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   parent_class = g_type_class_peek_parent (class);
448 }
449
450 GtkWidget*
451 psppire_variable_sheet_new (void)
452 {
453   PsppireVarSheetHeader *vsh =
454     g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
455
456   GObject *obj =
457     g_object_new (PSPPIRE_TYPE_VARIABLE_SHEET,
458                   "select-renderer-func", select_renderer_func,
459                   "hmodel", vsh,
460                   "forward-conversion", var_sheet_data_to_string,
461                   "editable", TRUE,
462                   NULL);
463
464   return GTK_WIDGET (obj);
465 }
466
467 static void
468 move_variable (PsppireVariableSheet *sheet, gint from, gint to, gpointer ud)
469 {
470   PsppireDict *dict = NULL;
471   g_object_get (sheet, "data-model", &dict, NULL);
472
473   if (dict == NULL)
474     return;
475
476   struct variable *var = psppire_dict_get_variable (dict, from);
477
478   if (var == NULL)
479     return;
480   gint new_pos = to;
481   /* The index refers to the final position, so if the source
482      is less than the destination, then we must subtract 1, to
483      account for the position vacated by the source */
484   if (from < to)
485     new_pos--;
486   dict_reorder_var (dict->dict, var, new_pos);
487 }
488
489 static void
490 psppire_variable_sheet_init (PsppireVariableSheet *sheet)
491 {
492   sheet->dispose_has_run = FALSE;
493
494   sheet->value_label_renderer = gtk_cell_renderer_text_new ();
495   g_signal_connect (sheet->value_label_renderer,
496                     "editing-started", G_CALLBACK (set_value_labels),
497                     sheet);
498
499   sheet->missing_values_renderer = gtk_cell_renderer_text_new ();
500   g_signal_connect (sheet->missing_values_renderer,
501                     "editing-started", G_CALLBACK (set_missing_values),
502                     sheet);
503
504   sheet->var_type_renderer = gtk_cell_renderer_text_new ();
505   g_signal_connect (sheet->var_type_renderer,
506                     "editing-started", G_CALLBACK (set_var_type),
507                     sheet);
508
509   sheet->row_popup = create_var_row_header_popup_menu (sheet);
510
511
512   g_signal_connect (sheet, "selection-changed",
513                     G_CALLBACK (set_var_popup_sensitivity), sheet);
514
515   g_signal_connect (sheet, "row-header-pressed",
516                     G_CALLBACK (show_variables_row_popup), sheet);
517
518   g_signal_connect_swapped (sheet, "value-changed",
519                             G_CALLBACK (change_var_property), sheet);
520
521   g_signal_connect (sheet, "row-moved",
522                     G_CALLBACK (move_variable), NULL);
523 }