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);
249 value_init (&v, var_get_width (var));
250 v.f = g_value_get_double (value);
252 psppire_data_store_set_value (store, row, var, &v);
254 value_destroy (&v, var_get_width (var));
258 psppire_data_editor_set_property (GObject *object,
263 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
267 case PROP_SPLIT_WINDOW:
268 psppire_data_editor_split_window (de, g_value_get_boolean (value));
270 case PROP_DATA_STORE:
273 g_signal_handlers_disconnect_by_func (de->data_store,
274 G_CALLBACK (refresh_entry),
276 g_object_unref (de->data_store);
279 de->data_store = g_value_get_pointer (value);
280 g_object_ref (de->data_store);
281 g_print ("NEW STORE\n");
283 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
284 psppire_data_editor_refresh_model (de);
286 g_signal_connect_swapped (de->data_sheet, "value-changed",
287 G_CALLBACK (change_data_value), de->data_store);
289 g_signal_connect_swapped (de->data_store, "case-changed",
290 G_CALLBACK (refresh_entry), de);
293 case PROP_DICTIONARY:
295 g_object_unref (de->dict);
296 de->dict = g_value_get_pointer (value);
297 g_object_ref (de->dict);
299 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
300 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
301 g_signal_connect_swapped (de->var_sheet, "value-changed",
302 G_CALLBACK (change_var_property), de->dict);
305 case PROP_VALUE_LABELS:
309 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
315 psppire_data_editor_get_property (GObject *object,
320 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
324 case PROP_SPLIT_WINDOW:
325 g_value_set_boolean (value, de->split);
327 case PROP_DATA_STORE:
328 g_value_set_pointer (value, de->data_store);
330 case PROP_DICTIONARY:
331 g_value_set_pointer (value, de->dict);
333 case PROP_VALUE_LABELS:
336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
342 psppire_data_editor_switch_page (GtkNotebook *notebook,
346 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
351 psppire_data_editor_set_focus_child (GtkContainer *container,
354 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
359 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
361 GParamSpec *data_store_spec ;
362 GParamSpec *dict_spec ;
363 GParamSpec *value_labels_spec;
364 GParamSpec *split_window_spec;
366 GObjectClass *object_class = G_OBJECT_CLASS (klass);
367 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
368 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
370 parent_class = g_type_class_peek_parent (klass);
372 object_class->dispose = psppire_data_editor_dispose;
373 object_class->set_property = psppire_data_editor_set_property;
374 object_class->get_property = psppire_data_editor_get_property;
376 container_class->set_focus_child = psppire_data_editor_set_focus_child;
378 notebook_class->switch_page = psppire_data_editor_switch_page;
381 g_param_spec_pointer ("data-store",
383 "A pointer to the data store associated with this editor",
384 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
386 g_object_class_install_property (object_class,
391 g_param_spec_pointer ("dictionary",
393 "A pointer to the dictionary associated with this editor",
394 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
396 g_object_class_install_property (object_class,
401 g_param_spec_boolean ("value-labels",
403 "Whether or not the data sheet should display labels instead of values",
405 G_PARAM_WRITABLE | G_PARAM_READABLE);
407 g_object_class_install_property (object_class,
413 g_param_spec_boolean ("split",
415 "True iff the data sheet is split",
417 G_PARAM_READABLE | G_PARAM_WRITABLE);
419 g_object_class_install_property (object_class,
427 on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
428 PsppireDataEditor *de)
430 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
431 PSPPIRE_DATA_EDITOR_DATA_VIEW);
433 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
438 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
439 PsppireDataEditor *de)
442 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
443 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
445 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
450 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
451 active cell or cells. */
453 refresh_entry (PsppireDataEditor *de)
455 g_print ("%s\n", __FUNCTION__);
459 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
465 disconnect_data_sheets (PsppireDataEditor *de)
469 /* Called when the active cell or the selection in the data sheet changes */
471 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
473 gchar *ref_cell_text = NULL;
475 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
476 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
478 if (n_cases == 1 && n_vars == 1)
480 /* A single cell is selected */
481 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
484 ref_cell_text = g_strdup_printf (_("%d : %s"),
485 sel->start_y + 1, var_get_name (var));
491 /* The glib string library does not understand the ' printf modifier
492 on all platforms, but the "struct string" library does (because
493 Gnulib fixes that problem), so use the latter. */
495 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
497 ds_put_byte (&s, ' ');
498 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
499 ds_put_byte (&s, ' ');
500 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
503 ref_cell_text = ds_steal_cstr (&s);
506 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
507 ref_cell_text ? ref_cell_text : "");
509 g_free (ref_cell_text);
513 static void set_font_recursively (GtkWidget *w, gpointer data);
516 psppire_data_editor_init (PsppireDataEditor *de)
519 gchar *fontname = NULL;
522 de->old_vbox_widget = NULL;
524 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
526 de->cell_ref_label = gtk_label_new ("");
527 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
528 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
530 de->datum_entry = psppire_value_entry_new ();
531 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
532 "activate", G_CALLBACK (on_datum_entry_activate), de);
534 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
535 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
536 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
539 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
540 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
541 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
542 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
543 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
544 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
547 g_signal_connect_swapped (de->data_sheet, "selection-changed",
548 G_CALLBACK (on_data_selection_change), de);
550 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
551 gtk_label_new_with_mnemonic (_("Data View")));
553 gtk_widget_show_all (de->vbox);
555 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
557 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
559 g_object_set (de->var_sheet,
561 "select-renderer-func", select_renderer_func,
565 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
566 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
568 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
569 gtk_label_new_with_mnemonic (_("Variable View")));
571 gtk_widget_show_all (de->var_sheet);
573 g_signal_connect (de->var_sheet, "row-header-double-clicked",
574 G_CALLBACK (on_var_sheet_var_double_clicked), de);
576 g_signal_connect (de->data_sheet, "column-header-double-clicked",
577 G_CALLBACK (on_data_sheet_var_double_clicked), de);
579 g_object_set (de, "can-focus", FALSE, NULL);
581 if (psppire_conf_get_string (psppire_conf_new (),
582 "Data Editor", "font",
585 de->font = pango_font_description_from_string (fontname);
587 set_font_recursively (GTK_WIDGET (de), de->font);
593 psppire_data_editor_new (PsppireDict *dict,
594 PsppireDataStore *data_store)
596 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
598 "data-store", data_store,
602 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
603 sheet(s) and variable sheet. */
605 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
607 g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
608 g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
613 set_font_recursively (GtkWidget *w, gpointer data)
615 PangoFontDescription *font_desc = data;
617 gtk_widget_override_font (w, font_desc);
619 if ( GTK_IS_CONTAINER (w))
620 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
623 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
625 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
628 set_font_recursively (GTK_WIDGET (de), font_desc);
631 pango_font_description_free (de->font);
632 de->font = pango_font_description_copy (font_desc);
633 font_name = pango_font_description_to_string (de->font);
635 psppire_conf_set_string (psppire_conf_new (),
636 "Data Editor", "font",
641 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
642 If SPLIT is FALSE, un-splits it into a single pane. */
644 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
646 if (split == de->split)
649 disconnect_data_sheets (de);
651 psppire_data_editor_refresh_model (de);
653 gtk_widget_show_all (de->vbox);
656 set_font_recursively (GTK_WIDGET (de), de->font);
659 g_object_notify (G_OBJECT (de), "split");
662 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
663 visible and selected in the active view in DE. */
665 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
670 /* Returns the "active" data sheet in DE. If DE is in single-paned mode, this
671 is the only data sheet. If DE is in split mode (showing four data sheets),
672 this is the focused data sheet or, if none is focused, the data sheet with
673 selected cells or, if none has selected cells, the upper-left data sheet. */
675 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
679 PsppireDataSheet *data_sheet;
683 /* If one of the datasheet's scrollers is focused, choose that one. */
684 scroller = gtk_container_get_focus_child (
685 GTK_CONTAINER (de->datasheet_vbox_widget));
686 if (scroller != NULL)
687 return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
689 /* Otherwise if there's a nonempty selection in some data sheet, choose
691 FOR_EACH_DATA_SHEET (data_sheet, i, de)
693 PsppSheetSelection *selection;
695 selection = pspp_sheet_view_get_selection (
696 PSPP_SHEET_VIEW (data_sheet));
697 if (pspp_sheet_selection_count_selected_rows (selection)
698 && pspp_sheet_selection_count_selected_columns (selection))
703 return PSPPIRE_DATA_SHEET (de->data_sheets[0]);