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 const struct fmt_spec *format = var_get_write_format (var);
55 struct fmt_spec fmt = *format;
56 GtkWindow *win = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet)));
57 if (GTK_RESPONSE_OK == psppire_var_type_dialog_run (win, &fmt))
59 var_set_width_and_formats (var, fmt_var_width (&fmt), &fmt, &fmt);
64 set_missing_values (PsppireVariableSheet *sheet)
66 gint row = -1, col = -1;
67 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
69 PsppireDict *dict = NULL;
70 g_object_get (sheet, "data-model", &dict, NULL);
72 struct variable *var =
73 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
78 struct missing_values mv;
79 if (GTK_RESPONSE_OK ==
80 psppire_missing_val_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))),
83 var_set_missing_values (var, &mv);
90 set_value_labels (PsppireVariableSheet *sheet)
92 gint row = -1, col = -1;
93 ssw_sheet_get_active_cell (SSW_SHEET (sheet), &col, &row);
95 PsppireDict *dict = NULL;
96 g_object_get (sheet, "data-model", &dict, NULL);
98 struct variable *var =
99 psppire_dict_get_variable (PSPPIRE_DICT (dict), row);
104 struct val_labs *vls =
105 psppire_val_labs_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (sheet))), var);
109 var_set_value_labels (var, vls);
110 val_labs_destroy (vls);
114 static GtkCellRenderer *
115 create_spin_renderer (GType type)
117 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
119 GtkAdjustment *adj = gtk_adjustment_new (0,
130 static GtkCellRenderer *
131 create_combo_renderer (GType type)
133 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
135 GEnumClass *ec = g_type_class_ref (type);
137 const GEnumValue *ev ;
138 for (ev = ec->values; ev->value_name; ++ev)
142 gtk_list_store_append (list_store, &iter);
144 gtk_list_store_set (list_store, &iter,
146 1, gettext (ev->value_nick),
150 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
161 static GtkCellRenderer *spin_renderer;
162 static GtkCellRenderer *column_width_renderer;
163 static GtkCellRenderer *measure_renderer;
164 static GtkCellRenderer *alignment_renderer;
166 static GtkCellRenderer *
167 select_renderer_func (PsppireVariableSheet *sheet, gint col, gint row, GType type, gpointer ud)
170 spin_renderer = create_spin_renderer (type);
172 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
173 column_width_renderer = create_combo_renderer (type);
175 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
176 measure_renderer = create_combo_renderer (type);
178 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
179 alignment_renderer = create_combo_renderer (type);
183 case DICT_TVM_COL_WIDTH:
184 case DICT_TVM_COL_DECIMAL:
185 case DICT_TVM_COL_COLUMNS:
186 return spin_renderer;
188 case DICT_TVM_COL_TYPE:
189 return sheet->var_type_renderer;
191 case DICT_TVM_COL_VALUE_LABELS:
192 return sheet->value_label_renderer;
194 case DICT_TVM_COL_MISSING_VALUES:
195 return sheet->missing_values_renderer;
197 case DICT_TVM_COL_ALIGNMENT:
198 return alignment_renderer;
200 case DICT_TVM_COL_MEASURE:
201 return measure_renderer;
203 case DICT_TVM_COL_ROLE:
204 return column_width_renderer;
213 show_variables_row_popup (SswSheet *sheet, int row, guint button,
214 guint state, gpointer p)
216 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
217 GListModel *vmodel = NULL;
218 g_object_get (sheet, "vmodel", &vmodel, NULL);
222 guint n_items = g_list_model_get_n_items (vmodel);
230 g_object_set_data (G_OBJECT (var_sheet->row_popup), "item",
231 GINT_TO_POINTER (row));
233 gtk_menu_popup_at_pointer (GTK_MENU (var_sheet->row_popup), NULL);
237 insert_new_variable_var (PsppireVariableSheet *var_sheet)
239 gint item = GPOINTER_TO_INT (g_object_get_data
240 (G_OBJECT (var_sheet->row_popup),
243 PsppireDict *dict = NULL;
244 g_object_get (var_sheet, "data-model", &dict, NULL);
246 psppire_dict_insert_variable (dict, item, NULL);
248 gtk_widget_queue_draw (GTK_WIDGET (var_sheet));
253 delete_variables (SswSheet *sheet)
255 SswRange *range = sheet->selection;
257 PsppireDict *dict = NULL;
258 g_object_get (sheet, "data-model", &dict, NULL);
260 if (range->start_x > range->end_x)
262 gint temp = range->start_x;
263 range->start_x = range->end_x;
267 psppire_dict_delete_variables (dict, range->start_y,
268 (range->end_y - range->start_y + 1));
270 gtk_widget_queue_draw (GTK_WIDGET (sheet));
274 create_var_row_header_popup_menu (PsppireVariableSheet *var_sheet)
276 GtkWidget *menu = gtk_menu_new ();
278 /* gtk_menu_shell_append does not sink/ref this object,
279 so we must do it ourselves (and remember to unref it). */
280 g_object_ref_sink (menu);
283 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
284 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
286 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
288 item = gtk_separator_menu_item_new ();
289 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
291 var_sheet->clear_variables_menu_item =
292 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
294 g_signal_connect_swapped (var_sheet->clear_variables_menu_item, "activate",
295 G_CALLBACK (delete_variables), var_sheet);
297 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item, FALSE);
298 gtk_menu_shell_append (GTK_MENU_SHELL (menu),
299 var_sheet->clear_variables_menu_item);
301 gtk_widget_show_all (menu);
307 set_var_popup_sensitivity (SswSheet *sheet, gpointer selection, gpointer p)
309 PsppireVariableSheet *var_sheet = PSPPIRE_VARIABLE_SHEET (sheet);
310 SswRange *range = selection;
311 gint width = gtk_tree_model_get_n_columns (sheet->data_model);
313 gboolean whole_row_selected = (range->start_x == 0 &&
314 range->end_x == width - 1 - 1);
315 /* PsppireDict has an "extra" column: TVM_COL_VAR ^^^ */
316 gtk_widget_set_sensitive (var_sheet->clear_variables_menu_item,
323 change_var_property (PsppireVariableSheet *var_sheet, gint col, gint row, const GValue *value)
325 PsppireDict *dict = NULL;
326 g_object_get (var_sheet, "data-model", &dict, NULL);
328 int n_rows = psppire_dict_get_n_vars (dict);
332 /* Return the IDXth variable */
333 struct variable *var = psppire_dict_get_variable (dict, row);
336 var = psppire_dict_insert_variable (dict, row, NULL);
338 g_return_if_fail (var);
342 case DICT_TVM_COL_NAME:
344 const char *name = g_value_get_string (value);
345 if (psppire_dict_check_name (dict, name, FALSE))
346 dict_rename_var (dict->dict, var, g_value_get_string (value));
349 case DICT_TVM_COL_WIDTH:
351 gint width = g_value_get_int (value);
352 if (var_is_numeric (var))
354 struct fmt_spec format = *var_get_print_format (var);
355 fmt_change_width (&format, width, FMT_FOR_OUTPUT);
356 var_set_both_formats (var, &format);
360 var_set_width (var, width);
364 case DICT_TVM_COL_DECIMAL:
366 gint decimals = g_value_get_int (value);
369 struct fmt_spec format = *var_get_print_format (var);
370 fmt_change_decimals (&format, decimals, FMT_FOR_OUTPUT);
371 var_set_both_formats (var, &format);
375 case DICT_TVM_COL_LABEL:
376 var_set_label (var, g_value_get_string (value));
378 case DICT_TVM_COL_COLUMNS:
379 var_set_display_width (var, g_value_get_int (value));
381 case DICT_TVM_COL_MEASURE:
382 var_set_measure (var, g_value_get_int (value));
384 case DICT_TVM_COL_ALIGNMENT:
385 var_set_alignment (var, g_value_get_int (value));
387 case DICT_TVM_COL_ROLE:
388 var_set_role (var, g_value_get_int (value));
391 g_warning ("Changing unknown column %d of variable sheet column not supported",
398 var_sheet_data_to_string (SswSheet *sheet, GtkTreeModel *m,
399 gint col, gint row, const GValue *in)
401 if (col >= n_DICT_COLS - 1) /* -1 because psppire-dict has an extra column */
404 const struct variable *var = psppire_dict_get_variable (PSPPIRE_DICT (m), row);
408 if (col == DICT_TVM_COL_TYPE)
410 const struct fmt_spec *print = var_get_print_format (var);
411 return strdup (fmt_gui_name (print->type));
413 else if (col == DICT_TVM_COL_MISSING_VALUES)
414 return missing_values_to_string (var, NULL);
415 else if (col == DICT_TVM_COL_VALUE_LABELS)
417 const struct val_labs *vls = var_get_value_labels (var);
418 if (vls == NULL || val_labs_count (vls) == 0)
419 return strdup (_("None"));
420 const struct val_lab **labels = val_labs_sorted (vls);
421 const struct val_lab *vl = labels[0];
422 gchar *vstr = value_to_text (vl->value, var);
423 char *text = xasprintf (_("{%s, %s}..."), vstr,
424 val_lab_get_escaped_label (vl));
430 return ssw_sheet_default_forward_conversion (sheet, m, col, row, in);
435 static GObjectClass * parent_class = NULL;
438 psppire_variable_sheet_dispose (GObject *obj)
440 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (obj);
442 if (sheet->dispose_has_run)
445 sheet->dispose_has_run = TRUE;
447 g_object_unref (sheet->value_label_renderer);
448 g_object_unref (sheet->missing_values_renderer);
449 g_object_unref (sheet->var_type_renderer);
450 g_object_unref (sheet->row_popup);
452 /* Chain up to the parent class */
453 G_OBJECT_CLASS (parent_class)->dispose (obj);
457 psppire_variable_sheet_finalize (GObject *object)
459 PsppireVariableSheet *sheet = PSPPIRE_VARIABLE_SHEET (object);
461 g_free (sheet->value_label_dispatch);
462 g_free (sheet->missing_values_dispatch);
463 g_free (sheet->var_type_dispatch);
465 if (G_OBJECT_CLASS (parent_class)->finalize)
466 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
470 psppire_variable_sheet_realize (GtkWidget *widget)
472 /* This is a kludge. These are properties from the parent class.
473 They should really be set immediately after initialisation, but there is no
474 simple way to do that. */
475 g_object_set (widget,
477 "select-renderer-func", select_renderer_func,
478 "vertical-draggable", TRUE,
479 "forward-conversion", var_sheet_data_to_string,
482 if (GTK_WIDGET_CLASS (parent_class)->realize)
483 (*GTK_WIDGET_CLASS (parent_class)->realize) (widget);
488 psppire_variable_sheet_class_init (PsppireVariableSheetClass *class)
490 GObjectClass *object_class = G_OBJECT_CLASS (class);
491 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
493 object_class->dispose = psppire_variable_sheet_dispose;
495 parent_class = g_type_class_peek_parent (class);
497 widget_class->realize = psppire_variable_sheet_realize;
498 object_class->finalize = psppire_variable_sheet_finalize;
502 psppire_variable_sheet_new (void)
504 return g_object_new (PSPPIRE_TYPE_VARIABLE_SHEET, NULL);
508 move_variable (PsppireVariableSheet *sheet, gint from, gint to, gpointer ud)
510 PsppireDict *dict = NULL;
511 g_object_get (sheet, "data-model", &dict, NULL);
516 struct variable *var = psppire_dict_get_variable (dict, from);
521 /* The index refers to the final position, so if the source
522 is less than the destination, then we must subtract 1, to
523 account for the position vacated by the source */
526 dict_reorder_var (dict->dict, var, new_pos);
531 is_printable_key (gint keyval)
536 case GDK_KEY_ISO_Left_Tab:
542 return (0 != gdk_keyval_to_unicode (keyval));
547 PsppireVariableSheet *sheet;
548 void (*payload) (PsppireVariableSheet *);
553 on_key_press (GtkWidget *w, GdkEventKey *e, gpointer user_data)
555 const struct dispatch *d = user_data;
556 if (is_printable_key (e->keyval))
558 d->payload (d->sheet);
566 on_button_press (GtkWidget *w, GdkEventButton *e, gpointer user_data)
568 const struct dispatch *d = user_data;
572 d->payload (d->sheet);
577 on_edit_start (GtkCellRenderer *renderer,
578 GtkCellEditable *editable,
582 gtk_widget_grab_focus (GTK_WIDGET (editable));
583 g_signal_connect (editable, "key-press-event",
584 G_CALLBACK (on_key_press), user_data);
585 g_signal_connect (editable, "button-press-event",
586 G_CALLBACK (on_button_press), user_data);
591 psppire_variable_sheet_init (PsppireVariableSheet *sheet)
593 sheet->dispose_has_run = FALSE;
595 sheet->value_label_renderer = gtk_cell_renderer_text_new ();
596 sheet->value_label_dispatch = g_malloc (sizeof *sheet->value_label_dispatch);
597 sheet->value_label_dispatch->sheet = sheet;
598 sheet->value_label_dispatch->payload = set_value_labels;
599 g_signal_connect_after (sheet->value_label_renderer,
600 "editing-started", G_CALLBACK (on_edit_start),
601 sheet->value_label_dispatch);
603 sheet->missing_values_renderer = gtk_cell_renderer_text_new ();
604 sheet->missing_values_dispatch = g_malloc (sizeof *sheet->missing_values_dispatch);
605 sheet->missing_values_dispatch->sheet = sheet;
606 sheet->missing_values_dispatch->payload = set_missing_values;
607 g_signal_connect_after (sheet->missing_values_renderer,
608 "editing-started", G_CALLBACK (on_edit_start),
609 sheet->missing_values_dispatch);
611 sheet->var_type_renderer = gtk_cell_renderer_text_new ();
612 sheet->var_type_dispatch = g_malloc (sizeof *sheet->var_type_dispatch);
613 sheet->var_type_dispatch->sheet = sheet;
614 sheet->var_type_dispatch->payload = set_var_type;
615 g_signal_connect_after (sheet->var_type_renderer,
616 "editing-started", G_CALLBACK (on_edit_start),
617 sheet->var_type_dispatch);
619 sheet->row_popup = create_var_row_header_popup_menu (sheet);
621 g_signal_connect (sheet, "selection-changed",
622 G_CALLBACK (set_var_popup_sensitivity), sheet);
624 g_signal_connect (sheet, "row-header-pressed",
625 G_CALLBACK (show_variables_row_popup), sheet);
627 g_signal_connect_swapped (sheet, "value-changed",
628 G_CALLBACK (change_var_property), sheet);
630 g_signal_connect (sheet, "row-moved",
631 G_CALLBACK (move_variable), NULL);
633 PsppireVarSheetHeader *vsh =
634 g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);