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);
54 struct fmt_spec fmt = var_get_write_format (var);
55 GtkWindow *win = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet)));
56 if (GTK_RESPONSE_OK == psppire_var_type_dialog_run (win, &fmt))
57 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);
75 struct missing_values mv;
76 if (GTK_RESPONSE_OK ==
77 psppire_missing_val_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))),
80 var_set_missing_values (var, &mv);
87 set_value_labels (PsppireVariableSheet *sheet)
89 gint row = -1, col = -1;
90 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
92 PsppireDict *dict = NULL;
93 g_object_get (sheet, "data-model", &dict, NULL);
95 struct variable *var =
96 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
101 struct val_labs *vls =
102 psppire_val_labs_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))), var);
106 var_set_value_labels (var, vls);
107 val_labs_destroy (vls);
111 static GtkCellRenderer *
112 create_spin_renderer (GType type)
114 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
116 GtkAdjustment *adj = gtk_adjustment_new (0,
127 static GtkCellRenderer *
128 create_combo_renderer (GType type)
130 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
132 GEnumClass *ec = g_type_class_ref (type);
134 const GEnumValue *ev ;
135 for (ev = ec->values; ev->value_name; ++ev)
139 gtk_list_store_append (list_store, &iter);
141 gtk_list_store_set (list_store, &iter,
143 1, gettext (ev->value_nick),
147 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
158 static GtkCellRenderer *spin_renderer;
159 static GtkCellRenderer *column_width_renderer;
160 static GtkCellRenderer *measure_renderer;
161 static GtkCellRenderer *alignment_renderer;
163 static GtkCellRenderer *
164 select_renderer_func (PsppireVariableSheet *sheet, gint col, gint row, GType type, gpointer ud)
167 spin_renderer = create_spin_renderer (type);
169 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
170 column_width_renderer = create_combo_renderer (type);
172 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
173 measure_renderer = create_combo_renderer (type);
175 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
176 alignment_renderer = create_combo_renderer (type);
180 case DICT_TVM_COL_WIDTH:
181 case DICT_TVM_COL_DECIMAL:
182 case DICT_TVM_COL_COLUMNS:
183 return spin_renderer;
185 case DICT_TVM_COL_TYPE:
186 return sheet->var_type_renderer;
188 case DICT_TVM_COL_VALUE_LABELS:
189 return sheet->value_label_renderer;
191 case DICT_TVM_COL_MISSING_VALUES:
192 return sheet->missing_values_renderer;
194 case DICT_TVM_COL_ALIGNMENT:
195 return alignment_renderer;
197 case DICT_TVM_COL_MEASURE:
198 return measure_renderer;
200 case DICT_TVM_COL_ROLE:
201 return column_width_renderer;
210 show_variables_row_popup (SswSheet *sheet, int row, guint button,
211 guint state, gpointer p)
213 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
214 GListModel *vmodel = NULL;
215 g_object_get (sheet, "vmodel", &vmodel, NULL);
219 guint n_items = g_list_model_get_n_items (vmodel);
227 g_object_set_data (G_OBJECT (var_sheet->row_popup), "item",
228 GINT_TO_POINTER (row));
230 gtk_menu_popup_at_pointer (GTK_MENU (var_sheet->row_popup), NULL);
234 insert_new_variable_var (PsppireVariableSheet *var_sheet)
236 gint item = GPOINTER_TO_INT (g_object_get_data
237 (G_OBJECT (var_sheet->row_popup),
240 PsppireDict *dict = NULL;
241 g_object_get (var_sheet, "data-model", &dict, NULL);
243 psppire_dict_insert_variable (dict, item, NULL);
245 gtk_widget_queue_draw (GTK_WIDGET (var_sheet));
250 delete_variables (SswSheet *sheet)
252 SswRange *range = sheet->selection;
254 PsppireDict *dict = NULL;
255 g_object_get (sheet, "data-model", &dict, NULL);
257 if (range->start_x > range->end_x)
259 gint temp = range->start_x;
260 range->start_x = range->end_x;
264 psppire_dict_delete_variables (dict, range->start_y,
265 (range->end_y - range->start_y + 1));
267 gtk_widget_queue_draw (GTK_WIDGET (sheet));
271 create_var_row_header_popup_menu (PsppireVariableSheet *var_sheet)
273 GtkWidget *menu = gtk_menu_new ();
275 /* gtk_menu_shell_append does not sink/ref this object,
276 so we must do it ourselves (and remember to unref it). */
277 g_object_ref_sink (menu);
280 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
281 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
283 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
285 item = gtk_separator_menu_item_new ();
286 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
288 var_sheet->clear_variables_menu_item =
289 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
291 g_signal_connect_swapped (var_sheet->clear_variables_menu_item, "activate",
292 G_CALLBACK (delete_variables), var_sheet);
294 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item, FALSE);
295 gtk_menu_shell_append (GTK_MENU_SHELL (menu),
296 var_sheet->clear_variables_menu_item);
298 gtk_widget_show_all (menu);
304 set_var_popup_sensitivity (SswSheet *sheet, gpointer selection, gpointer p)
306 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
307 SswRange *range = selection;
308 gint width = gtk_tree_model_get_n_columns (sheet->data_model);
310 gboolean whole_row_selected = (range->start_x == 0 &&
311 range->end_x == width - 1 - 1);
312 /* PsppireDict has an "extra" column: TVM_COL_VAR ^^^ */
313 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item,
320 change_var_property (PsppireVariableSheet *var_sheet, gint col, gint row, const GValue *value)
322 PsppireDict *dict = NULL;
323 g_object_get (var_sheet, "data-model", &dict, NULL);
325 int n_rows = psppire_dict_get_n_vars (dict);
329 /* Return the IDXth variable */
330 struct variable *var = psppire_dict_get_variable (dict, row);
333 var = psppire_dict_insert_variable (dict, row, NULL);
335 g_return_if_fail (var);
339 case DICT_TVM_COL_NAME:
341 const char *name = g_value_get_string (value);
342 if (psppire_dict_check_name (dict, name))
343 dict_rename_var (dict->dict, var, g_value_get_string (value));
346 case DICT_TVM_COL_WIDTH:
348 gint width = g_value_get_int (value);
349 if (var_is_numeric (var))
351 struct fmt_spec format = var_get_print_format (var);
352 fmt_change_width (&format, width, FMT_FOR_OUTPUT);
353 var_set_both_formats (var, format);
357 var_set_width (var, width);
361 case DICT_TVM_COL_DECIMAL:
363 gint decimals = g_value_get_int (value);
366 struct fmt_spec format = var_get_print_format (var);
367 fmt_change_decimals (&format, decimals, FMT_FOR_OUTPUT);
368 var_set_both_formats (var, format);
372 case DICT_TVM_COL_LABEL:
373 var_set_label (var, g_value_get_string (value));
375 case DICT_TVM_COL_COLUMNS:
376 var_set_display_width (var, g_value_get_int (value));
378 case DICT_TVM_COL_MEASURE:
379 var_set_measure (var, g_value_get_int (value));
381 case DICT_TVM_COL_ALIGNMENT:
382 var_set_alignment (var, g_value_get_int (value));
384 case DICT_TVM_COL_ROLE:
385 var_set_role (var, g_value_get_int (value));
388 g_warning ("Changing unknown column %d of variable sheet column not supported",
395 var_sheet_data_to_string (SswSheet *sheet, GtkTreeModel *m,
396 gint col, gint row, const GValue *in)
398 if (col >= n_DICT_COLS - 1) /* -1 because psppire-dict has an extra column */
401 const struct variable *var = psppire_dict_get_variable (PSPPIRE_DICT (m), row);
405 if (col == DICT_TVM_COL_TYPE)
407 struct fmt_spec print = var_get_print_format (var);
408 return strdup (fmt_gui_name (print.type));
410 else if (col == DICT_TVM_COL_MISSING_VALUES)
411 return missing_values_to_string (var, NULL);
412 else if (col == DICT_TVM_COL_VALUE_LABELS)
414 const struct val_labs *vls = var_get_value_labels (var);
415 if (vls == NULL || val_labs_count (vls) == 0)
416 return strdup (_("None"));
417 const struct val_lab **labels = val_labs_sorted (vls);
418 const struct val_lab *vl = labels[0];
419 gchar *vstr = value_to_text (vl->value, var);
420 char *text = xasprintf (_("{%s, %s}..."), vstr,
421 val_lab_get_escaped_label (vl));
427 return ssw_sheet_default_forward_conversion (sheet, m, col, row, in);
432 static GObjectClass * parent_class = NULL;
435 psppire_variable_sheet_dispose (GObject *obj)
437 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (obj);
439 if (sheet->dispose_has_run)
442 sheet->dispose_has_run = TRUE;
444 g_object_unref (sheet->value_label_renderer);
445 g_object_unref (sheet->missing_values_renderer);
446 g_object_unref (sheet->var_type_renderer);
447 g_object_unref (sheet->row_popup);
449 /* Chain up to the parent class */
450 G_OBJECT_CLASS (parent_class)->dispose (obj);
454 psppire_variable_sheet_finalize (GObject *object)
456 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (object);
458 g_free (sheet->value_label_dispatch);
459 g_free (sheet->missing_values_dispatch);
460 g_free (sheet->var_type_dispatch);
462 if (G_OBJECT_CLASS (parent_class)->finalize)
463 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
467 psppire_variable_sheet_realize (GtkWidget *widget)
469 /* This is a kludge. These are properties from the parent class.
470 They should really be set immediately after initialisation, but there is no
471 simple way to do that. */
472 g_object_set (widget,
474 "select-renderer-func", select_renderer_func,
475 "vertical-draggable", TRUE,
476 "forward-conversion", var_sheet_data_to_string,
479 if (GTK_WIDGET_CLASS (parent_class)->realize)
480 (*GTK_WIDGET_CLASS (parent_class)->realize) (widget);
485 psppire_variable_sheet_class_init (PsppireVariableSheetClass *class)
487 GObjectClass *object_class = G_OBJECT_CLASS (class);
488 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
490 object_class->dispose = psppire_variable_sheet_dispose;
492 parent_class = g_type_class_peek_parent (class);
494 widget_class->realize = psppire_variable_sheet_realize;
495 object_class->finalize = psppire_variable_sheet_finalize;
499 psppire_variable_sheet_new (void)
501 return g_object_new (PSPPIRE_TYPE_VARIABLE_SHEET, NULL);
505 move_variable (PsppireVariableSheet *sheet, gint from, gint to, gpointer ud)
507 PsppireDict *dict = NULL;
508 g_object_get (sheet, "data-model", &dict, NULL);
513 struct variable *var = psppire_dict_get_variable (dict, from);
518 /* The index refers to the final position, so if the source
519 is less than the destination, then we must subtract 1, to
520 account for the position vacated by the source */
523 dict_reorder_var (dict->dict, var, new_pos);
528 is_printable_key (gint keyval)
533 case GDK_KEY_ISO_Left_Tab:
539 return (0 != gdk_keyval_to_unicode (keyval));
544 PsppireVariableSheet *sheet;
545 void (*payload) (PsppireVariableSheet *);
550 on_key_press (GtkWidget *w, GdkEventKey *e, gpointer user_data)
552 const struct dispatch *d = user_data;
553 if (is_printable_key (e->keyval))
555 d->payload (d->sheet);
563 on_button_press (GtkWidget *w, GdkEventButton *e, gpointer user_data)
565 const struct dispatch *d = user_data;
569 d->payload (d->sheet);
574 on_edit_start (GtkCellRenderer *renderer,
575 GtkCellEditable *editable,
579 gtk_widget_grab_focus (GTK_WIDGET (editable));
580 g_signal_connect (editable, "key-press-event",
581 G_CALLBACK (on_key_press), user_data);
582 g_signal_connect (editable, "button-press-event",
583 G_CALLBACK (on_button_press), user_data);
588 psppire_variable_sheet_init (PsppireVariableSheet *sheet)
590 sheet->dispose_has_run = FALSE;
592 sheet->value_label_renderer = gtk_cell_renderer_text_new ();
593 sheet->value_label_dispatch = g_malloc (sizeof *sheet->value_label_dispatch);
594 sheet->value_label_dispatch->sheet = sheet;
595 sheet->value_label_dispatch->payload = set_value_labels;
596 g_signal_connect_after (sheet->value_label_renderer,
597 "editing-started", G_CALLBACK (on_edit_start),
598 sheet->value_label_dispatch);
600 sheet->missing_values_renderer = gtk_cell_renderer_text_new ();
601 sheet->missing_values_dispatch = g_malloc (sizeof *sheet->missing_values_dispatch);
602 sheet->missing_values_dispatch->sheet = sheet;
603 sheet->missing_values_dispatch->payload = set_missing_values;
604 g_signal_connect_after (sheet->missing_values_renderer,
605 "editing-started", G_CALLBACK (on_edit_start),
606 sheet->missing_values_dispatch);
608 sheet->var_type_renderer = gtk_cell_renderer_text_new ();
609 sheet->var_type_dispatch = g_malloc (sizeof *sheet->var_type_dispatch);
610 sheet->var_type_dispatch->sheet = sheet;
611 sheet->var_type_dispatch->payload = set_var_type;
612 g_signal_connect_after (sheet->var_type_renderer,
613 "editing-started", G_CALLBACK (on_edit_start),
614 sheet->var_type_dispatch);
616 sheet->row_popup = create_var_row_header_popup_menu (sheet);
618 g_signal_connect (sheet, "selection-changed",
619 G_CALLBACK (set_var_popup_sensitivity), sheet);
621 g_signal_connect (sheet, "row-header-pressed",
622 G_CALLBACK (show_variables_row_popup), sheet);
624 g_signal_connect_swapped (sheet, "value-changed",
625 G_CALLBACK (change_var_property), sheet);
627 g_signal_connect (sheet, "row-moved",
628 G_CALLBACK (move_variable), NULL);
630 PsppireVarSheetHeader *vsh =
631 g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);