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 "value-variant.h"
37 #include "ui/gui/efficient-sheet/jmd-sheet.h"
38 #include "ui/gui/efficient-sheet/jmd-sheet-body.h"
41 #define _(msgid) gettext (msgid)
44 static GtkCellRenderer *
45 create_spin_renderer (GType type)
47 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
49 GtkAdjustment *adj = gtk_adjustment_new (0,
60 static GtkCellRenderer *
61 create_combo_renderer (GType type)
63 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
65 GEnumClass *ec = g_type_class_ref (type);
67 const GEnumValue *ev ;
68 for (ev = ec->values; ev->value_name; ++ev)
72 gtk_list_store_append (list_store, &iter);
74 gtk_list_store_set (list_store, &iter,
76 1, gettext (ev->value_nick),
80 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
92 GtkCellRenderer *column_width_renderer ;
93 GtkCellRenderer *measure_renderer ;
94 GtkCellRenderer *alignment_renderer ;
98 static GtkCellRenderer *
99 select_renderer_func (gint col, gint row, GType type)
102 xx = create_spin_renderer (type);
104 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
105 column_width_renderer = create_combo_renderer (type);
107 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
108 measure_renderer = create_combo_renderer (type);
110 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
111 alignment_renderer = create_combo_renderer (type);
115 case DICT_TVM_COL_WIDTH:
116 case DICT_TVM_COL_DECIMAL:
117 case DICT_TVM_COL_COLUMNS:
120 case DICT_TVM_COL_ALIGNMENT:
121 return alignment_renderer;
123 case DICT_TVM_COL_MEASURE:
124 return measure_renderer;
126 case DICT_TVM_COL_ROLE:
127 return column_width_renderer;
134 static void psppire_data_editor_class_init (PsppireDataEditorClass *klass);
135 static void psppire_data_editor_init (PsppireDataEditor *de);
137 static void disconnect_data_sheets (PsppireDataEditor *);
138 static void refresh_entry (PsppireDataEditor *);
141 psppire_data_editor_get_type (void)
143 static GType de_type = 0;
147 static const GTypeInfo de_info =
149 sizeof (PsppireDataEditorClass),
150 NULL, /* base_init */
151 NULL, /* base_finalize */
152 (GClassInitFunc) psppire_data_editor_class_init,
153 NULL, /* class_finalize */
154 NULL, /* class_data */
155 sizeof (PsppireDataEditor),
157 (GInstanceInitFunc) psppire_data_editor_init,
160 de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
167 static GObjectClass * parent_class = NULL;
170 psppire_data_editor_dispose (GObject *obj)
172 PsppireDataEditor *de = (PsppireDataEditor *) obj;
174 disconnect_data_sheets (de);
178 g_object_unref (de->data_store);
179 de->data_store = NULL;
184 g_object_unref (de->dict);
188 if (de->font != NULL)
190 pango_font_description_free (de->font);
194 /* Chain up to the parent class */
195 G_OBJECT_CLASS (parent_class)->dispose (obj);
208 psppire_data_editor_refresh_model (PsppireDataEditor *de)
213 change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
215 /* Return the IDXth variable */
216 struct variable *var = psppire_dict_get_variable (dict, row);
220 case DICT_TVM_COL_NAME:
221 dict_rename_var (dict->dict, var, g_value_get_string (value));
223 case DICT_TVM_COL_LABEL:
224 var_set_label (var, g_value_get_string (value));
226 case DICT_TVM_COL_COLUMNS:
227 var_set_display_width (var, g_value_get_int (value));
229 case DICT_TVM_COL_MEASURE:
230 var_set_measure (var, g_value_get_enum (value));
232 case DICT_TVM_COL_ALIGNMENT:
233 var_set_alignment (var, g_value_get_enum (value));
235 case DICT_TVM_COL_ROLE:
236 var_set_role (var, g_value_get_enum (value));
239 g_message ("Changing col %d of var sheet not yet supported", col);
245 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
247 const struct variable *var = psppire_dict_get_variable (store->dict, col);
254 GVariant *vrnt = g_value_get_variant (value);
256 value_variant_get (&v, vrnt);
258 psppire_data_store_set_value (store, row, var, &v);
260 value_destroy_from_variant (&v, vrnt);
264 psppire_data_editor_set_property (GObject *object,
269 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
273 case PROP_SPLIT_WINDOW:
274 psppire_data_editor_split_window (de, g_value_get_boolean (value));
276 case PROP_DATA_STORE:
279 g_signal_handlers_disconnect_by_func (de->data_store,
280 G_CALLBACK (refresh_entry),
282 g_object_unref (de->data_store);
285 de->data_store = g_value_get_pointer (value);
286 g_object_ref (de->data_store);
287 g_print ("NEW STORE\n");
289 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
290 psppire_data_editor_refresh_model (de);
292 g_signal_connect_swapped (de->data_sheet, "value-changed",
293 G_CALLBACK (change_data_value), de->data_store);
295 g_signal_connect_swapped (de->data_store, "case-changed",
296 G_CALLBACK (refresh_entry), de);
299 case PROP_DICTIONARY:
301 g_object_unref (de->dict);
302 de->dict = g_value_get_pointer (value);
303 g_object_ref (de->dict);
305 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
306 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
307 g_signal_connect_swapped (de->var_sheet, "value-changed",
308 G_CALLBACK (change_var_property), de->dict);
311 case PROP_VALUE_LABELS:
315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321 psppire_data_editor_get_property (GObject *object,
326 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
330 case PROP_SPLIT_WINDOW:
331 g_value_set_boolean (value, de->split);
333 case PROP_DATA_STORE:
334 g_value_set_pointer (value, de->data_store);
336 case PROP_DICTIONARY:
337 g_value_set_pointer (value, de->dict);
339 case PROP_VALUE_LABELS:
342 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
348 psppire_data_editor_switch_page (GtkNotebook *notebook,
352 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
357 psppire_data_editor_set_focus_child (GtkContainer *container,
360 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
365 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
367 GParamSpec *data_store_spec ;
368 GParamSpec *dict_spec ;
369 GParamSpec *value_labels_spec;
370 GParamSpec *split_window_spec;
372 GObjectClass *object_class = G_OBJECT_CLASS (klass);
373 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
374 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
376 parent_class = g_type_class_peek_parent (klass);
378 object_class->dispose = psppire_data_editor_dispose;
379 object_class->set_property = psppire_data_editor_set_property;
380 object_class->get_property = psppire_data_editor_get_property;
382 container_class->set_focus_child = psppire_data_editor_set_focus_child;
384 notebook_class->switch_page = psppire_data_editor_switch_page;
387 g_param_spec_pointer ("data-store",
389 "A pointer to the data store associated with this editor",
390 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
392 g_object_class_install_property (object_class,
397 g_param_spec_pointer ("dictionary",
399 "A pointer to the dictionary associated with this editor",
400 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
402 g_object_class_install_property (object_class,
407 g_param_spec_boolean ("value-labels",
409 "Whether or not the data sheet should display labels instead of values",
411 G_PARAM_WRITABLE | G_PARAM_READABLE);
413 g_object_class_install_property (object_class,
419 g_param_spec_boolean ("split",
421 "True iff the data sheet is split",
423 G_PARAM_READABLE | G_PARAM_WRITABLE);
425 g_object_class_install_property (object_class,
433 on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
434 PsppireDataEditor *de)
436 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
437 PSPPIRE_DATA_EDITOR_DATA_VIEW);
439 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
444 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
445 PsppireDataEditor *de)
448 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
449 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
451 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
456 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
457 active cell or cells. */
459 refresh_entry (PsppireDataEditor *de)
461 g_print ("%s\n", __FUNCTION__);
465 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
471 disconnect_data_sheets (PsppireDataEditor *de)
475 /* Called when the active cell or the selection in the data sheet changes */
477 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
479 gchar *ref_cell_text = NULL;
481 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
482 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
484 if (n_cases == 1 && n_vars == 1)
486 /* A single cell is selected */
487 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
490 ref_cell_text = g_strdup_printf (_("%d : %s"),
491 sel->start_y + 1, var_get_name (var));
497 /* The glib string library does not understand the ' printf modifier
498 on all platforms, but the "struct string" library does (because
499 Gnulib fixes that problem), so use the latter. */
501 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
503 ds_put_byte (&s, ' ');
504 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
505 ds_put_byte (&s, ' ');
506 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
509 ref_cell_text = ds_steal_cstr (&s);
512 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
513 ref_cell_text ? ref_cell_text : "");
515 g_free (ref_cell_text);
519 static void set_font_recursively (GtkWidget *w, gpointer data);
521 gchar *myconvfunc (GtkTreeModel *m, gint col, gint row, const GValue *v);
522 void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in, GValue *out);
526 psppire_data_editor_init (PsppireDataEditor *de)
529 gchar *fontname = NULL;
532 de->old_vbox_widget = NULL;
534 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
536 de->cell_ref_label = gtk_label_new ("");
537 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
538 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
540 de->datum_entry = psppire_value_entry_new ();
541 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
542 "activate", G_CALLBACK (on_datum_entry_activate), de);
544 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
545 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
546 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
549 de->data_sheet = jmd_sheet_new ();
550 jmd_sheet_body_set_conversion_func
551 (JMD_SHEET_BODY (JMD_SHEET(de->data_sheet)->selected_body),
552 myconvfunc, myreversefunc);
554 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
555 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
556 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
557 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
558 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
561 g_signal_connect_swapped (de->data_sheet, "selection-changed",
562 G_CALLBACK (on_data_selection_change), de);
564 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
565 gtk_label_new_with_mnemonic (_("Data View")));
567 gtk_widget_show_all (de->vbox);
569 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
571 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
573 g_object_set (de->var_sheet,
575 "select-renderer-func", select_renderer_func,
579 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
580 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
582 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
583 gtk_label_new_with_mnemonic (_("Variable View")));
585 gtk_widget_show_all (de->var_sheet);
587 g_signal_connect (de->var_sheet, "row-header-double-clicked",
588 G_CALLBACK (on_var_sheet_var_double_clicked), de);
590 g_signal_connect (de->data_sheet, "column-header-double-clicked",
591 G_CALLBACK (on_data_sheet_var_double_clicked), de);
593 g_object_set (de, "can-focus", FALSE, NULL);
595 if (psppire_conf_get_string (psppire_conf_new (),
596 "Data Editor", "font",
599 de->font = pango_font_description_from_string (fontname);
601 set_font_recursively (GTK_WIDGET (de), de->font);
607 psppire_data_editor_new (PsppireDict *dict,
608 PsppireDataStore *data_store)
610 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
612 "data-store", data_store,
616 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
617 sheet(s) and variable sheet. */
619 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
621 g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
622 g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
627 set_font_recursively (GtkWidget *w, gpointer data)
629 PangoFontDescription *font_desc = data;
631 gtk_widget_override_font (w, font_desc);
633 if ( GTK_IS_CONTAINER (w))
634 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
637 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
639 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
642 set_font_recursively (GTK_WIDGET (de), font_desc);
645 pango_font_description_free (de->font);
646 de->font = pango_font_description_copy (font_desc);
647 font_name = pango_font_description_to_string (de->font);
649 psppire_conf_set_string (psppire_conf_new (),
650 "Data Editor", "font",
655 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
656 If SPLIT is FALSE, un-splits it into a single pane. */
658 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
660 if (split == de->split)
663 disconnect_data_sheets (de);
665 psppire_data_editor_refresh_model (de);
667 gtk_widget_show_all (de->vbox);
670 set_font_recursively (GTK_WIDGET (de), de->font);
673 g_object_notify (G_OBJECT (de), "split");
676 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
677 visible and selected in the active view in DE. */
679 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
684 /* Returns the "active" data sheet in DE. If DE is in single-paned mode, this
685 is the only data sheet. If DE is in split mode (showing four data sheets),
686 this is the focused data sheet or, if none is focused, the data sheet with
687 selected cells or, if none has selected cells, the upper-left data sheet. */
689 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
693 PsppireDataSheet *data_sheet;
697 /* If one of the datasheet's scrollers is focused, choose that one. */
698 scroller = gtk_container_get_focus_child (
699 GTK_CONTAINER (de->datasheet_vbox_widget));
700 if (scroller != NULL)
701 return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
703 /* Otherwise if there's a nonempty selection in some data sheet, choose
705 FOR_EACH_DATA_SHEET (data_sheet, i, de)
707 PsppSheetSelection *selection;
709 selection = pspp_sheet_view_get_selection (
710 PSPP_SHEET_VIEW (data_sheet));
711 if (pspp_sheet_selection_count_selected_rows (selection)
712 && pspp_sheet_selection_count_selected_columns (selection))
717 return PSPPIRE_DATA_SHEET (de->data_sheets[0]);