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));
236 g_message ("Changing col %d of var sheet not yet supported", col);
242 change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
244 const struct variable *var = psppire_dict_get_variable (store->dict, col);
247 value_init (&v, var_get_width (var));
248 v.f = g_value_get_double (value);
250 psppire_data_store_set_value (store, row, var, &v);
252 value_destroy (&v, var_get_width (var));
256 psppire_data_editor_set_property (GObject *object,
261 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
265 case PROP_SPLIT_WINDOW:
266 psppire_data_editor_split_window (de, g_value_get_boolean (value));
268 case PROP_DATA_STORE:
271 g_signal_handlers_disconnect_by_func (de->data_store,
272 G_CALLBACK (refresh_entry),
274 g_object_unref (de->data_store);
277 de->data_store = g_value_get_pointer (value);
278 g_object_ref (de->data_store);
279 g_print ("NEW STORE\n");
281 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
282 psppire_data_editor_refresh_model (de);
284 g_signal_connect_swapped (de->data_sheet, "value-changed",
285 G_CALLBACK (change_data_value), de->data_store);
287 g_signal_connect_swapped (de->data_store, "case-changed",
288 G_CALLBACK (refresh_entry), de);
291 case PROP_DICTIONARY:
293 g_object_unref (de->dict);
294 de->dict = g_value_get_pointer (value);
295 g_object_ref (de->dict);
297 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
298 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
299 g_signal_connect_swapped (de->var_sheet, "value-changed",
300 G_CALLBACK (change_var_property), de->dict);
303 case PROP_VALUE_LABELS:
305 case PROP_UI_MANAGER:
307 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
313 psppire_data_editor_get_property (GObject *object,
318 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
322 case PROP_SPLIT_WINDOW:
323 g_value_set_boolean (value, de->split);
325 case PROP_DATA_STORE:
326 g_value_set_pointer (value, de->data_store);
328 case PROP_DICTIONARY:
329 g_value_set_pointer (value, de->dict);
331 case PROP_VALUE_LABELS:
333 case PROP_UI_MANAGER:
334 g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
337 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
343 psppire_data_editor_switch_page (GtkNotebook *notebook,
347 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
348 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
352 psppire_data_editor_set_focus_child (GtkContainer *container,
355 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
356 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
360 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
362 GParamSpec *data_store_spec ;
363 GParamSpec *dict_spec ;
364 GParamSpec *value_labels_spec;
365 GParamSpec *split_window_spec;
366 GParamSpec *ui_manager_spec;
367 GObjectClass *object_class = G_OBJECT_CLASS (klass);
368 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
369 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
371 parent_class = g_type_class_peek_parent (klass);
373 object_class->dispose = psppire_data_editor_dispose;
374 object_class->set_property = psppire_data_editor_set_property;
375 object_class->get_property = psppire_data_editor_get_property;
377 container_class->set_focus_child = psppire_data_editor_set_focus_child;
379 notebook_class->switch_page = psppire_data_editor_switch_page;
382 g_param_spec_pointer ("data-store",
384 "A pointer to the data store associated with this editor",
385 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
387 g_object_class_install_property (object_class,
392 g_param_spec_pointer ("dictionary",
394 "A pointer to the dictionary associated with this editor",
395 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
397 g_object_class_install_property (object_class,
402 g_param_spec_boolean ("value-labels",
404 "Whether or not the data sheet should display labels instead of values",
406 G_PARAM_WRITABLE | G_PARAM_READABLE);
408 g_object_class_install_property (object_class,
414 g_param_spec_boolean ("split",
416 "True iff the data sheet is split",
418 G_PARAM_READABLE | G_PARAM_WRITABLE);
420 g_object_class_install_property (object_class,
425 g_param_spec_object ("ui-manager",
427 "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.",
430 g_object_class_install_property (object_class,
437 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
438 PsppireDataEditor *de)
440 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
441 PSPPIRE_DATA_EDITOR_DATA_VIEW);
443 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
447 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
448 PsppireDataEditor *de)
450 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
451 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
453 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
457 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
458 active cell or cells. */
460 refresh_entry (PsppireDataEditor *de)
462 g_print ("%s\n", __FUNCTION__);
466 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
472 disconnect_data_sheets (PsppireDataEditor *de)
476 /* Called when the active cell or the selection in the data sheet changes */
478 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
480 gchar *ref_cell_text = NULL;
482 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
483 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
485 if (n_cases == 1 && n_vars == 1)
487 /* A single cell is selected */
488 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);
522 psppire_data_editor_init (PsppireDataEditor *de)
525 gchar *fontname = NULL;
528 de->ui_manager = NULL;
529 de->old_vbox_widget = NULL;
531 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
533 de->cell_ref_label = gtk_label_new ("");
534 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
535 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
537 de->datum_entry = psppire_value_entry_new ();
538 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
539 "activate", G_CALLBACK (on_datum_entry_activate), de);
541 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
542 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
543 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
546 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
547 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
548 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
549 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
550 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
551 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
554 g_signal_connect_swapped (de->data_sheet, "selection-changed",
555 G_CALLBACK (on_data_selection_change), de);
557 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
558 gtk_label_new_with_mnemonic (_("Data View")));
560 gtk_widget_show_all (de->vbox);
562 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
564 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
566 g_object_set (de->var_sheet,
568 "select-renderer-func", select_renderer_func,
572 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
573 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
575 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
576 gtk_label_new_with_mnemonic (_("Variable View")));
578 gtk_widget_show_all (de->var_sheet);
580 g_signal_connect (de->var_sheet, "row-header-double-clicked",
581 G_CALLBACK (on_var_sheet_var_double_clicked), de);
583 g_signal_connect (de->data_sheet, "column-header-double-clicked",
584 G_CALLBACK (on_data_sheet_var_double_clicked), de);
586 g_object_set (de, "can-focus", FALSE, NULL);
588 if (psppire_conf_get_string (psppire_conf_new (),
589 "Data Editor", "font",
592 de->font = pango_font_description_from_string (fontname);
594 set_font_recursively (GTK_WIDGET (de), de->font);
597 psppire_data_editor_update_ui_manager (de);
601 psppire_data_editor_new (PsppireDict *dict,
602 PsppireDataStore *data_store)
604 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
606 "data-store", data_store,
610 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
611 sheet(s) and variable sheet. */
613 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
615 GtkTreeViewGridLines grid;
618 ? GTK_TREE_VIEW_GRID_LINES_BOTH
619 : GTK_TREE_VIEW_GRID_LINES_NONE);
621 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
626 set_font_recursively (GtkWidget *w, gpointer data)
628 PangoFontDescription *font_desc = data;
630 gtk_widget_override_font (w, font_desc);
632 if ( GTK_IS_CONTAINER (w))
633 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
636 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
638 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
641 set_font_recursively (GTK_WIDGET (de), font_desc);
644 pango_font_description_free (de->font);
645 de->font = pango_font_description_copy (font_desc);
646 font_name = pango_font_description_to_string (de->font);
648 psppire_conf_set_string (psppire_conf_new (),
649 "Data Editor", "font",
654 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
655 If SPLIT is FALSE, un-splits it into a single pane. */
657 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
659 if (split == de->split)
662 disconnect_data_sheets (de);
664 psppire_data_editor_refresh_model (de);
666 gtk_widget_show_all (de->vbox);
669 set_font_recursively (GTK_WIDGET (de), de->font);
672 g_object_notify (G_OBJECT (de), "split");
673 psppire_data_editor_update_ui_manager (de);
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 UI manager that should be merged into DE's toplevel widget's UI
685 manager to display menu items and toolbar items specific to DE's current
688 DE's toplevel widget can watch for changes by connecting to DE's
689 notify::ui-manager signal. */
691 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
693 psppire_data_editor_update_ui_manager (de);
694 return de->ui_manager;
698 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
700 GtkUIManager *ui_manager;
704 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
706 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
709 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
713 /* This happens transiently in psppire_data_editor_init(). */
717 if (ui_manager != de->ui_manager)
720 g_object_unref (de->ui_manager);
722 g_object_ref (ui_manager);
723 de->ui_manager = ui_manager;
725 g_object_notify (G_OBJECT (de), "ui-manager");