1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017, 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/>.
19 #include "psppire-variable-sheet.h"
21 #include "ui/gui/psppire-var-sheet-header.h"
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"
33 #define _(msgid) gettext (msgid)
37 G_DEFINE_TYPE (PsppireVariableSheet, psppire_variable_sheet, SSW_TYPE_SHEET)
40 set_var_type (PsppireVariableSheet *sheet)
42 gint row = -1, col = -1;
43 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
45 PsppireDict *dict = NULL;
46 g_object_get (sheet, "data-model", &dict, NULL);
48 struct variable *var =
49 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
51 const struct fmt_spec *format = var_get_write_format (var);
52 struct fmt_spec fmt = *format;
53 GtkWindow *win = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet)));
54 if (GTK_RESPONSE_OK == psppire_var_type_dialog_run (win, &fmt))
56 var_set_width_and_formats (var, fmt_var_width (&fmt), &fmt, &fmt);
61 set_missing_values (PsppireVariableSheet *sheet)
63 gint row = -1, col = -1;
64 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
66 PsppireDict *dict = NULL;
67 g_object_get (sheet, "data-model", &dict, NULL);
69 struct variable *var =
70 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
72 struct missing_values mv;
73 if (GTK_RESPONSE_OK ==
74 psppire_missing_val_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))),
77 var_set_missing_values (var, &mv);
84 set_value_labels (PsppireVariableSheet *sheet)
86 gint row = -1, col = -1;
87 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
89 PsppireDict *dict = NULL;
90 g_object_get (sheet, "data-model", &dict, NULL);
92 struct variable *var =
93 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
95 struct val_labs *vls =
96 psppire_val_labs_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))), var);
100 var_set_value_labels (var, vls);
101 val_labs_destroy (vls);
105 static GtkCellRenderer *
106 create_spin_renderer (GType type)
108 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
110 GtkAdjustment *adj = gtk_adjustment_new (0,
121 static GtkCellRenderer *
122 create_combo_renderer (GType type)
124 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
126 GEnumClass *ec = g_type_class_ref (type);
128 const GEnumValue *ev ;
129 for (ev = ec->values; ev->value_name; ++ev)
133 gtk_list_store_append (list_store, &iter);
135 gtk_list_store_set (list_store, &iter,
137 1, gettext (ev->value_nick),
141 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
152 static GtkCellRenderer *spin_renderer;
153 static GtkCellRenderer *column_width_renderer;
154 static GtkCellRenderer *measure_renderer;
155 static GtkCellRenderer *alignment_renderer;
157 static GtkCellRenderer *
158 select_renderer_func (PsppireVariableSheet *sheet, gint col, gint row, GType type, gpointer ud)
161 spin_renderer = create_spin_renderer (type);
163 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
164 column_width_renderer = create_combo_renderer (type);
166 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
167 measure_renderer = create_combo_renderer (type);
169 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
170 alignment_renderer = create_combo_renderer (type);
174 case DICT_TVM_COL_WIDTH:
175 case DICT_TVM_COL_DECIMAL:
176 case DICT_TVM_COL_COLUMNS:
177 return spin_renderer;
179 case DICT_TVM_COL_TYPE:
180 return sheet->var_type_renderer;
182 case DICT_TVM_COL_VALUE_LABELS:
183 return sheet->value_label_renderer;
185 case DICT_TVM_COL_MISSING_VALUES:
186 return sheet->missing_values_renderer;
188 case DICT_TVM_COL_ALIGNMENT:
189 return alignment_renderer;
191 case DICT_TVM_COL_MEASURE:
192 return measure_renderer;
194 case DICT_TVM_COL_ROLE:
195 return column_width_renderer;
204 show_variables_row_popup (SswSheet *sheet, int row, guint button,
205 guint state, gpointer p)
207 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
208 GListModel *vmodel = NULL;
209 g_object_get (sheet, "vmodel", &vmodel, NULL);
213 guint n_items = g_list_model_get_n_items (vmodel);
221 g_object_set_data (G_OBJECT (var_sheet->row_popup), "item",
222 GINT_TO_POINTER (row));
224 gtk_menu_popup_at_pointer (GTK_MENU (var_sheet->row_popup), NULL);
228 insert_new_variable_var (PsppireVariableSheet *var_sheet)
230 gint item = GPOINTER_TO_INT (g_object_get_data
231 (G_OBJECT (var_sheet->row_popup),
234 PsppireDict *dict = NULL;
235 g_object_get (var_sheet, "data-model", &dict, NULL);
237 psppire_dict_insert_variable (dict, item, NULL);
239 gtk_widget_queue_draw (GTK_WIDGET (var_sheet));
244 delete_variables (SswSheet *sheet)
246 SswRange *range = sheet->selection;
248 PsppireDict *dict = NULL;
249 g_object_get (sheet, "data-model", &dict, NULL);
251 if (range->start_x > range->end_x)
253 gint temp = range->start_x;
254 range->start_x = range->end_x;
258 psppire_dict_delete_variables (dict, range->start_y,
259 (range->end_y - range->start_y + 1));
261 gtk_widget_queue_draw (GTK_WIDGET (sheet));
265 create_var_row_header_popup_menu (PsppireVariableSheet *var_sheet)
267 GtkWidget *menu = gtk_menu_new ();
269 /* gtk_menu_shell_append does not sink/ref this object,
270 so we must do it ourselves (and remember to unref it). */
271 g_object_ref_sink (menu);
274 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
275 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
277 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
279 item = gtk_separator_menu_item_new ();
280 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
282 var_sheet->clear_variables_menu_item =
283 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
285 g_signal_connect_swapped (var_sheet->clear_variables_menu_item, "activate",
286 G_CALLBACK (delete_variables), var_sheet);
288 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item, FALSE);
289 gtk_menu_shell_append (GTK_MENU_SHELL (menu),
290 var_sheet->clear_variables_menu_item);
292 gtk_widget_show_all (menu);
298 set_var_popup_sensitivity (SswSheet *sheet, gpointer selection, gpointer p)
300 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
301 SswRange *range = selection;
302 gint width = gtk_tree_model_get_n_columns (sheet->data_model);
304 gboolean whole_row_selected = (range->start_x == 0 &&
305 range->end_x == width - 1 - 1);
306 /* PsppireDict has an "extra" column: TVM_COL_VAR ^^^ */
307 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item,
314 change_var_property (PsppireVariableSheet *var_sheet, gint col, gint row, const GValue *value)
316 PsppireDict *dict = NULL;
317 g_object_get (var_sheet, "data-model", &dict, NULL);
319 int n_rows = psppire_dict_get_var_cnt (dict);
323 /* Return the IDXth variable */
324 struct variable *var = psppire_dict_get_variable (dict, row);
327 var = psppire_dict_insert_variable (dict, row, NULL);
331 case DICT_TVM_COL_NAME:
333 const char *name = g_value_get_string (value);
334 if (psppire_dict_check_name (dict, name, FALSE))
335 dict_rename_var (dict->dict, var, g_value_get_string (value));
338 case DICT_TVM_COL_WIDTH:
340 gint width = g_value_get_int (value);
341 if (var_is_numeric (var))
343 struct fmt_spec format = *var_get_print_format (var);
344 fmt_change_width (&format, width, FMT_FOR_OUTPUT);
345 var_set_both_formats (var, &format);
349 var_set_width (var, width);
353 case DICT_TVM_COL_DECIMAL:
355 gint decimals = g_value_get_int (value);
358 struct fmt_spec format = *var_get_print_format (var);
359 fmt_change_decimals (&format, decimals, FMT_FOR_OUTPUT);
360 var_set_both_formats (var, &format);
364 case DICT_TVM_COL_LABEL:
365 var_set_label (var, g_value_get_string (value));
367 case DICT_TVM_COL_COLUMNS:
368 var_set_display_width (var, g_value_get_int (value));
370 case DICT_TVM_COL_MEASURE:
371 var_set_measure (var, g_value_get_int (value));
373 case DICT_TVM_COL_ALIGNMENT:
374 var_set_alignment (var, g_value_get_int (value));
376 case DICT_TVM_COL_ROLE:
377 var_set_role (var, g_value_get_int (value));
380 g_warning ("Changing unknown column %d of variable sheet column not supported",
387 var_sheet_data_to_string (SswSheet *sheet, GtkTreeModel *m,
388 gint col, gint row, const GValue *in)
390 if (col >= n_DICT_COLS - 1) /* -1 because psppire-dict has an extra column */
393 const struct variable *var = psppire_dict_get_variable (PSPPIRE_DICT (m), row);
397 if (col == DICT_TVM_COL_TYPE)
399 const struct fmt_spec *print = var_get_print_format (var);
400 return strdup (fmt_gui_name (print->type));
402 else if (col == DICT_TVM_COL_MISSING_VALUES)
403 return missing_values_to_string (var, NULL);
404 else if (col == DICT_TVM_COL_VALUE_LABELS)
406 const struct val_labs *vls = var_get_value_labels (var);
407 if (vls == NULL || val_labs_count (vls) == 0)
408 return strdup (_("None"));
409 const struct val_lab **labels = val_labs_sorted (vls);
410 const struct val_lab *vl = labels[0];
411 gchar *vstr = value_to_text (vl->value, var);
412 char *text = xasprintf (_("{%s, %s}..."), vstr,
413 val_lab_get_escaped_label (vl));
419 return ssw_sheet_default_forward_conversion (sheet, m, col, row, in);
424 static GObjectClass * parent_class = NULL;
427 psppire_variable_sheet_dispose (GObject *obj)
429 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (obj);
431 if (sheet->dispose_has_run)
434 sheet->dispose_has_run = TRUE;
436 g_object_unref (sheet->value_label_renderer);
437 g_object_unref (sheet->missing_values_renderer);
438 g_object_unref (sheet->var_type_renderer);
439 g_object_unref (sheet->row_popup);
441 /* Chain up to the parent class */
442 G_OBJECT_CLASS (parent_class)->dispose (obj);
446 psppire_variable_sheet_finalize (GObject *object)
448 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (object);
450 g_free (sheet->value_label_dispatch);
451 g_free (sheet->missing_values_dispatch);
452 g_free (sheet->var_type_dispatch);
454 if (G_OBJECT_CLASS (parent_class)->finalize)
455 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
459 psppire_variable_sheet_realize (GtkWidget *widget)
461 /* This is a kludge. These are properties from the parent class.
462 They should really be set immediately after initialisation, but there is no
463 simple way to do that. */
464 g_object_set (widget,
466 "select-renderer-func", select_renderer_func,
467 "vertical-draggable", TRUE,
468 "forward-conversion", var_sheet_data_to_string,
471 if (GTK_WIDGET_CLASS (parent_class)->realize)
472 (*GTK_WIDGET_CLASS (parent_class)->realize) (widget);
477 psppire_variable_sheet_class_init (PsppireVariableSheetClass *class)
479 GObjectClass *object_class = G_OBJECT_CLASS (class);
480 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
482 object_class->dispose = psppire_variable_sheet_dispose;
484 parent_class = g_type_class_peek_parent (class);
486 widget_class->realize = psppire_variable_sheet_realize;
487 object_class->finalize = psppire_variable_sheet_finalize;
491 psppire_variable_sheet_new (void)
493 return g_object_new (PSPPIRE_TYPE_VARIABLE_SHEET, NULL);
497 move_variable (PsppireVariableSheet *sheet, gint from, gint to, gpointer ud)
499 PsppireDict *dict = NULL;
500 g_object_get (sheet, "data-model", &dict, NULL);
505 struct variable *var = psppire_dict_get_variable (dict, from);
510 /* The index refers to the final position, so if the source
511 is less than the destination, then we must subtract 1, to
512 account for the position vacated by the source */
515 dict_reorder_var (dict->dict, var, new_pos);
520 is_printable_key (gint keyval)
525 case GDK_KEY_ISO_Left_Tab:
531 return (0 != gdk_keyval_to_unicode (keyval));
536 PsppireVariableSheet *sheet;
537 void (*payload) (PsppireVariableSheet *);
542 on_key_press (GtkWidget *w, GdkEventKey *e, gpointer user_data)
544 const struct dispatch *d = user_data;
545 if (is_printable_key (e->keyval))
547 d->payload (d->sheet);
555 on_button_press (GtkWidget *w, GdkEventButton *e, gpointer user_data)
557 const struct dispatch *d = user_data;
561 d->payload (d->sheet);
566 on_edit_start (GtkCellRenderer *renderer,
567 GtkCellEditable *editable,
571 gtk_widget_grab_focus (GTK_WIDGET (editable));
572 g_signal_connect (editable, "key-press-event",
573 G_CALLBACK (on_key_press), user_data);
574 g_signal_connect (editable, "button-press-event",
575 G_CALLBACK (on_button_press), user_data);
580 psppire_variable_sheet_init (PsppireVariableSheet *sheet)
582 sheet->dispose_has_run = FALSE;
584 sheet->value_label_renderer = gtk_cell_renderer_text_new ();
585 sheet->value_label_dispatch = g_malloc (sizeof *sheet->value_label_dispatch);
586 sheet->value_label_dispatch->sheet = sheet;
587 sheet->value_label_dispatch->payload = set_value_labels;
588 g_signal_connect_after (sheet->value_label_renderer,
589 "editing-started", G_CALLBACK (on_edit_start),
590 sheet->value_label_dispatch);
592 sheet->missing_values_renderer = gtk_cell_renderer_text_new ();
593 sheet->missing_values_dispatch = g_malloc (sizeof *sheet->missing_values_dispatch);
594 sheet->missing_values_dispatch->sheet = sheet;
595 sheet->missing_values_dispatch->payload = set_missing_values;
596 g_signal_connect_after (sheet->missing_values_renderer,
597 "editing-started", G_CALLBACK (on_edit_start),
598 sheet->missing_values_dispatch);
600 sheet->var_type_renderer = gtk_cell_renderer_text_new ();
601 sheet->var_type_dispatch = g_malloc (sizeof *sheet->var_type_dispatch);
602 sheet->var_type_dispatch->sheet = sheet;
603 sheet->var_type_dispatch->payload = set_var_type;
604 g_signal_connect_after (sheet->var_type_renderer,
605 "editing-started", G_CALLBACK (on_edit_start),
606 sheet->var_type_dispatch);
608 sheet->row_popup = create_var_row_header_popup_menu (sheet);
610 g_signal_connect (sheet, "selection-changed",
611 G_CALLBACK (set_var_popup_sensitivity), sheet);
613 g_signal_connect (sheet, "row-header-pressed",
614 G_CALLBACK (show_variables_row_popup), sheet);
616 g_signal_connect_swapped (sheet, "value-changed",
617 G_CALLBACK (change_var_property), sheet);
619 g_signal_connect (sheet, "row-moved",
620 G_CALLBACK (move_variable), NULL);
622 PsppireVarSheetHeader *vsh =
623 g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);