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/pspp-sheet-selection.h"
30 #include "ui/gui/psppire-data-store.h"
31 #include "ui/gui/psppire-value-entry.h"
32 #include "ui/gui/psppire-var-sheet.h"
33 #include "ui/gui/psppire-conf.h"
34 #include "ui/gui/psppire-var-sheet-header.h"
36 #include "ui/gui/efficient-sheet/jmd-sheet.h"
39 #define _(msgid) gettext (msgid)
42 static GtkCellRenderer *
43 create_spin_renderer (GType type)
45 GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
47 GtkAdjustment *adj = gtk_adjustment_new (0,
58 static GtkCellRenderer *
59 create_combo_renderer (GType type)
61 GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
63 GEnumClass *ec = g_type_class_ref (type);
65 const GEnumValue *ev ;
66 for (ev = ec->values; ev->value_name; ++ev)
70 gtk_list_store_append (list_store, &iter);
72 gtk_list_store_set (list_store, &iter,
74 1, gettext (ev->value_nick),
78 GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
90 GtkCellRenderer *column_width_renderer ;
91 GtkCellRenderer *measure_renderer ;
92 GtkCellRenderer *alignment_renderer ;
96 static GtkCellRenderer *
97 select_renderer_func (gint col, gint row, GType type)
100 xx = create_spin_renderer (type);
102 if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
103 column_width_renderer = create_combo_renderer (type);
105 if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
106 measure_renderer = create_combo_renderer (type);
108 if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
109 alignment_renderer = create_combo_renderer (type);
113 case DICT_TVM_COL_WIDTH:
114 case DICT_TVM_COL_DECIMAL:
115 case DICT_TVM_COL_COLUMNS:
118 case DICT_TVM_COL_ALIGNMENT:
119 return alignment_renderer;
121 case DICT_TVM_COL_MEASURE:
122 return measure_renderer;
124 case DICT_TVM_COL_ROLE:
125 return column_width_renderer;
132 static void psppire_data_editor_class_init (PsppireDataEditorClass *klass);
133 static void psppire_data_editor_init (PsppireDataEditor *de);
135 static void disconnect_data_sheets (PsppireDataEditor *);
136 static void refresh_entry (PsppireDataEditor *);
137 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
140 psppire_data_editor_get_type (void)
142 static GType de_type = 0;
146 static const GTypeInfo de_info =
148 sizeof (PsppireDataEditorClass),
149 NULL, /* base_init */
150 NULL, /* base_finalize */
151 (GClassInitFunc) psppire_data_editor_class_init,
152 NULL, /* class_finalize */
153 NULL, /* class_data */
154 sizeof (PsppireDataEditor),
156 (GInstanceInitFunc) psppire_data_editor_init,
159 de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
166 static GObjectClass * parent_class = NULL;
169 psppire_data_editor_dispose (GObject *obj)
171 PsppireDataEditor *de = (PsppireDataEditor *) obj;
173 disconnect_data_sheets (de);
177 g_object_unref (de->data_store);
178 de->data_store = NULL;
183 g_object_unref (de->dict);
187 if (de->font != NULL)
189 pango_font_description_free (de->font);
195 g_object_unref (de->ui_manager);
196 de->ui_manager = NULL;
199 /* Chain up to the parent class */
200 G_OBJECT_CLASS (parent_class)->dispose (obj);
214 psppire_data_editor_refresh_model (PsppireDataEditor *de)
219 change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
221 /* Return the IDXth variable */
222 struct variable *var = psppire_dict_get_variable (dict, row);
226 case DICT_TVM_COL_NAME:
227 dict_rename_var (dict->dict, var, g_value_get_string (value));
229 case DICT_TVM_COL_LABEL:
230 var_set_label (var, g_value_get_string (value));
232 case DICT_TVM_COL_COLUMNS:
233 var_set_display_width (var, g_value_get_int (value));
235 case DICT_TVM_COL_MEASURE:
236 var_set_measure (var, g_value_get_enum (value));
238 case DICT_TVM_COL_ALIGNMENT:
239 var_set_alignment (var, g_value_get_enum (value));
241 case DICT_TVM_COL_ROLE:
242 var_set_role (var, g_value_get_enum (value));
245 g_message ("Changing col %d of var sheet not yet supported", col);
251 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
253 const struct variable *var = psppire_dict_get_variable (store->dict, col);
256 value_init (&v, var_get_width (var));
257 v.f = g_value_get_double (value);
259 psppire_data_store_set_value (store, row, var, &v);
261 value_destroy (&v, var_get_width (var));
265 psppire_data_editor_set_property (GObject *object,
270 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
274 case PROP_SPLIT_WINDOW:
275 psppire_data_editor_split_window (de, g_value_get_boolean (value));
277 case PROP_DATA_STORE:
280 g_signal_handlers_disconnect_by_func (de->data_store,
281 G_CALLBACK (refresh_entry),
283 g_object_unref (de->data_store);
286 de->data_store = g_value_get_pointer (value);
287 g_object_ref (de->data_store);
288 g_print ("NEW STORE\n");
290 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
291 psppire_data_editor_refresh_model (de);
293 g_signal_connect_swapped (de->data_sheet, "value-changed",
294 G_CALLBACK (change_data_value), de->data_store);
296 g_signal_connect_swapped (de->data_store, "case-changed",
297 G_CALLBACK (refresh_entry), de);
300 case PROP_DICTIONARY:
302 g_object_unref (de->dict);
303 de->dict = g_value_get_pointer (value);
304 g_object_ref (de->dict);
306 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
307 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
308 g_signal_connect_swapped (de->var_sheet, "value-changed",
309 G_CALLBACK (change_var_property), de->dict);
312 case PROP_VALUE_LABELS:
314 case PROP_UI_MANAGER:
316 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
322 psppire_data_editor_get_property (GObject *object,
327 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
331 case PROP_SPLIT_WINDOW:
332 g_value_set_boolean (value, de->split);
334 case PROP_DATA_STORE:
335 g_value_set_pointer (value, de->data_store);
337 case PROP_DICTIONARY:
338 g_value_set_pointer (value, de->dict);
340 case PROP_VALUE_LABELS:
342 case PROP_UI_MANAGER:
343 g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
346 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
352 psppire_data_editor_switch_page (GtkNotebook *notebook,
356 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
357 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
361 psppire_data_editor_set_focus_child (GtkContainer *container,
364 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
365 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
369 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
371 GParamSpec *data_store_spec ;
372 GParamSpec *dict_spec ;
373 GParamSpec *value_labels_spec;
374 GParamSpec *split_window_spec;
375 GParamSpec *ui_manager_spec;
376 GObjectClass *object_class = G_OBJECT_CLASS (klass);
377 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
378 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
380 parent_class = g_type_class_peek_parent (klass);
382 object_class->dispose = psppire_data_editor_dispose;
383 object_class->set_property = psppire_data_editor_set_property;
384 object_class->get_property = psppire_data_editor_get_property;
386 container_class->set_focus_child = psppire_data_editor_set_focus_child;
388 notebook_class->switch_page = psppire_data_editor_switch_page;
391 g_param_spec_pointer ("data-store",
393 "A pointer to the data store 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_pointer ("dictionary",
403 "A pointer to the dictionary associated with this editor",
404 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
406 g_object_class_install_property (object_class,
411 g_param_spec_boolean ("value-labels",
413 "Whether or not the data sheet should display labels instead of values",
415 G_PARAM_WRITABLE | G_PARAM_READABLE);
417 g_object_class_install_property (object_class,
423 g_param_spec_boolean ("split",
425 "True iff the data sheet is split",
427 G_PARAM_READABLE | G_PARAM_WRITABLE);
429 g_object_class_install_property (object_class,
434 g_param_spec_object ("ui-manager",
436 "UI manager for the active notebook tab. The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
439 g_object_class_install_property (object_class,
446 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
447 PsppireDataEditor *de)
449 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
450 PSPPIRE_DATA_EDITOR_DATA_VIEW);
452 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
456 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
457 PsppireDataEditor *de)
459 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
460 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
462 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
466 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
467 active cell or cells. */
469 refresh_entry (PsppireDataEditor *de)
471 g_print ("%s\n", __FUNCTION__);
475 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
481 disconnect_data_sheets (PsppireDataEditor *de)
485 /* Called when the active cell or the selection in the data sheet changes */
487 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
489 gchar *ref_cell_text = NULL;
491 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
492 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
494 if (n_cases == 1 && n_vars == 1)
496 /* A single cell is selected */
497 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
499 ref_cell_text = g_strdup_printf (_("%d : %s"),
500 sel->start_y + 1, var_get_name (var));
506 /* The glib string library does not understand the ' printf modifier
507 on all platforms, but the "struct string" library does (because
508 Gnulib fixes that problem), so use the latter. */
510 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
512 ds_put_byte (&s, ' ');
513 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
514 ds_put_byte (&s, ' ');
515 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
518 ref_cell_text = ds_steal_cstr (&s);
521 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
522 ref_cell_text ? ref_cell_text : "");
524 g_free (ref_cell_text);
528 static void set_font_recursively (GtkWidget *w, gpointer data);
531 psppire_data_editor_init (PsppireDataEditor *de)
534 gchar *fontname = NULL;
537 de->ui_manager = NULL;
538 de->old_vbox_widget = NULL;
540 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
542 de->cell_ref_label = gtk_label_new ("");
543 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
544 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
546 de->datum_entry = psppire_value_entry_new ();
547 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
548 "activate", G_CALLBACK (on_datum_entry_activate), de);
550 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
551 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
552 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
555 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
556 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
557 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
558 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
559 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
560 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
563 g_signal_connect_swapped (de->data_sheet, "selection-changed",
564 G_CALLBACK (on_data_selection_change), de);
566 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
567 gtk_label_new_with_mnemonic (_("Data View")));
569 gtk_widget_show_all (de->vbox);
571 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
573 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
575 g_object_set (de->var_sheet,
577 "select-renderer-func", select_renderer_func,
581 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
582 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
584 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
585 gtk_label_new_with_mnemonic (_("Variable View")));
587 gtk_widget_show_all (de->var_sheet);
589 g_signal_connect (de->var_sheet, "row-header-double-clicked",
590 G_CALLBACK (on_var_sheet_var_double_clicked), de);
592 g_signal_connect (de->data_sheet, "column-header-double-clicked",
593 G_CALLBACK (on_data_sheet_var_double_clicked), de);
595 g_object_set (de, "can-focus", FALSE, NULL);
597 if (psppire_conf_get_string (psppire_conf_new (),
598 "Data Editor", "font",
601 de->font = pango_font_description_from_string (fontname);
603 set_font_recursively (GTK_WIDGET (de), de->font);
606 psppire_data_editor_update_ui_manager (de);
610 psppire_data_editor_new (PsppireDict *dict,
611 PsppireDataStore *data_store)
613 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
615 "data-store", data_store,
619 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
620 sheet(s) and variable sheet. */
622 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
624 GtkTreeViewGridLines grid;
627 ? GTK_TREE_VIEW_GRID_LINES_BOTH
628 : GTK_TREE_VIEW_GRID_LINES_NONE);
630 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
635 set_font_recursively (GtkWidget *w, gpointer data)
637 PangoFontDescription *font_desc = data;
639 gtk_widget_override_font (w, font_desc);
641 if ( GTK_IS_CONTAINER (w))
642 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
645 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
647 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
650 set_font_recursively (GTK_WIDGET (de), font_desc);
653 pango_font_description_free (de->font);
654 de->font = pango_font_description_copy (font_desc);
655 font_name = pango_font_description_to_string (de->font);
657 psppire_conf_set_string (psppire_conf_new (),
658 "Data Editor", "font",
663 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
664 If SPLIT is FALSE, un-splits it into a single pane. */
666 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
668 if (split == de->split)
671 disconnect_data_sheets (de);
673 psppire_data_editor_refresh_model (de);
675 gtk_widget_show_all (de->vbox);
678 set_font_recursively (GTK_WIDGET (de), de->font);
681 g_object_notify (G_OBJECT (de), "split");
682 psppire_data_editor_update_ui_manager (de);
685 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
686 visible and selected in the active view in DE. */
688 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
693 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
694 manager to display menu items and toolbar items specific to DE's current
697 DE's toplevel widget can watch for changes by connecting to DE's
698 notify::ui-manager signal. */
700 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
702 psppire_data_editor_update_ui_manager (de);
703 return de->ui_manager;
707 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
709 GtkUIManager *ui_manager;
713 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
715 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
718 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
722 /* This happens transiently in psppire_data_editor_init(). */
726 if (ui_manager != de->ui_manager)
729 g_object_unref (de->ui_manager);
731 g_object_ref (ui_manager);
732 de->ui_manager = ui_manager;
734 g_object_notify (G_OBJECT (de), "ui-manager");