1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
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 "ui/gui/psppire-data-editor.h"
22 #include <gtk-contrib/gtkxpaned.h>
24 #include "data/datasheet.h"
25 #include "data/value-labels.h"
26 #include "libpspp/range-set.h"
27 #include "libpspp/str.h"
28 #include "ui/gui/helper.h"
29 #include "ui/gui/psppire-data-store.h"
30 #include "ui/gui/psppire-value-entry.h"
31 #include "ui/gui/psppire-conf.h"
32 #include "ui/gui/psppire-var-sheet-header.h"
34 #include "ui/gui/efficient-sheet/jmd-sheet.h"
37 #define _(msgid) gettext (msgid)
40 static GtkCellRenderer *
41 create_spin_renderer (GType type)
43 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
45 GtkAdjustment *adj = gtk_adjustment_new (0,
56 static GtkCellRenderer *
57 create_combo_renderer (GType type)
59 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
61 GEnumClass *ec = g_type_class_ref (type);
63 const GEnumValue *ev ;
64 for (ev = ec->values; ev->value_name; ++ev)
68 gtk_list_store_append (list_store, &iter);
70 gtk_list_store_set (list_store, &iter,
72 1, gettext (ev->value_nick),
76 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
88 GtkCellRenderer *column_width_renderer ;
89 GtkCellRenderer *measure_renderer ;
90 GtkCellRenderer *alignment_renderer ;
94 static GtkCellRenderer *
95 select_renderer_func (gint col, gint row, GType type)
98 xx = create_spin_renderer (type);
100 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
101 column_width_renderer = create_combo_renderer (type);
103 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
104 measure_renderer = create_combo_renderer (type);
106 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
107 alignment_renderer = create_combo_renderer (type);
111 case DICT_TVM_COL_WIDTH:
112 case DICT_TVM_COL_DECIMAL:
113 case DICT_TVM_COL_COLUMNS:
116 case DICT_TVM_COL_ALIGNMENT:
117 return alignment_renderer;
119 case DICT_TVM_COL_MEASURE:
120 return measure_renderer;
122 case DICT_TVM_COL_ROLE:
123 return column_width_renderer;
130 static void psppire_data_editor_class_init (PsppireDataEditorClass *klass);
131 static void psppire_data_editor_init (PsppireDataEditor *de);
133 static void disconnect_data_sheets (PsppireDataEditor *);
134 static void refresh_entry (PsppireDataEditor *);
137 psppire_data_editor_get_type (void)
139 static GType de_type = 0;
143 static const GTypeInfo de_info =
145 sizeof (PsppireDataEditorClass),
146 NULL, /* base_init */
147 NULL, /* base_finalize */
148 (GClassInitFunc) psppire_data_editor_class_init,
149 NULL, /* class_finalize */
150 NULL, /* class_data */
151 sizeof (PsppireDataEditor),
153 (GInstanceInitFunc) psppire_data_editor_init,
156 de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
163 static GObjectClass * parent_class = NULL;
166 psppire_data_editor_dispose (GObject *obj)
168 PsppireDataEditor *de = (PsppireDataEditor *) obj;
170 disconnect_data_sheets (de);
174 g_object_unref (de->data_store);
175 de->data_store = NULL;
180 g_object_unref (de->dict);
184 if (de->font != NULL)
186 pango_font_description_free (de->font);
190 /* Chain up to the parent class */
191 G_OBJECT_CLASS (parent_class)->dispose (obj);
204 psppire_data_editor_refresh_model (PsppireDataEditor *de)
209 change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
211 /* Return the IDXth variable */
212 struct variable *var = psppire_dict_get_variable (dict, row);
216 case DICT_TVM_COL_NAME:
217 dict_rename_var (dict->dict, var, g_value_get_string (value));
219 case DICT_TVM_COL_LABEL:
220 var_set_label (var, g_value_get_string (value));
222 case DICT_TVM_COL_COLUMNS:
223 var_set_display_width (var, g_value_get_int (value));
225 case DICT_TVM_COL_MEASURE:
226 var_set_measure (var, g_value_get_enum (value));
228 case DICT_TVM_COL_ALIGNMENT:
229 var_set_alignment (var, g_value_get_enum (value));
231 case DICT_TVM_COL_ROLE:
232 var_set_role (var, g_value_get_enum (value));
235 g_message ("Changing col %d of var sheet not yet supported", col);
241 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
243 const struct variable *var = psppire_dict_get_variable (store->dict, col);
246 value_init (&v, var_get_width (var));
247 v.f = g_value_get_double (value);
249 psppire_data_store_set_value (store, row, var, &v);
251 value_destroy (&v, var_get_width (var));
255 psppire_data_editor_set_property (GObject *object,
260 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
264 case PROP_SPLIT_WINDOW:
265 psppire_data_editor_split_window (de, g_value_get_boolean (value));
267 case PROP_DATA_STORE:
270 g_signal_handlers_disconnect_by_func (de->data_store,
271 G_CALLBACK (refresh_entry),
273 g_object_unref (de->data_store);
276 de->data_store = g_value_get_pointer (value);
277 g_object_ref (de->data_store);
278 g_print ("NEW STORE\n");
280 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
281 psppire_data_editor_refresh_model (de);
283 g_signal_connect_swapped (de->data_sheet, "value-changed",
284 G_CALLBACK (change_data_value), de->data_store);
286 g_signal_connect_swapped (de->data_store, "case-changed",
287 G_CALLBACK (refresh_entry), de);
290 case PROP_DICTIONARY:
292 g_object_unref (de->dict);
293 de->dict = g_value_get_pointer (value);
294 g_object_ref (de->dict);
296 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
297 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
298 g_signal_connect_swapped (de->var_sheet, "value-changed",
299 G_CALLBACK (change_var_property), de->dict);
302 case PROP_VALUE_LABELS:
306 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
312 psppire_data_editor_get_property (GObject *object,
317 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
321 case PROP_SPLIT_WINDOW:
322 g_value_set_boolean (value, de->split);
324 case PROP_DATA_STORE:
325 g_value_set_pointer (value, de->data_store);
327 case PROP_DICTIONARY:
328 g_value_set_pointer (value, de->dict);
330 case PROP_VALUE_LABELS:
333 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
339 psppire_data_editor_switch_page (GtkNotebook *notebook,
343 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
348 psppire_data_editor_set_focus_child (GtkContainer *container,
351 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
356 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
358 GParamSpec *data_store_spec ;
359 GParamSpec *dict_spec ;
360 GParamSpec *value_labels_spec;
361 GParamSpec *split_window_spec;
363 GObjectClass *object_class = G_OBJECT_CLASS (klass);
364 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
365 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
367 parent_class = g_type_class_peek_parent (klass);
369 object_class->dispose = psppire_data_editor_dispose;
370 object_class->set_property = psppire_data_editor_set_property;
371 object_class->get_property = psppire_data_editor_get_property;
373 container_class->set_focus_child = psppire_data_editor_set_focus_child;
375 notebook_class->switch_page = psppire_data_editor_switch_page;
378 g_param_spec_pointer ("data-store",
380 "A pointer to the data store associated with this editor",
381 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
383 g_object_class_install_property (object_class,
388 g_param_spec_pointer ("dictionary",
390 "A pointer to the dictionary associated with this editor",
391 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
393 g_object_class_install_property (object_class,
398 g_param_spec_boolean ("value-labels",
400 "Whether or not the data sheet should display labels instead of values",
402 G_PARAM_WRITABLE | G_PARAM_READABLE);
404 g_object_class_install_property (object_class,
410 g_param_spec_boolean ("split",
412 "True iff the data sheet is split",
414 G_PARAM_READABLE | G_PARAM_WRITABLE);
416 g_object_class_install_property (object_class,
424 on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
425 PsppireDataEditor *de)
427 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
428 PSPPIRE_DATA_EDITOR_DATA_VIEW);
430 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
435 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
436 PsppireDataEditor *de)
439 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
440 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
442 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
447 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
448 active cell or cells. */
450 refresh_entry (PsppireDataEditor *de)
452 g_print ("%s\n", __FUNCTION__);
456 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
462 disconnect_data_sheets (PsppireDataEditor *de)
466 /* Called when the active cell or the selection in the data sheet changes */
468 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
470 gchar *ref_cell_text = NULL;
472 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
473 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
475 if (n_cases == 1 && n_vars == 1)
477 /* A single cell is selected */
478 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
480 ref_cell_text = g_strdup_printf (_("%d : %s"),
481 sel->start_y + 1, var_get_name (var));
487 /* The glib string library does not understand the ' printf modifier
488 on all platforms, but the "struct string" library does (because
489 Gnulib fixes that problem), so use the latter. */
491 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
493 ds_put_byte (&s, ' ');
494 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
495 ds_put_byte (&s, ' ');
496 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
499 ref_cell_text = ds_steal_cstr (&s);
502 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
503 ref_cell_text ? ref_cell_text : "");
505 g_free (ref_cell_text);
509 static void set_font_recursively (GtkWidget *w, gpointer data);
512 psppire_data_editor_init (PsppireDataEditor *de)
515 gchar *fontname = NULL;
518 de->old_vbox_widget = NULL;
520 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
522 de->cell_ref_label = gtk_label_new ("");
523 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
524 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
526 de->datum_entry = psppire_value_entry_new ();
527 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
528 "activate", G_CALLBACK (on_datum_entry_activate), de);
530 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
531 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
532 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
535 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
536 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
537 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
538 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
539 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
540 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
543 g_signal_connect_swapped (de->data_sheet, "selection-changed",
544 G_CALLBACK (on_data_selection_change), de);
546 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
547 gtk_label_new_with_mnemonic (_("Data View")));
549 gtk_widget_show_all (de->vbox);
551 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
553 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
555 g_object_set (de->var_sheet,
557 "select-renderer-func", select_renderer_func,
561 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
562 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
564 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
565 gtk_label_new_with_mnemonic (_("Variable View")));
567 gtk_widget_show_all (de->var_sheet);
569 g_signal_connect (de->var_sheet, "row-header-double-clicked",
570 G_CALLBACK (on_var_sheet_var_double_clicked), de);
572 g_signal_connect (de->data_sheet, "column-header-double-clicked",
573 G_CALLBACK (on_data_sheet_var_double_clicked), de);
575 g_object_set (de, "can-focus", FALSE, NULL);
577 if (psppire_conf_get_string (psppire_conf_new (),
578 "Data Editor", "font",
581 de->font = pango_font_description_from_string (fontname);
583 set_font_recursively (GTK_WIDGET (de), de->font);
589 psppire_data_editor_new (PsppireDict *dict,
590 PsppireDataStore *data_store)
592 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
594 "data-store", data_store,
598 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
599 sheet(s) and variable sheet. */
601 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
603 g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
604 g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
609 set_font_recursively (GtkWidget *w, gpointer data)
611 PangoFontDescription *font_desc = data;
613 gtk_widget_override_font (w, font_desc);
615 if ( GTK_IS_CONTAINER (w))
616 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
619 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
621 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
624 set_font_recursively (GTK_WIDGET (de), font_desc);
627 pango_font_description_free (de->font);
628 de->font = pango_font_description_copy (font_desc);
629 font_name = pango_font_description_to_string (de->font);
631 psppire_conf_set_string (psppire_conf_new (),
632 "Data Editor", "font",
637 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
638 If SPLIT is FALSE, un-splits it into a single pane. */
640 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
642 if (split == de->split)
645 disconnect_data_sheets (de);
647 psppire_data_editor_refresh_model (de);
649 gtk_widget_show_all (de->vbox);
652 set_font_recursively (GTK_WIDGET (de), de->font);
655 g_object_notify (G_OBJECT (de), "split");
658 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
659 visible and selected in the active view in DE. */
661 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
666 /* Returns the "active" data sheet in DE. If DE is in single-paned mode, this
667 is the only data sheet. If DE is in split mode (showing four data sheets),
668 this is the focused data sheet or, if none is focused, the data sheet with
669 selected cells or, if none has selected cells, the upper-left data sheet. */
671 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
675 PsppireDataSheet *data_sheet;
679 /* If one of the datasheet's scrollers is focused, choose that one. */
680 scroller = gtk_container_get_focus_child (
681 GTK_CONTAINER (de->datasheet_vbox_widget));
682 if (scroller != NULL)
683 return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
685 /* Otherwise if there's a nonempty selection in some data sheet, choose
687 FOR_EACH_DATA_SHEET (data_sheet, i, de)
689 PsppSheetSelection *selection;
691 selection = pspp_sheet_view_get_selection (
692 PSPP_SHEET_VIEW (data_sheet));
693 if (pspp_sheet_selection_count_selected_rows (selection)
694 && pspp_sheet_selection_count_selected_columns (selection))
699 return PSPPIRE_DATA_SHEET (de->data_sheets[0]);